diff --git a/SESSION_LOG_2025-12-03.md b/SESSION_LOG_2025-12-03.md
new file mode 100644
index 0000000..489bb75
--- /dev/null
+++ b/SESSION_LOG_2025-12-03.md
@@ -0,0 +1,198 @@
+# Лог сессии 2025-12-03
+
+## Задача 1: Получение cf_2624 из MySQL при загрузке черновика
+
+### Проблема
+Пользователь заметил, что для `claim_id: "226564ce-d7cf-48ee-a820-690e8f5ec8e5"` доступно редактирование, хотя в CRM стоит галка "Данные подтверждены" (`cf_2624 = "1"`).
+
+### Решение
+Вместо передачи `cf_2624` через события Redis, реализован прямой SQL запрос к MySQL БД vtiger CRM при загрузке черновика.
+
+## Изменения
+
+### 1. Добавлены credentials для MySQL CRM в `config.py`
+```python
+# MySQL CRM (vtiger CRM)
+mysql_crm_host: str = "localhost"
+mysql_crm_port: int = 3306
+mysql_crm_db: str = "ci20465_72new"
+mysql_crm_user: str = "ci20465_72new"
+mysql_crm_password: str = "EcY979Rn"
+```
+
+### 2. Создан сервис `CrmMySQLService`
+**Файл:** `ticket_form/backend/app/services/crm_mysql_service.py`
+
+- Подключение к MySQL БД vtiger CRM
+- Методы: `fetch_one()`, `fetch_all()`, `execute()`
+- Использует `aiomysql` для асинхронных запросов
+
+### 3. Обновлён `main.py`
+- Добавлено подключение к MySQL CRM при старте
+- Добавлено закрытие соединения при остановке
+
+### 4. Обновлён `claims.py` - метод `get_draft()`
+**Эндпоинт:** `GET /api/v1/claims/drafts/{claim_id}`
+
+**Изменения:**
+- Убран webservice API (getchallenge → login → retrieve)
+- Добавлен прямой SQL запрос к MySQL для получения `cf_2624`
+- Получаем все данные контакта, включая `cf_2624`
+- Добавлено логирование для отладки
+
+**SQL запрос:**
+```sql
+SELECT
+ cd.contactid,
+ cd.firstname,
+ cd.lastname,
+ cd.email,
+ cd.mobile,
+ ccf.cf_2624 AS cf_2624
+FROM vtiger_contactdetails cd
+LEFT JOIN vtiger_contactscf ccf ON ccf.contactid = cd.contactid
+LEFT JOIN vtiger_crmentity ce ON ce.crmid = cd.contactid
+WHERE cd.contactid = %s
+ AND ce.deleted = 0
+LIMIT 1
+```
+
+**Логика:**
+- Если `cf_2624 = "1"` → `contact_data_confirmed = True`, `contact_data_can_edit = False`
+- Если `cf_2624 = "0"` или `NULL` → `contact_data_confirmed = False`, `contact_data_can_edit = True`
+
+### 5. Обновлены SQL файлы и документация
+- `N8N_POSTGRESQL_GET_CONTACT_DATA.sql` → `N8N_MYSQL_GET_CONTACT_DATA.sql`
+- Изменён синтаксис: `$1` → `?` (для n8n MySQL ноды)
+- Обновлена документация `BACKEND_GET_CONTACT_CF_2624_FROM_POSTGRESQL.md`
+- Создан `N8N_MYSQL_GET_CONTACT_DATA.md`
+
+## Преимущества нового подхода
+
+1. ✅ **Проще** - один SQL запрос вместо цепочки HTTP запросов
+2. ✅ **Быстрее** - прямой запрос к БД
+3. ✅ **Надёжнее** - не зависит от webservice API
+4. ✅ **Актуальнее** - всегда получаем свежие данные из БД
+
+## Проблемы и решения
+
+### Проблема 1: Файл crm_mysql_service.py отсутствовал в контейнере
+**Решение:** Пересобран контейнер через `docker-compose build ticket_form_backend`
+
+### Проблема 2: MySQL не подключался из Docker контейнера
+**Ошибка:** `Can't connect to MySQL server on 'localhost'`
+
+**Решение:**
+- Изменён `docker-compose.yml`: добавлен `network_mode: host`
+- Изменён `config.py`: `mysql_crm_host = "localhost"` (в режиме host работает)
+
+**Результат:** `✅ MySQL CRM DB connected: localhost:3306/ci20465_72new`
+
+### Проблема 3: contact_data_confirmed возвращал None
+**Причина:** Флаг не передавался в компонент `StepClaimConfirmation`
+
+**Решение:**
+- Добавлен prop `contact_data_confirmed` в `StepClaimConfirmation`
+- Передача флага из `formData.contact_data_confirmed` в компонент
+- Исправлена логика получения флага (приоритет: props > claimPlanData > false)
+
+## Проверка
+
+**MySQL запрос:**
+```bash
+mysql -h localhost -u ci20465_72new -p'EcY979Rn' ci20465_72new \
+ -e "SELECT contactid, cf_2624 FROM vtiger_contactscf WHERE contactid = '399542' LIMIT 1;"
+```
+
+**Результат:**
+```
+contactid cf_2624
+399542 1
+```
+
+✅ В MySQL `cf_2624 = "1"` для `contact_id = "399542"` - данные подтверждены.
+
+**API тест:**
+```bash
+curl "http://localhost:8200/api/v1/claims/drafts/226564ce-d7cf-48ee-a820-690e8f5ec8e5"
+```
+
+**Результат:**
+```json
+{
+ "contact_data_confirmed": true,
+ "contact_data_can_edit": false,
+ "contact_data_from_crm": {
+ "contactid": "399542",
+ "cf_2624": "1",
+ ...
+ }
+}
+```
+
+## Текущий статус
+
+- ✅ Код обновлён
+- ✅ Бэкенд пересобран и перезапущен
+- ✅ MySQL CRM подключён
+- ✅ API возвращает правильные данные
+- ✅ Фронтенд получает `contact_data_confirmed` и блокирует поля
+- ✅ Поля формы блокируются (readonly) при `contact_data_confirmed = true`
+
+## Блокировка полей
+
+При `contact_data_confirmed = true` блокируются следующие поля:
+- `firstname` (Имя)
+- `lastname` (Фамилия)
+- `secondname` / `middle_name` (Отчество)
+- `inn` (ИНН)
+- `birthday` (Дата рождения)
+- `birthplace` / `birth_place` (Место рождения)
+- `address` / `mailingstreet` (Адрес)
+- `email` (E-mail)
+
+Поля становятся `readonly` и отображаются с серым фоном.
+
+---
+
+## Задача 2: Выбор банка для СБП выплат
+
+### Реализация
+- Динамическая загрузка списка банков из API `http://212.193.27.93/api/payouts/dictionaries/nspk-banks`
+- Добавлено в форму создания заявки (`Step3Payment.tsx`)
+- Добавлено в форму редактирования (`generateConfirmationFormHTML.ts`)
+- Используется `input` + `datalist` для автоподстановки
+
+---
+
+## Файлы изменены
+
+### Backend:
+- `ticket_form/backend/app/config.py` - добавлены credentials для MySQL CRM
+- `ticket_form/backend/app/services/crm_mysql_service.py` - новый сервис
+- `ticket_form/backend/app/main.py` - подключение к MySQL CRM
+- `ticket_form/backend/app/api/claims.py` - прямой SQL запрос к MySQL
+- `ticket_form/docker-compose.yml` - добавлен `network_mode: host`
+
+### Frontend:
+- `ticket_form/frontend/src/components/form/StepClaimConfirmation.tsx` - передача `contact_data_confirmed`
+- `ticket_form/frontend/src/pages/ClaimForm.tsx` - передача флага в компонент
+- `ticket_form/frontend/src/components/form/generateConfirmationFormHTML.ts` - блокировка полей
+
+### Документация:
+- `ticket_form/docs/N8N_MYSQL_GET_CONTACT_DATA.sql` - SQL запрос для n8n
+- `ticket_form/docs/N8N_MYSQL_GET_CONTACT_DATA.md` - документация
+- `ticket_form/docs/BACKEND_GET_CONTACT_CF_2624_FROM_POSTGRESQL.md` - обновлена документация
+
+---
+
+## Коммиты
+
+1. `e1142315` - feat: Получение cf_2624 из MySQL при загрузке черновика
+2. `a86120dd` - fix: передача contact_data_confirmed в StepClaimConfirmation для блокировки полей
+
+---
+
+**Время работы:** 2025-12-03 16:00-17:00
+**Статус:** ✅ Завершено успешно
+
diff --git a/backend/app/api/claims.py b/backend/app/api/claims.py
index 4bbc25e..0378be0 100644
--- a/backend/app/api/claims.py
+++ b/backend/app/api/claims.py
@@ -15,6 +15,7 @@ import json
import logging
from ..services.redis_service import redis_service
from ..services.database import db
+from ..services.crm_mysql_service import crm_mysql_service
from ..config import settings
router = APIRouter(prefix="/api/v1/claims", tags=["Claims"])
@@ -201,17 +202,19 @@ async def list_drafts(
c.updated_at
FROM clpr_claims c
WHERE c.unified_id = $1
- AND (c.status_code != 'approved' OR c.status_code IS NULL)
- AND (c.is_confirmed IS NULL OR c.is_confirmed = false)
+ -- ВРЕМЕННО: убираем все фильтры для диагностики
+ -- TODO: вернуть фильтры после выяснения проблемы
+ -- AND (c.is_confirmed IS NULL OR c.is_confirmed = false)
ORDER BY c.updated_at DESC
LIMIT 20
"""
params = [unified_id]
logger.info(f"🔍 Searching by unified_id: {unified_id}")
elif phone:
- # Fallback: ищем через clpr_user_accounts и clpr_users
+ # Fallback: ищем через clpr_user_accounts и clpr_users, ИЛИ напрямую по телефону в payload
+ # Поддерживаем разные форматы телефона: 71234543212, +71234543212, 81234543212
query = """
- SELECT
+ SELECT DISTINCT
c.id,
c.payload->>'claim_id' as claim_id,
c.session_token,
@@ -221,21 +224,35 @@ async def list_drafts(
c.created_at,
c.updated_at
FROM clpr_claims c
- WHERE c.unified_id = (
- SELECT u.unified_id
- FROM clpr_user_accounts ua
- JOIN clpr_users u ON u.id = ua.user_id
- WHERE ua.channel = 'web_form'
- AND ua.channel_user_id = $1
- LIMIT 1
- )
+ WHERE c.channel = 'web_form'
+ AND (
+ -- Вариант 1: Поиск через unified_id (если есть запись в clpr_user_accounts)
+ c.unified_id = (
+ SELECT u.unified_id
+ FROM clpr_user_accounts ua
+ JOIN clpr_users u ON u.id = ua.user_id
+ WHERE ua.channel = 'web_form'
+ AND (ua.channel_user_id = $1 OR ua.channel_user_id = $2 OR ua.channel_user_id = $3)
+ LIMIT 1
+ )
+ -- Вариант 2: Прямой поиск по телефону в payload (в разных форматах)
+ OR c.payload->>'phone' = $1
+ OR c.payload->>'phone' = $2
+ OR c.payload->>'phone' = $3
+ )
AND (c.status_code != 'approved' OR c.status_code IS NULL)
AND (c.is_confirmed IS NULL OR c.is_confirmed = false)
ORDER BY c.updated_at DESC
LIMIT 20
"""
- params = [phone]
- logger.info(f"🔍 Searching by phone (fallback): {phone}")
+ # Подготавливаем варианты телефона для поиска
+ phone_variants = [
+ phone, # Оригинальный формат
+ f"+{phone}", # С плюсом
+ phone.replace('7', '8', 1) if phone.startswith('7') else phone # С 8 вместо 7
+ ]
+ params = phone_variants
+ logger.info(f"🔍 Searching by phone (fallback): {phone}, variants: {phone_variants}")
elif session_id:
# Fallback: поиск по session_token
query = """
@@ -264,9 +281,22 @@ async def list_drafts(
# Простой тест: проверяем, что unified_id вообще есть в базе
test_count = 0
test_count_null = 0
+ test_count_approved = 0
+ test_count_confirmed = 0
if unified_id:
try:
+ # Все заявления с этим unified_id
test_count = await db.fetch_val("SELECT COUNT(*) FROM clpr_claims WHERE unified_id = $1", unified_id)
+ # Заявления со статусом approved
+ test_count_approved = await db.fetch_val("""
+ SELECT COUNT(*) FROM clpr_claims
+ WHERE unified_id = $1 AND status_code = 'approved'
+ """, unified_id)
+ # Заявления с is_confirmed = true
+ test_count_confirmed = await db.fetch_val("""
+ SELECT COUNT(*) FROM clpr_claims
+ WHERE unified_id = $1 AND is_confirmed = true
+ """, unified_id)
# Также проверяем, сколько записей с NULL unified_id для этого пользователя (через phone)
if phone:
test_count_null = await db.fetch_val("""
@@ -275,7 +305,7 @@ async def list_drafts(
AND c.channel = 'web_form'
AND c.payload->>'phone' = $1
""", phone)
- logger.info(f"🔍 Test COUNT: unified_id={unified_id} → {test_count} records")
+ logger.info(f"🔍 Test COUNT: unified_id={unified_id} → {test_count} total, {test_count_approved} approved, {test_count_confirmed} confirmed")
if test_count_null > 0:
logger.warning(f"⚠️ Found {test_count_null} records with NULL unified_id for phone={phone}")
except Exception as e:
@@ -290,10 +320,25 @@ async def list_drafts(
logger.info(f"🔍 Test COUNT result: {test_count}")
logger.info(f"🔍 Rows found: {len(rows)}")
+ # Если заявления есть, но не возвращаются - проверяем статусы
+ if len(rows) == 0 and test_count > 0 and unified_id:
+ logger.warning(f"⚠️ Заявления есть (test_count={test_count}), но запрос вернул 0 строк!")
+ try:
+ all_statuses = await db.fetch_all("""
+ SELECT status_code, is_confirmed, channel, id
+ FROM clpr_claims
+ WHERE unified_id = $1
+ """, unified_id)
+ logger.warning(f"⚠️ Все заявления для unified_id: {[dict(r) for r in all_statuses]}")
+ except Exception as e:
+ logger.error(f"❌ Ошибка при проверке статусов: {e}")
+
# ВРЕМЕННО: возвращаем тестовые данные для отладки
debug_info = {
"unified_id": unified_id,
"test_count": test_count,
+ "test_count_approved": test_count_approved or 0,
+ "test_count_confirmed": test_count_confirmed or 0,
"test_count_null": test_count_null,
"rows_found": len(rows),
"query": query[:200] if len(query) > 200 else query,
@@ -316,18 +361,68 @@ async def list_drafts(
else:
payload = {}
+ # Извлекаем данные из ai_analysis или wizard_plan
+ ai_analysis = payload.get('ai_analysis') or {}
+ wizard_plan = payload.get('wizard_plan') or {}
+
+ # Краткое описание проблемы (заголовок)
+ problem_title = ai_analysis.get('problem') or payload.get('problem') or None
+
+ # Категория проблемы
+ category = ai_analysis.get('category') or wizard_plan.get('category') or None
+
+ # Подробное описание (для превью)
+ problem_text = payload.get('problem_description', '')
+
+ # Считаем документы
+ documents_meta = payload.get('documents_meta') or []
+ documents_required = payload.get('documents_required') or []
+
+ # Считаем загруженные (уникальные по field_label)
+ uploaded_labels = set()
+ for doc in documents_meta:
+ label = doc.get('field_label') or doc.get('field_name')
+ if label:
+ uploaded_labels.add(label)
+
+ documents_uploaded = len(uploaded_labels)
+ documents_total = len(documents_required) if documents_required else 0
+
+ # Формируем список документов со статусами
+ documents_list = []
+ for doc_req in documents_required:
+ doc_name = doc_req.get('name', 'Документ')
+ doc_id = doc_req.get('id', '')
+ is_required = doc_req.get('required', False)
+ # Проверяем загружен ли (по name или id)
+ is_uploaded = doc_name in uploaded_labels or doc_id in uploaded_labels
+ documents_list.append({
+ "name": doc_name,
+ "required": is_required,
+ "uploaded": is_uploaded,
+ })
+
drafts.append({
"id": str(row['id']),
"claim_id": row.get('claim_id'),
"session_token": row.get('session_token'),
"status_code": row.get('status_code'),
- "channel": row.get('channel'), # Добавляем канал в ответ
+ "channel": row.get('channel'),
"created_at": row['created_at'].isoformat() if row.get('created_at') else None,
"updated_at": row['updated_at'].isoformat() if row.get('updated_at') else None,
- "problem_description": payload.get('problem_description', '')[:100] if payload.get('problem_description') else None,
+ # Заголовок - краткое описание проблемы из AI
+ "problem_title": problem_title[:150] if problem_title else None,
+ # Полное описание
+ "problem_description": problem_text[:500] if problem_text else None,
+ "category": category,
"wizard_plan": payload.get('wizard_plan') is not None,
"wizard_answers": payload.get('answers') is not None,
- "has_documents": len(payload.get('documents_meta', [])) > 0 if payload.get('documents_meta') else False,
+ "has_documents": documents_uploaded > 0,
+ # Прогресс документов
+ "documents_total": documents_total,
+ "documents_uploaded": documents_uploaded,
+ "documents_skipped": 0, # TODO: считать пропущенные
+ "documents_list": documents_list, # Список со статусами
})
return {
@@ -406,18 +501,114 @@ async def get_draft(claim_id: str):
if documents_required:
logger.info(f"🔍 documents_required: {documents_required[:2]}...") # Первые 2 для примера
+ # ✅ Проверяем флаг подтверждения данных контакта из CRM (поле cf_2624)
+ # Простой способ: делаем прямой SQL запрос к БД (таблицы vtiger_*)
+ # ПРИМЕЧАНИЕ: Если таблицы vtiger_* находятся в MySQL (а не PostgreSQL),
+ # нужно использовать отдельный connection через policy_service или создать новый MySQL connection
+ unified_id = row.get('unified_id')
+ contact_data_confirmed = False
+ contact_data_can_edit = True
+ contact_data_from_crm = None
+
+ # Получаем contact_id из payload
+ contact_id = payload.get('contact_id') if isinstance(payload, dict) else None
+
+ # Преобразуем contact_id в строку, если он есть
+ if contact_id:
+ contact_id = str(contact_id).strip()
+ logger.info(f"🔍 Получен contact_id из черновика: {contact_id} (type: {type(contact_id)})")
+
+ if contact_id:
+ try:
+ # ✅ Прямой SQL запрос к MySQL для получения cf_2624
+ # Таблицы vtiger_* находятся в MySQL БД
+ contact_query = """
+ SELECT
+ cd.contactid,
+ cd.firstname,
+ cd.lastname,
+ cd.email,
+ cd.mobile,
+ cd.phone,
+ cs.birthday,
+ ca.mailingstreet,
+ ca.mailingcity,
+ ca.mailingstate,
+ ca.mailingzip,
+ ca.mailingcountry,
+ ccf.cf_1157 AS middle_name,
+ ccf.cf_1263 AS birthplace,
+ ccf.cf_1257 AS inn,
+ ccf.cf_1849 AS requisites,
+ ccf.cf_1580 AS code,
+ ccf.cf_1706 AS sms,
+ ccf.cf_2624 AS cf_2624
+ FROM vtiger_contactdetails cd
+ LEFT JOIN vtiger_contactscf ccf ON ccf.contactid = cd.contactid
+ LEFT JOIN vtiger_contactsubdetails cs ON cs.contactsubscriptionid = cd.contactid
+ LEFT JOIN vtiger_contactaddress ca ON ca.contactaddressid = cd.contactid
+ LEFT JOIN vtiger_crmentity ce ON ce.crmid = cd.contactid
+ WHERE cd.contactid = %s
+ AND ce.deleted = 0
+ LIMIT 1
+ """
+
+ contact_row = await crm_mysql_service.fetch_one(contact_query, contact_id)
+
+ if contact_row:
+ # Формируем объект с данными контакта
+ contact_data_from_crm = {
+ "contactid": contact_row.get("contactid"),
+ "firstname": contact_row.get("firstname"),
+ "lastname": contact_row.get("lastname"),
+ "email": contact_row.get("email"),
+ "mobile": contact_row.get("mobile"),
+ "phone": contact_row.get("phone"),
+ "birthday": contact_row.get("birthday"),
+ "mailingstreet": contact_row.get("mailingstreet"),
+ "mailingcity": contact_row.get("mailingcity"),
+ "mailingstate": contact_row.get("mailingstate"),
+ "mailingzip": contact_row.get("mailingzip"),
+ "mailingcountry": contact_row.get("mailingcountry"),
+ "cf_1157": contact_row.get("middle_name"), # Отчество
+ "cf_1263": contact_row.get("birthplace"), # Место рождения
+ "cf_1257": contact_row.get("inn"), # ИНН
+ "cf_1849": contact_row.get("requisites"), # Реквизиты
+ "cf_1580": contact_row.get("code"), # Код
+ "cf_1706": contact_row.get("sms"), # SMS
+ "cf_2624": contact_row.get("cf_2624") or "0" # ✅ Данные подтверждены
+ }
+
+ # ✅ Проверяем кастомное поле "Данные подтверждены" (cf_2624)
+ confirmed_field = contact_data_from_crm.get("cf_2624", "0")
+ contact_data_confirmed = confirmed_field == "1" or confirmed_field == "true" or confirmed_field is True
+ contact_data_can_edit = not contact_data_confirmed
+
+ logger.info(
+ f"🔒 Статус данных контакта из MySQL CRM: confirmed={contact_data_confirmed}, "
+ f"field_value={confirmed_field}, contact_id={contact_id}"
+ )
+ else:
+ logger.warning(f"⚠️ Контакт не найден в MySQL CRM: contact_id={contact_id}")
+ except Exception as e:
+ logger.warning(f"⚠️ Не удалось загрузить данные контакта из MySQL CRM: {str(e)}")
+
return {
"success": True,
"claim": {
"id": str(row['id']),
- "claim_id": final_claim_id, # ✅ Используем claim_id из payload, если его нет в row
+ "claim_id": final_claim_id,
"session_token": row.get('session_token'),
"status_code": row.get('status_code'),
- "channel": row.get('channel'), # ✅ Добавляем channel для отладки
+ "channel": row.get('channel'),
"created_at": row['created_at'].isoformat() if row.get('created_at') else None,
"updated_at": row['updated_at'].isoformat() if row.get('updated_at') else None,
"payload": payload
- }
+ },
+ # ✅ Флаги подтверждения данных контакта (из CRM поля cf_2624)
+ "contact_data_confirmed": contact_data_confirmed,
+ "contact_data_can_edit": contact_data_can_edit,
+ "contact_data_from_crm": contact_data_from_crm # Данные из CRM (всегда загружаем, если есть contact_id)
}
except HTTPException:
@@ -483,6 +674,10 @@ async def publish_form_approval(request: Request):
"body_type": type(body).__name__,
"sms_code_in_body": "sms_code" in body if isinstance(body, dict) else False,
"sms_code_value": body.get("sms_code", "NOT_FOUND") if isinstance(body, dict) else "NOT_DICT",
+ "contact_data_confirmed_in_body": "contact_data_confirmed" in body if isinstance(body, dict) else False,
+ "cf_2624_in_body": "cf_2624" in body if isinstance(body, dict) else False,
+ "bank_id_in_body": "bank_id" in body if isinstance(body, dict) else False,
+ "bank_name_in_body": "bank_name" in body if isinstance(body, dict) else False,
},
)
@@ -508,6 +703,27 @@ async def publish_form_approval(request: Request):
import time
idempotency_key = f"{claim_id}_{int(time.time() * 1000)}_{body.get('user_id', 'unknown')}"
+ # ✅ Получаем флаг подтверждения данных контакта и данные банка
+ contact_data_confirmed = body.get("contact_data_confirmed", False)
+ cf_2624 = body.get("cf_2624", "0")
+ bank_id = body.get("bank_id", "")
+ bank_name = body.get("bank_name", "")
+
+ # Логируем полученные значения для отладки
+ logger.info(
+ f"📥 Извлеченные дополнительные поля",
+ extra={
+ "contact_data_confirmed": contact_data_confirmed,
+ "cf_2624": cf_2624,
+ "bank_id": bank_id,
+ "bank_name": bank_name,
+ "has_contact_data_confirmed": "contact_data_confirmed" in body,
+ "has_cf_2624": "cf_2624" in body,
+ "has_bank_id": "bank_id" in body,
+ "has_bank_name": "bank_name" in body,
+ },
+ )
+
# Формируем событие для Redis
event_data = {
"event_type": "form_approve",
@@ -522,6 +738,14 @@ async def publish_form_approval(request: Request):
"idempotency_key": idempotency_key, # Для защиты от дублей в RabbitMQ
"timestamp": datetime.utcnow().isoformat(),
+ # ✅ Флаг редактирования перс данных (cf_2624)
+ "contact_data_confirmed": contact_data_confirmed,
+ "cf_2624": cf_2624, # Значение для CRM (1 = подтверждено, 0 = не подтверждено)
+
+ # ✅ Данные банка для СБП выплаты
+ "bank_id": bank_id,
+ "bank_name": bank_name,
+
# Данные формы подтверждения
"form_data": body.get("form_data", {}),
"user": body.get("user", {}),
@@ -547,6 +771,10 @@ async def publish_form_approval(request: Request):
"sms_code_in_event_data": "sms_code" in event_data,
"event_data_sms_code_value": event_data.get("sms_code", "NOT_FOUND"),
"event_data_keys": list(event_data.keys()),
+ "contact_data_confirmed_in_event": "contact_data_confirmed" in event_data,
+ "cf_2624_in_event": "cf_2624" in event_data,
+ "bank_id_in_event": "bank_id" in event_data,
+ "bank_name_in_event": "bank_name" in event_data,
},
)
diff --git a/backend/app/api/documents.py b/backend/app/api/documents.py
index 147ab71..6a35796 100644
--- a/backend/app/api/documents.py
+++ b/backend/app/api/documents.py
@@ -8,9 +8,11 @@ from typing import Optional, List
import httpx
import json
import uuid
+import hashlib
from datetime import datetime
import logging
from ..services.redis_service import redis_service
+from ..services.database import db
from ..config import settings
router = APIRouter(prefix="/api/v1/documents", tags=["Documents"])
@@ -20,6 +22,22 @@ logger = logging.getLogger(__name__)
N8N_DOCUMENT_UPLOAD_WEBHOOK = "https://n8n.clientright.pro/webhook/webform_document_upload"
+def get_client_ip(request: Request) -> str:
+ """Получить реальный IP клиента (с учётом proxy заголовков)"""
+ # Сначала проверяем заголовки от reverse proxy
+ forwarded_for = request.headers.get("x-forwarded-for", "").split(",")[0].strip()
+ real_ip = request.headers.get("x-real-ip", "").strip()
+
+ # X-Forwarded-For имеет приоритет
+ if forwarded_for and forwarded_for not in ("127.0.0.1", "192.168.0.1", "::1"):
+ return forwarded_for
+ if real_ip and real_ip not in ("127.0.0.1", "192.168.0.1", "::1"):
+ return real_ip
+
+ # Fallback на request.client
+ return request.client.host if request.client else "unknown"
+
+
@router.post("/upload")
async def upload_document(
request: Request,
@@ -29,6 +47,7 @@ async def upload_document(
document_type: str = Form(...),
document_name: Optional[str] = Form(None),
document_description: Optional[str] = Form(None),
+ group_index: Optional[str] = Form(None),
unified_id: Optional[str] = Form(None),
contact_id: Optional[str] = Form(None),
phone: Optional[str] = Form(None),
@@ -64,10 +83,7 @@ async def upload_document(
file_size = len(file_content)
# Получаем IP клиента
- client_ip = request.client.host if request.client else "unknown"
- forwarded_for = request.headers.get("x-forwarded-for", "").split(",")[0].strip()
- if forwarded_for:
- client_ip = forwarded_for
+ client_ip = get_client_ip(request)
# Формируем данные в формате совместимом с существующим n8n воркфлоу
form_data = {
@@ -92,14 +108,21 @@ async def upload_document(
"upload_timestamp": datetime.utcnow().isoformat(),
# Формат uploads_* для совместимости
- "uploads_field_names[0]": document_type,
- "uploads_field_labels[0]": document_name or document_type,
- "uploads_descriptions[0]": document_description or "",
+ # ✅ Используем group_index для правильной индексации (по умолчанию 0)
+ "uploads_field_names[{idx}]".format(idx=group_index or "0"): document_type,
+ "uploads_field_labels[{idx}]".format(idx=group_index or "0"): document_name or document_type,
+ "uploads_descriptions[{idx}]".format(idx=group_index or "0"): document_description or "",
}
- # Файл для multipart (ключ uploads[0] для совместимости)
+ # ✅ Добавляем group_index в данные формы
+ if group_index:
+ form_data["group_index"] = group_index
+ logger.info(f"📋 group_index передан в n8n: {group_index}")
+
+ # Файл для multipart (ключ uploads[group_index] для совместимости)
+ idx = group_index or "0"
files = {
- "uploads[0]": (file.filename, file_content, file.content_type or "application/octet-stream")
+ f"uploads[{idx}]": (file.filename, file_content, file.content_type or "application/octet-stream")
}
# Отправляем в n8n
@@ -213,10 +236,7 @@ async def upload_multiple_documents(
)
# Получаем IP клиента
- client_ip = request.client.host if request.client else "unknown"
- forwarded_for = request.headers.get("x-forwarded-for", "").split(",")[0].strip()
- if forwarded_for:
- client_ip = forwarded_for
+ client_ip = get_client_ip(request)
# Генерируем ID для каждого файла и читаем контент
file_ids = []
@@ -386,145 +406,43 @@ async def get_documents_status(claim_id: str):
)
-@router.post("/generate-list")
-async def generate_documents_list(request: Request):
- """
- Запрос на генерацию списка документов для проблемы.
-
- Принимает описание проблемы, отправляет в n8n для быстрого AI-анализа.
- n8n публикует результат в Redis канал ocr_events:{session_id} с event_type=documents_list_ready.
- """
- try:
- body = await request.json()
-
- session_id = body.get("session_id")
- problem_description = body.get("problem_description")
-
- if not session_id or not problem_description:
- raise HTTPException(
- status_code=400,
- detail="session_id и problem_description обязательны",
- )
-
- logger.info(
- "📝 Generate documents list request",
- extra={
- "session_id": session_id,
- "description_length": len(problem_description),
- },
- )
-
- # Публикуем событие в Redis для n8n
- event_data = {
- "type": "generate_documents_list",
- "session_id": session_id,
- "claim_id": body.get("claim_id"),
- "unified_id": body.get("unified_id"),
- "phone": body.get("phone"),
- "problem_description": problem_description,
- "timestamp": datetime.utcnow().isoformat(),
- }
-
- channel = f"{settings.redis_prefix}documents_list"
-
- subscribers = await redis_service.publish(
- channel,
- json.dumps(event_data, ensure_ascii=False)
- )
-
- logger.info(
- "✅ Documents list request published",
- extra={
- "channel": channel,
- "subscribers": subscribers,
- },
- )
-
- return {
- "success": True,
- "message": "Запрос на генерацию списка документов отправлен",
- "channel": channel,
- }
-
- except HTTPException:
- raise
-
- except Exception as e:
- logger.exception("❌ Error generating documents list")
- raise HTTPException(
- status_code=500,
- detail=f"Ошибка генерации списка: {str(e)}",
- )
-from typing import Optional, List
-import httpx
-import json
-import uuid
-from datetime import datetime
-import logging
-from ..services.redis_service import redis_service
-from ..config import settings
-router = APIRouter(prefix="/api/v1/documents", tags=["Documents"])
-logger = logging.getLogger(__name__)
-
-# n8n webhook для загрузки документов
-N8N_DOCUMENT_UPLOAD_WEBHOOK = "https://n8n.clientright.pro/webhook/webform_document_upload"
-
-
-@router.post("/upload")
-async def upload_document(
+async def skip_document(
request: Request,
- file: UploadFile = File(...),
claim_id: str = Form(...),
session_id: str = Form(...),
document_type: str = Form(...),
document_name: Optional[str] = Form(None),
- document_description: Optional[str] = Form(None),
+ group_index: Optional[str] = Form(None),
unified_id: Optional[str] = Form(None),
contact_id: Optional[str] = Form(None),
phone: Optional[str] = Form(None),
):
"""
- Загрузка одного документа.
+ Пропуск документа (пользователь указал, что документа нет).
- Принимает файл и метаданные, отправляет в n8n для:
- 1. Сохранения в S3
- 2. OCR обработки
- 3. Обновления черновика в PostgreSQL
-
- После успешной обработки n8n публикует событие document_ocr_completed в Redis.
+ Отправляет событие в n8n на тот же webhook, что и загрузка файлов,
+ но с флагом skipped=true для обработки пропуска.
"""
try:
- # Генерируем уникальный ID файла
- file_id = f"doc_{uuid.uuid4().hex[:12]}"
-
logger.info(
- "📤 Document upload received",
+ "⏭️ Document skip received",
extra={
"claim_id": claim_id,
"session_id": session_id,
"document_type": document_type,
- "file_name": file.filename,
- "file_size": file.size if hasattr(file, 'size') else 'unknown',
- "content_type": file.content_type,
+ "group_index": group_index,
},
)
- # Читаем содержимое файла
- file_content = await file.read()
- file_size = len(file_content)
-
# Получаем IP клиента
- client_ip = request.client.host if request.client else "unknown"
- forwarded_for = request.headers.get("x-forwarded-for", "").split(",")[0].strip()
- if forwarded_for:
- client_ip = forwarded_for
+ client_ip = get_client_ip(request)
# Формируем данные в формате совместимом с существующим n8n воркфлоу
form_data = {
# Основные идентификаторы
"form_id": "ticket_form",
- "stage": "document_upload",
+ "stage": "document_skip",
"session_id": session_id,
"claim_id": claim_id,
"client_ip": client_ip,
@@ -536,40 +454,39 @@ async def upload_document(
# Информация о документе
"document_type": document_type,
- "file_id": file_id,
- "original_filename": file.filename,
- "content_type": file.content_type or "application/octet-stream",
- "file_size": str(file_size),
- "upload_timestamp": datetime.utcnow().isoformat(),
+ "document_name": document_name or document_type,
+ "skipped": "true", # ✅ Флаг пропуска документа
+ "action": "skip", # ✅ Действие: пропуск
+ "skip_timestamp": datetime.utcnow().isoformat(),
- # Формат uploads_* для совместимости
- "uploads_field_names[0]": document_type,
- "uploads_field_labels[0]": document_name or document_type,
- "uploads_descriptions[0]": document_description or "",
+ # Формат uploads_* для совместимости (без файлов)
+ # ✅ Используем group_index для правильной индексации (по умолчанию 0)
+ "uploads_field_names[{idx}]".format(idx=group_index or "0"): document_type,
+ "uploads_field_labels[{idx}]".format(idx=group_index or "0"): document_name or document_type,
+ "uploads_descriptions[{idx}]".format(idx=group_index or "0"): "",
+ "files_count": "0", # ✅ Нет файлов
}
- # Файл для multipart (ключ uploads[0] для совместимости)
- files = {
- "uploads[0]": (file.filename, file_content, file.content_type or "application/octet-stream")
- }
+ # ✅ Добавляем group_index в данные формы
+ if group_index:
+ form_data["group_index"] = group_index
+ logger.info(f"📋 group_index передан в n8n: {group_index}")
- # Отправляем в n8n
- async with httpx.AsyncClient(timeout=120.0) as client:
+ # Отправляем в n8n на тот же webhook (без файлов)
+ async with httpx.AsyncClient(timeout=60.0) as client:
response = await client.post(
N8N_DOCUMENT_UPLOAD_WEBHOOK,
data=form_data,
- files=files,
)
response_text = response.text or ""
if response.status_code == 200:
logger.info(
- "✅ Document uploaded to n8n",
+ "✅ Document skip sent to n8n",
extra={
"claim_id": claim_id,
"document_type": document_type,
- "file_id": file_id,
"response_preview": response_text[:200],
},
)
@@ -582,13 +499,12 @@ async def upload_document(
# Публикуем событие в Redis для фронтенда
event_data = {
- "event_type": "document_uploaded",
- "status": "processing",
+ "event_type": "document_skipped",
+ "status": "skipped",
"claim_id": claim_id,
"session_id": session_id,
"document_type": document_type,
- "file_id": file_id,
- "original_filename": file.filename,
+ "document_name": document_name or document_type,
"timestamp": datetime.utcnow().isoformat(),
}
@@ -599,16 +515,15 @@ async def upload_document(
return {
"success": True,
- "file_id": file_id,
"document_type": document_type,
- "ocr_status": "processing",
- "message": "Документ загружен и отправлен на обработку",
+ "status": "skipped",
+ "message": "Документ пропущен и сохранён",
"n8n_response": n8n_response,
}
else:
logger.error(
- "❌ n8n document upload error",
+ "❌ n8n document skip error",
extra={
"status_code": response.status_code,
"body": response_text[:500],
@@ -620,222 +535,17 @@ async def upload_document(
)
except httpx.TimeoutException:
- logger.error("⏱️ n8n document upload timeout")
- raise HTTPException(status_code=504, detail="Таймаут загрузки документа")
+ logger.error("⏱️ n8n document skip timeout")
+ raise HTTPException(status_code=504, detail="Таймаут отправки пропуска документа")
except HTTPException:
raise
except Exception as e:
- logger.exception("❌ Document upload error")
- raise HTTPException(
- status_code=500,
- detail=f"Ошибка загрузки документа: {str(e)}",
- )
+ logger.exception("❌ Document skip error")
+ raise HTTPException(status_code=500, detail=f"Ошибка пропуска документа: {str(e)}")
-@router.post("/upload-multiple")
-async def upload_multiple_documents(
- request: Request,
- files: List[UploadFile] = File(...),
- claim_id: str = Form(...),
- session_id: str = Form(...),
- document_type: str = Form(...),
- document_name: Optional[str] = Form(None),
- document_description: Optional[str] = Form(None),
- unified_id: Optional[str] = Form(None),
- contact_id: Optional[str] = Form(None),
- phone: Optional[str] = Form(None),
-):
- """
- Загрузка нескольких файлов для одного документа (например, несколько страниц паспорта).
- Все файлы отправляются одним запросом в n8n.
- """
- try:
- logger.info(
- "📤 Multiple documents upload received",
- extra={
- "claim_id": claim_id,
- "session_id": session_id,
- "document_type": document_type,
- "files_count": len(files),
- "file_names": [f.filename for f in files],
- },
- )
-
- # Получаем IP клиента
- client_ip = request.client.host if request.client else "unknown"
- forwarded_for = request.headers.get("x-forwarded-for", "").split(",")[0].strip()
- if forwarded_for:
- client_ip = forwarded_for
-
- # Генерируем ID для каждого файла и читаем контент
- file_ids = []
- files_multipart = {}
-
- for i, file in enumerate(files):
- file_id = f"doc_{uuid.uuid4().hex[:12]}"
- file_ids.append(file_id)
-
- file_content = await file.read()
- files_multipart[f"uploads[{i}]"] = (
- file.filename,
- file_content,
- file.content_type or "application/octet-stream"
- )
-
- # Формируем данные формы
- form_data = {
- # Основные идентификаторы
- "form_id": "ticket_form",
- "stage": "document_upload",
- "session_id": session_id,
- "claim_id": claim_id,
- "client_ip": client_ip,
-
- # Идентификаторы пользователя
- "unified_id": unified_id or "",
- "contact_id": contact_id or "",
- "phone": phone or "",
-
- # Информация о документе
- "document_type": document_type,
- "files_count": str(len(files)),
- "upload_timestamp": datetime.utcnow().isoformat(),
- }
-
- # ✅ Получаем group_index из Form (индекс документа в documents_required)
- form_params = await request.form()
- group_index = form_params.get("group_index")
- if group_index:
- form_data["group_index"] = group_index
- logger.info(f"📋 group_index передан в n8n: {group_index}")
-
- # Добавляем информацию о каждом файле
- for i, (file, file_id) in enumerate(zip(files, file_ids)):
- form_data[f"file_ids[{i}]"] = file_id
- form_data[f"uploads_field_names[{i}]"] = document_type
- form_data[f"uploads_field_labels[{i}]"] = document_name or document_type
- form_data[f"uploads_descriptions[{i}]"] = document_description or ""
- form_data[f"original_filenames[{i}]"] = file.filename
-
- # Отправляем в n8n одним запросом
- async with httpx.AsyncClient(timeout=180.0) as client:
- response = await client.post(
- N8N_DOCUMENT_UPLOAD_WEBHOOK,
- data=form_data,
- files=files_multipart,
- )
-
- response_text = response.text or ""
-
- if response.status_code == 200:
- logger.info(
- "✅ Multiple documents uploaded to n8n",
- extra={
- "claim_id": claim_id,
- "document_type": document_type,
- "file_ids": file_ids,
- "files_count": len(files),
- },
- )
-
- # Парсим ответ от n8n
- try:
- n8n_response = json.loads(response_text)
- except json.JSONDecodeError:
- n8n_response = {"raw": response_text}
-
- # Публикуем событие в Redis
- event_data = {
- "event_type": "documents_uploaded",
- "status": "processing",
- "claim_id": claim_id,
- "session_id": session_id,
- "document_type": document_type,
- "file_ids": file_ids,
- "files_count": len(files),
- "original_filenames": [f.filename for f in files],
- "timestamp": datetime.utcnow().isoformat(),
- }
-
- await redis_service.publish(
- f"ocr_events:{session_id}",
- json.dumps(event_data, ensure_ascii=False)
- )
-
- return {
- "success": True,
- "file_ids": file_ids,
- "files_count": len(files),
- "document_type": document_type,
- "ocr_status": "processing",
- "message": f"Загружено {len(files)} файл(ов)",
- "n8n_response": n8n_response,
- }
-
- else:
- logger.error(
- "❌ n8n multiple upload error",
- extra={
- "status_code": response.status_code,
- "body": response_text[:500],
- },
- )
- raise HTTPException(
- status_code=response.status_code,
- detail=f"Ошибка n8n: {response_text}",
- )
-
- except httpx.TimeoutException:
- logger.error("⏱️ n8n multiple upload timeout")
- raise HTTPException(status_code=504, detail="Таймаут загрузки документов")
-
- except HTTPException:
- raise
-
- except Exception as e:
- logger.exception("❌ Multiple upload error")
- raise HTTPException(
- status_code=500,
- detail=f"Ошибка загрузки документов: {str(e)}",
- )
-
-
-@router.get("/status/{claim_id}")
-async def get_documents_status(claim_id: str):
- """
- Получить статус обработки документов для заявки.
-
- Возвращает:
- - Список загруженных документов и их OCR статус
- - Общий прогресс обработки
- """
- try:
- # TODO: Запрос в PostgreSQL для получения статуса документов
- # Пока возвращаем mock данные
-
- return {
- "success": True,
- "claim_id": claim_id,
- "documents": [],
- "ocr_progress": {
- "total": 0,
- "completed": 0,
- "processing": 0,
- "failed": 0,
- },
- "wizard_ready": False,
- "claim_ready": False,
- }
-
- except Exception as e:
- logger.exception("❌ Error getting documents status")
- raise HTTPException(
- status_code=500,
- detail=f"Ошибка получения статуса: {str(e)}",
- )
-
@router.post("/generate-list")
async def generate_documents_list(request: Request):
@@ -907,3 +617,193 @@ async def generate_documents_list(request: Request):
detail=f"Ошибка генерации списка: {str(e)}",
)
+
+
+
+def compute_documents_hash(doc_ids: List[str]) -> str:
+ """
+ Вычисляет hash от списка document_id для проверки актуальности черновика.
+ Должен совпадать с JS алгоритмом в n8n build_form_draft.
+ """
+ import ctypes
+
+ sorted_ids = sorted([d for d in doc_ids if d])
+ hash_input = ','.join(sorted_ids)
+
+ # djb2 hash — эмуляция JS поведения
+ # В JS: (hash << 5) возвращает 32-битный signed int
+ hash_val = 5381
+ for char in hash_input:
+ # ctypes.c_int32 эмулирует JS 32-битный signed int при сдвиге
+ shifted = ctypes.c_int32(hash_val << 5).value
+ hash_val = shifted + hash_val + ord(char)
+
+ # В JS: Math.abs(hash).toString(16).padStart(8, '0')
+ return format(abs(hash_val), 'x').zfill(8)
+
+
+@router.post("/check-ocr-status")
+async def check_ocr_status(request: Request):
+ """
+ Проверка статуса OCR обработки документов.
+
+ Вызывается при нажатии "Продолжить" после загрузки документов.
+
+ Логика:
+ 1. Проверяем наличие form_draft в payload
+ 2. Если черновик есть и documents_hash совпадает — возвращаем его
+ 3. Если черновика нет или он устарел — запускаем RAG workflow
+ """
+ try:
+ body = await request.json()
+
+ claim_id = body.get("claim_id")
+ session_id = body.get("session_id")
+ force_refresh = body.get("force_refresh", False) # Принудительное обновление
+
+ if not claim_id or not session_id:
+ raise HTTPException(
+ status_code=400,
+ detail="claim_id и session_id обязательны",
+ )
+
+ logger.info(
+ "🔍 Check OCR status request",
+ extra={
+ "claim_id": claim_id,
+ "session_id": session_id,
+ "force_refresh": force_refresh,
+ },
+ )
+
+ # =====================================================
+ # ШАГ 1: Проверяем наличие черновика в БД
+ # =====================================================
+ if not force_refresh:
+ try:
+ # Получаем form_draft и список документов
+ claim_data = await db.fetch_one("""
+ SELECT
+ c.payload->'form_draft' AS form_draft,
+ (
+ SELECT array_agg(cd.id::text ORDER BY cd.id)
+ FROM clpr_claim_documents cd
+ WHERE cd.claim_id::uuid = c.id
+ ) AS document_ids
+ FROM clpr_claims c
+ WHERE c.id = $1::uuid
+ """, claim_id)
+
+ if claim_data and claim_data.get('form_draft'):
+ form_draft = claim_data['form_draft']
+ # Если form_draft — строка, парсим JSON
+ if isinstance(form_draft, str):
+ form_draft = json.loads(form_draft)
+
+ saved_hash = form_draft.get('documents_hash', '')
+ document_ids = claim_data.get('document_ids') or []
+ current_hash = compute_documents_hash(document_ids)
+
+ logger.info(
+ "📋 Draft check",
+ extra={
+ "saved_hash": saved_hash,
+ "current_hash": current_hash,
+ "docs_count": len(document_ids),
+ },
+ )
+
+ # ✅ Черновик актуален — возвращаем его!
+ if saved_hash == current_hash:
+ logger.info(
+ "✅ Using cached form_draft",
+ extra={
+ "claim_id": claim_id,
+ "hash": saved_hash,
+ },
+ )
+
+ # Публикуем событие что данные готовы
+ event_data = {
+ "event_type": "form_draft_ready",
+ "status": "ready",
+ "message": "Черновик формы готов",
+ "claim_id": claim_id,
+ "session_id": session_id,
+ "form_draft": form_draft,
+ "from_cache": True,
+ "timestamp": datetime.utcnow().isoformat(),
+ }
+
+ await redis_service.publish(
+ f"ocr_events:{session_id}",
+ json.dumps(event_data, ensure_ascii=False)
+ )
+
+ return {
+ "success": True,
+ "status": "ready",
+ "message": "Черновик формы готов (из кэша)",
+ "from_cache": True,
+ "form_draft": form_draft,
+ "listen_channel": f"ocr_events:{session_id}",
+ }
+ else:
+ logger.info(
+ "🔄 Draft outdated, running RAG",
+ extra={
+ "reason": "documents_hash mismatch",
+ "saved_hash": saved_hash,
+ "current_hash": current_hash,
+ },
+ )
+
+ except Exception as e:
+ logger.warning(f"⚠️ Draft check failed: {e}, proceeding with RAG")
+
+ # =====================================================
+ # ШАГ 2: Черновика нет или устарел — запускаем RAG
+ # =====================================================
+ event_data = {
+ "claim_id": claim_id,
+ "session_token": session_id,
+ "timestamp": datetime.utcnow().isoformat(),
+ }
+
+ channel = "clpr:check:ocr_status"
+
+ subscribers = await redis_service.publish(
+ channel,
+ json.dumps(event_data, ensure_ascii=False)
+ )
+
+ logger.info(
+ "✅ OCR status check published (running RAG)",
+ extra={
+ "channel": channel,
+ "subscribers": subscribers,
+ "claim_id": claim_id,
+ },
+ )
+
+ return {
+ "success": True,
+ "status": "processing",
+ "message": "Запрос на обработку документов отправлен",
+ "from_cache": False,
+ "channel": channel,
+ "listen_channel": f"ocr_events:{session_id}",
+ }
+
+ except HTTPException:
+ raise
+
+ except Exception as e:
+ logger.exception("❌ Error checking OCR status")
+ raise HTTPException(
+ status_code=500,
+ detail=f"Ошибка проверки статуса: {str(e)}",
+ )
+
+
+router.add_api_route("/skip", skip_document, methods=["POST"], tags=["Documents"])
diff --git a/backend/app/api/events.py b/backend/app/api/events.py
index 88240a4..3ee1057 100644
--- a/backend/app/api/events.py
+++ b/backend/app/api/events.py
@@ -13,7 +13,7 @@ import logging
logger = logging.getLogger(__name__)
-router = APIRouter()
+router = APIRouter(prefix="/api/v1", tags=["Events"])
class EventPublish(BaseModel):
@@ -215,11 +215,97 @@ async def stream_events(task_id: str):
except Exception as e:
logger.error(f"❌ Error loading wizard data from PostgreSQL: {e}")
+ # ✅ Обработка ocr_status ready: загружаем form_draft из PostgreSQL
+ if actual_event.get('event_type') == 'ocr_status' and actual_event.get('status') == 'ready':
+ claim_id = actual_event.get('claim_id') or actual_event.get('data', {}).get('claim_id')
+ # ✅ Получаем cf_2624 из события (Данные подтверждены)
+ cf_2624 = actual_event.get('cf_2624')
+
+ if claim_id:
+ logger.info(f"🔍 OCR ready event received, loading form_draft for claim_id={claim_id}, cf_2624={cf_2624}")
+
+ try:
+ # ✅ Если есть cf_2624 в событии - сохраняем в черновик
+ if cf_2624 is not None:
+ try:
+ update_query = """
+ UPDATE clpr_claims
+ SET payload = jsonb_set(
+ COALESCE(payload, '{}'::jsonb),
+ '{cf_2624}',
+ $1::jsonb
+ )
+ WHERE id::text = $2 OR payload->>'claim_id' = $2
+ RETURNING id;
+ """
+ await db.execute(update_query, json.dumps(cf_2624), claim_id)
+ logger.info(f"✅ Сохранён cf_2624={cf_2624} в черновик claim_id={claim_id}")
+ except Exception as e:
+ logger.warning(f"⚠️ Не удалось сохранить cf_2624 в черновик: {e}")
+
+ # Загружаем form_draft и documents из PostgreSQL
+ query = """
+ SELECT
+ c.id,
+ c.payload->'form_draft' as form_draft,
+ c.payload->'documents_required' as documents_required,
+ c.payload->'documents_meta' as documents_meta,
+ c.payload->>'cf_2624' as cf_2624
+ FROM clpr_claims c
+ WHERE c.id::text = $1 OR c.payload->>'claim_id' = $1
+ LIMIT 1
+ """
+
+ row = await db.fetch_one(query, claim_id)
+
+ if row:
+ # Парсим JSONB поля (могут быть строками)
+ form_draft_raw = row.get('form_draft')
+ documents_required_raw = row.get('documents_required')
+ documents_meta_raw = row.get('documents_meta')
+ cf_2624_from_db = row.get('cf_2624') # ✅ Получаем cf_2624 из БД
+
+ # Парсим если строка
+ def parse_json_field(val):
+ if val is None:
+ return None
+ if isinstance(val, str):
+ try:
+ return json.loads(val)
+ except:
+ return val
+ return val
+
+ form_draft = parse_json_field(form_draft_raw)
+ documents_required = parse_json_field(documents_required_raw)
+ documents_meta = parse_json_field(documents_meta_raw)
+
+ # Обогащаем событие данными из БД
+ actual_event['data'] = {
+ 'claim_id': claim_id,
+ 'all_ready': True,
+ 'form_draft': form_draft,
+ 'documents_required': documents_required,
+ 'documents_meta': documents_meta,
+ }
+
+ # ✅ Добавляем cf_2624 в событие (из БД или из события)
+ actual_event['cf_2624'] = cf_2624_from_db or cf_2624 or "0"
+
+ logger.info(f"✅ Form draft loaded from PostgreSQL for claim_id={claim_id}, has_form_draft={form_draft is not None}, cf_2624={actual_event.get('cf_2624')}")
+ else:
+ logger.warning(f"⚠️ Claim not found in PostgreSQL: claim_id={claim_id}")
+ except Exception as e:
+ logger.error(f"❌ Error loading form_draft from PostgreSQL: {e}")
+
# Отправляем событие клиенту (плоский формат)
- event_json = json.dumps(actual_event, ensure_ascii=False)
+ event_json = json.dumps(actual_event, ensure_ascii=False, default=str)
event_type_sent = actual_event.get('event_type', 'unknown')
event_status = actual_event.get('status', 'unknown')
- logger.info(f"📤 Sending event to client: type={event_type_sent}, status={event_status}")
+ # Логируем размер и наличие данных
+ data_info = actual_event.get('data', {})
+ has_form_draft = 'form_draft' in data_info if isinstance(data_info, dict) else False
+ logger.info(f"📤 Sending event to client: type={event_type_sent}, status={event_status}, json_len={len(event_json)}, has_form_draft={has_form_draft}")
yield f"data: {event_json}\n\n"
# Если обработка завершена - закрываем соединение
@@ -232,6 +318,11 @@ async def stream_events(task_id: str):
if event_type_sent in ['claim_ready', 'claim_plan_ready']:
logger.info(f"✅ Final event {event_type_sent} sent, closing SSE")
break
+
+ # Закрываем для ocr_status ready (форма заявления готова)
+ if event_type_sent == 'ocr_status' and event_status == 'ready':
+ logger.info(f"✅ OCR ready event sent, closing SSE")
+ break
else:
logger.info(f"⏰ Timeout waiting for message on {channel}")
diff --git a/backend/app/api/models.py b/backend/app/api/models.py
index 165fc66..eb6ad23 100644
--- a/backend/app/api/models.py
+++ b/backend/app/api/models.py
@@ -44,7 +44,8 @@ class ClaimCreateRequest(BaseModel):
# Шаг 3: Данные для выплаты
payment_method: str = "sbp" # "sbp", "card", "bank_transfer"
- bank_name: Optional[str] = None
+ bank_id: Optional[str] = None # ID банка из NSPK API (bankid)
+ bank_name: Optional[str] = None # Название банка для отображения
card_number: Optional[str] = None
account_number: Optional[str] = None
diff --git a/backend/app/config.py b/backend/app/config.py
index c9f2cd2..ad11425 100644
--- a/backend/app/config.py
+++ b/backend/app/config.py
@@ -42,6 +42,15 @@ class Settings(BaseSettings):
mysql_user: str = "root"
mysql_password: str = ""
+ # ============================================
+ # MYSQL CRM (vtiger CRM)
+ # ============================================
+ mysql_crm_host: str = "localhost" # В режиме network_mode: host используем localhost # Доступ к хосту из Docker контейнера
+ mysql_crm_port: int = 3306
+ mysql_crm_db: str = "ci20465_72new"
+ mysql_crm_user: str = "ci20465_72new"
+ mysql_crm_password: str = "EcY979Rn"
+
@property
def database_url(self) -> str:
"""Формирует URL для подключения к PostgreSQL"""
diff --git a/backend/app/main.py b/backend/app/main.py
index 1a51a5d..6bdf28d 100644
--- a/backend/app/main.py
+++ b/backend/app/main.py
@@ -11,6 +11,7 @@ from .services.database import db
from .services.redis_service import redis_service
from .services.rabbitmq_service import rabbitmq_service
from .services.policy_service import policy_service
+from .services.crm_mysql_service import crm_mysql_service
from .services.s3_service import s3_service
from .api import sms, claims, policy, upload, draft, events, n8n_proxy, session, documents
@@ -56,6 +57,12 @@ async def lifespan(app: FastAPI):
except Exception as e:
logger.warning(f"⚠️ MySQL Policy DB not available: {e}")
+ try:
+ # Подключаем MySQL CRM (vtiger)
+ await crm_mysql_service.connect()
+ except Exception as e:
+ logger.warning(f"⚠️ MySQL CRM DB not available: {e}")
+
try:
# Подключаем S3 (для загрузки файлов)
s3_service.connect()
@@ -73,6 +80,7 @@ async def lifespan(app: FastAPI):
await redis_service.disconnect()
await rabbitmq_service.disconnect()
await policy_service.close()
+ await crm_mysql_service.close()
logger.info("👋 Ticket Form Intake Platform stopped")
diff --git a/backend/app/services/crm_mysql_service.py b/backend/app/services/crm_mysql_service.py
new file mode 100644
index 0000000..091a1b2
--- /dev/null
+++ b/backend/app/services/crm_mysql_service.py
@@ -0,0 +1,118 @@
+"""
+CRM MySQL Service - Подключение к MySQL БД vtiger CRM
+"""
+import aiomysql
+from typing import Optional, Dict, Any, List
+from ..config import settings
+import logging
+
+logger = logging.getLogger(__name__)
+
+
+class CrmMySQLService:
+ """Сервис для работы с MySQL БД vtiger CRM"""
+
+ def __init__(self):
+ self.pool: Optional[aiomysql.Pool] = None
+
+ async def connect(self):
+ """Подключение к MySQL БД vtiger CRM"""
+ try:
+ self.pool = await aiomysql.create_pool(
+ host=settings.mysql_crm_host,
+ port=settings.mysql_crm_port,
+ user=settings.mysql_crm_user,
+ password=settings.mysql_crm_password,
+ db=settings.mysql_crm_db,
+ autocommit=True,
+ minsize=1,
+ maxsize=5
+ )
+ logger.info(f"✅ MySQL CRM DB connected: {settings.mysql_crm_host}:{settings.mysql_crm_port}/{settings.mysql_crm_db}")
+ except Exception as e:
+ logger.error(f"❌ MySQL CRM DB connection error: {e}")
+ raise
+
+ async def fetch_one(self, query: str, *args) -> Optional[Dict[str, Any]]:
+ """
+ Выполнить SQL запрос и вернуть одну запись
+
+ Args:
+ query: SQL запрос с плейсхолдерами %s
+ *args: Параметры для запроса
+
+ Returns:
+ Dict с данными или None если не найдено
+ """
+ if not self.pool:
+ await self.connect()
+
+ try:
+ async with self.pool.acquire() as conn:
+ async with conn.cursor(aiomysql.DictCursor) as cursor:
+ await cursor.execute(query, args)
+ result = await cursor.fetchone()
+ return dict(result) if result else None
+ except Exception as e:
+ logger.error(f"❌ Error executing query: {e}")
+ raise
+
+ async def fetch_all(self, query: str, *args) -> List[Dict[str, Any]]:
+ """
+ Выполнить SQL запрос и вернуть все записи
+
+ Args:
+ query: SQL запрос с плейсхолдерами %s
+ *args: Параметры для запроса
+
+ Returns:
+ List[Dict] с данными
+ """
+ if not self.pool:
+ await self.connect()
+
+ try:
+ async with self.pool.acquire() as conn:
+ async with conn.cursor(aiomysql.DictCursor) as cursor:
+ await cursor.execute(query, args)
+ results = await cursor.fetchall()
+ return [dict(row) for row in results] if results else []
+ except Exception as e:
+ logger.error(f"❌ Error executing query: {e}")
+ raise
+
+ async def execute(self, query: str, *args) -> int:
+ """
+ Выполнить SQL запрос (INSERT, UPDATE, DELETE)
+
+ Args:
+ query: SQL запрос с плейсхолдерами %s
+ *args: Параметры для запроса
+
+ Returns:
+ Количество затронутых строк
+ """
+ if not self.pool:
+ await self.connect()
+
+ try:
+ async with self.pool.acquire() as conn:
+ async with conn.cursor() as cursor:
+ await cursor.execute(query, args)
+ return cursor.rowcount
+ except Exception as e:
+ logger.error(f"❌ Error executing query: {e}")
+ raise
+
+ async def close(self):
+ """Закрыть пул подключений"""
+ if self.pool:
+ self.pool.close()
+ await self.pool.wait_closed()
+ logger.info("MySQL CRM DB pool closed")
+
+
+# Глобальный экземпляр
+crm_mysql_service = CrmMySQLService()
+
+
diff --git a/docker-compose.yml b/docker-compose.yml
index 96232e1..0855658 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -19,12 +19,9 @@ services:
ticket_form_backend:
container_name: ticket_form_backend
build: ./backend
- ports:
- - "${TICKET_FORM_BACKEND_PORT:-8200}:8200"
+ network_mode: host
env_file:
- .env
- networks:
- - ticket-form-network
restart: unless-stopped
redis:
diff --git a/docs/BACKEND_GET_CONTACT_CF_2624_FROM_POSTGRESQL.md b/docs/BACKEND_GET_CONTACT_CF_2624_FROM_POSTGRESQL.md
new file mode 100644
index 0000000..3a931b8
--- /dev/null
+++ b/docs/BACKEND_GET_CONTACT_CF_2624_FROM_POSTGRESQL.md
@@ -0,0 +1,97 @@
+# Получение cf_2624 из MySQL при загрузке черновика
+
+## ✅ Упрощённый подход
+
+Вместо передачи `cf_2624` через события Redis, просто делаем прямой SQL запрос к MySQL при загрузке черновика.
+
+## Где это происходит
+
+**Файл:** `ticket_form/backend/app/api/claims.py`
+**Эндпоинт:** `GET /api/v1/claims/drafts/{claim_id}`
+**Функция:** `get_draft()`
+
+## Как работает
+
+1. **Получаем `contact_id` из черновика:**
+ ```python
+ contact_id = payload.get('contact_id')
+ ```
+
+2. **Делаем SQL запрос к MySQL:**
+ ```sql
+ SELECT
+ cd.contactid,
+ cd.firstname,
+ cd.lastname,
+ cd.email,
+ cd.mobile,
+ ccf.cf_2624 AS cf_2624
+ FROM vtiger_contactdetails cd
+ LEFT JOIN vtiger_contactscf ccf ON ccf.contactid = cd.contactid
+ LEFT JOIN vtiger_crmentity ce ON ce.crmid = cd.contactid
+ WHERE cd.contactid = %s
+ AND ce.deleted = 0
+ LIMIT 1
+ ```
+
+3. **Используем `cf_2624` для блокировки полей:**
+ ```python
+ contact_data_confirmed = (cf_2624 == "1")
+ contact_data_can_edit = not contact_data_confirmed
+ ```
+
+## Преимущества
+
+1. ✅ **Проще** - один SQL запрос вместо цепочки событий
+2. ✅ **Быстрее** - прямой запрос к БД
+3. ✅ **Надёжнее** - не зависит от событий Redis
+4. ✅ **Актуальнее** - всегда получаем свежие данные из БД
+
+## Что не нужно делать
+
+- ❌ Передавать `cf_2624` через события Redis
+- ❌ Сохранять `cf_2624` в черновик при обработке событий
+- ❌ Использовать webservice API для получения `cf_2624`
+
+## Проверка
+
+1. ✅ При загрузке черновика делается SQL запрос к PostgreSQL
+2. ✅ Получаем `cf_2624` из таблицы `vtiger_contactscf`
+3. ✅ Используем для блокировки полей на фронтенде
+
+---
+
+## Реализация
+
+### MySQL Connection для CRM
+
+Создан отдельный сервис `CrmMySQLService` для подключения к MySQL БД vtiger CRM:
+
+**Файл:** `ticket_form/backend/app/services/crm_mysql_service.py`
+
+**Credentials (из config.php):**
+- Host: `localhost`
+- Port: `3306`
+- Database: `ci20465_72new`
+- User: `ci20465_72new`
+- Password: `EcY979Rn`
+
+### Использование в коде
+
+```python
+from ..services.crm_mysql_service import crm_mysql_service
+
+# SQL запрос с MySQL синтаксисом (%s вместо $1)
+contact_query = """
+SELECT ... FROM vtiger_contactdetails cd
+WHERE cd.contactid = %s
+"""
+contact_row = await crm_mysql_service.fetch_one(contact_query, contact_id)
+```
+
+### Отличия от PostgreSQL
+
+- Параметры: `%s` вместо `$1`
+- Синтаксис JOIN: тот же
+- LIMIT: тот же
+
diff --git a/docs/CF_2624_IMPLEMENTATION_SUMMARY.md b/docs/CF_2624_IMPLEMENTATION_SUMMARY.md
new file mode 100644
index 0000000..d777f17
--- /dev/null
+++ b/docs/CF_2624_IMPLEMENTATION_SUMMARY.md
@@ -0,0 +1,136 @@
+# Реализация проверки cf_2624 при формировании заявления
+
+## ✅ Что сделано
+
+### 1. Backend API (`/drafts/{claim_id}`)
+- ✅ Получает `cf_2624` из CRM через webservice `retrieve`
+- ✅ Преобразует в `contact_data_confirmed` (boolean)
+- ✅ Возвращает в ответе API вместе с `contact_data_from_crm`
+
+**Файл:** `ticket_form/backend/app/api/claims.py` (строки 459-539)
+
+### 2. Frontend - Загрузка черновика
+- ✅ Получает `contact_data_confirmed` из ответа API
+- ✅ Сохраняет в `formData`
+- ✅ Передаёт в `claimPlanData` для `StepClaimConfirmation`
+
+**Файл:** `ticket_form/frontend/src/pages/ClaimForm.tsx` (строки 564-848)
+
+### 3. Frontend - Форма подтверждения
+- ✅ `StepClaimConfirmation` получает `contact_data_confirmed` из `claimPlanData`
+- ✅ Передаёт в `generateConfirmationFormHTML`
+- ✅ Форма блокирует персональные данные если `contact_data_confirmed = true`
+
+**Файлы:**
+- `ticket_form/frontend/src/components/form/StepClaimConfirmation.tsx` (строки 89-96)
+- `ticket_form/frontend/src/components/form/generateConfirmationFormHTML.ts` (строки 4, 293, 724-740, 840, 907-915)
+
+### 4. CreateWebContact
+- ✅ Возвращает `cf_2624` в JSON ответе
+- ✅ Для новых контактов: `cf_2624 = "0"`
+- ✅ Для существующих: берёт значение из CRM
+
+**Файл:** `include/Webservices/CreateWebContact.php`
+
+---
+
+## ⏳ Что нужно сделать
+
+### 1. Обновить n8n workflow `6mxRJ2LLHmQXyaDz`
+
+**После ноды `CreateWebContacКлиентправ`:**
+
+Добавить ноду `Code: Extract Contact Data Confirmed`:
+
+```javascript
+// Парсим результат CreateWebContact
+const rawResult = $node["CreateWebContacКлиентправ"].json.result;
+const contactData = JSON.parse(rawResult);
+
+// Извлекаем cf_2624 (Данные подтверждены)
+const cf_2624 = contactData.cf_2624 || "0";
+const contact_data_confirmed = cf_2624 === "1";
+
+return {
+ contact_id: contactData.contact_id,
+ is_new_contact: contactData.is_new,
+ cf_2624: cf_2624,
+ contact_data_confirmed: contact_data_confirmed,
+ contact_data_can_edit: !contact_data_confirmed
+};
+```
+
+**В ноде `Code in JavaScriptКлиентправ` (формирование ответа):**
+
+Добавить в return:
+
+```javascript
+const contactStatus = $('Code: Extract Contact Data Confirmed').first().json;
+
+return {
+ // ... существующие поля ...
+ contact_data_confirmed: contactStatus.contact_data_confirmed || false,
+ contact_data_can_edit: contactStatus.contact_data_can_edit !== false,
+ cf_2624: contactStatus.cf_2624 || "0",
+ // ... остальные поля ...
+};
+```
+
+**См. подробности:** `ticket_form/docs/N8N_UPDATE_CF_2624_IN_RESPONSE.md`
+
+---
+
+## 🔄 Логика работы
+
+### Сценарий 1: Загрузка черновика
+1. Пользователь выбирает черновик
+2. Frontend вызывает `/api/v1/claims/drafts/{claim_id}`
+3. Backend получает `cf_2624` из CRM
+4. Backend возвращает `contact_data_confirmed = (cf_2624 === "1")`
+5. Frontend передаёт флаг в форму подтверждения
+6. Форма блокирует поля если `contact_data_confirmed = true`
+
+### Сценарий 2: Новое заявление (через n8n)
+1. Пользователь вводит телефон
+2. n8n вызывает `CreateWebContact`
+3. `CreateWebContact` возвращает `cf_2624` в ответе
+4. n8n извлекает `cf_2624` и передаёт в ответе для фронтенда
+5. Frontend получает `contact_data_confirmed` из ответа n8n
+6. Форма блокирует поля если `contact_data_confirmed = true`
+
+---
+
+## 📋 Какие поля блокируются
+
+Если `contact_data_confirmed = true`, блокируются следующие поля:
+- ✅ Фамилия (`lastname`)
+- ✅ Имя (`firstname`)
+- ✅ Отчество (`secondname`, `middle_name`)
+- ✅ ИНН (`inn`)
+- ✅ Дата рождения (`birthday`)
+- ✅ Место рождения (`birthplace`, `birth_place`)
+- ✅ Адрес (`mailingstreet`, `address`)
+- ✅ Email (`email`)
+
+**Телефон (`mobile`) всегда только для чтения** (не зависит от флага)
+
+---
+
+## 🧪 Проверка
+
+1. ✅ Создать контакт в CRM → `cf_2624` должен быть "0"
+2. ✅ Загрузить черновик → поля должны быть редактируемыми
+3. ⏳ Установить `cf_2624 = "1"` в CRM
+4. ⏳ Загрузить черновик → поля должны быть заблокированы
+5. ⏳ Проверить предупреждение "⚠️ Данные подтверждены" в форме
+
+---
+
+## 📝 Документация
+
+- `ticket_form/docs/CRM_CONTACT_DATA_CONFIRMED_FIELD.md` - Описание поля cf_2624
+- `ticket_form/docs/CREATE_WEB_CONTACT_RESPONSE_FORMAT.md` - Формат ответа CreateWebContact
+- `ticket_form/docs/N8N_UPDATE_CF_2624_IN_RESPONSE.md` - Обновление n8n workflow
+- `ticket_form/docs/CODE_CREATE_WEB_CONTACT_FINAL.js` - Код для n8n (обновлён)
+
+
diff --git a/docs/CF_2624_IN_OCR_STATUS_EVENT.md b/docs/CF_2624_IN_OCR_STATUS_EVENT.md
new file mode 100644
index 0000000..9a0e440
--- /dev/null
+++ b/docs/CF_2624_IN_OCR_STATUS_EVENT.md
@@ -0,0 +1,114 @@
+# Добавление cf_2624 в событие ocr_status ready
+
+## ✅ Да, правильно!
+
+Событие `ocr_status` с `status: "ready"` должно содержать поле `cf_2624` и сохраняться в черновик.
+
+## Формат события в Redis
+
+**Канал:** `ocr_events:sess_5fc7cdd1-a848-4e92-aed4-3ee4bfb19b4c`
+
+**Событие:**
+```json
+{
+ "event_type": "ocr_status",
+ "status": "ready",
+ "claim_id": "ef853bac-f54b-46aa-adf8-f0c9c0cd76bc",
+ "message": "Заявление сформировано",
+ "timestamp": "2025-12-03T12:44:12.347Z",
+ "cf_2624": "0"
+}
+```
+
+## Что происходит
+
+### 1. n8n workflow публикует событие
+
+После сохранения черновика (после `claimsave`) n8n публикует событие в Redis канал `ocr_events:{session_id}` с полем `cf_2624`.
+
+**Где добавить:** После ноды `claimsave`, перед публикацией в Redis.
+
+**См. подробности:** `ticket_form/docs/N8N_ADD_CF_2624_TO_OCR_STATUS_EVENT.md`
+
+---
+
+### 2. Backend обрабатывает событие
+
+Backend получает событие из Redis и:
+- ✅ Загружает `form_draft` из PostgreSQL
+- ✅ **Сохраняет `cf_2624` в черновик** (в `payload.cf_2624`)
+- ✅ Отправляет событие на фронтенд через SSE
+
+**Файл:** `ticket_form/backend/app/api/events.py` (строки 218-273)
+
+---
+
+### 3. Сохранение в черновик
+
+`cf_2624` сохраняется в таблицу `clpr_claims` в поле `payload.cf_2624`:
+
+```sql
+UPDATE clpr_claims
+SET payload = jsonb_set(
+ COALESCE(payload, '{}'::jsonb),
+ '{cf_2624}',
+ '"0"'::jsonb -- или '"1"'
+)
+WHERE id::text = $1 OR payload->>'claim_id' = $1;
+```
+
+---
+
+## Порядок работы
+
+1. **n8n workflow:**
+ - `CreateWebContacКлиентправ` → получает `cf_2624` из CRM
+ - `claimsave` → сохраняет черновик
+ - `Code: Prepare OCR Status Event` → формирует событие с `cf_2624`
+ - `HTTP Request` или `Redis Publish` → публикует в `ocr_events:{session_id}`
+
+2. **Backend:**
+ - Получает событие из Redis
+ - Сохраняет `cf_2624` в черновик
+ - Загружает `form_draft` из PostgreSQL
+ - Отправляет на фронтенд через SSE
+
+3. **Фронтенд:**
+ - Получает событие через SSE
+ - Использует `cf_2624` для блокировки полей
+
+---
+
+## Проверка
+
+1. ✅ Событие публикуется в `ocr_events:{session_id}` с `cf_2624`
+2. ✅ Backend сохраняет `cf_2624` в черновик (`payload.cf_2624`)
+3. ✅ При загрузке черновика `cf_2624` доступен в `payload.cf_2624`
+
+---
+
+## SQL для проверки
+
+```sql
+-- Проверить, что cf_2624 сохранён в черновик
+SELECT
+ id,
+ payload->>'claim_id' as claim_id,
+ payload->>'cf_2624' as cf_2624,
+ updated_at
+FROM clpr_claims
+WHERE payload->>'claim_id' = 'ef853bac-f54b-46aa-adf8-f0c9c0cd76bc'
+ORDER BY updated_at DESC
+LIMIT 1;
+```
+
+---
+
+## Итого
+
+✅ **Да, правильно!** Событие `ocr_status` с `status: "ready"` должно содержать `cf_2624`, и это значение будет:
+- Публиковаться в Redis канал `ocr_events:{session_id}`
+- Сохраняться в черновик в `payload.cf_2624`
+- Использоваться для блокировки полей на фронтенде
+
+
diff --git a/docs/CLAIM_226564ce_STATUS.md b/docs/CLAIM_226564ce_STATUS.md
new file mode 100644
index 0000000..1f8f226
--- /dev/null
+++ b/docs/CLAIM_226564ce_STATUS.md
@@ -0,0 +1,94 @@
+# Статус заявки 226564ce-d7cf-48ee-a820-690e8f5ec8e5
+
+## ✅ Общая информация
+
+- **ID**: `226564ce-d7cf-48ee-a820-690e8f5ec8e5`
+- **Status**: `draft_docs_complete`
+- **Unified ID**: `usr_b1fbffa0-477b-4abb-95d6-8d6f849ddc71`
+- **Session Token**: `sess_c278abf8-1603-484d-af98-8b93843e5253`
+- **Phone**: `71234543212`
+- **Channel**: `web_form`
+- **Is Confirmed**: `false` (должна отображаться в списке)
+- **Created**: `2025-12-01 14:38:11`
+- **Updated**: `2025-12-01 20:06:18`
+- **Expires**: `2025-12-15 19:35:30`
+
+## ✅ Документы
+
+### documents_meta (2 записи)
+
+1. **uploads[1][0]**
+ - `field_label`: "Чек или подтверждение оплаты" ✅ (правильно, не "group-2")
+ - `file_id`: `/f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c/crm2/CRM_Active_Files/Documents/Project/ERV_3212_КлиентПрав_399543/e34f2f9e-e48d-47f4-9c2d-6957012c0800__chek-ili-podtverzhdenie-oplaty.pdf`
+ - `file_name`: `e34f2f9e-e48d-47f4-9c2d-6957012c0800__chek-ili-podtverzhdenie-oplaty.pdf`
+ - `uploaded_at`: `2025-12-01T14:15:54.122Z`
+
+2. **uploads[0][0]**
+ - `field_label`: "Договор или заказ" ✅ (правильно)
+ - `file_id`: `/f9825c87-4e3558f6-f9b6-405c-ad3d-d1535c49b61c/crm2/CRM_Active_Files/Documents/Project/ERV_3212_КлиентПрав_399543/344deab2-1a3a-46ce-931b-5a29bb2c40a3__dogovor-ili-zakaz.pdf`
+ - `file_name`: `344deab2-1a3a-46ce-931b-5a29bb2c40a3__dogovor-ili-zakaz.pdf`
+ - `uploaded_at`: `2025-12-01T13:47:15.772Z`
+
+### clpr_claim_documents (2 записи)
+
+1. **uploads[1][0]**
+ - `id`: `e34f2f9e-e48d-47f4-9c2d-6957012c0800`
+ - `file_hash`: `3e1f1332a76b7f26df1628c49579f30a873de9170f3b8007b0bac5e4a439ca67` ✅
+
+2. **uploads[0][0]**
+ - `id`: `344deab2-1a3a-46ce-931b-5a29bb2c40a3`
+ - `file_hash`: `83822e59662aa2037977dc5a8661d8a057ae6572e6f99936a31c6cdd7d66f1d9` ✅
+
+## ✅ Проверки
+
+- ✅ **Дубликатов нет** — все `field_name` уникальны
+- ✅ **field_label правильные** — не "group-2", а реальные названия
+- ✅ **Синхронизация** — `documents_meta` и `clpr_claim_documents` совпадают
+- ✅ **file_hash заполнен** — оба документа имеют хеш
+- ✅ **Заявка должна отображаться** — `is_confirmed = false`, `status_code != 'approved'`
+
+## 📋 Payload структура
+
+Заявка содержит следующие ключи в `payload`:
+- `body`
+- `email`
+- `phone`
+- `tg_id`
+- `answers`
+- `claim_id`
+- `applicant`
+- `contact_id`
+- `form_draft`
+- `ai_analysis`
+- `claim_ready`
+- `wizard_plan`
+- `wizard_ready`
+- `ai_agent13_rag`
+- `documents_meta` ✅
+- `ai_agent1_facts`
+- `answers_prefill`
+- `current_doc_index`
+- `documents_skipped`
+- `documents_required`
+- `documents_uploaded`
+- `problem_description`
+
+## 🔍 Возможные проблемы с отображением
+
+Если заявка не отображается или отображается неправильно, проверьте:
+
+1. **API endpoint `/drafts/list`** — должен находить заявку по `unified_id`, `phone` или `session_token`
+2. **Фронтенд фильтрация** — возможно, фильтруется по `status_code`
+3. **Отображение `field_label`** — должно использовать `documents_meta[].field_label`, а не вычислять из `field_name`
+
+## ✅ Вывод
+
+**Заявка в порядке!** Все данные корректны:
+- ✅ Нет дубликатов в `documents_meta`
+- ✅ `field_label` правильные
+- ✅ Документы синхронизированы
+- ✅ `file_hash` заполнен
+- ✅ Заявка должна отображаться в списке
+
+Если есть проблемы с отображением, они скорее всего на стороне фронтенда или API фильтрации.
+
diff --git a/docs/CODE_CREATE_WEB_CONTACT_FINAL.js b/docs/CODE_CREATE_WEB_CONTACT_FINAL.js
index b7c22a9..c536748 100644
--- a/docs/CODE_CREATE_WEB_CONTACT_FINAL.js
+++ b/docs/CODE_CREATE_WEB_CONTACT_FINAL.js
@@ -1,7 +1,12 @@
// Парсим результат CreateWebContact
const rawResult = $node["CreateWebContact"].json.result;
-const contactData = JSON.parse(rawResult); // {"contact_id": "396625", "is_new": false}
+const contactData = JSON.parse(rawResult); // {"contact_id": "396625", "is_new": false, "cf_2624": "1"}
+
+// ✅ Извлекаем cf_2624 (Данные подтверждены)
+// "1" = данные подтверждены, "0" = не подтверждены
+const cf_2624 = contactData.cf_2624 || "0";
+const contact_data_confirmed = cf_2624 === "1";
const phone = $('Edit Fields').first().json.phone;
@@ -18,6 +23,8 @@ const sessionData = {
contact_id: contactData.contact_id, // ← распарсенный ID из CreateWebContact
phone: phone,
is_new_contact: contactData.is_new, // ← флаг нового контакта
+ cf_2624: cf_2624, // ✅ Сохраняем cf_2624 в сессию
+ contact_data_confirmed: contact_data_confirmed, // ✅ Сохраняем флаг подтверждения
status: "draft",
current_step: 1,
created_at: new Date().toISOString(),
@@ -34,6 +41,10 @@ return {
contact_id: contactData.contact_id,
is_new_contact: contactData.is_new,
phone: phone,
+ // ✅ Флаги подтверждения данных контакта (из cf_2624)
+ cf_2624: cf_2624,
+ contact_data_confirmed: contact_data_confirmed,
+ contact_data_can_edit: !contact_data_confirmed,
redis_key: `session:${session_id}`, // ✅ Используем session_id для ключа Redis
redis_value: JSON.stringify(sessionData),
ttl: 604800
diff --git a/docs/CREATE_WEB_CONTACT_RESPONSE_FORMAT.md b/docs/CREATE_WEB_CONTACT_RESPONSE_FORMAT.md
new file mode 100644
index 0000000..227f081
--- /dev/null
+++ b/docs/CREATE_WEB_CONTACT_RESPONSE_FORMAT.md
@@ -0,0 +1,56 @@
+# Формат ответа CreateWebContact
+
+## Обновление: добавлено поле cf_2624
+
+### Старый формат:
+```json
+{
+ "contact_id": "396625",
+ "is_new": false
+}
+```
+
+### Новый формат (с cf_2624):
+```json
+{
+ "contact_id": "396625",
+ "is_new": false,
+ "cf_2624": "1"
+}
+```
+
+## Описание полей:
+
+- **contact_id** (string) - ID контакта в CRM
+- **is_new** (boolean) - `true` если контакт только что создан, `false` если найден существующий
+- **cf_2624** (string) - "Данные подтверждены":
+ - `"1"` = "Да" (данные подтверждены)
+ - `"0"` = "Нет" (данные не подтверждены)
+
+## Использование в n8n:
+
+```javascript
+// Парсим результат CreateWebContact
+const rawResult = $node["CreateWebContact"].json.result;
+const contactData = JSON.parse(rawResult);
+
+// Получаем данные
+const contact_id = contactData.contact_id;
+const is_new = contactData.is_new;
+const data_confirmed = contactData.cf_2624 === "1"; // true/false
+
+// Используем в дальнейшей логике
+if (data_confirmed) {
+ // Данные подтверждены - блокируем редактирование
+}
+```
+
+## Логика работы:
+
+1. **Новый контакт** (`is_new: true`):
+ - `cf_2624` всегда `"0"` (данные не подтверждены)
+
+2. **Существующий контакт** (`is_new: false`):
+ - `cf_2624` берётся из базы данных CRM
+ - Если поле пустое → возвращается `"0"`
+
diff --git a/docs/CRM_CONTACT_DATA_CONFIRMED_FIELD.md b/docs/CRM_CONTACT_DATA_CONFIRMED_FIELD.md
new file mode 100644
index 0000000..981879a
--- /dev/null
+++ b/docs/CRM_CONTACT_DATA_CONFIRMED_FIELD.md
@@ -0,0 +1,149 @@
+# Добавление поля "Данные подтверждены" в CRM
+
+## Шаг 1: Создание кастомного поля в CRM
+
+1. Зайти в CRM → Настройки → Кастомные поля → Модуль "Контакты"
+2. Создать новое поле:
+ - **Название:** "Данные подтверждены"
+ - **Тип:** "Да/Нет" (Checkbox) или "Список" (Picklist) со значениями "Да"/"Нет"
+ - **Код поля:** `cf_2624` ✅ (уже создано)
+ - **По умолчанию:** "Нет" (false)
+
+3. **ВАЖНО:** Записать номер поля (например, `cf_2624`)
+
+---
+
+## Шаг 2: Обновление backend для проверки поля в CRM
+
+### Файл: `ticket_form/backend/app/api/claims.py`
+
+В функции `get_draft()` вместо проверки PostgreSQL, проверяем поле в CRM:
+
+```python
+# ✅ Проверяем флаг подтверждения данных контакта из CRM
+unified_id = row.get('unified_id')
+contact_data_confirmed = False
+contact_data_can_edit = True
+contact_data_confirmed_at = None
+contact_data_from_crm = None
+
+if unified_id:
+ # Получаем contact_id из payload
+ contact_id = payload.get('contact_id') if isinstance(payload, dict) else None
+
+ if contact_id:
+ try:
+ # Получаем данные контакта из CRM
+ async with httpx.AsyncClient(timeout=30.0) as client:
+ # 1. Get Challenge
+ challenge_response = await client.get(
+ f"{settings.crm_webservice_url}",
+ params={"operation": "getchallenge", "username": "api"}
+ )
+ challenge_data = challenge_response.json()
+ token = challenge_data.get("result", {}).get("token", "")
+
+ # 2. Login
+ import hashlib
+ salt = "4r9ANex8PT2IuRV"
+ access_key = hashlib.md5((token + salt).encode()).hexdigest()
+
+ login_response = await client.post(
+ f"{settings.crm_webservice_url}",
+ data={
+ "operation": "login",
+ "username": "api",
+ "accessKey": access_key
+ }
+ )
+ login_data = login_response.json()
+ session_name = login_data.get("result", {}).get("sessionName", "")
+
+ # 3. Retrieve Contact
+ retrieve_response = await client.post(
+ f"{settings.crm_webservice_url}",
+ data={
+ "operation": "retrieve",
+ "sessionName": session_name,
+ "id": f"12x{contact_id}"
+ }
+ )
+ retrieve_data = retrieve_response.json()
+
+ if retrieve_data.get("success") and retrieve_data.get("result"):
+ contact_data_from_crm = retrieve_data["result"]
+
+ # ✅ Проверяем кастомное поле "Данные подтверждены"
+ confirmed_field = contact_data_from_crm.get("cf_2624", "0") # "1" = да, "0" = нет
+ contact_data_confirmed = confirmed_field == "1" or confirmed_field == "true"
+ contact_data_can_edit = not contact_data_confirmed
+
+ logger.info(
+ f"🔒 Статус данных контакта из CRM: confirmed={contact_data_confirmed}, "
+ f"field_value={confirmed_field}"
+ )
+ except Exception as e:
+ logger.warning(f"⚠️ Не удалось загрузить данные из CRM: {str(e)}")
+```
+
+---
+
+## Шаг 3: Обновление n8n workflow для установки поля
+
+### В workflow `6mxRJ2LLHmQXyaDz`
+
+После подтверждения формы (после SMS-верификации) добавить ноду:
+
+**Название:** `HTTP Request: Set Contact Data Confirmed`
+
+**Метод:** POST
+
+**URL:** `{{ $env.CRM_WEBSERVICE_URL }}`
+
+**Body (form-data):**
+```
+operation: revise
+sessionName: {{ $('Login to CRM').json.sessionName }}
+id: 12x{{ JSON.parse($node['CreateWebContacКлиентправ'].json.result).contact_id }}
+cf_2624: 1
+```
+
+**Где:**
+- `cf_2624` - поле "Данные подтверждены"
+- `1` = "Да" (данные подтверждены)
+
+---
+
+## Шаг 4: Обновление UpsertContact (если используется)
+
+Если используется `UpsertContact.php`, добавить поддержку нового поля:
+
+```php
+// В функции vtws_upsertcontact()
+if (!empty($data_confirmed)) {
+ $params['cf_2624'] = $data_confirmed; // "1" или "0"
+}
+```
+
+---
+
+## Преимущества подхода:
+
+1. ✅ **CRM - источник истины** - все данные в одном месте
+2. ✅ **Нет синхронизации** - не нужно синхронизировать флаги между PostgreSQL и CRM
+3. ✅ **Простота** - один флаг в CRM, проверяем его напрямую
+4. ✅ **Видимость** - менеджеры видят статус в карточке контакта
+5. ✅ **Гибкость** - можно менять статус вручную в CRM
+
+---
+
+## Проверка:
+
+1. ✅ Поле создано в CRM: `cf_2624`
+2. ⏳ Обновить код backend (использовать `cf_2624`)
+3. ⏳ Обновить n8n workflow (использовать `cf_2624`)
+4. ⏳ Протестировать:
+ - Создать контакт → поле должно быть "Нет"
+ - Подтвердить форму → поле должно стать "Да"
+ - Загрузить черновик → поля должны быть заблокированы
+
diff --git a/docs/FRONTEND_UPDATE_CONTACT_DATA_CONFIRMED.md b/docs/FRONTEND_UPDATE_CONTACT_DATA_CONFIRMED.md
new file mode 100644
index 0000000..67ca5d4
--- /dev/null
+++ b/docs/FRONTEND_UPDATE_CONTACT_DATA_CONFIRMED.md
@@ -0,0 +1,217 @@
+# Обновление фронтенда: Блокировка редактирования подтверждённых данных
+
+## Изменения
+
+### 1. Step1Phone.tsx - Получение флага из n8n
+
+**После получения ответа от n8n (после строки ~150):**
+
+```typescript
+// ✅ Извлекаем флаг подтверждения данных
+const contact_data_confirmed = result.contact_data_confirmed || false;
+const contact_data_can_edit = result.contact_data_can_edit !== false; // По умолчанию true
+const contact_data_confirmed_at = result.contact_data_confirmed_at || null;
+
+// Сохраняем в formData
+updateFormData({
+ // ... существующие поля ...
+ contact_data_confirmed: contact_data_confirmed,
+ contact_data_can_edit: contact_data_can_edit,
+ contact_data_confirmed_at: contact_data_confirmed_at,
+});
+```
+
+---
+
+### 2. generateConfirmationFormHTML.ts - Блокировка полей
+
+**Добавить параметр `contact_data_confirmed` в функцию:**
+
+```typescript
+export function generateConfirmationFormHTML(
+ data: any,
+ contact_data_confirmed: boolean = false
+): string {
+ // ... существующий код ...
+
+ // В функции createInputField добавить проверку:
+ function createInputField(root: string, key: string, value: any, label: string, type: string = 'text') {
+ const isReadOnly = contact_data_confirmed && (
+ key === 'firstname' ||
+ key === 'lastname' ||
+ key === 'middle_name' ||
+ key === 'inn' ||
+ key === 'birthday' ||
+ key === 'birthplace' ||
+ key === 'mailingstreet' ||
+ key === 'email'
+ );
+
+ const readonlyAttr = isReadOnly ? 'readonly' : '';
+ const readonlyClass = isReadOnly ? 'readonly-field' : '';
+
+ // ... остальной код с добавлением readonlyAttr и readonlyClass ...
+ }
+}
+```
+
+**Добавить CSS для readonly полей:**
+
+```css
+.readonly-field {
+ background-color: #f5f5f5 !important;
+ cursor: not-allowed !important;
+ opacity: 0.7;
+}
+```
+
+---
+
+### 3. StepClaimConfirmation.tsx - Передача флага в форму
+
+**В useEffect (после строки ~90):**
+
+```typescript
+// Получаем флаг подтверждения из claimPlanData или formData
+const contact_data_confirmed =
+ claimPlanData?.contact_data_confirmed ||
+ claimPlanData?.propertyName?.meta?.contact_data_confirmed ||
+ formData?.contact_data_confirmed ||
+ false;
+
+// Передаём в generateConfirmationFormHTML
+const html = generateConfirmationFormHTML(formData, contact_data_confirmed);
+```
+
+---
+
+### 4. Добавить кнопку "Изменить данные" (опционально)
+
+**В generateConfirmationFormHTML.ts:**
+
+```typescript
+// После заголовка формы, если contact_data_confirmed = true
+if (contact_data_confirmed) {
+ html += `
+
+
+ ⚠️ Данные подтверждены
+
+
+ Для изменения данных требуется подтверждение через SMS.
+
+
+ Изменить данные
+
+
+ `;
+}
+```
+
+**В JavaScript внутри формы:**
+
+```javascript
+// Обработчик кнопки "Изменить данные"
+const editBtn = document.getElementById('btn-edit-data');
+if (editBtn) {
+ editBtn.addEventListener('click', function() {
+ // Отправляем сообщение родительскому окну
+ window.parent.postMessage({
+ type: 'request_edit_contact_data',
+ eventData: {
+ phone: state.user?.mobile || '',
+ unified_id: state.meta?.unified_id || ''
+ }
+ }, '*');
+ });
+}
+```
+
+---
+
+### 5. Обработка запроса на изменение данных
+
+**В StepClaimConfirmation.tsx:**
+
+```typescript
+useEffect(() => {
+ const handleMessage = (event: MessageEvent) => {
+ // ... существующие обработчики ...
+
+ if (event.data.type === 'request_edit_contact_data') {
+ const { phone, unified_id } = event.data.eventData;
+
+ // Показываем модалку SMS для подтверждения
+ setSmsModalVisible(true);
+ setSmsCodeSent(false);
+ sendSMSCode(phone);
+
+ // Сохраняем флаг, что это запрос на изменение данных
+ setPendingFormData({
+ ...pendingFormData,
+ is_edit_request: true,
+ unified_id: unified_id
+ });
+ }
+ };
+
+ window.addEventListener('message', handleMessage);
+ return () => window.removeEventListener('message', handleMessage);
+}, []);
+```
+
+---
+
+### 6. После SMS подтверждения - сброс флага
+
+**В verifySMSCode (после успешной верификации):**
+
+```typescript
+// Если это запрос на изменение данных
+if (pendingFormData?.is_edit_request) {
+ // Отправляем запрос в n8n для сброса флага
+ await fetch('/api/v1/claims/contact-data/reset-confirmed', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ unified_id: pendingFormData.unified_id,
+ sms_code: code
+ })
+ });
+
+ // Обновляем флаг в formData
+ updateFormData({
+ contact_data_confirmed: false,
+ contact_data_can_edit: true
+ });
+
+ // Перезагружаем форму с разблокированными полями
+ // (можно просто обновить страницу или пересоздать форму)
+ window.location.reload();
+}
+```
+
+---
+
+## Порядок реализации
+
+1. ✅ Обновить Step1Phone для получения флага
+2. ✅ Обновить generateConfirmationFormHTML для блокировки полей
+3. ✅ Обновить StepClaimConfirmation для передачи флага
+4. ⏳ Добавить кнопку "Изменить данные" (опционально)
+5. ⏳ Реализовать механизм переподтверждения через SMS
+
+---
+
+## Тестирование
+
+После обновления проверить:
+- ✅ Флаг получается из n8n
+- ✅ Поля блокируются при `contact_data_confirmed = true`
+- ✅ Данные из CRM загружаются и отображаются
+- ✅ Кнопка "Изменить данные" работает (если реализована)
+
diff --git a/docs/N8N_ADD_CF_2624_TO_OCR_STATUS_EVENT.md b/docs/N8N_ADD_CF_2624_TO_OCR_STATUS_EVENT.md
new file mode 100644
index 0000000..7153b6e
--- /dev/null
+++ b/docs/N8N_ADD_CF_2624_TO_OCR_STATUS_EVENT.md
@@ -0,0 +1,210 @@
+# Добавление cf_2624 в событие ocr_status ready
+
+## Задача
+
+После сохранения черновика (после `claimsave`) публиковать событие `ocr_status` с `status: "ready"` в Redis канал `ocr_events:{session_id}` с полем `cf_2624`.
+
+## Формат события
+
+```json
+{
+ "event_type": "ocr_status",
+ "status": "ready",
+ "claim_id": "ef853bac-f54b-46aa-adf8-f0c9c0cd76bc",
+ "message": "Заявление сформировано",
+ "timestamp": "2025-12-03T12:44:12.347Z",
+ "cf_2624": "0"
+}
+```
+
+## Где добавить в n8n workflow
+
+### Вариант 1: После ноды `claimsave` (PostgreSQL)
+
+**Название ноды:** `Code: Prepare OCR Status Event`
+
+**Расположение:** После ноды `claimsave` (PostgreSQL), перед нодой публикации в Redis
+
+**Код:**
+```javascript
+// Получаем результат из claimsave
+const claimResult = $input.first().json;
+const claim = claimResult.claim || claimResult;
+
+// Получаем contact_id из claim
+const contact_id = claim.contact_id;
+
+// ✅ Получаем cf_2624 из PostgreSQL (если есть нода Get Contact Data)
+let cf_2624 = "0"; // По умолчанию "0" (не подтверждено)
+
+try {
+ // Пытаемся получить из предыдущей ноды PostgreSQL: Get Contact Data
+ const contactData = $('PostgreSQL: Get Contact Data')?.first()?.json;
+ if (contactData && contactData.cf_2624) {
+ cf_2624 = contactData.cf_2624;
+ } else {
+ // Альтернатива: получаем из CreateWebContact
+ const createWebContactResult = $node["CreateWebContacКлиентправ"]?.json?.result || "";
+ if (createWebContactResult) {
+ const contactData = typeof createWebContactResult === 'string'
+ ? JSON.parse(createWebContactResult)
+ : createWebContactResult;
+ if (contactData.cf_2624) {
+ cf_2624 = contactData.cf_2624;
+ }
+ }
+ }
+} catch (e) {
+ console.warn('⚠️ Не удалось получить cf_2624, используем значение по умолчанию "0"');
+}
+
+// Формируем событие для Redis
+const event = {
+ event_type: 'ocr_status',
+ status: 'ready',
+ claim_id: claim.claim_id || claim.id,
+ message: 'Заявление сформировано',
+ timestamp: new Date().toISOString(),
+ cf_2624: cf_2624 // ✅ Добавляем cf_2624
+};
+
+console.log('📤 Подготовлено событие ocr_status ready:', {
+ claim_id: event.claim_id,
+ cf_2624: event.cf_2624,
+ contact_id: contact_id
+});
+
+return {
+ json: {
+ // Данные для публикации в Redis
+ channel: `ocr_events:${claim.session_token || claim.session_id}`,
+ message: JSON.stringify(event),
+
+ // Передаём дальше для следующих нод
+ claim_id: event.claim_id,
+ session_token: claim.session_token || claim.session_id,
+ cf_2624: cf_2624
+ }
+};
+```
+
+---
+
+### Вариант 2: Прямо в ноде публикации (HTTP Request или Redis Publish)
+
+**Если используется HTTP Request:**
+
+**URL:** `{{ $env.BACKEND_URL }}/api/v1/events/{{ $json.session_token }}`
+
+**Body (JSON):**
+```json
+{
+ "event_type": "ocr_status",
+ "status": "ready",
+ "message": "Заявление сформировано",
+ "data": {
+ "claim_id": "{{ $json.claim_id }}",
+ "cf_2624": "{{ $json.cf_2624 || '0' }}"
+ },
+ "timestamp": "{{ $now.toISO() }}"
+}
+```
+
+**Если используется Redis Publish:**
+
+**Channel:** `ocr_events:{{ $json.session_token }}`
+
+**Message:**
+```javascript
+={{ JSON.stringify({
+ event_type: 'ocr_status',
+ status: 'ready',
+ claim_id: $json.claim_id,
+ message: 'Заявление сформировано',
+ timestamp: new Date().toISOString(),
+ cf_2624: $json.cf_2624 || '0'
+}) }}
+```
+
+---
+
+## Порядок нод в workflow
+
+1. **CreateWebContacКлиентправ** → получаем `contact_id` и `cf_2624`
+2. **PostgreSQL: Get Contact Data** (опционально) → получаем полные данные контакта включая `cf_2624`
+3. **claimsave** (PostgreSQL) → сохраняем черновик
+4. **Code: Prepare OCR Status Event** → формируем событие с `cf_2624`
+5. **HTTP Request** или **Redis Publish** → публикуем событие в `ocr_events:{session_id}`
+
+---
+
+## Сохранение в черновик
+
+Событие с `cf_2624` будет:
+1. ✅ Публиковаться в Redis канал `ocr_events:{session_id}`
+2. ✅ Обрабатываться backend'ом (загружает `form_draft` из PostgreSQL)
+3. ⏳ **Нужно добавить:** Сохранение `cf_2624` в черновик при обработке события
+
+### Обновление backend для сохранения cf_2624
+
+В файле `ticket_form/backend/app/api/events.py` (строка 218-267):
+
+После загрузки `form_draft` из PostgreSQL, если в событии есть `cf_2624`, нужно сохранить его в черновик:
+
+```python
+# ✅ Обработка ocr_status ready: загружаем form_draft из PostgreSQL
+if actual_event.get('event_type') == 'ocr_status' and actual_event.get('status') == 'ready':
+ claim_id = actual_event.get('claim_id') or actual_event.get('data', {}).get('claim_id')
+ cf_2624 = actual_event.get('cf_2624') # ✅ Получаем cf_2624 из события
+
+ if claim_id:
+ # ... существующий код загрузки form_draft ...
+
+ # ✅ Если есть cf_2624 в событии - сохраняем в черновик
+ if cf_2624:
+ try:
+ update_query = """
+ UPDATE clpr_claims
+ SET payload = jsonb_set(
+ payload,
+ '{cf_2624}',
+ $1::jsonb
+ )
+ WHERE id::text = $2
+ RETURNING id;
+ """
+ await db.execute(update_query, json.dumps(cf_2624), claim_id)
+ logger.info(f"✅ Сохранён cf_2624={cf_2624} в черновик claim_id={claim_id}")
+ except Exception as e:
+ logger.warning(f"⚠️ Не удалось сохранить cf_2624: {e}")
+```
+
+---
+
+## Проверка
+
+1. ✅ Событие публикуется в `ocr_events:{session_id}` с `cf_2624`
+2. ⏳ Backend обрабатывает событие и сохраняет `cf_2624` в черновик
+3. ⏳ При загрузке черновика `cf_2624` доступен в `payload.cf_2624`
+
+---
+
+## Пример полного события
+
+```json
+{
+ "event_type": "ocr_status",
+ "status": "ready",
+ "claim_id": "ef853bac-f54b-46aa-adf8-f0c9c0cd76bc",
+ "message": "Заявление сформировано",
+ "timestamp": "2025-12-03T12:44:12.347Z",
+ "cf_2624": "0"
+}
+```
+
+Это событие будет:
+- ✅ Публиковаться в Redis канал `ocr_events:sess_5fc7cdd1-a848-4e92-aed4-3ee4bfb19b4c`
+- ✅ Обрабатываться backend'ом
+- ✅ Сохраняться в черновик в поле `payload.cf_2624`
+
+
diff --git a/docs/N8N_CODE_CHECK_CONTACT_DATA_CONFIRMED.js b/docs/N8N_CODE_CHECK_CONTACT_DATA_CONFIRMED.js
new file mode 100644
index 0000000..bec24e8
--- /dev/null
+++ b/docs/N8N_CODE_CHECK_CONTACT_DATA_CONFIRMED.js
@@ -0,0 +1,44 @@
+// ============================================================================
+// Code Node для n8n: Проверка подтверждения данных контакта
+// ============================================================================
+// Назначение: Проверить, подтверждены ли данные контакта пользователя
+// и нужно ли блокировать редактирование
+//
+// Использование: После получения unified_id, перед загрузкой данных формы
+// ============================================================================
+
+// Получаем unified_id из предыдущих шагов
+const unified_id = $('user_get').first().json.unified_id ||
+ $('Edit Fields').first().json.unified_id ||
+ $json.unified_id;
+
+if (!unified_id) {
+ throw new Error('unified_id не найден');
+}
+
+// Выполняем SQL запрос для проверки статуса
+// (это должно быть в PostgreSQL ноде, но для примера показываю логику)
+
+// SQL запрос:
+// SELECT * FROM clpr_get_contact_data_status($1);
+// Параметр: unified_id
+
+// Ожидаемый результат:
+// {
+// is_confirmed: true/false,
+// confirmed_at: "2025-12-02T14:30:00Z" или null,
+// can_edit: true/false
+// }
+
+// Для Code Node (если нужно обработать результат):
+const status = $('PostgreSQL Check Status').first().json; // Предполагаем, что есть такая нода
+
+return {
+ unified_id: unified_id,
+ is_confirmed: status.is_confirmed || false,
+ confirmed_at: status.confirmed_at || null,
+ can_edit: status.can_edit !== false, // По умолчанию можно редактировать
+ // Флаг для фронтенда
+ lock_editing: status.is_confirmed || false
+};
+
diff --git a/docs/N8N_CODE_IN_JAVASCRIPT_КЛИЕНТПРАВ_FULL.js b/docs/N8N_CODE_IN_JAVASCRIPT_КЛИЕНТПРАВ_FULL.js
new file mode 100644
index 0000000..87fab60
--- /dev/null
+++ b/docs/N8N_CODE_IN_JAVASCRIPT_КЛИЕНТПРАВ_FULL.js
@@ -0,0 +1,264 @@
+// ========================================
+// Code Node: Code in JavaScriptКлиентправ
+// Формирование Response для фронтенда с поддержкой cf_2624
+// ========================================
+
+// --- 1. Генерация UUIDv4 ---
+function generateUUIDv4() {
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
+ const r = Math.random() * 16 | 0;
+ const v = c === 'x' ? r : ((r & 0x3) | 0x8);
+ return v.toString(16);
+ });
+}
+
+// --- 2. Парсим контакт из CreateWebContacКлиентправ ---
+const createWebContactNode = $node["CreateWebContacКлиентправ"] || $node["CreateWebContact"];
+const rawResult = createWebContactNode?.json?.result || "";
+
+let contactData = {};
+try {
+ contactData = typeof rawResult === 'string'
+ ? JSON.parse(rawResult)
+ : rawResult;
+} catch (e) {
+ console.error('❌ Ошибка парсинга CreateWebContact:', e);
+ contactData = {};
+}
+
+// ✅ Извлекаем cf_2624 (Данные подтверждены) из CreateWebContact
+// "1" = данные подтверждены, "0" = не подтверждены
+const cf_2624 = contactData.cf_2624 || "0";
+const contact_data_confirmed = cf_2624 === "1" || cf_2624 === "true" || cf_2624 === true;
+const contact_data_can_edit = !contact_data_confirmed;
+
+console.log('🔒 Статус данных контакта из CreateWebContact:', {
+ contact_id: contactData.contact_id,
+ is_new: contactData.is_new,
+ cf_2624: cf_2624,
+ contact_data_confirmed: contact_data_confirmed,
+ contact_data_can_edit: contact_data_can_edit
+});
+
+// --- 2.1. Получаем полные данные контакта из PostgreSQL (если есть) ---
+let contactFromDB = null;
+try {
+ // Пытаемся найти ноду PostgreSQL, которая получила данные контакта
+ const possiblePostgresNodes = [
+ 'PostgreSQL: Get Contact Data',
+ 'Get Contact from DB',
+ 'PostgreSQL',
+ 'Get Contact Details'
+ ];
+
+ for (const nodeName of possiblePostgresNodes) {
+ try {
+ const node = $(nodeName)?.first();
+ if (node && node.json) {
+ // Проверяем, что это данные контакта (есть contactid)
+ if (node.json.contactid || node.json.contact_id) {
+ contactFromDB = node.json;
+ console.log('✅ Получены данные контакта из PostgreSQL:', {
+ contactid: contactFromDB.contactid || contactFromDB.contact_id,
+ firstname: contactFromDB.firstname,
+ lastname: contactFromDB.lastname
+ });
+ break;
+ }
+ }
+ } catch (e) {
+ continue;
+ }
+ }
+
+ // Альтернативный способ: ищем по структуре данных
+ if (!contactFromDB) {
+ // Может быть в предыдущей ноде с результатом запроса
+ const inputData = $input.all();
+ for (const item of inputData) {
+ if (item.json && (item.json.contactid || item.json.contact_id)) {
+ contactFromDB = item.json;
+ break;
+ }
+ }
+ }
+} catch (e) {
+ console.warn('⚠️ Не удалось получить данные контакта из PostgreSQL:', e.message);
+}
+
+// Если данные из БД получены - используем их для дополнения информации
+if (contactFromDB) {
+ console.log('📋 Данные контакта из БД:', {
+ contactid: contactFromDB.contactid,
+ firstname: contactFromDB.firstname,
+ lastname: contactFromDB.lastname,
+ email: contactFromDB.email,
+ mobile: contactFromDB.mobile,
+ birthday: contactFromDB.birthday,
+ mailingstreet: contactFromDB.mailingstreet,
+ middle_name: contactFromDB.middle_name,
+ birthplace: contactFromDB.birthplace,
+ inn: contactFromDB.inn
+ });
+}
+
+// --- 3. Телефон из Edit Fields ---
+let phone = null;
+try {
+ const editFields = $('Edit Fields')?.first();
+ if (editFields && editFields.json) {
+ phone = editFields.json.phone;
+ }
+} catch (e) {
+ console.warn('⚠️ Не удалось получить phone из Edit Fields:', e.message);
+}
+
+// --- 4. unified_id из user_get ---
+let unified_id = null;
+try {
+ const possibleUserNodes = ['user_get', 'Find or Create User', 'PostgreSQL: Find User'];
+ for (const nodeName of possibleUserNodes) {
+ try {
+ const node = $node[nodeName];
+ if (node && node.json && node.json.unified_id) {
+ unified_id = node.json.unified_id;
+ break;
+ }
+ } catch (e) {
+ // Нода не существует или не выполнена - продолжаем поиск
+ continue;
+ }
+ }
+
+ if (!unified_id) {
+ console.warn('⚠️ unified_id не получен из ноды user_get. Проверьте, что нода выполнена.');
+ }
+} catch (e) {
+ console.warn('⚠️ Не удалось получить unified_id:', e.message);
+}
+
+// --- 5. Генерируем session_id (если не получен из предыдущих нод) ---
+let session_id = null;
+
+// Пытаемся получить session_id из предыдущих нод
+try {
+ const possibleSessionNodes = [
+ 'Code in JavaScript1',
+ 'Code in JavaScript',
+ 'Set Session Data',
+ 'Create Session'
+ ];
+
+ for (const nodeName of possibleSessionNodes) {
+ try {
+ const node = $(nodeName)?.first();
+ if (node && node.json) {
+ if (node.json.session_id) {
+ session_id = node.json.session_id;
+ break;
+ } else if (node.json.redis_value) {
+ const parsed = JSON.parse(node.json.redis_value);
+ if (parsed.session_id) {
+ session_id = parsed.session_id;
+ break;
+ }
+ }
+ }
+ } catch (e) {
+ continue;
+ }
+ }
+
+ // Пытаемся получить из Edit Fields
+ if (!session_id) {
+ try {
+ const editFields = $('Edit Fields')?.first();
+ if (editFields && editFields.json && editFields.json.session_id) {
+ session_id = editFields.json.session_id;
+ }
+ } catch (e) {
+ // Игнорируем
+ }
+ }
+} catch (e) {
+ console.warn('⚠️ Не удалось получить session_id из предыдущих нод:', e.message);
+}
+
+// Если session_id не найден - генерируем новый
+if (!session_id) {
+ session_id = 'sess_' + generateUUIDv4();
+ console.log('✅ Сгенерирован новый session_id:', session_id);
+}
+
+// --- 6. Формируем sessionData для Redis ---
+const sessionData = {
+ session_id, // ← теперь сохраняем внутрь
+ unified_id,
+ contact_id: contactData.contact_id,
+ phone,
+ is_new_contact: contactData.is_new || contactData.is_new_contact || false,
+ // ✅ Флаги подтверждения данных контакта (из cf_2624)
+ cf_2624: cf_2624,
+ contact_data_confirmed: contact_data_confirmed,
+ contact_data_can_edit: contact_data_can_edit,
+ // ✅ Данные контакта из PostgreSQL (если получены)
+ contact_from_db: contactFromDB ? {
+ contactid: contactFromDB.contactid || contactFromDB.contact_id,
+ firstname: contactFromDB.firstname,
+ lastname: contactFromDB.lastname,
+ email: contactFromDB.email,
+ mobile: contactFromDB.mobile,
+ phone: contactFromDB.phone,
+ birthday: contactFromDB.birthday,
+ mailingstreet: contactFromDB.mailingstreet,
+ mailingcity: contactFromDB.mailingcity,
+ mailingstate: contactFromDB.mailingstate,
+ mailingzip: contactFromDB.mailingzip,
+ mailingcountry: contactFromDB.mailingcountry,
+ middle_name: contactFromDB.middle_name,
+ birthplace: contactFromDB.birthplace,
+ inn: contactFromDB.inn,
+ requisites: contactFromDB.requisites,
+ code: contactFromDB.code,
+ sms: contactFromDB.sms
+ } : null,
+ status: "draft",
+ current_step: 1,
+ created_at: new Date().toISOString(),
+ updated_at: new Date().toISOString(),
+ documents: {},
+ email: contactFromDB?.email || null,
+ bank_name: null
+};
+
+// --- 7. Возвращаем результат в формате items ---
+const result = {
+ json: {
+ session: session_id,
+ session_id,
+ unified_id,
+ contact_id: contactData.contact_id,
+ is_new_contact: contactData.is_new || contactData.is_new_contact || false,
+ phone,
+ // ✅ Флаги подтверждения данных контакта (из cf_2624)
+ cf_2624: cf_2624,
+ contact_data_confirmed: contact_data_confirmed,
+ contact_data_can_edit: contact_data_can_edit,
+ redis_key: `session:${session_id}`,
+ redis_value: JSON.stringify(sessionData),
+ ttl: 604800
+ }
+};
+
+// Логируем финальный ответ для отладки
+console.log('✅ Сформирован ответ для фронтенда:', {
+ session_id: result.json.session_id,
+ has_unified_id: !!result.json.unified_id,
+ has_contact_id: !!result.json.contact_id,
+ contact_data_confirmed: result.json.contact_data_confirmed,
+ cf_2624: result.json.cf_2624,
+ is_new_contact: result.json.is_new_contact
+});
+
+return [result];
+
diff --git a/docs/N8N_CODE_PREPARE_DOCUMENT_SKIP_SQL.js b/docs/N8N_CODE_PREPARE_DOCUMENT_SKIP_SQL.js
new file mode 100644
index 0000000..ef901b2
--- /dev/null
+++ b/docs/N8N_CODE_PREPARE_DOCUMENT_SKIP_SQL.js
@@ -0,0 +1,113 @@
+// ============================================================================
+// n8n Code Node: Подготовка параметров для SQL при пропуске документа
+// ============================================================================
+// Входные данные: массив с объектом [{ propertyName: {...}, body: {...} }]
+// Выходные данные: { $1: jsonb_payload, $2: claim_id_string }
+// ============================================================================
+
+// Получаем входные данные
+const inputData = $input.all();
+
+if (!inputData || inputData.length === 0) {
+ return [{
+ json: {
+ error: "Нет входных данных",
+ $1: null,
+ $2: null
+ }
+ }];
+}
+
+// Берём первый элемент
+// Если это массив - берём первый элемент массива
+// Если это объект - используем его напрямую
+let firstItem = inputData[0].json;
+
+if (Array.isArray(firstItem)) {
+ firstItem = firstItem[0];
+}
+
+// Извлекаем данные
+const propertyName = firstItem.propertyName || {};
+const body = firstItem.body || {};
+
+// Извлекаем claim_id (приоритет: body -> propertyName)
+const claim_id = body.claim_id || propertyName.claim_id || null;
+
+if (!claim_id) {
+ return [{
+ json: {
+ error: "claim_id не найден",
+ $1: null,
+ $2: null,
+ debug: {
+ body_keys: Object.keys(body),
+ propertyName_keys: Object.keys(propertyName)
+ }
+ }
+ }];
+}
+
+// Формируем payload для $1 (jsonb)
+// SQL ищет данные в разных местах: p->>'document_type', p->'body'->>'document_type', p->'edit_fields_raw'->'body'->>'document_type'
+const payload = {
+ // ✅ Основные идентификаторы (в корне для быстрого доступа)
+ session_id: body.session_id || propertyName.session_id,
+ claim_id: claim_id,
+ unified_id: body.unified_id || propertyName.unified_id,
+ contact_id: body.contact_id || propertyName.contact_id,
+ phone: body.phone || propertyName.phone,
+
+ // ✅ Информация о пропущенном документе (в корне для быстрого доступа)
+ document_type: body.document_type,
+ document_name: body.document_name || body.document_type,
+ group_index: body.group_index ? parseInt(body.group_index) : (body.group_index || null),
+
+ // ✅ Метаданные пропуска
+ skipped: body.skipped,
+ action: body.action,
+ skip_timestamp: body.skip_timestamp || new Date().toISOString(),
+
+ // ✅ Данные из propertyName (для сохранения в payload)
+ problem_description: propertyName.description || propertyName.problem_description,
+ email: propertyName.email,
+
+ // ✅ Данные из body (для совместимости)
+ form_id: body.form_id,
+ stage: body.stage,
+ client_ip: body.client_ip,
+
+ // ✅ Поля для совместимости с существующим SQL (SQL ищет данные здесь)
+ body: {
+ document_type: body.document_type,
+ document_name: body.document_name || body.document_type,
+ group_index: body.group_index ? parseInt(body.group_index) : (body.group_index || null),
+ session_id: body.session_id,
+ claim_id: claim_id,
+ unified_id: body.unified_id,
+ contact_id: body.contact_id,
+ phone: body.phone
+ },
+ edit_fields_raw: {
+ propertyName: propertyName,
+ body: body
+ },
+ edit_fields_parsed: {
+ propertyName: propertyName,
+ body: body
+ }
+};
+
+// Возвращаем параметры для SQL
+return [{
+ json: {
+ $1: payload, // JSONB payload для SQL (будет передан как $1::jsonb)
+ $2: claim_id, // TEXT claim_id для SQL (будет передан как $2::text)
+ // Дополнительные поля для отладки
+ claim_id: claim_id,
+ document_type: body.document_type,
+ document_name: body.document_name,
+ group_index: body.group_index
+ }
+}];
+
diff --git a/docs/N8N_CODE_PROCESS_UPLOADED_FILES_FIXED.js b/docs/N8N_CODE_PROCESS_UPLOADED_FILES_FIXED.js
index dbd6489..3f37127 100644
--- a/docs/N8N_CODE_PROCESS_UPLOADED_FILES_FIXED.js
+++ b/docs/N8N_CODE_PROCESS_UPLOADED_FILES_FIXED.js
@@ -90,12 +90,15 @@ for (const it of items) {
const file_index = 0; // После объединения всегда один файл на группу
const field_name = `uploads[${grp}][${file_index}]`;
- const field_label = uploads_field_labels[grp] || uploads_field_names[grp] || uploads_descriptions[grp] || `group-${grp}`;
+
+ // ✅ ИСПРАВЛЕНО: uploads_field_labels содержит элементы с индексом 0 (текущий запрос),
+ // а grp - это позиция в documents_required. Используем индекс 0 для массивов текущего запроса.
+ const field_label = uploads_field_labels[0] || uploads_field_names[0] || uploads_descriptions[0] || `group-${grp}`;
// OCR уже объединил файлы, используем newfile (путь к объединённому файлу)
const draft_key = safeStr(it.newfile || (it.folder && it.file_name ? `${it.folder}/${it.file_name}` : ''));
const original_name = safeStr(it.file_name || `group_${grp}.pdf`);
- const description = safeStr(it.description || uploads_descriptions[grp] || '');
+ const description = safeStr(it.description || uploads_descriptions[0] || '');
const prefix = safeStr(it.prefix || '');
// files_count показывает, сколько исходных файлов было объединено
diff --git a/docs/N8N_CODE_SET_CONTACT_DATA_CONFIRMED.js b/docs/N8N_CODE_SET_CONTACT_DATA_CONFIRMED.js
new file mode 100644
index 0000000..3911a43
--- /dev/null
+++ b/docs/N8N_CODE_SET_CONTACT_DATA_CONFIRMED.js
@@ -0,0 +1,51 @@
+// ============================================================================
+// Code Node для n8n: Установка флага подтверждения данных
+// ============================================================================
+// Назначение: Установить флаг contact_data_confirmed_at после подтверждения формы
+//
+// Использование: После успешного сохранения данных в CRM через claim_confirmed
+// ============================================================================
+
+// Получаем unified_id
+const unified_id = $('user_get').first().json.unified_id ||
+ $json.unified_id;
+
+if (!unified_id) {
+ throw new Error('unified_id не найден для установки флага подтверждения');
+}
+
+// Получаем contact_id из CRM (если есть)
+const contact_id = $node['CreateWebContacКлиентправ']?.json?.result?.contact_id ||
+ $json.contact_id ||
+ null;
+
+// Проверяем, есть ли данные в CRM (для автоматического подтверждения)
+// Если contact_id > 0, значит данные уже есть в CRM - подтверждаем автоматически
+const has_crm_data = contact_id && parseInt(contact_id) > 0;
+
+// Формируем данные для PostgreSQL
+return {
+ unified_id: unified_id,
+ contact_id: contact_id,
+ has_crm_data: has_crm_data,
+ // Флаг для SQL функции
+ should_confirm: true, // Всегда подтверждаем после сохранения формы
+ confirmed_at: new Date().toISOString()
+};
+
+// ============================================================================
+// SQL запрос для PostgreSQL ноды (после этого Code Node):
+// ============================================================================
+// SELECT clpr_set_contact_data_confirmed($1, $2::timestamptz);
+//
+// Параметры:
+// $1 = {{ $json.unified_id }}
+// $2 = {{ $json.confirmed_at }}
+//
+// ИЛИ для автоматического подтверждения существующих данных:
+// SELECT clpr_auto_confirm_if_crm_has_data($1, $2::integer);
+//
+// Параметры:
+// $1 = {{ $json.unified_id }}
+// $2 = {{ $json.contact_id }}
+
diff --git a/docs/N8N_MYSQL_GET_CONTACT_DATA.md b/docs/N8N_MYSQL_GET_CONTACT_DATA.md
new file mode 100644
index 0000000..83111c5
--- /dev/null
+++ b/docs/N8N_MYSQL_GET_CONTACT_DATA.md
@@ -0,0 +1,74 @@
+# Получение данных контакта из MySQL в n8n
+
+## Задача
+
+В n8n workflow нужно получить полные данные контакта из MySQL БД vtiger CRM перед формированием финального ответа.
+
+## SQL запрос
+
+**Файл:** `ticket_form/docs/N8N_POSTGRESQL_GET_CONTACT_DATA.sql` (название файла устарело, но запрос для MySQL)
+
+```sql
+SELECT
+ cd.contactid,
+ cd.firstname,
+ cd.lastname,
+ cd.email,
+ cd.mobile,
+ cd.phone,
+ cs.birthday,
+ ca.mailingstreet,
+ ca.mailingcity,
+ ca.mailingstate,
+ ca.mailingzip,
+ ca.mailingcountry,
+ ccf.cf_1157 AS middle_name,
+ ccf.cf_1263 AS birthplace,
+ ccf.cf_1257 AS inn,
+ ccf.cf_1849 AS requisites,
+ ccf.cf_1580 AS code,
+ ccf.cf_1706 AS sms,
+ ccf.cf_2624 AS cf_2624
+FROM vtiger_contactdetails cd
+LEFT JOIN vtiger_contactscf ccf ON ccf.contactid = cd.contactid
+LEFT JOIN vtiger_contactsubdetails cs ON cs.contactsubscriptionid = cd.contactid
+LEFT JOIN vtiger_contactaddress ca ON ca.contactaddressid = cd.contactid
+LEFT JOIN vtiger_crmentity ce ON ce.crmid = cd.contactid
+WHERE cd.contactid = ?
+ AND ce.deleted = 0
+LIMIT 1;
+```
+
+## Настройка ноды MySQL в n8n
+
+1. **Тип ноды:** MySQL
+2. **Operation:** Execute Query
+3. **Query:** (см. выше)
+4. **Parameters:**
+ - `?` = `{{ JSON.parse($node["CreateWebContacКлиентправ"].json.result).contact_id }}`
+
+## Credentials для MySQL
+
+- **Host:** `localhost`
+- **Port:** `3306`
+- **Database:** `ci20465_72new`
+- **User:** `ci20465_72new`
+- **Password:** `EcY979Rn`
+
+## Использование в Code node
+
+После выполнения MySQL запроса, данные доступны в Code node:
+
+```javascript
+const pgContactNode = $('MySQL: Get Contact Data')?.first();
+if (pgContactNode && pgContactNode.json && pgContactNode.json.length > 0) {
+ const contactFromDb = pgContactNode.json[0];
+ // Используем contactFromDb.cf_2624, contactFromDb.firstname, и т.д.
+}
+```
+
+---
+
+**Примечание:** Название файла `N8N_POSTGRESQL_GET_CONTACT_DATA.sql` устарело, но запрос работает для MySQL.
+
+
diff --git a/docs/N8N_MYSQL_GET_CONTACT_DATA.sql b/docs/N8N_MYSQL_GET_CONTACT_DATA.sql
new file mode 100644
index 0000000..632eb37
--- /dev/null
+++ b/docs/N8N_MYSQL_GET_CONTACT_DATA.sql
@@ -0,0 +1,35 @@
+-- SQL запрос для получения полных данных контакта из CRM
+-- Используется в ноде MySQL перед Code in JavaScriptКлиентправ
+-- ПРИМЕЧАНИЕ: Таблицы vtiger_* находятся в MySQL БД
+
+SELECT
+ cd.contactid,
+ cd.firstname,
+ cd.lastname,
+ cd.email,
+ cd.mobile,
+ cd.phone,
+ cs.birthday, -- ✅ Из vtiger_contactsubdetails
+ ca.mailingstreet, -- ✅ Из vtiger_contactaddress
+ ca.mailingcity,
+ ca.mailingstate,
+ ca.mailingzip,
+ ca.mailingcountry,
+ -- Кастомные поля из vtiger_contactscf:
+ ccf.cf_1157 AS middle_name, -- Отчество
+ ccf.cf_1263 AS birthplace, -- Место рождения
+ ccf.cf_1257 AS inn, -- ИНН
+ ccf.cf_1849 AS requisites, -- Реквизиты
+ ccf.cf_1580 AS code, -- Код
+ ccf.cf_1706 AS sms, -- SMS
+ ccf.cf_2624 AS cf_2624 -- ✅ Данные подтверждены
+FROM vtiger_contactdetails cd
+LEFT JOIN vtiger_contactscf ccf ON ccf.contactid = cd.contactid
+LEFT JOIN vtiger_contactsubdetails cs ON cs.contactsubscriptionid = cd.contactid
+LEFT JOIN vtiger_contactaddress ca ON ca.contactaddressid = cd.contactid
+LEFT JOIN vtiger_crmentity ce ON ce.crmid = cd.contactid
+WHERE cd.contactid = ?
+ AND ce.deleted = 0
+LIMIT 1;
+
+
diff --git a/docs/N8N_SET_CF_2624_CONTACT_CONFIRMED.md b/docs/N8N_SET_CF_2624_CONTACT_CONFIRMED.md
new file mode 100644
index 0000000..c743671
--- /dev/null
+++ b/docs/N8N_SET_CF_2624_CONTACT_CONFIRMED.md
@@ -0,0 +1,62 @@
+# Установка поля cf_2624 "Данные подтверждены" в n8n workflow
+
+## Обновление workflow 6mxRJ2LLHmQXyaDz
+
+### После подтверждения формы (после SMS-верификации)
+
+**Добавить ноду:** `HTTP Request: Set Contact Data Confirmed`
+
+**Параметры:**
+- **Method:** POST
+- **URL:** `{{ $env.CRM_WEBSERVICE_URL }}` (или полный URL CRM webservice)
+- **Body Type:** form-data
+
+**Body (form-data):**
+```
+operation: revise
+sessionName: {{ $('Login to CRM').json.sessionName }}
+id: 12x{{ JSON.parse($node['CreateWebContacКлиентправ'].json.result).contact_id }}
+cf_2624: 1
+```
+
+**Где:**
+- `cf_2624` - поле "Данные подтверждены" в CRM
+- `1` = "Да" (данные подтверждены)
+- `0` = "Нет" (данные не подтверждены)
+
+---
+
+## Альтернативный вариант: через Code Node
+
+Если нужно более гибкое управление, можно использовать Code Node:
+
+**Название:** `Code: Set Contact Data Confirmed`
+
+**Код:**
+```javascript
+// Получаем contact_id из CreateWebContact
+const contactResult = JSON.parse($node['CreateWebContacКлиентправ'].json.result);
+const contact_id = contactResult.contact_id;
+
+// Получаем sessionName из Login to CRM
+const sessionName = $('Login to CRM').json.sessionName;
+
+// Формируем данные для обновления
+return {
+ operation: 'revise',
+ sessionName: sessionName,
+ id: `12x${contact_id}`,
+ cf_2624: '1' // Устанавливаем "Да" (данные подтверждены)
+};
+```
+
+Затем подключить к **HTTP Request** ноде, которая отправит эти данные в CRM.
+
+---
+
+## Проверка работы:
+
+1. После SMS-верификации и подтверждения формы
+2. Проверить в CRM, что у контакта поле `cf_2624` = "Да"
+3. При следующей загрузке черновика поля должны быть заблокированы
+
diff --git a/docs/N8N_SQL_PARAMETERS_DOCUMENT_SKIP.md b/docs/N8N_SQL_PARAMETERS_DOCUMENT_SKIP.md
new file mode 100644
index 0000000..f9432aa
--- /dev/null
+++ b/docs/N8N_SQL_PARAMETERS_DOCUMENT_SKIP.md
@@ -0,0 +1,112 @@
+# Параметры для SQL при пропуске документа
+
+## Входные данные n8n
+
+Массив с объектом:
+```json
+[
+ {
+ "propertyName": {
+ "session_id": "sess_f47c9f47-a727-4176-bf3d-26a02bb2fe24",
+ "phone": "79262306381",
+ "unified_id": "usr_90599ff2-ac79-4236-b950-0df85395096c",
+ "contact_id": "320096",
+ "claim_id": "bddb6815-8e17-4d54-a721-5e94382942c7",
+ "description": "...",
+ "email": "help@clientright.ru",
+ ...
+ },
+ "body": {
+ "form_id": "ticket_form",
+ "stage": "document_skip",
+ "session_id": "sess_f47c9f47-a727-4176-bf3d-26a02bb2fe24",
+ "claim_id": "bddb6815-8e17-4d54-a721-5e94382942c7",
+ "unified_id": "usr_90599ff2-ac79-4236-b950-0df85395096c",
+ "contact_id": "320096",
+ "phone": "79262306381",
+ "document_type": "correspondence",
+ "document_name": "Переписка",
+ "skipped": "true",
+ "action": "skip",
+ "skip_timestamp": "2025-11-27T12:35:46.915646",
+ "group_index": "2"
+ }
+ }
+]
+```
+
+## Параметры для SQL
+
+### $1 (JSONB payload)
+
+Структура payload должна содержать данные в разных местах для совместимости с SQL:
+
+```json
+{
+ // В корне (для быстрого доступа)
+ "session_id": "sess_f47c9f47-a727-4176-bf3d-26a02bb2fe24",
+ "claim_id": "bddb6815-8e17-4d54-a721-5e94382942c7",
+ "unified_id": "usr_90599ff2-ac79-4236-b950-0df85395096c",
+ "contact_id": "320096",
+ "phone": "79262306381",
+ "document_type": "correspondence",
+ "document_name": "Переписка",
+ "group_index": 2,
+
+ // В body (SQL ищет здесь: p->'body'->>'document_type')
+ "body": {
+ "document_type": "correspondence",
+ "document_name": "Переписка",
+ "group_index": 2,
+ "session_id": "sess_f47c9f47-a727-4176-bf3d-26a02bb2fe24",
+ "claim_id": "bddb6815-8e17-4d54-a721-5e94382942c7",
+ "unified_id": "usr_90599ff2-ac79-4236-b950-0df85395096c",
+ "contact_id": "320096",
+ "phone": "79262306381"
+ },
+
+ // В edit_fields_raw (SQL ищет здесь: p->'edit_fields_raw'->'body'->>'document_type')
+ "edit_fields_raw": {
+ "propertyName": { ... },
+ "body": { ... }
+ },
+
+ // В edit_fields_parsed (SQL ищет здесь: p->'edit_fields_parsed'->'body'->>'document_type')
+ "edit_fields_parsed": {
+ "propertyName": { ... },
+ "body": { ... }
+ },
+
+ // Дополнительные поля
+ "problem_description": "...",
+ "email": "help@clientright.ru",
+ "skipped": "true",
+ "action": "skip",
+ "skip_timestamp": "2025-11-27T12:35:46.915646"
+}
+```
+
+### $2 (TEXT claim_id)
+
+Просто строка с claim_id:
+```
+"bddb6815-8e17-4d54-a721-5e94382942c7"
+```
+
+## Использование в n8n
+
+1. **Code Node** (`N8N_CODE_PREPARE_DOCUMENT_SKIP_SQL.js`) - подготавливает параметры
+2. **PostgreSQL Node** - выполняет SQL запрос `SQL_CLAIMSAVE_DOCUMENT_SKIP.sql` с параметрами:
+ - Parameter Name: `$1`, Value: `={{ $json.$1 }}` (JSON)
+ - Parameter Name: `$2`, Value: `={{ $json.$2 }}` (String)
+
+## Важно
+
+SQL запрос ищет данные в следующем порядке:
+1. `partial.p->>'document_type'` - в корне payload
+2. `partial.p->'body'->>'document_type'` - в body
+3. `partial.p->'edit_fields_raw'->'body'->>'document_type'` - в edit_fields_raw.body
+4. `partial.p->'edit_fields_parsed'->'body'->>'document_type'` - в edit_fields_parsed.body
+
+Поэтому payload должен содержать данные во всех этих местах для надёжности.
+
diff --git a/docs/N8N_UPDATE_CF_2624_IN_RESPONSE.md b/docs/N8N_UPDATE_CF_2624_IN_RESPONSE.md
new file mode 100644
index 0000000..2b1c166
--- /dev/null
+++ b/docs/N8N_UPDATE_CF_2624_IN_RESPONSE.md
@@ -0,0 +1,147 @@
+# Обновление n8n workflow: Использование cf_2624 из CreateWebContact
+
+## Задача
+
+При формировании заявления проверять значение `cf_2624` из ответа `CreateWebContact`:
+- Если `cf_2624 = "0"` → данные можно редактировать
+- Если `cf_2624 = "1"` → данные только для просмотра (readonly)
+
+## Изменения в workflow 6mxRJ2LLHmQXyaDz
+
+### 1. После ноды `CreateWebContacКлиентправ`
+
+**Название ноды:** `Code: Extract Contact Data Confirmed`
+
+**Код:**
+```javascript
+// Парсим результат CreateWebContact
+const rawResult = $node["CreateWebContacКлиентправ"].json.result;
+const contactData = JSON.parse(rawResult);
+
+// Извлекаем cf_2624 (Данные подтверждены)
+// "1" = данные подтверждены, "0" = не подтверждены
+const cf_2624 = contactData.cf_2624 || "0";
+const contact_data_confirmed = cf_2624 === "1";
+
+console.log('🔒 Статус данных контакта:', {
+ contact_id: contactData.contact_id,
+ is_new: contactData.is_new,
+ cf_2624: cf_2624,
+ contact_data_confirmed: contact_data_confirmed
+});
+
+return {
+ contact_id: contactData.contact_id,
+ is_new_contact: contactData.is_new,
+ cf_2624: cf_2624,
+ contact_data_confirmed: contact_data_confirmed,
+ contact_data_can_edit: !contact_data_confirmed
+};
+```
+
+---
+
+### 2. В ноде `Code in JavaScriptКлиентправ` (формирование ответа для фронтенда)
+
+**Добавить в return:**
+
+```javascript
+// Получаем данные о подтверждении из предыдущей ноды
+const contactStatus = $('Code: Extract Contact Data Confirmed').first().json;
+
+return {
+ // ... существующие поля ...
+ session: session_id,
+ session_id: session_id,
+ unified_id: unified_id,
+ contact_id: contactStatus.contact_id,
+ is_new_contact: contactStatus.is_new_contact,
+
+ // ✅ Флаги подтверждения данных контакта (из cf_2624)
+ contact_data_confirmed: contactStatus.contact_data_confirmed || false,
+ contact_data_can_edit: contactStatus.contact_data_can_edit !== false,
+ cf_2624: contactStatus.cf_2624 || "0",
+
+ // ... остальные поля ...
+};
+```
+
+---
+
+### 3. При загрузке черновика (если используется отдельный workflow)
+
+**Если есть нода для загрузки черновика:**
+
+```javascript
+// Получаем contact_id из черновика
+const contact_id = $json.contact_id || $json.payload?.contact_id;
+
+if (contact_id) {
+ // Вызываем CreateWebContact для получения cf_2624
+ // (или используем retrieve из CRM)
+
+ // Для простоты можно использовать retrieve:
+ const retrieveResult = await $http.post('{{ $env.CRM_WEBSERVICE_URL }}', {
+ operation: 'retrieve',
+ sessionName: $('Login to CRM').json.sessionName,
+ id: `12x${contact_id}`
+ });
+
+ const cf_2624 = retrieveResult.result?.cf_2624 || "0";
+ const contact_data_confirmed = cf_2624 === "1";
+
+ return {
+ // ... данные черновика ...
+ contact_data_confirmed: contact_data_confirmed,
+ contact_data_can_edit: !contact_data_confirmed,
+ cf_2624: cf_2624
+ };
+}
+```
+
+---
+
+## Логика работы
+
+1. **При создании/поиске контакта:**
+ - `CreateWebContact` возвращает `cf_2624` в ответе
+ - Извлекаем значение и передаём в ответе для фронтенда
+
+2. **При загрузке черновика:**
+ - Backend API `/drafts/{claim_id}` уже получает `cf_2624` из CRM
+ - Фронтенд получает `contact_data_confirmed` из ответа API
+ - Передаёт в `StepClaimConfirmation` → `generateConfirmationFormHTML`
+
+3. **При формировании заявления:**
+ - Если `cf_2624 = "1"` → поля персональных данных блокируются (readonly)
+ - Если `cf_2624 = "0"` → поля можно редактировать
+
+---
+
+## Проверка
+
+1. ✅ `CreateWebContact` возвращает `cf_2624` в ответе
+2. ⏳ n8n workflow извлекает `cf_2624` и передаёт в ответе
+3. ⏳ Фронтенд получает `contact_data_confirmed` и блокирует поля
+4. ⏳ Backend API `/drafts/{claim_id}` получает `cf_2624` из CRM
+
+---
+
+## Пример ответа от n8n:
+
+```json
+{
+ "success": true,
+ "result": {
+ "session": "sess_...",
+ "contact_id": "399542",
+ "unified_id": "usr_...",
+ "contact_data_confirmed": true,
+ "contact_data_can_edit": false,
+ "cf_2624": "1",
+ "is_new_contact": false
+ }
+}
+```
+
+
diff --git a/docs/N8N_WORKFLOW_6mxRJ2LLHmQXyaDz_CHANGES.md b/docs/N8N_WORKFLOW_6mxRJ2LLHmQXyaDz_CHANGES.md
new file mode 100644
index 0000000..e91c8bc
--- /dev/null
+++ b/docs/N8N_WORKFLOW_6mxRJ2LLHmQXyaDz_CHANGES.md
@@ -0,0 +1,135 @@
+# Конкретные изменения в workflow 6mxRJ2LLHmQXyaDz
+
+## Что менять:
+
+### 1. После ноды `user_get` → добавить PostgreSQL ноду (ПЕРВАЯ)
+
+**Название ноды:** `PostgreSQL: Auto Confirm Contact Data`
+
+**Параметры:**
+- **Operation:** Execute Query
+- **Query:**
+```sql
+SELECT clpr_auto_confirm_if_crm_has_data($1, $2::integer);
+```
+- **Parameters:**
+ - `$1` = `{{ $json.unified_id }}` ← используем данные из предыдущей ноды (user_get)
+ - `$2` = `{{ JSON.parse($node['CreateWebContacКлиентправ'].json.result).contact_id }}`
+
+**Подключение:**
+- `user_get` → `PostgreSQL: Auto Confirm Contact Data` → `Execute a SQL query2`
+
+---
+
+### 2. После ноды `PostgreSQL: Auto Confirm Contact Data` → добавить PostgreSQL ноду (ВТОРАЯ)
+
+**Название ноды:** `PostgreSQL: Check Contact Data Status`
+
+**Параметры:**
+- **Operation:** Execute Query
+- **Query:**
+```sql
+SELECT * FROM clpr_get_contact_data_status($1);
+```
+- **Parameters:**
+ - `$1` = `{{ $json.unified_id }}` ← unified_id передаётся дальше по цепочке
+
+**Подключение:**
+- `PostgreSQL: Auto Confirm Contact Data` → `PostgreSQL: Check Contact Data Status` → `Execute a SQL query2`
+
+---
+
+### 3. В ноде `Code in JavaScript` (та что перед `Respond to Webhook1`) → добавить флаг в ответ
+
+**Найти эту строку:**
+```javascript
+// Unified ID из PostgreSQL (обязательно!)
+unified_id: userData.unified_id, // из ноды user_get (PostgreSQL: Find or Create User)
+```
+
+**Добавить ПОСЛЕ неё:**
+```javascript
+// Флаг подтверждения данных контакта
+contact_data_confirmed: $('PostgreSQL: Check Contact Data Status').first().json.is_confirmed || false,
+contact_data_can_edit: $('PostgreSQL: Check Contact Data Status').first().json.can_edit !== false,
+contact_data_confirmed_at: $('PostgreSQL: Check Contact Data Status').first().json.confirmed_at || null,
+```
+
+**Полный return должен быть:**
+```javascript
+return {
+ success: true,
+ result: {
+ session: $('Code in JavaScript3').first().json.session_id,
+ contact_id: sessionData.contact_id || claimResult.contact_id,
+ project_id: sessionData.project_id,
+
+ // Unified ID из PostgreSQL (обязательно!)
+ unified_id: userData.unified_id,
+
+ // Флаг подтверждения данных контакта
+ contact_data_confirmed: $('PostgreSQL: Check Contact Data Status').first().json.is_confirmed || false,
+ contact_data_can_edit: $('PostgreSQL: Check Contact Data Status').first().json.can_edit !== false,
+ contact_data_confirmed_at: $('PostgreSQL: Check Contact Data Status').first().json.confirmed_at || null,
+
+ // Данные заявки
+ ticket_id: claimResult.ticket_id,
+ ticket_number: claimResult.ticket_number,
+ title: claimResult.title,
+ category: claimResult.category,
+ status: claimResult.status,
+
+ // Метаданные
+ event_type: sessionData.event_type,
+ current_step: sessionData.current_step || 1,
+ updated_at: sessionData.updated_at || new Date().toISOString(),
+
+ // Дополнительно
+ is_new_contact: claimResult.is_new_contact || false
+ }
+};
+```
+
+---
+
+## Итого: 3 изменения
+
+1. ✅ Добавить ноду `PostgreSQL: Auto Confirm Contact Data` после `CreateWebContacКлиентправ`
+2. ✅ Добавить ноду `PostgreSQL: Check Contact Data Status` после `user_get`
+3. ✅ Добавить 3 строки в `Code in JavaScript` перед `Respond to Webhook1`
+
+---
+
+## Порядок выполнения в workflow:
+
+```
+contact → Edit Fields → Get Challenge → ... → Login to CRM → form_id
+ ↓
+ CreateWebContacКлиентправ
+ ↓
+ [НОВАЯ] PostgreSQL: Auto Confirm Contact Data
+ ↓
+ Code in JavaScriptКлиентправ
+ ↓
+ user_get
+ ↓
+ [НОВАЯ] PostgreSQL: Check Contact Data Status
+ ↓
+ Execute a SQL query2
+ ↓
+ ...
+ ↓
+ Code in JavaScript (← ДОБАВИТЬ ФЛАГИ)
+ ↓
+ Respond to Webhook1
+```
+
+---
+
+## Проверка:
+
+После изменений в ответе n8n должны быть поля:
+- `contact_data_confirmed` (true/false)
+- `contact_data_can_edit` (true/false)
+- `contact_data_confirmed_at` (дата или null)
+
diff --git a/docs/N8N_WORKFLOW_ADD_POSTGRESQL_CONTACT.md b/docs/N8N_WORKFLOW_ADD_POSTGRESQL_CONTACT.md
new file mode 100644
index 0000000..9d5d8b8
--- /dev/null
+++ b/docs/N8N_WORKFLOW_ADD_POSTGRESQL_CONTACT.md
@@ -0,0 +1,100 @@
+# Добавление ноды PostgreSQL для получения данных контакта
+
+## Задача
+
+Добавить ноду PostgreSQL перед "Code in JavaScriptКлиентправ" для получения полных данных контакта из CRM.
+
+## Шаги
+
+### 1. Добавить ноду PostgreSQL
+
+**Название ноды:** `PostgreSQL: Get Contact Data`
+
+**Параметры:**
+- **Operation:** Execute Query
+- **Query:** (см. файл `N8N_POSTGRESQL_GET_CONTACT_DATA.sql`)
+
+**SQL запрос:**
+```sql
+SELECT
+ cd.contactid,
+ cd.firstname,
+ cd.lastname,
+ cd.email,
+ cd.mobile,
+ cd.phone,
+ cs.birthday,
+ ca.mailingstreet,
+ ca.mailingcity,
+ ca.mailingstate,
+ ca.mailingzip,
+ ca.mailingcountry,
+ ccf.cf_1157 AS middle_name,
+ ccf.cf_1263 AS birthplace,
+ ccf.cf_1257 AS inn,
+ ccf.cf_1849 AS requisites,
+ ccf.cf_1580 AS code,
+ ccf.cf_1706 AS sms,
+ ccf.cf_2624 AS cf_2624
+FROM vtiger_contactdetails cd
+LEFT JOIN vtiger_contactscf ccf ON ccf.contactid = cd.contactid
+LEFT JOIN vtiger_contactsubdetails cs ON cs.contactsubscriptionid = cd.contactid
+LEFT JOIN vtiger_contactaddress ca ON ca.contactaddressid = cd.contactid
+LEFT JOIN vtiger_crmentity ce ON ce.crmid = cd.contactid
+WHERE cd.contactid = $1
+ AND ce.deleted = 0
+LIMIT 1;
+```
+
+**Параметры запроса:**
+- `$1` = `{{ JSON.parse($node["CreateWebContacКлиентправ"].json.result).contact_id }}`
+
+---
+
+### 2. Порядок нод в workflow
+
+1. **CreateWebContacКлиентправ** → создаёт/находит контакт
+2. **PostgreSQL: Get Contact Data** → получает полные данные контакта
+3. **Code in JavaScriptКлиентправ** → использует данные из обеих нод
+
+---
+
+### 3. Что получает Code node
+
+После добавления ноды PostgreSQL, Code node получит доступ к:
+- `$('PostgreSQL: Get Contact Data').first().json` - полные данные контакта
+
+**Доступные поля:**
+- `contactid` - ID контакта
+- `firstname`, `lastname` - ФИО
+- `email`, `mobile`, `phone` - Контакты
+- `birthday` - Дата рождения
+- `mailingstreet`, `mailingcity`, etc. - Адрес
+- `middle_name` (cf_1157) - Отчество
+- `birthplace` (cf_1263) - Место рождения
+- `inn` (cf_1257) - ИНН
+- `requisites` (cf_1849) - Реквизиты
+- `code` (cf_1580) - Код
+- `sms` (cf_1706) - SMS
+- `cf_2624` - Данные подтверждены
+
+---
+
+### 4. Использование в Code node
+
+Код в "Code in JavaScriptКлиентправ" автоматически найдёт данные из PostgreSQL ноды и добавит их в `sessionData.contact_from_db`.
+
+---
+
+## Альтернатива: если нет доступа к PostgreSQL
+
+Если нет прямого доступа к PostgreSQL, можно использовать HTTP Request к backend API:
+
+**Название ноды:** `HTTP Request: Get Contact Data`
+
+**Метод:** GET
+**URL:** `{{ $env.BACKEND_URL }}/api/v1/contacts/{{ JSON.parse($node["CreateWebContacКлиентправ"].json.result).contact_id }}`
+
+Но лучше использовать PostgreSQL напрямую для скорости.
+
+
diff --git a/docs/N8N_WORKFLOW_UPDATE_CONTACT_DATA_CONFIRMED.md b/docs/N8N_WORKFLOW_UPDATE_CONTACT_DATA_CONFIRMED.md
new file mode 100644
index 0000000..9ff65ac
--- /dev/null
+++ b/docs/N8N_WORKFLOW_UPDATE_CONTACT_DATA_CONFIRMED.md
@@ -0,0 +1,87 @@
+# Обновление workflow 6mxRJ2LLHmQXyaDz: Подтверждение данных контакта
+
+## Изменения в workflow
+
+### 1. После ноды `CreateWebContacКлиентправ`
+
+**Добавить ноду:** `PostgreSQL: Auto Confirm if CRM has data`
+
+**SQL запрос:**
+```sql
+SELECT clpr_auto_confirm_if_crm_has_data($1, $2::integer);
+```
+
+**Параметры:**
+- `$1` = `{{ $('user_get').first().json.unified_id }}`
+- `$2` = `{{ JSON.parse($node['CreateWebContacКлиентправ'].json.result).contact_id }}`
+
+**Назначение:** Если данные уже есть в CRM (contact_id > 0), автоматически ставим флаг подтверждения.
+
+---
+
+### 2. После ноды `Code in JavaScriptКлиентправ`
+
+**Добавить ноду:** `PostgreSQL: Check Contact Data Status`
+
+**SQL запрос:**
+```sql
+SELECT * FROM clpr_get_contact_data_status($1);
+```
+
+**Параметры:**
+- `$1` = `{{ $('user_get').first().json.unified_id }}`
+
+**Назначение:** Проверяем, подтверждены ли данные. Результат передаём дальше.
+
+---
+
+### 3. В ответе для фронтенда (нода `Code in JavaScript`)
+
+**Добавить в return:**
+```javascript
+const contactStatus = $('PostgreSQL: Check Contact Data Status').first().json;
+
+return {
+ // ... существующие поля ...
+ contact_data_confirmed: contactStatus.is_confirmed || false,
+ contact_data_can_edit: contactStatus.can_edit !== false,
+ contact_data_confirmed_at: contactStatus.confirmed_at || null
+};
+```
+
+---
+
+### 4. После подтверждения формы (workflow для `claim_confirmed`)
+
+**Добавить ноду:** `PostgreSQL: Set Contact Data Confirmed`
+
+**SQL запрос:**
+```sql
+SELECT clpr_set_contact_data_confirmed($1, NOW());
+```
+
+**Параметры:**
+- `$1` = `{{ $json.unified_id }}`
+
+**Назначение:** Устанавливаем флаг подтверждения после успешного сохранения данных.
+
+---
+
+## Порядок выполнения
+
+1. **Создание контакта** → `CreateWebContacКлиентправ`
+2. **Автоподтверждение** → Если данные есть в CRM → `clpr_auto_confirm_if_crm_has_data`
+3. **Проверка статуса** → `clpr_get_contact_data_status` → передаём фронтенду
+4. **Фронтенд** → Если `contact_data_confirmed = true` → блокируем редактирование
+5. **После подтверждения** → `clpr_set_contact_data_confirmed` → устанавливаем флаг
+
+---
+
+## Проверка в n8n
+
+После обновления workflow проверить:
+- ✅ Флаг устанавливается при наличии данных в CRM
+- ✅ Флаг устанавливается после подтверждения формы
+- ✅ Статус передаётся фронтенду
+- ✅ Фронтенд блокирует редактирование при `contact_data_confirmed = true`
+
diff --git a/docs/SESSION_LOG_2025-11-28_documents_dedup.md b/docs/SESSION_LOG_2025-11-28_documents_dedup.md
new file mode 100644
index 0000000..210470b
--- /dev/null
+++ b/docs/SESSION_LOG_2025-11-28_documents_dedup.md
@@ -0,0 +1,96 @@
+# Лог сессии 28.11.2025 — Дедупликация документов и исправление field_label
+
+## Проблемы, которые были решены
+
+### 1. Неправильный `field_label` ("group-2" вместо "Переписка")
+
+**Причина:** В коде `N8N_CODE_PROCESS_UPLOADED_FILES_FIXED.js` использовался индекс `grp` (позиция в `documents_required`) для доступа к массиву `uploads_field_labels`, но этот массив содержит элементы с индексами от 0 (текущий запрос).
+
+**Исправление:** Изменён доступ к массивам на индекс `0`:
+```javascript
+// Было:
+const field_label = uploads_field_labels[grp] || ...
+
+// Стало:
+const field_label = uploads_field_labels[0] || uploads_field_names[0] || uploads_descriptions[0] || `group-${grp}`;
+```
+
+**Файл:** `ticket_form/docs/N8N_CODE_PROCESS_UPLOADED_FILES_FIXED.js`
+
+---
+
+### 2. Дублирование записей в `documents_meta`
+
+**Причина:** SQL использовал простую конкатенацию `||` для объединения новых и существующих `documents_meta`, что приводило к накоплению дубликатов (было 28 записей вместо 2).
+
+**Исправление:** Создан новый SQL с дедупликацией — новые записи заменяют старые с тем же `field_name`:
+```sql
+SELECT DISTINCT ON (doc->>'field_name') doc
+FROM (
+ SELECT ... AS doc, 1 AS priority -- новые (приоритет)
+ UNION ALL
+ SELECT ... AS doc, 2 AS priority -- существующие
+) all_docs
+ORDER BY doc->>'field_name', priority, (doc->>'uploaded_at') DESC NULLS LAST
+```
+
+**Файл:** `ticket_form/docs/SQL_CLAIMSAVE_FIXED_NEW_FLOW_DEDUP.sql`
+
+---
+
+### 3. Ошибка `ON CONFLICT` для `document_texts`
+
+**Причина:** Уникальный индекс на `file_hash` был частичным (`WHERE file_hash IS NOT NULL`), что не позволяло использовать `ON CONFLICT (file_hash)`.
+
+**Исправление:** Создан полный уникальный индекс:
+```sql
+DROP INDEX IF EXISTS idx_document_texts_hash_unique;
+CREATE UNIQUE INDEX idx_document_texts_hash_unique ON document_texts(file_hash);
+```
+
+---
+
+## Созданные/изменённые файлы
+
+| Файл | Описание |
+|------|----------|
+| `SQL_CLAIMSAVE_FIXED_NEW_FLOW_DEDUP.sql` | SQL с дедупликацией `documents_meta` |
+| `SQL_CLEANUP_DOCUMENTS_META_DUPLICATES.sql` | SQL для очистки существующих дубликатов |
+| `N8N_CODE_PROCESS_UPLOADED_FILES_FIXED.js` | Исправлен доступ к `uploads_field_labels[0]` |
+
+---
+
+## SQL-запросы для n8n
+
+### Проверка дубликата по хешу
+```sql
+SELECT
+ EXISTS (SELECT 1 FROM document_texts WHERE file_hash = '{{ $json.file_hash }}') AS found,
+ (SELECT id FROM document_texts WHERE file_hash = '{{ $json.file_hash }}' LIMIT 1) AS existing_id;
+```
+
+### Вставка с дедупликацией
+```sql
+INSERT INTO document_texts
+(file_id, file_url, path, title, filename_for_upload, "text", description, file_hash)
+VALUES (...)
+ON CONFLICT (file_hash) DO NOTHING
+RETURNING id, file_id, title, file_hash;
+```
+
+---
+
+## Изменения в БД
+
+1. Создан уникальный индекс `idx_document_texts_hash_unique` на `document_texts(file_hash)`
+2. Очищены дубликаты в `documents_meta` для заявки `ef853bac-f54b-46aa-adf8-f0c9c0cd76bc` (было 28 → стало 2)
+3. Исправлен `field_label` для `uploads[2][0]` на "Переписка"
+
+---
+
+## Рекомендации
+
+1. **Обновить SQL в n8n** ноде `claimsave` на версию из `SQL_CLAIMSAVE_FIXED_NEW_FLOW_DEDUP.sql`
+2. **Обновить код** в ноде `editfiletobd1` на версию из `N8N_CODE_PROCESS_UPLOADED_FILES_FIXED.js`
+3. **Добавить проверку хеша** перед вставкой в `document_texts` для информирования о дубликатах
+
diff --git a/docs/SESSION_LOG_2025-11-29_RAG_WORKFLOW.md b/docs/SESSION_LOG_2025-11-29_RAG_WORKFLOW.md
new file mode 100644
index 0000000..45036e1
--- /dev/null
+++ b/docs/SESSION_LOG_2025-11-29_RAG_WORKFLOW.md
@@ -0,0 +1,438 @@
+# Лог сессии: Настройка RAG workflow для извлечения данных
+**Дата:** 2025-11-29
+**Workflow ID:** `itX62h38faB51y9J` ("6 ocr_check:attempt")
+
+---
+
+## 🎯 Цель сессии
+
+Настроить workflow для автоматического извлечения данных из документов с использованием RAG (Retrieval-Augmented Generation) для заполнения формы заявления.
+
+---
+
+## 📊 Структура workflow
+
+```
+ocr_check:attempt (Redis Trigger)
+ ↓
+ clime_id (Set) → извлекает claim_id, session_id
+ ↓
+ analiz (Set) → добавляет prefix, session_token
+ ↓
+ give_data1 (PostgreSQL) → большой SQL, собирает все данные
+ ↓
+ Code1 (Code) → нормализует данные
+ ↓
+ prepare_rag_items (Code) → создаёт 3 items: user, project, offenders
+ ↓
+ Loop Over Items → итерация по типам
+ ↓
+ Code6 (Code) → генерация промптов для AI
+ ↓
+ AI Agent2 (LLM + RAG) → извлечение данных из документов
+ ↓
+ Code5 (Code) → парсинг JSON из LLM
+ ↓
+ Edit Fields4 → извлекает output
+ ↓
+ Aggregate → собирает все результаты
+ ↓
+ dataset (Set) → финальная сборка
+```
+
+---
+
+## 📝 Обновлённые ноды
+
+### 1. Code1 — нормализация данных из give_data1
+
+```javascript
+// Code1 — нормализация данных из give_data1
+// ИСПРАВЛЕНО: извлекаем payload.applicant, ai_analysis, wizard_plan, полные documents
+
+function toNullish(v) {
+ if (v === undefined || v === null) return null;
+ if (typeof v === 'string' && v.trim() === '') return null;
+ return v;
+}
+
+function pick(...vals) {
+ return vals.find(v => v !== undefined && v !== null && v !== '') ?? null;
+}
+
+function mapDocuments(docs = []) {
+ if (!docs || !Array.isArray(docs)) return [];
+ return docs.map(d => ({
+ id: toNullish(d.id),
+ claim_document_id: toNullish(d.id),
+ file_id: toNullish(d.file_id),
+ file_url: toNullish(d.file_url),
+ file_name: toNullish(d.file_name),
+ original_file_name: toNullish(d.original_file_name),
+ field_name: toNullish(d.field_name),
+ uploaded_at: toNullish(d.uploaded_at),
+ filename_for_upload: toNullish(d.filename_for_upload),
+ // AI данные
+ document_type: toNullish(d.document_type),
+ document_label: toNullish(d.document_label),
+ document_summary: toNullish(d.document_summary),
+ ocr_status: toNullish(d.ocr_status),
+ match_score: toNullish(d.match_score),
+ match_status: toNullish(d.match_status),
+ match_reason: toNullish(d.match_reason),
+ }));
+}
+
+function mapCombinedDocs(cds = []) {
+ if (!cds || !Array.isArray(cds)) return [];
+ return cds.map(c => ({
+ claim_document_id: toNullish(c.claim_document_id),
+ combined_document_id: toNullish(c.combined_document_id),
+ pages: toNullish(c.pages),
+ combined_text: toNullish(c.combined_text),
+ }));
+}
+
+function normalizeOne(src) {
+ const claim = src.claim ?? {};
+ const payload = claim.payload ?? {};
+ const userInfo = src.user_info ?? {};
+
+ // Извлекаем applicant из payload
+ const applicant = payload.applicant ?? {};
+ const aiAnalysis = payload.ai_analysis ?? {};
+ const answersPrefill = payload.answers_prefill ?? [];
+ const wizardPlan = payload.wizard_plan ?? null;
+
+ // USER: приоритет payload.applicant
+ const user = {
+ firstname: pick(applicant.firstname, applicant.first_name),
+ secondname: pick(applicant.middle_name, applicant.secondname),
+ lastname: pick(applicant.lastname, applicant.last_name),
+ mobile: pick(payload.phone),
+ email: pick(payload.email),
+ tgid: pick(claim.telegram_id, payload.tg_id),
+ birthday: pick(applicant.birthday, applicant.birth_date),
+ birthplace: pick(applicant.birthplace, applicant.birth_place),
+ mailingstreet: pick(applicant.address),
+ inn: pick(applicant.inn),
+ zip: pick(applicant.zip),
+ channel: pick(userInfo.channel, claim.channel),
+ unified_id: pick(claim.unified_id),
+ session_token: pick(claim.session_token),
+ };
+
+ // CASE
+ const caseData = {
+ id: toNullish(claim.id),
+ prefix: toNullish(claim.prefix),
+ channel: toNullish(claim.channel),
+ type_code: toNullish(claim.type_code),
+ status_code: toNullish(claim.status_code),
+ created_at: toNullish(claim.created_at),
+ updated_at: toNullish(claim.updated_at),
+ telegram_id: toNullish(claim.telegram_id),
+ session_token: toNullish(claim.session_token),
+ unified_id: toNullish(claim.unified_id),
+ case_type: pick(wizardPlan?.case_type, claim.type_code),
+ };
+
+ // ANSWERS
+ const answers = {};
+ if (Array.isArray(answersPrefill)) {
+ answersPrefill.forEach(a => {
+ if (a?.name && a?.value !== undefined) {
+ answers[a.name] = a.value;
+ }
+ });
+ }
+
+ // AI_ANALYSIS
+ const ai = {
+ problem: toNullish(aiAnalysis.problem),
+ facts_short: toNullish(aiAnalysis.facts_short),
+ facts_full: toNullish(aiAnalysis.facts_full),
+ recommendation: toNullish(aiAnalysis.recommendation),
+ };
+
+ const problemDescription = toNullish(payload.problem_description);
+
+ return {
+ case: caseData,
+ user,
+ answers: Object.keys(answers).length ? answers : null,
+ answers_prefill: answersPrefill.length ? answersPrefill : null,
+ ai_analysis: ai,
+ problem_description: problemDescription,
+ documents: mapDocuments(src.documents),
+ combined_docs: mapCombinedDocs(src.combined_docs),
+ wizard_plan: wizardPlan,
+ meta: {
+ claim_id: caseData.id,
+ session_token: caseData.session_token,
+ unified_id: caseData.unified_id,
+ }
+ };
+}
+
+const raw = items[0]?.json ?? {};
+const arr = Array.isArray(raw) ? raw : [raw];
+const results = arr.map(normalizeOne).map(obj => ({ json: obj }));
+return results.length ? results : [{ json: null }];
+```
+
+---
+
+### 2. Code6 — генерация промптов для RAG
+
+```javascript
+// n8n Code node: Генерация prompt'а под конкретный тип
+// ВХОД: { type: 'user'|'project'|'offenders', data: {...} }
+
+const type = $json.type;
+const data = $json.data;
+
+const code1Data = (() => {
+ try {
+ return $('Code1').first().json || {};
+ } catch(_) {
+ return {};
+ }
+})();
+
+const aiAnalysis = code1Data.ai_analysis || {};
+const problemDescription = code1Data.problem_description || '';
+const wizardPlan = code1Data.wizard_plan || {};
+const caseType = wizardPlan.case_type || code1Data.case?.type_code || 'consumer';
+
+let schema = '';
+let searchHints = '';
+let contextInfo = '';
+
+contextInfo = `
+КОНТЕКСТ ДЕЛА:
+- Тип: ${caseType}
+- Проблема: ${aiAnalysis.problem || 'не указана'}
+- Краткие факты: ${aiAnalysis.facts_short || 'не указаны'}
+`;
+
+if (type === 'user') {
+ schema = `{
+ "user": {
+ "firstname": string|null,
+ "secondname": string|null,
+ "lastname": string|null,
+ "mobile": string|null,
+ "email": string|null,
+ "tgid": number|null,
+ "birthday": "YYYY-MM-DD"|null,
+ "birthplace": string|null,
+ "mailingstreet": string|null,
+ "inn": string|null (12 цифр для физлица)
+ }
+ }`;
+
+ searchHints = `Ищи данные ПОКУПАТЕЛЯ/ЗАКАЗЧИКА:
+- ФИО: после "Покупатель:", "Заказчик:", "Потребитель:"
+- Адрес: "адрес регистрации", "адрес проживания", "место жительства"
+- ИНН физлица = 12 цифр
+- Телефон: в реквизитах, после "тел:", "моб:"
+- Email: в реквизитах`;
+
+} else if (type === 'project') {
+ schema = `{
+ "project": {
+ "category": string|null (тема обращения),
+ "direction": string|null,
+ "agrprice": number|null (сумма в рублях, только цифры!),
+ "subject": string|null (предмет договора - что купили/заказали),
+ "agrdate": "YYYY-MM-DD"|null (дата заключения договора),
+ "startdate": "YYYY-MM-DD"|null (дата начала услуги/поездки),
+ "finishdate": "YYYY-MM-DD"|null (дата окончания),
+ "country": string|null (страна для турпутёвок),
+ "hotel": string|null (название отеля),
+ "transport": "да"|"нет"|null (включён ли трансфер),
+ "insurance": "да"|"нет"|null (включена ли страховка),
+ "description": string|null (краткое описание сделки)
+ }
+ }`;
+
+ searchHints = `Ищи данные ДОГОВОРА/СДЕЛКИ:
+- Сумма: "Цена договора", "Стоимость", "Итого к оплате", "Сумма заказа"
+- Дата: "Дата заключения", "Договор № ... от ...", "Заказ от ..."
+- Предмет: что именно купили или заказали (товар, услуга, тур)
+- Для туров: страна, отель, даты заезда/выезда`;
+
+} else if (type === 'offenders') {
+ schema = `{
+ "offenders": [
+ {
+ "role": "seller"|"service_provider"|"tour_agent"|"tour_operator"|"delivery"|"installer"|"intermediary"|null,
+ "accountname": string|null (название организации или ФИО ИП),
+ "address": string|null (юридический адрес),
+ "email": string|null,
+ "website": string|null,
+ "phone": string|null,
+ "inn": string|null (10 цифр для юрлица, 12 для ИП),
+ "ogrn": string|null (13 цифр ОГРН или 15 цифр ОГРНИП)
+ }
+ ]
+ }`;
+
+ searchHints = `Ищи данные ВСЕХ КОНТРАГЕНТОВ (может быть несколько!):
+
+ГДЕ ИСКАТЬ:
+- "Продавец:", "Исполнитель:", "Поставщик:"
+- После ООО, ИП, ЗАО, ОАО, ПАО, АО
+- В реквизитах договора, в шапке чека
+
+РЕКВИЗИТЫ:
+- ИНН юрлица = 10 цифр, ИП = 12 цифр
+- ОГРН = 13 цифр, ОГРНИП = 15 цифр
+
+РОЛИ (определи по контексту):
+- seller — продавец товара (магазин, салон)
+- service_provider — исполнитель услуги
+- tour_agent — турагент (кто продал путёвку)
+- tour_operator — туроператор (кто организует тур, указан в договоре отдельно)
+- delivery — служба доставки
+- installer — сборщик/установщик
+- intermediary — посредник, маркетплейс
+
+ВАЖНО: Если в документах несколько организаций — добавь всех!`;
+}
+
+const filledCount = Object.values(data || {}).filter(v => v !== null && v !== undefined && v !== '').length;
+const totalCount = Object.keys(data || {}).length;
+
+return [{
+ json: {
+ systemMessage: `Ты — юридический помощник-экстрактор. У тебя есть инструмент vectorStore для поиска по документам.
+
+${contextInfo}
+
+${searchHints}
+
+ПРАВИЛА:
+1. Ищи только поля из схемы ниже
+2. Возвращай строго JSON в указанном формате
+3. Если данные не найдены — ставь null
+4. НЕ ПРИДУМЫВАЙ данные!
+5. Дозаполняй только пустые/null поля`,
+
+ userMessage: `Текущие данные (заполнено ${filledCount} из ${totalCount}, дозаполни остальное):
+${JSON.stringify(data, null, 2)}
+
+Схема для ответа:
+${schema}`,
+
+ _meta: {
+ type,
+ filledCount,
+ totalCount,
+ caseType
+ }
+ }
+}];
+```
+
+---
+
+### 3. prepare_rag_items — создание 3 items для RAG (НУЖНО ДОБАВИТЬ!)
+
+```javascript
+// Code node: prepare_rag_items
+// Создаёт 3 items для RAG: user, project, offenders
+// Вставить между Code1 и Loop Over Items
+
+const src = $('Code1').first().json;
+
+// USER — из уже собранных данных Code1
+const userData = src.user || {};
+
+// PROJECT — пока пустой, RAG заполнит
+const projectData = {
+ category: null,
+ direction: null,
+ agrprice: null,
+ subject: null,
+ agrdate: null,
+ startdate: null,
+ finishdate: null,
+ country: null,
+ hotel: null,
+ transport: null,
+ insurance: null,
+ description: src.problem_description || null,
+};
+
+// OFFENDERS — пока пустой массив, RAG найдёт
+const offendersData = [];
+
+// Выдаём 3 items для Loop Over Items
+return [
+ { json: { type: 'user', data: userData } },
+ { json: { type: 'project', data: projectData } },
+ { json: { type: 'offenders', data: offendersData } }
+];
+```
+
+---
+
+## ✅ Результат работы workflow
+
+Тестовый запуск на claim `509872e2-9666-4c5e-8ab7-2304dd6a5d18`:
+
+### USER — полностью заполнен
+- firstname: Федор
+- secondname: Владимирович
+- lastname: Коробков
+- mobile: 79262306381
+- email: help@clientright.ru
+- tgid: 295410106
+- birthday: 1981-09-18
+- birthplace: Москва
+- mailingstreet: МО, г. Балашиха, мкр. Железнодорожный, ул. Советская, д.20, кв. 52
+
+### PROJECT — основное заполнено
+- category: задержка ремонта/недоставка комплектующих и отказ в оказании услуги сборки
+- agrprice: **89620** (сумма договора)
+- subject: кровать-подиум Hemwood Base 180х200 и тумбы к ней
+- agrdate: **2025-08-09** (дата договора)
+- startdate: **2025-08-16** (дата доставки)
+- description: полное описание проблемы
+
+### OFFENDERS — найдено 2 контрагента!
+
+**1. Продавец (seller):**
+- accountname: ИП Хациев Зелимхан Зелимханович
+- inn: 201471261963 (12 цифр — ИП ✅)
+- ogrn: 315774600000123 (15 цифр — ОГРНИП ✅)
+- website: raiton.ru
+
+**2. Исполнитель услуг (service_provider):**
+- accountname: АО «ОРМАТЕК»
+- inn: 7724890784 (10 цифр — юрлицо ✅)
+- email: kassa@ormatek.com
+
+---
+
+## 📋 TODO (следующие шаги)
+
+1. [ ] Добавить ноду `prepare_rag_items` между Code1 и Loop Over Items
+2. [ ] Добавить постобработку данных (валидация, исправление ошибок AI)
+3. [ ] Сохранение результата в Redis для формы
+4. [ ] Подключить к генерации формы заявления
+
+---
+
+## 📁 Связанные файлы
+
+- `ticket_form/docs/SQL_CLAIMSAVE_FINAL_FIXED_NEW_FLOW_WITH_UPLOADED_FIXED.sql` — SQL для сохранения документов
+- `ticket_form/frontend/src/components/form/generateConfirmationFormHTML.ts` — шаблон формы заявления
+
+
+
+
+
+
diff --git a/docs/SESSION_LOG_2025-12-01.md b/docs/SESSION_LOG_2025-12-01.md
new file mode 100644
index 0000000..920a3a2
--- /dev/null
+++ b/docs/SESSION_LOG_2025-12-01.md
@@ -0,0 +1,281 @@
+# Session Log 2025-12-01
+
+## Сессия: UI/UX улучшения + CRM интеграция + Исправление дубликатов
+
+### Участники
+- Пользователь: Фёдор
+- AI: Claude (Cursor)
+
+---
+
+## 1. UI/UX Улучшения формы заявки
+
+### 1.1 Изменение заголовков
+- **Главный заголовок формы**: "Подать заявку на выплату" → "Подать обращение о защите прав потребителя"
+- **Заголовок вкладки браузера**: "ERV Insurance Platform" → "Clientright — защита прав потребителей"
+
+**Файлы:**
+- `ticket_form/frontend/src/pages/ClaimForm.tsx`
+- `ticket_form/frontend/index.html`
+- `ticket_form/frontend/public/index.html`
+
+### 1.2 Улучшение отображения черновиков
+**Файлы:**
+- `ticket_form/frontend/src/components/form/StepDraftSelection.tsx`
+- `ticket_form/backend/app/api/claims.py`
+
+**Изменения:**
+- Описание проблемы: увеличено до 250 символов, многострочное отображение
+- Добавлен заголовок проблемы (`problem_title` из `ai_analysis.problem`)
+- Добавлена категория (`category`) как фиолетовый тег
+- Прогресс-бар документов: `X / Y` с иконками статуса (✓ зелёный / ○ красный/серый)
+- Удалены избыточные теги "✓ Описание", "✓ План", "✓ Документы"
+- Убран дублирующий "++" из кнопки "Создать новую заявку"
+
+### 1.3 Исправление навигации
+- Кнопка "Назад" теперь всегда возвращает к списку черновиков (Step 0)
+- Пропуск шагов "Проверка полиса" и "Тип события" для нового флоу (`documents_required` present)
+
+**Файлы:**
+- `ticket_form/frontend/src/pages/ClaimForm.tsx`
+- `ticket_form/frontend/src/components/form/StepWizardPlan.tsx`
+
+### 1.4 Переименование шагов
+```
+'Телефон' → 'Вход'
+'Описание' → 'Обращение'
+'Рекомендации' → 'Документы'
+'Оплата' → 'Заявление'
+```
+
+---
+
+## 2. Backend улучшения
+
+### 2.1 Исправление IP клиента
+**Проблема:** Отображался Docker IP `192.168.0.1`
+
+**Решение:** Добавлена функция `_get_client_ip()` с проверкой заголовков:
+```python
+def _get_client_ip(request: Request) -> str:
+ # 1. X-Forwarded-For
+ # 2. X-Real-IP
+ # 3. request.client.host (fallback)
+```
+
+**Файл:** `ticket_form/backend/app/api/documents.py`
+
+### 2.2 Расширение API списка черновиков
+**Добавлены поля:**
+- `problem_title` - заголовок проблемы
+- `category` - категория
+- `documents_total` - всего документов
+- `documents_uploaded` - загружено (уникальных типов)
+- `documents_skipped` - пропущено
+- `documents_required_list` - детальный список с статусами
+
+**Файл:** `ticket_form/backend/app/api/claims.py`
+
+### 2.3 SSE для OCR статуса
+Добавлено подключение SSE для получения статуса OCR обработки после загрузки документов.
+
+**Файл:** `ticket_form/frontend/src/components/form/StepWizardPlan.tsx`
+
+---
+
+## 3. CRM Webservices (PHP)
+
+### 3.1 UpsertContact.php
+**Назначение:** Создание/обновление контакта с поддержкой `tgid`
+
+**Приоритет поиска:**
+1. `contact_id` - если передан
+2. `mobile` - поиск по телефону
+3. `tgid` - поиск по Telegram ID
+
+**Параметры:** `contact_json` (JSON строка)
+
+**Регистрация в БД:**
+```sql
+INSERT INTO vtiger_ws_operation (operationid, name, handler_path, handler_method, type, prelogin)
+VALUES (57, 'UpsertContact', 'include/Webservices/UpsertContact.php', 'vtws_upsertcontact', 'POST', 0);
+
+INSERT INTO vtiger_ws_operation_parameters (operationid, name, type, sequence)
+VALUES (57, 'contact_json', 'string', 1);
+```
+
+### 3.2 UpsertAccounts.php
+**Назначение:** Пакетное создание/поиск контрагентов (offenders) по ИНН
+
+**Логика:**
+- Поиск по ИНН
+- Если найден - возвращает ID без обновления
+- Если не найден - создаёт новый
+
+**Параметры:** `offenders_json` (JSON массив)
+
+**Регистрация в БД:**
+```sql
+INSERT INTO vtiger_ws_operation (operationid, name, handler_path, handler_method, type, prelogin)
+VALUES (58, 'UpsertAccounts', 'include/Webservices/UpsertAccounts.php', 'vtws_upsertaccounts', 'POST', 0);
+
+INSERT INTO vtiger_ws_operation_parameters (operationid, name, type, sequence)
+VALUES (58, 'offenders_json', 'string', 1);
+```
+
+### 3.3 UpsertProject.php
+**Назначение:** Создание/обновление проекта с маппингом ответчиков
+
+**Параметры:** `project_json` (JSON объект содержит):
+- `project_id` - ID проекта для обновления
+- `claim_id` - ID заявки
+- `contact_id` - ID контакта
+- `result` - JSON строка с `offender_ids`
+- `projectdata` - данные проекта
+
+**Маппинг ответчиков:**
+- Первый ответчик → `cf_2274` (основной)
+- Второй ответчик → `cf_2276` (агент)
+
+**Регистрация в БД:**
+```sql
+INSERT INTO vtiger_ws_operation (operationid, name, handler_path, handler_method, type, prelogin)
+VALUES (59, 'UpsertProject', 'include/Webservices/UpsertProject.php', 'vtws_upsertproject', 'POST', 0);
+
+INSERT INTO vtiger_ws_operation_parameters (operationid, name, type, sequence)
+VALUES (59, 'project_json', 'string', 1);
+```
+
+---
+
+## 4. Исправление дубликатов documents_meta
+
+### 4.1 Проблема
+В `documents_meta` накапливались дубликаты при каждой загрузке документа.
+
+**Причина:** SQL-запрос использовал простую конкатенацию `||` без дедупликации:
+```sql
+'{documents_meta}',
+COALESCE(...новые...) || COALESCE(...старые...)
+```
+
+### 4.2 Найденные дубликаты
+| claim_id | Было записей | Уникальных |
+|----------|--------------|------------|
+| `bddb6815-8e17-4d54-a721-5e94382942c7` | 11 | 5 |
+| `226564ce-d7cf-48ee-a820-690e8f5ec8e5` | 3 | 2 |
+| `509872e2-9666-4c5e-8ab7-2304dd6a5d18` | 4 | 3 |
+| `ef853bac-f54b-46aa-adf8-f0c9c0cd76bc` | 4 | 3 |
+
+### 4.3 SQL для исправления
+```sql
+-- Дедупликация по field_name (оставляем последний файл)
+UPDATE clpr_claims
+SET payload = jsonb_set(
+ payload,
+ '{documents_meta}',
+ (
+ SELECT COALESCE(
+ jsonb_agg(doc ORDER BY (doc->>'uploaded_at') DESC NULLS LAST),
+ '[]'::jsonb
+ )
+ FROM (
+ SELECT DISTINCT ON (doc->>'field_name') doc
+ FROM jsonb_array_elements(payload->'documents_meta') doc
+ ORDER BY doc->>'field_name', (doc->>'uploaded_at') DESC NULLS LAST
+ ) unique_docs
+ ),
+ true
+),
+updated_at = now()
+WHERE id IN (...);
+```
+
+### 4.4 Исправленный SQL для загрузки документов
+Добавлен CTE `documents_meta_dedup`:
+```sql
+documents_meta_dedup AS (
+ SELECT COALESCE(
+ (
+ SELECT jsonb_agg(doc ORDER BY (doc->>'uploaded_at') DESC NULLS LAST)
+ FROM (
+ SELECT DISTINCT ON (doc->>'field_name', doc->>'file_id') doc
+ FROM (
+ -- Новые записи (приоритет 1)
+ SELECT jsonb_array_elements(...) AS doc, 1 AS priority
+ UNION ALL
+ -- Существующие записи (приоритет 2)
+ SELECT jsonb_array_elements(...) AS doc, 2 AS priority
+ ) all_docs
+ ORDER BY doc->>'field_name', doc->>'file_id', priority
+ ) unique_docs
+ ),
+ '[]'::jsonb
+ ) AS documents_meta
+)
+```
+
+---
+
+## 5. n8n Workflows
+
+### 5.1 Проблема с Redis каналами
+**Бэкенд публикует:** `clpr:check:ocr_status`
+
+**n8n слушает:**
+- `fnSo3FTTbQcMjwt3` → `clpr:ocr:clime_file`
+- `1IKe2PccqXLkD2KR` → `clpr:ocr:jobs`
+
+**Нужно:** Либо создать новый workflow для `clpr:check:ocr_status`, либо изменить канал в бэкенде.
+
+---
+
+## 6. Файлы изменены
+
+### Frontend
+- `ticket_form/frontend/index.html`
+- `ticket_form/frontend/public/index.html`
+- `ticket_form/frontend/src/pages/ClaimForm.tsx`
+- `ticket_form/frontend/src/components/form/StepDraftSelection.tsx`
+- `ticket_form/frontend/src/components/form/StepWizardPlan.tsx`
+- `ticket_form/frontend/src/components/form/generateConfirmationFormHTML.ts`
+
+### Backend
+- `ticket_form/backend/app/api/claims.py`
+- `ticket_form/backend/app/api/documents.py`
+- `ticket_form/backend/app/api/events.py`
+
+### CRM
+- `include/Webservices/UpsertContact.php` (NEW)
+- `include/Webservices/UpsertAccounts.php` (NEW)
+- `include/Webservices/UpsertProject.php` (NEW)
+
+---
+
+## 7. Коммит
+
+```
+git commit -m "feat: UI/UX improvements + CRM integration methods + documents_meta deduplication"
+Commit: da82100b
+12 files changed, 1531 insertions(+), 145 deletions(-)
+```
+
+---
+
+## 8. Нерешённые задачи
+
+1. **n8n workflow для `clpr:check:ocr_status`** - нужно либо создать новый, либо изменить канал
+2. **Обновить SQL в бэкенде** - заменить SQL загрузки документов на версию с дедупликацией
+3. **Обновить n8n ноды** для использования новых CRM методов:
+ - `Create Contact` → `UpsertContact`
+ - `Create Account` → `UpsertAccounts`
+ - `Create Project` → `UpsertProject`
+
+---
+
+## Метаданные сессии
+
+- **Дата:** 2025-12-01
+- **Продолжительность:** ~2 часа
+- **Основной фокус:** UI/UX, CRM интеграция, исправление дубликатов
+
diff --git a/docs/SQL_ADD_CONTACT_DATA_CONFIRMED.sql b/docs/SQL_ADD_CONTACT_DATA_CONFIRMED.sql
new file mode 100644
index 0000000..9f4f29f
--- /dev/null
+++ b/docs/SQL_ADD_CONTACT_DATA_CONFIRMED.sql
@@ -0,0 +1,141 @@
+-- ============================================================================
+-- SQL миграция: Добавление флага подтверждения данных контакта
+-- ============================================================================
+-- Назначение: Предотвратить изменение данных контакта после первого подтверждения
+--
+-- Логика:
+-- 1. При первом подтверждении формы ставим contact_data_confirmed_at = NOW()
+-- 2. Если данные уже есть в CRM (созданы менеджером) - считаем подтверждёнными
+-- 3. При следующих обращениях проверяем флаг и блокируем редактирование
+-- 4. При изменении данных обновляем timestamp
+-- ============================================================================
+
+-- 1. Добавляем поле contact_data_confirmed_at в clpr_users
+ALTER TABLE clpr_users
+ADD COLUMN IF NOT EXISTS contact_data_confirmed_at TIMESTAMPTZ;
+
+-- 2. Создаём индекс для быстрого поиска
+CREATE INDEX IF NOT EXISTS idx_clpr_users_contact_data_confirmed
+ON clpr_users(contact_data_confirmed_at)
+WHERE contact_data_confirmed_at IS NOT NULL;
+
+-- 3. Комментарий к полю
+COMMENT ON COLUMN clpr_users.contact_data_confirmed_at IS
+'Дата и время подтверждения данных контакта пользователем. Если NULL - данные можно редактировать. Если NOT NULL - данные только для чтения.';
+
+-- ============================================================================
+-- Функция: Проверка, подтверждены ли данные контакта
+-- ============================================================================
+CREATE OR REPLACE FUNCTION clpr_is_contact_data_confirmed(p_unified_id VARCHAR)
+RETURNS BOOLEAN AS $$
+BEGIN
+ RETURN EXISTS (
+ SELECT 1
+ FROM clpr_users
+ WHERE unified_id = p_unified_id
+ AND contact_data_confirmed_at IS NOT NULL
+ );
+END;
+$$ LANGUAGE plpgsql;
+
+-- ============================================================================
+-- Функция: Установить флаг подтверждения данных
+-- ============================================================================
+CREATE OR REPLACE FUNCTION clpr_set_contact_data_confirmed(
+ p_unified_id VARCHAR,
+ p_confirmed_at TIMESTAMPTZ DEFAULT NOW()
+)
+RETURNS VOID AS $$
+BEGIN
+ UPDATE clpr_users
+ SET contact_data_confirmed_at = p_confirmed_at,
+ updated_at = NOW()
+ WHERE unified_id = p_unified_id;
+
+ -- Если пользователь не найден - создаём запись (на всякий случай)
+ IF NOT FOUND THEN
+ INSERT INTO clpr_users (unified_id, contact_data_confirmed_at, created_at, updated_at)
+ VALUES (p_unified_id, p_confirmed_at, NOW(), NOW())
+ ON CONFLICT (unified_id) DO UPDATE
+ SET contact_data_confirmed_at = p_confirmed_at,
+ updated_at = NOW();
+ END IF;
+END;
+$$ LANGUAGE plpgsql;
+
+-- ============================================================================
+-- Функция: Проверка и автоматическая установка флага для существующих контактов
+-- ============================================================================
+-- Если в CRM уже есть данные контакта (firstname, lastname, inn и т.д. заполнены),
+-- считаем их подтверждёнными автоматически
+--
+-- ВАЖНО: Эта функция должна вызываться после синхронизации данных из CRM
+-- ============================================================================
+CREATE OR REPLACE FUNCTION clpr_auto_confirm_if_crm_has_data(
+ p_unified_id VARCHAR,
+ p_contact_id INTEGER
+)
+RETURNS VOID AS $$
+DECLARE
+ v_has_data BOOLEAN;
+BEGIN
+ -- Проверяем, есть ли уже подтверждённые данные
+ IF EXISTS (
+ SELECT 1 FROM clpr_users
+ WHERE unified_id = p_unified_id
+ AND contact_data_confirmed_at IS NOT NULL
+ ) THEN
+ RETURN; -- Уже подтверждено
+ END IF;
+
+ -- Проверяем наличие данных в CRM через webservice
+ -- Если contact_id передан и > 0, считаем что данные есть в CRM
+ -- (это упрощённая проверка, можно расширить через API)
+ IF p_contact_id IS NOT NULL AND p_contact_id > 0 THEN
+ -- Устанавливаем флаг подтверждения
+ PERFORM clpr_set_contact_data_confirmed(p_unified_id);
+ END IF;
+END;
+$$ LANGUAGE plpgsql;
+
+-- ============================================================================
+-- Функция: Получить статус подтверждения данных
+-- ============================================================================
+CREATE OR REPLACE FUNCTION clpr_get_contact_data_status(p_unified_id VARCHAR)
+RETURNS TABLE(
+ is_confirmed BOOLEAN,
+ confirmed_at TIMESTAMPTZ,
+ can_edit BOOLEAN
+) AS $$
+BEGIN
+ RETURN QUERY
+ SELECT
+ COALESCE(u.contact_data_confirmed_at IS NOT NULL, false) AS is_confirmed,
+ u.contact_data_confirmed_at AS confirmed_at,
+ COALESCE(u.contact_data_confirmed_at IS NULL, true) AS can_edit
+ FROM clpr_users u
+ WHERE u.unified_id = p_unified_id;
+
+ -- Если пользователь не найден - возвращаем false (можно редактировать)
+ IF NOT FOUND THEN
+ RETURN QUERY SELECT false, NULL::TIMESTAMPTZ, true;
+ END IF;
+END;
+$$ LANGUAGE plpgsql;
+
+-- ============================================================================
+-- Примеры использования:
+-- ============================================================================
+
+-- 1. Проверить, подтверждены ли данные
+-- SELECT clpr_is_contact_data_confirmed('usr_abc123...');
+
+-- 2. Установить флаг подтверждения
+-- SELECT clpr_set_contact_data_confirmed('usr_abc123...');
+
+-- 3. Получить статус
+-- SELECT * FROM clpr_get_contact_data_status('usr_abc123...');
+
+-- 4. Автоматически подтвердить, если данные есть в CRM
+-- SELECT clpr_auto_confirm_if_crm_has_data('usr_abc123...', 396625);
+
diff --git a/docs/SQL_CLAIMSAVE_DOCUMENT_SKIP.sql b/docs/SQL_CLAIMSAVE_DOCUMENT_SKIP.sql
new file mode 100644
index 0000000..eb1ac91
--- /dev/null
+++ b/docs/SQL_CLAIMSAVE_DOCUMENT_SKIP.sql
@@ -0,0 +1,379 @@
+-- ============================================================================
+-- SQL для сохранения claim при пропуске документа (document skip) - НОВЫЙ ФЛОУ
+-- ============================================================================
+-- Проблема: При пропуске документа нужно обновить documents_skipped и current_doc_index
+-- Решение: Добавляем документ в documents_skipped, обновляем current_doc_index и status_code
+-- ============================================================================
+
+WITH partial AS (
+ SELECT
+ $1::jsonb AS p,
+ $2::text AS claim_id_str
+),
+
+existing_claim AS (
+ SELECT
+ id,
+ session_token,
+ unified_id,
+ contact_id,
+ phone,
+ payload,
+ status_code,
+ created_at
+ FROM clpr_claims
+ WHERE id = (SELECT claim_id_str::uuid FROM partial)
+ OR payload->>'claim_id' = (SELECT claim_id_str FROM partial)
+ ORDER BY
+ CASE WHEN id = (SELECT claim_id_str::uuid FROM partial) THEN 1 ELSE 2 END,
+ updated_at DESC
+ LIMIT 1
+),
+
+-- Парсим информацию о пропущенном документе
+skipped_document_info AS (
+ SELECT
+ COALESCE(
+ partial.p->>'document_type',
+ partial.p->'body'->>'document_type',
+ partial.p->'edit_fields_raw'->'body'->>'document_type',
+ partial.p->'edit_fields_parsed'->'body'->>'document_type'
+ ) AS document_type,
+ COALESCE(
+ partial.p->>'document_name',
+ partial.p->'body'->>'document_name',
+ partial.p->'edit_fields_raw'->'body'->>'document_name',
+ partial.p->'edit_fields_parsed'->'body'->>'document_name'
+ ) AS document_name,
+ COALESCE(
+ (partial.p->>'group_index')::int,
+ (partial.p->'body'->>'group_index')::int,
+ (partial.p->'edit_fields_raw'->'body'->>'group_index')::int,
+ (partial.p->'edit_fields_parsed'->'body'->>'group_index')::int,
+ NULL
+ ) AS group_index
+ FROM partial
+),
+
+-- Парсим documents_required (или берём из БД)
+documents_required_parsed AS (
+ SELECT
+ CASE
+ WHEN partial.p->'documents_required' IS NOT NULL
+ AND jsonb_typeof(partial.p->'documents_required') = 'array'
+ THEN partial.p->'documents_required'
+ WHEN EXISTS (SELECT 1 FROM existing_claim WHERE payload->'documents_required' IS NOT NULL)
+ THEN (SELECT payload->'documents_required' FROM existing_claim)
+ ELSE '[]'::jsonb
+ END AS documents_required
+ FROM partial
+),
+
+-- Парсим documents_uploaded (или берём из БД)
+documents_uploaded_parsed AS (
+ SELECT
+ CASE
+ WHEN partial.p->'documents_uploaded' IS NOT NULL
+ AND jsonb_typeof(partial.p->'documents_uploaded') = 'array'
+ THEN partial.p->'documents_uploaded'
+ WHEN EXISTS (SELECT 1 FROM existing_claim WHERE payload->'documents_uploaded' IS NOT NULL)
+ THEN (SELECT payload->'documents_uploaded' FROM existing_claim)
+ ELSE '[]'::jsonb
+ END AS documents_uploaded
+ FROM partial
+),
+
+-- Парсим documents_skipped (или берём из БД) и ДОБАВЛЯЕМ/ОБНОВЛЯЕМ новый пропущенный документ
+documents_skipped_parsed AS (
+ SELECT
+ CASE
+ -- Если документ уже есть в списке пропущенных - ОБНОВЛЯЕМ его (добавляем group_index, если его нет)
+ WHEN EXISTS (
+ SELECT 1
+ FROM existing_claim
+ WHERE payload->'documents_skipped' @> jsonb_build_array(
+ jsonb_build_object(
+ 'id', (SELECT document_type FROM skipped_document_info)
+ )
+ )
+ )
+ THEN (
+ -- Удаляем старую запись и добавляем новую с group_index
+ SELECT jsonb_agg(
+ CASE
+ WHEN doc->>'id' = (SELECT document_type FROM skipped_document_info)
+ THEN jsonb_build_object(
+ 'id', (SELECT document_type FROM skipped_document_info),
+ 'name', COALESCE((SELECT document_name FROM skipped_document_info), (SELECT document_type FROM skipped_document_info)),
+ 'group_index', (SELECT group_index FROM skipped_document_info),
+ 'skipped_at', COALESCE(doc->>'skipped_at', now()::text)
+ )
+ ELSE doc
+ END
+ )
+ FROM existing_claim,
+ jsonb_array_elements(payload->'documents_skipped') AS doc
+ )
+ -- Добавляем новый пропущенный документ
+ WHEN EXISTS (SELECT 1 FROM existing_claim WHERE payload->'documents_skipped' IS NOT NULL)
+ THEN (
+ SELECT payload->'documents_skipped' || jsonb_build_array(
+ jsonb_build_object(
+ 'id', (SELECT document_type FROM skipped_document_info),
+ 'name', COALESCE((SELECT document_name FROM skipped_document_info), (SELECT document_type FROM skipped_document_info)),
+ 'group_index', (SELECT group_index FROM skipped_document_info),
+ 'skipped_at', now()::text
+ )
+ )
+ FROM existing_claim
+ )
+ -- Создаём новый массив с пропущенным документом
+ ELSE jsonb_build_array(
+ jsonb_build_object(
+ 'id', (SELECT document_type FROM skipped_document_info),
+ 'name', COALESCE((SELECT document_name FROM skipped_document_info), (SELECT document_type FROM skipped_document_info)),
+ 'group_index', (SELECT group_index FROM skipped_document_info),
+ 'skipped_at', now()::text
+ )
+ )
+ END AS documents_skipped
+ FROM partial
+),
+
+-- Парсим current_doc_index и ОБНОВЛЯЕМ его (увеличиваем на 1 или находим следующий непропущенный)
+current_doc_index_parsed AS (
+ SELECT
+ CASE
+ -- Если передан group_index - используем его + 1
+ WHEN (SELECT group_index FROM skipped_document_info) IS NOT NULL
+ THEN (SELECT group_index FROM skipped_document_info) + 1
+ -- Иначе берём из payload и увеличиваем на 1
+ WHEN partial.p->'current_doc_index' IS NOT NULL
+ THEN (partial.p->'current_doc_index')::int + 1
+ WHEN EXISTS (SELECT 1 FROM existing_claim WHERE payload->'current_doc_index' IS NOT NULL)
+ THEN (SELECT (payload->'current_doc_index')::int FROM existing_claim) + 1
+ ELSE 1
+ END AS current_doc_index
+ FROM partial
+),
+
+-- Определяем правильный статус на основе прогресса документов
+status_code_resolved AS (
+ SELECT
+ CASE
+ -- Если есть documents_required - новый флоу
+ WHEN (SELECT jsonb_array_length(documents_required) FROM documents_required_parsed) > 0
+ THEN CASE
+ -- Все документы загружены или пропущены
+ WHEN (SELECT jsonb_array_length(documents_uploaded) FROM documents_uploaded_parsed) +
+ (SELECT jsonb_array_length(documents_skipped) FROM documents_skipped_parsed) >=
+ (SELECT jsonb_array_length(documents_required) FROM documents_required_parsed)
+ THEN 'draft_docs_complete'
+ -- Документы обрабатываются (есть загруженные или пропущенные)
+ WHEN (SELECT jsonb_array_length(documents_uploaded) FROM documents_uploaded_parsed) > 0
+ OR (SELECT jsonb_array_length(documents_skipped) FROM documents_skipped_parsed) > 0
+ THEN 'draft_docs_progress'
+ -- Только описание
+ ELSE 'draft_new'
+ END
+ -- Сохраняем существующий статус, если он новый
+ WHEN EXISTS (SELECT 1 FROM existing_claim
+ WHERE status_code IN ('draft_new', 'draft_docs_progress', 'draft_docs_complete', 'draft_claim_ready'))
+ THEN (SELECT status_code FROM existing_claim)
+ -- По умолчанию
+ ELSE 'draft'
+ END AS status_code
+ FROM partial
+),
+
+-- UPSERT claim
+claim_upsert AS (
+ INSERT INTO clpr_claims (
+ id,
+ session_token,
+ unified_id,
+ contact_id,
+ phone,
+ channel,
+ type_code,
+ status_code,
+ payload,
+ created_at,
+ updated_at,
+ expires_at
+ )
+ SELECT
+ COALESCE((SELECT id FROM existing_claim), partial.claim_id_str::uuid),
+ COALESCE(
+ partial.p->>'session_id',
+ partial.p->'body'->>'session_id',
+ partial.p->'edit_fields_parsed'->'body'->>'session_id',
+ partial.p->'edit_fields_raw'->'body'->>'session_id',
+ (SELECT payload->>'session_id' FROM existing_claim),
+ 'sess-unknown'
+ ),
+ COALESCE(
+ partial.p->>'unified_id',
+ partial.p->'body'->>'unified_id',
+ partial.p->'edit_fields_parsed'->'body'->>'unified_id',
+ partial.p->'edit_fields_raw'->'body'->>'unified_id',
+ (SELECT unified_id FROM existing_claim)
+ ),
+ COALESCE(
+ partial.p->>'contact_id',
+ partial.p->'body'->>'contact_id',
+ partial.p->'edit_fields_parsed'->'body'->>'contact_id',
+ partial.p->'edit_fields_raw'->'body'->>'contact_id',
+ (SELECT contact_id FROM existing_claim)
+ ),
+ COALESCE(
+ partial.p->>'phone',
+ partial.p->'body'->>'phone',
+ partial.p->'edit_fields_parsed'->'body'->>'phone',
+ partial.p->'edit_fields_raw'->'body'->>'phone',
+ (SELECT phone FROM existing_claim)
+ ),
+ 'web_form',
+ COALESCE(partial.p->>'type_code', 'consumer'),
+ (SELECT status_code FROM status_code_resolved),
+ jsonb_build_object(
+ 'claim_id', partial.claim_id_str,
+ -- Сохраняем существующие поля из payload
+ 'problem_description', COALESCE(
+ partial.p->>'problem_description',
+ (SELECT payload->>'problem_description' FROM existing_claim)
+ ),
+ 'answers', COALESCE(
+ partial.p->'answers',
+ (SELECT payload->'answers' FROM existing_claim),
+ '{}'::jsonb
+ ),
+ 'wizard_plan', COALESCE(
+ partial.p->'wizard_plan',
+ (SELECT payload->'wizard_plan' FROM existing_claim)
+ ),
+ -- ✅ Сохраняем documents_meta (если есть)
+ 'documents_meta', COALESCE(
+ partial.p->'documents_meta',
+ (SELECT payload->'documents_meta' FROM existing_claim),
+ '[]'::jsonb
+ ),
+ -- ✅ НОВЫЙ ФЛОУ: Обновляем documents_required, documents_uploaded, documents_skipped, current_doc_index
+ 'documents_required', (SELECT documents_required FROM documents_required_parsed),
+ 'documents_uploaded', (SELECT documents_uploaded FROM documents_uploaded_parsed),
+ 'documents_skipped', (SELECT documents_skipped FROM documents_skipped_parsed),
+ 'current_doc_index', (SELECT current_doc_index FROM current_doc_index_parsed),
+ 'phone', COALESCE(
+ partial.p->>'phone',
+ (SELECT payload->>'phone' FROM existing_claim)
+ ),
+ 'email', COALESCE(
+ partial.p->>'email',
+ (SELECT payload->>'email' FROM existing_claim)
+ )
+ ),
+ COALESCE((SELECT created_at FROM existing_claim), now()),
+ now(),
+ now() + interval '14 days'
+ FROM partial
+ ON CONFLICT (id) DO UPDATE SET
+ session_token = COALESCE(EXCLUDED.session_token, clpr_claims.session_token),
+ unified_id = COALESCE(EXCLUDED.unified_id, clpr_claims.unified_id),
+ contact_id = COALESCE(EXCLUDED.contact_id, clpr_claims.contact_id),
+ phone = COALESCE(EXCLUDED.phone, clpr_claims.phone),
+ -- ✅ Обновляем status_code на основе прогресса документов
+ status_code = (SELECT status_code FROM status_code_resolved),
+ -- ✅ Объединяем payload правильно: аккуратно обновляем критичные поля
+ payload = jsonb_set(
+ jsonb_set(
+ jsonb_set(
+ jsonb_set(
+ -- Сначала берём существующий payload и объединяем с новым (без критичных полей)
+ COALESCE(clpr_claims.payload, '{}'::jsonb) ||
+ (EXCLUDED.payload - 'documents_required' - 'documents_uploaded' - 'documents_skipped' - 'current_doc_index'),
+ '{documents_required}',
+ COALESCE(
+ EXCLUDED.payload->'documents_required',
+ clpr_claims.payload->'documents_required',
+ '[]'::jsonb
+ ),
+ true
+ ),
+ '{documents_uploaded}',
+ COALESCE(
+ EXCLUDED.payload->'documents_uploaded',
+ clpr_claims.payload->'documents_uploaded',
+ '[]'::jsonb
+ ),
+ true
+ ),
+ '{documents_skipped}',
+ -- ✅ ОБЪЕДИНЯЕМ documents_skipped (добавляем новый пропущенный документ или ОБНОВЛЯЕМ существующий)
+ CASE
+ -- Если новый документ уже есть в существующем списке - ОБНОВЛЯЕМ его (добавляем group_index)
+ WHEN clpr_claims.payload->'documents_skipped' @> jsonb_build_array(
+ jsonb_build_object(
+ 'id', (SELECT document_type FROM skipped_document_info)
+ )
+ )
+ THEN (
+ -- Обновляем существующую запись, добавляя group_index
+ SELECT jsonb_agg(
+ CASE
+ WHEN doc->>'id' = (SELECT document_type FROM skipped_document_info)
+ THEN jsonb_build_object(
+ 'id', (SELECT document_type FROM skipped_document_info),
+ 'name', COALESCE((SELECT document_name FROM skipped_document_info), (SELECT document_type FROM skipped_document_info)),
+ 'group_index', (SELECT group_index FROM skipped_document_info),
+ 'skipped_at', COALESCE(doc->>'skipped_at', now()::text)
+ )
+ ELSE doc
+ END
+ )
+ FROM jsonb_array_elements(clpr_claims.payload->'documents_skipped') AS doc
+ )
+ -- Добавляем новый пропущенный документ
+ ELSE COALESCE(clpr_claims.payload->'documents_skipped', '[]'::jsonb) || jsonb_build_array(
+ jsonb_build_object(
+ 'id', (SELECT document_type FROM skipped_document_info),
+ 'name', COALESCE((SELECT document_name FROM skipped_document_info), (SELECT document_type FROM skipped_document_info)),
+ 'group_index', (SELECT group_index FROM skipped_document_info),
+ 'skipped_at', now()::text
+ )
+ )
+ END,
+ true
+ ),
+ '{current_doc_index}',
+ COALESCE(
+ EXCLUDED.payload->'current_doc_index',
+ -- Если не передан, увеличиваем существующий на 1
+ CASE
+ WHEN clpr_claims.payload->'current_doc_index' IS NOT NULL
+ THEN to_jsonb((clpr_claims.payload->'current_doc_index')::int + 1)
+ ELSE to_jsonb(1)
+ END
+ ),
+ true
+ ),
+ updated_at = now(),
+ expires_at = now() + interval '14 days'
+ RETURNING id, status_code, payload, unified_id, contact_id, phone, session_token
+)
+
+-- Возвращаем результат
+SELECT
+ jsonb_build_object(
+ 'claim_id', cu.id::text,
+ 'claim_id_str', (cu.payload->>'claim_id'),
+ 'status_code', cu.status_code,
+ 'unified_id', cu.unified_id,
+ 'contact_id', cu.contact_id,
+ 'phone', cu.phone,
+ 'session_token', cu.session_token,
+ 'payload', cu.payload,
+ 'documents_skipped', cu.payload->'documents_skipped',
+ 'current_doc_index', cu.payload->'current_doc_index'
+ ) AS claim
+FROM claim_upsert cu;
+
diff --git a/docs/SQL_CLAIMSAVE_FINAL_FIXED_NEW_FLOW_WITH_UPLOADED.sql b/docs/SQL_CLAIMSAVE_FINAL_FIXED_NEW_FLOW_WITH_UPLOADED.sql
index 047b865..8c98d33 100644
--- a/docs/SQL_CLAIMSAVE_FINAL_FIXED_NEW_FLOW_WITH_UPLOADED.sql
+++ b/docs/SQL_CLAIMSAVE_FINAL_FIXED_NEW_FLOW_WITH_UPLOADED.sql
@@ -37,7 +37,7 @@ claim_lookup AS (
),
docs AS (
- SELECT
+ SELECT DISTINCT ON (claim_lookup.id::text, doc.field_name, doc.file_id)
claim_lookup.id::text AS claim_id,
doc.field_name::text AS field_name,
doc.field_label::text AS field_label,
@@ -62,6 +62,9 @@ docs AS (
files_count int,
pages int
)
+ -- ✅ Приоритет: записи с валидным file_url идут первыми
+ ORDER BY claim_lookup.id::text, doc.field_name, doc.file_id,
+ CASE WHEN doc.file_url IS NOT NULL AND doc.file_url <> '' AND doc.file_url ~* '^https?://' THEN 0 ELSE 1 END
),
-- ✅ НОВОЕ: Создаём documents_uploaded на основе documents_meta
@@ -181,7 +184,7 @@ upsert_docs AS (
uploaded_at = EXCLUDED.uploaded_at,
file_name = EXCLUDED.file_name,
original_file_name = EXCLUDED.original_file_name
- RETURNING id, claim_id, field_name, file_id
+ RETURNING id, claim_id, field_name, file_id, uploaded_at, file_name, original_file_name -- ✅ Возвращаем все поля для использования в финальном SELECT
),
-- ✅ ИСПРАВЛЕНО: Сохраняем documents_required, documents_uploaded и обновляем статус правильно
@@ -274,26 +277,48 @@ SELECT
'payload', u.payload
) FROM upd_claim u) AS claim,
+ -- ✅ ИСПРАВЛЕНО: Возвращаем ВСЕ документы из upsert_docs с правильным claim_document_id
(
SELECT jsonb_agg(
jsonb_build_object(
- 'id', u.id,
+ 'id', u.id::text, -- ✅ Это claim_document_id из таблицы clpr_claim_documents
+ 'claim_document_id', u.id::text, -- ✅ Явно указываем для ясности
'field_name', u.field_name,
'file_id', u.file_id,
- 'file_url', d.file_url,
- 'file_name', d.file_name,
- 'original_file_name', d.original_file_name,
- 'uploaded_at', d.uploaded_at,
+ -- ✅ Получаем file_url из docs (если есть) или из documents_meta в payload
+ 'file_url', COALESCE(
+ d.file_url,
+ (
+ SELECT meta->>'file_url'
+ FROM upd_claim uc, jsonb_array_elements(uc.payload->'documents_meta') AS meta
+ WHERE meta->>'field_name' = u.field_name
+ AND meta->>'file_id' = u.file_id
+ AND meta->>'file_url' IS NOT NULL
+ AND meta->>'file_url' <> ''
+ LIMIT 1
+ )
+ ),
+ 'file_name', COALESCE(d.file_name, u.file_name),
+ 'original_file_name', COALESCE(d.original_file_name, u.original_file_name),
+ 'uploaded_at', COALESCE(
+ d.uploaded_at::text,
+ u.uploaded_at::text
+ ),
'filename_for_upload',
COALESCE(
- NULLIF(d.original_file_name, ''),
- NULLIF(d.file_name, ''),
- regexp_replace(d.file_id, '^.*/', '')
+ NULLIF(COALESCE(d.original_file_name, u.original_file_name), ''),
+ NULLIF(COALESCE(d.file_name, u.file_name), ''),
+ regexp_replace(u.file_id, '^.*/', '')
)
)
+ ORDER BY u.field_name -- ✅ Сортируем для предсказуемости
)
FROM upsert_docs u
- JOIN docs d ON d.claim_id = u.claim_id AND d.field_name = u.field_name
- WHERE d.file_url IS NOT NULL AND d.file_url <> ''
+ -- ✅ LEFT JOIN: возвращаем ВСЕ документы из таблицы, даже если нет file_url в docs
+ LEFT JOIN docs d ON d.claim_id = u.claim_id
+ AND d.field_name = u.field_name
+ AND d.file_id = u.file_id
+ AND d.file_url IS NOT NULL
+ AND d.file_url <> ''
) AS documents;
diff --git a/docs/SQL_CLAIMSAVE_FINAL_FIXED_NEW_FLOW_WITH_UPLOADED_FIXED.sql b/docs/SQL_CLAIMSAVE_FINAL_FIXED_NEW_FLOW_WITH_UPLOADED_FIXED.sql
new file mode 100644
index 0000000..5c58dd5
--- /dev/null
+++ b/docs/SQL_CLAIMSAVE_FINAL_FIXED_NEW_FLOW_WITH_UPLOADED_FIXED.sql
@@ -0,0 +1,345 @@
+-- ============================================================================
+-- Исправленный SQL для сохранения документов (claimsave_final) - ПОДДЕРЖКА НОВОГО ФЛОУ
+-- ============================================================================
+-- ИСПРАВЛЕНИЕ: Возврат правильного claim_document_id из таблицы clpr_claim_documents
+-- Проблема: Возвращался только один документ и неправильный id
+-- Решение: Используем u.id из upsert_docs (таблица clpr_claim_documents) и фильтруем правильно
+-- ============================================================================
+
+WITH partial AS (
+ SELECT $1::jsonb AS p, $2::text AS claim_id_str
+),
+
+claim_lookup AS (
+ SELECT
+ c.id,
+ c.payload,
+ c.status_code
+ FROM clpr_claims c, partial
+ WHERE c.id::text = partial.claim_id_str
+ OR c.payload->>'claim_id' = partial.claim_id_str
+ ORDER BY
+ CASE WHEN c.id::text = partial.claim_id_str THEN 1 ELSE 2 END,
+ c.updated_at DESC
+ LIMIT 1
+),
+
+docs AS (
+ SELECT
+ claim_lookup.id::text AS claim_id,
+ doc.field_name::text AS field_name,
+ doc.field_label::text AS field_label,
+ doc.file_id::text AS file_id,
+ doc.file_name::text AS file_name,
+ doc.original_file_name::text AS original_file_name,
+ (doc.uploaded_at)::timestamptz AS uploaded_at,
+ doc.file_url::text AS file_url,
+ doc.files_count::int AS files_count,
+ doc.pages::int AS pages
+ FROM partial, claim_lookup
+ CROSS JOIN LATERAL jsonb_to_recordset(
+ COALESCE(partial.p->'documents_meta','[]'::jsonb)
+ ) AS doc(
+ field_name text,
+ field_label text,
+ file_id text,
+ file_name text,
+ original_file_name text,
+ uploaded_at text,
+ file_url text,
+ files_count int,
+ pages int
+ )
+ -- ✅ ФИЛЬТРУЕМ: берём только документы с валидным file_url И уникальным field_name+file_id
+ WHERE doc.file_url IS NOT NULL
+ AND doc.file_url <> ''
+ AND doc.file_url ~* '^https?://'
+ -- ✅ Убираем дубликаты: берём только первую запись для каждого field_name с валидным file_url
+ AND NOT EXISTS (
+ SELECT 1 FROM jsonb_to_recordset(COALESCE(partial.p->'documents_meta','[]'::jsonb)) AS doc2(
+ field_name text,
+ file_id text,
+ file_url text
+ )
+ WHERE doc2.field_name = doc.field_name
+ AND doc2.file_id = doc.file_id
+ AND doc2.file_url ~* '^https?://'
+ AND doc2.file_url <> ''
+ -- Сравниваем по позиции в массиве (берем первый)
+ AND (SELECT ordinality FROM jsonb_array_elements(COALESCE(partial.p->'documents_meta','[]'::jsonb)) WITH ORDINALITY AS d3 WHERE d3.value->>'field_name' = doc.field_name AND d3.value->>'file_id' = doc.file_id ORDER BY d3.ordinality LIMIT 1) <
+ (SELECT ordinality FROM jsonb_array_elements(COALESCE(partial.p->'documents_meta','[]'::jsonb)) WITH ORDINALITY AS d4 WHERE d4.value->>'field_name' = doc.field_name AND d4.value->>'file_id' = doc.file_id ORDER BY d4.ordinality LIMIT 1)
+ )
+),
+
+-- ✅ НОВОЕ: Создаём documents_uploaded на основе documents_meta
+documents_uploaded_built AS (
+ SELECT
+ -- ✅ ВАЖНО: Всегда начинаем с существующих documents_uploaded
+ COALESCE(
+ (SELECT claim_lookup.payload->'documents_uploaded' FROM claim_lookup),
+ '[]'::jsonb
+ ) ||
+ -- ✅ Добавляем только НОВЫЕ документы из documents_meta (которых нет в существующих)
+ COALESCE(
+ (
+ SELECT jsonb_agg(
+ jsonb_build_object(
+ 'id',
+ CASE
+ -- ✅ СНАЧАЛА проверяем field_label (более точный способ определения типа)
+ WHEN doc.field_label ILIKE '%договор%' OR doc.field_label ILIKE '%заказ%'
+ THEN 'contract'
+ WHEN doc.field_label ILIKE '%чек%' OR doc.field_label ILIKE '%оплат%'
+ THEN 'payment'
+ WHEN doc.field_label ILIKE '%переписк%'
+ THEN 'correspondence'
+ WHEN doc.field_label ILIKE '%доказательств%' OR doc.field_label ILIKE '%фото%'
+ THEN 'evidence_photo'
+ -- ✅ ПОТОМ проверяем field_name (fallback, если field_label не определён)
+ WHEN doc.field_name LIKE 'uploads[0]%'
+ THEN 'contract'
+ WHEN doc.field_name LIKE 'uploads[1]%'
+ THEN 'payment'
+ WHEN doc.field_name LIKE 'uploads[2]%'
+ THEN 'correspondence'
+ WHEN doc.field_name LIKE 'uploads[3]%'
+ THEN 'evidence_photo'
+ ELSE 'unknown'
+ END,
+ 'type',
+ CASE
+ -- ✅ СНАЧАЛА проверяем field_label (более точный способ определения типа)
+ WHEN doc.field_label ILIKE '%договор%' OR doc.field_label ILIKE '%заказ%'
+ THEN 'contract'
+ WHEN doc.field_label ILIKE '%чек%' OR doc.field_label ILIKE '%оплат%'
+ THEN 'payment'
+ WHEN doc.field_label ILIKE '%переписк%'
+ THEN 'correspondence'
+ WHEN doc.field_label ILIKE '%доказательств%' OR doc.field_label ILIKE '%фото%'
+ THEN 'evidence_photo'
+ -- ✅ ПОТОМ проверяем field_name (fallback, если field_label не определён)
+ WHEN doc.field_name LIKE 'uploads[0]%'
+ THEN 'contract'
+ WHEN doc.field_name LIKE 'uploads[1]%'
+ THEN 'payment'
+ WHEN doc.field_name LIKE 'uploads[2]%'
+ THEN 'correspondence'
+ WHEN doc.field_name LIKE 'uploads[3]%'
+ THEN 'evidence_photo'
+ ELSE 'unknown'
+ END,
+ 'file_id', doc.file_id,
+ 'file_name', doc.file_name,
+ 'original_file_name', doc.original_file_name,
+ 'uploaded_at', doc.uploaded_at::text,
+ 'ocr_status', 'completed',
+ 'files_count', COALESCE(doc.files_count, 1),
+ 'pages', doc.pages
+ )
+ ORDER BY doc.field_name
+ )
+ FROM docs doc, claim_lookup
+ -- ✅ Исключаем документы, которые уже есть в documents_uploaded (по file_id)
+ WHERE NOT EXISTS (
+ SELECT 1
+ FROM jsonb_array_elements(COALESCE(claim_lookup.payload->'documents_uploaded', '[]'::jsonb)) AS existing
+ WHERE existing->>'file_id' = doc.file_id
+ )
+ AND doc.file_id IS NOT NULL
+ ),
+ '[]'::jsonb -- Если новых документов нет - возвращаем пустой массив для объединения
+ ) AS documents_uploaded_array
+ FROM claim_lookup
+),
+
+-- ✅ НОВОЕ: Определяем current_doc_index (следующий незагруженный документ)
+current_doc_index_calculated AS (
+ SELECT
+ CASE
+ WHEN claim_lookup.payload->'documents_required' IS NOT NULL THEN
+ -- Находим первый незагруженный документ
+ COALESCE(
+ (
+ SELECT idx
+ FROM jsonb_array_elements(claim_lookup.payload->'documents_required') WITH ORDINALITY AS req(doc, idx)
+ WHERE NOT EXISTS (
+ SELECT 1
+ FROM documents_uploaded_built, jsonb_array_elements(documents_uploaded_built.documents_uploaded_array) AS uploaded
+ WHERE (uploaded->>'id') = (req.doc->>'id')
+ )
+ ORDER BY idx
+ LIMIT 1
+ ),
+ -- Если все документы загружены, возвращаем количество документов
+ jsonb_array_length(claim_lookup.payload->'documents_required')
+ )
+ ELSE 0
+ END AS current_doc_index
+ FROM claim_lookup
+),
+
+-- ✅ ИСПРАВЛЕНО: Сохраняем документы в таблицу clpr_claim_documents
+-- ✅ ДОБАВЛЕНО: document_type и document_label для AI matching
+upsert_docs AS (
+ INSERT INTO clpr_claim_documents
+ (claim_id, field_name, file_id, uploaded_at, file_name, original_file_name,
+ document_type, document_label)
+ SELECT
+ claim_id,
+ field_name,
+ file_id,
+ uploaded_at,
+ file_name,
+ original_file_name,
+ -- document_type: вычисляем из field_label или field_name
+ CASE
+ WHEN field_label ILIKE '%договор%' OR field_label ILIKE '%заказ%'
+ THEN 'contract'
+ WHEN field_label ILIKE '%чек%' OR field_label ILIKE '%оплат%'
+ THEN 'payment'
+ WHEN field_label ILIKE '%переписк%'
+ THEN 'correspondence'
+ WHEN field_label ILIKE '%доказательств%' OR field_label ILIKE '%фото%'
+ THEN 'evidence_photo'
+ WHEN field_name LIKE 'uploads[0]%'
+ THEN 'contract'
+ WHEN field_name LIKE 'uploads[1]%'
+ THEN 'payment'
+ WHEN field_name LIKE 'uploads[2]%'
+ THEN 'correspondence'
+ WHEN field_name LIKE 'uploads[3]%'
+ THEN 'evidence_photo'
+ ELSE 'unknown'
+ END,
+ -- document_label: сохраняем как есть из формы
+ field_label
+ FROM docs
+ ON CONFLICT (claim_id, field_name) DO UPDATE
+ SET file_id = EXCLUDED.file_id,
+ uploaded_at = EXCLUDED.uploaded_at,
+ file_name = EXCLUDED.file_name,
+ original_file_name = EXCLUDED.original_file_name,
+ document_type = EXCLUDED.document_type,
+ document_label = EXCLUDED.document_label
+ RETURNING id, claim_id, field_name, file_id, document_type, document_label
+),
+
+-- ✅ ИСПРАВЛЕНО: Сохраняем documents_required, documents_uploaded и обновляем статус правильно
+upd_claim AS (
+ UPDATE clpr_claims c
+ SET
+ -- ✅ Объединяем payload: сохраняем documents_required, documents_meta, documents_uploaded и current_doc_index
+ payload = jsonb_set(
+ jsonb_set(
+ jsonb_set(
+ jsonb_set(
+ COALESCE(c.payload, '{}'::jsonb),
+ '{documents_meta}',
+ -- ✅ ОБЪЕДИНЯЕМ существующие documents_meta с новыми (не перезаписываем!)
+ COALESCE(
+ (SELECT p->'documents_meta' FROM partial WHERE partial.p->'documents_meta' IS NOT NULL),
+ '[]'::jsonb
+ ) || COALESCE(
+ c.payload->'documents_meta',
+ '[]'::jsonb
+ ),
+ true
+ ),
+ '{documents_required}',
+ COALESCE(
+ (SELECT p->'documents_required' FROM partial WHERE partial.p->'documents_required' IS NOT NULL),
+ c.payload->'documents_required, -- Сохраняем существующий, если новый не пришёл
+ '[]'::jsonb
+ ),
+ true
+ ),
+ '{documents_uploaded}',
+ -- ✅ ВАЖНО: Используем объединённый массив из documents_uploaded_built
+ CASE
+ WHEN EXISTS (
+ SELECT 1 FROM documents_uploaded_built
+ WHERE documents_uploaded_array IS NOT NULL
+ AND jsonb_array_length(documents_uploaded_array) > 0
+ )
+ THEN (SELECT documents_uploaded_array FROM documents_uploaded_built LIMIT 1)
+ ELSE COALESCE(c.payload->'documents_uploaded', '[]'::jsonb)
+ END,
+ true
+ ),
+ '{current_doc_index}',
+ to_jsonb((SELECT current_doc_index FROM current_doc_index_calculated)),
+ true
+ ),
+ -- ✅ Обновляем статус только если нужно (не перезаписываем новые статусы)
+ status_code = CASE
+ -- Если статус уже новый - сохраняем его (кроме случаев, когда нужно обновить)
+ WHEN c.status_code IN ('draft_new', 'draft_docs_progress', 'draft_docs_complete', 'draft_claim_ready')
+ THEN CASE
+ -- Если есть documents_required и документы загружены - обновляем статус
+ WHEN c.payload->'documents_required' IS NOT NULL
+ AND jsonb_array_length(COALESCE(c.payload->'documents_required', '[]'::jsonb)) > 0
+ AND (SELECT COUNT(*) FROM docs) > 0
+ THEN CASE
+ WHEN (SELECT COUNT(*) FROM docs) >= jsonb_array_length(COALESCE(c.payload->'documents_required', '[]'::jsonb))
+ THEN 'draft_docs_complete'
+ ELSE 'draft_docs_progress'
+ END
+ ELSE c.status_code
+ END
+ -- Если есть documents_required и документы загружены - обновляем статус
+ WHEN c.payload->'documents_required' IS NOT NULL
+ AND jsonb_array_length(COALESCE(c.payload->'documents_required', '[]'::jsonb)) > 0
+ AND (SELECT COUNT(*) FROM docs) > 0
+ THEN CASE
+ WHEN (SELECT COUNT(*) FROM docs) >= jsonb_array_length(COALESCE(c.payload->'documents_required', '[]'::jsonb))
+ THEN 'draft_docs_complete'
+ ELSE 'draft_docs_progress'
+ END
+ -- Иначе сохраняем существующий
+ ELSE c.status_code
+ END,
+ updated_at = now(),
+ expires_at = now() + interval '14 days'
+ FROM partial, claim_lookup
+ WHERE c.id = claim_lookup.id
+ RETURNING c.id, c.payload, c.status_code
+)
+
+SELECT
+ (SELECT jsonb_build_object(
+ 'claim_id', u.id::text,
+ 'status_code', u.status_code,
+ 'payload', u.payload
+ ) FROM upd_claim u) AS claim,
+
+ -- ✅ ИСПРАВЛЕНО: Возвращаем ВСЕ документы из upsert_docs с правильным claim_document_id
+ -- ✅ ДОБАВЛЕНО: document_type и document_label для передачи в OCR workflow
+ (
+ SELECT jsonb_agg(
+ jsonb_build_object(
+ 'id', u.id::text, -- ✅ Это claim_document_id из таблицы clpr_claim_documents
+ 'claim_document_id', u.id::text, -- ✅ Явно указываем для ясности
+ 'field_name', u.field_name,
+ 'file_id', u.file_id,
+ 'file_url', d.file_url,
+ 'file_name', d.file_name,
+ 'original_file_name', d.original_file_name,
+ 'uploaded_at', d.uploaded_at::text,
+ 'document_type', u.document_type, -- ✅ Тип документа (contract, payment, etc.)
+ 'document_label', u.document_label, -- ✅ Название поля формы
+ 'filename_for_upload',
+ COALESCE(
+ NULLIF(d.original_file_name, ''),
+ NULLIF(d.file_name, ''),
+ regexp_replace(d.file_id, '^.*/', '')
+ )
+ )
+ ORDER BY u.field_name -- ✅ Сортируем для предсказуемости
+ )
+ FROM upsert_docs u
+ -- ✅ JOIN с docs для получения file_url и других метаданных
+ LEFT JOIN docs d ON d.claim_id = u.claim_id AND d.field_name = u.field_name AND d.file_id = u.file_id
+ -- ✅ Возвращаем ВСЕ документы из таблицы, даже если нет file_url в docs
+ -- (file_url может быть в documents_meta в payload)
+ ) AS documents;
+
diff --git a/docs/SQL_CLAIMSAVE_FIXED_NEW_FLOW_DEDUP.sql b/docs/SQL_CLAIMSAVE_FIXED_NEW_FLOW_DEDUP.sql
new file mode 100644
index 0000000..58def3b
--- /dev/null
+++ b/docs/SQL_CLAIMSAVE_FIXED_NEW_FLOW_DEDUP.sql
@@ -0,0 +1,391 @@
+-- ============================================================================
+-- Исправленный SQL для сохранения claim (claimsave) - С ДЕДУПЛИКАЦИЕЙ
+-- ============================================================================
+-- Проблема: documents_meta накапливает дубликаты при каждом сохранении
+-- Решение: При добавлении новых записей удаляем старые с тем же field_name
+-- ============================================================================
+
+WITH partial AS (
+ SELECT
+ $1::jsonb AS p,
+ $2::text AS claim_id_str
+),
+
+existing_claim AS (
+ SELECT
+ id,
+ payload,
+ status_code,
+ created_at
+ FROM clpr_claims
+ WHERE id = (SELECT claim_id_str::uuid FROM partial)
+ OR payload->>'claim_id' = (SELECT claim_id_str FROM partial)
+ ORDER BY
+ CASE WHEN id = (SELECT claim_id_str::uuid FROM partial) THEN 1 ELSE 2 END,
+ updated_at DESC
+ LIMIT 1
+),
+
+-- ✅ НОВОЕ: Дедуплицированный documents_meta
+-- Приоритет: новые записи перезаписывают старые с тем же field_name
+documents_meta_dedup AS (
+ SELECT COALESCE(
+ (
+ SELECT jsonb_agg(doc ORDER BY (doc->>'uploaded_at') DESC NULLS LAST)
+ FROM (
+ -- Уникальные записи: приоритет новым по field_name
+ SELECT DISTINCT ON (doc->>'field_name') doc
+ FROM (
+ -- 1. Сначала новые записи (приоритет)
+ SELECT jsonb_array_elements(
+ COALESCE((SELECT p->'documents_meta' FROM partial WHERE p->'documents_meta' IS NOT NULL), '[]'::jsonb)
+ ) AS doc, 1 AS priority
+ UNION ALL
+ -- 2. Потом существующие записи
+ SELECT jsonb_array_elements(
+ COALESCE((SELECT payload->'documents_meta' FROM existing_claim), '[]'::jsonb)
+ ) AS doc, 2 AS priority
+ ) all_docs
+ -- Сортируем: сначала новые (priority=1), потом по дате
+ ORDER BY doc->>'field_name', priority, (doc->>'uploaded_at') DESC NULLS LAST
+ ) unique_docs
+ ),
+ '[]'::jsonb
+ ) AS documents_meta
+),
+
+-- Парсим documents_required (или берём из БД)
+documents_required_parsed AS (
+ SELECT
+ CASE
+ WHEN partial.p->'documents_required' IS NOT NULL
+ AND jsonb_typeof(partial.p->'documents_required') = 'array'
+ THEN partial.p->'documents_required'
+ WHEN partial.p->'edit_fields_parsed'->'documents_required' IS NOT NULL
+ AND jsonb_typeof(partial.p->'edit_fields_parsed'->'documents_required') = 'array'
+ THEN partial.p->'edit_fields_parsed'->'documents_required'
+ WHEN EXISTS (SELECT 1 FROM existing_claim WHERE payload->'documents_required' IS NOT NULL)
+ THEN (SELECT payload->'documents_required' FROM existing_claim)
+ ELSE '[]'::jsonb
+ END AS documents_required
+ FROM partial
+),
+
+-- Парсим documents_uploaded (или берём из БД)
+documents_uploaded_parsed AS (
+ SELECT
+ CASE
+ WHEN partial.p->'documents_uploaded' IS NOT NULL
+ AND jsonb_typeof(partial.p->'documents_uploaded') = 'array'
+ THEN partial.p->'documents_uploaded'
+ WHEN EXISTS (SELECT 1 FROM existing_claim WHERE payload->'documents_uploaded' IS NOT NULL)
+ THEN (SELECT payload->'documents_uploaded' FROM existing_claim)
+ ELSE '[]'::jsonb
+ END AS documents_uploaded
+ FROM partial
+),
+
+-- Парсим documents_skipped (или берём из БД)
+documents_skipped_parsed AS (
+ SELECT
+ CASE
+ WHEN partial.p->'documents_skipped' IS NOT NULL
+ AND jsonb_typeof(partial.p->'documents_skipped') = 'array'
+ THEN partial.p->'documents_skipped'
+ WHEN EXISTS (SELECT 1 FROM existing_claim WHERE payload->'documents_skipped' IS NOT NULL)
+ THEN (SELECT payload->'documents_skipped' FROM existing_claim)
+ ELSE '[]'::jsonb
+ END AS documents_skipped
+ FROM partial
+),
+
+-- Парсим current_doc_index (или берём из БД)
+current_doc_index_parsed AS (
+ SELECT
+ CASE
+ WHEN partial.p->'current_doc_index' IS NOT NULL
+ THEN (partial.p->'current_doc_index')::int
+ WHEN EXISTS (SELECT 1 FROM existing_claim WHERE payload->'current_doc_index' IS NOT NULL)
+ THEN (SELECT (payload->'current_doc_index')::int FROM existing_claim)
+ ELSE 0
+ END AS current_doc_index
+ FROM partial
+),
+
+-- Парсим wizard_answers
+wizard_answers_parsed AS (
+ SELECT
+ CASE
+ WHEN partial.p->'edit_fields_raw'->'body'->>'wizard_answers' IS NOT NULL
+ THEN (partial.p->'edit_fields_raw'->'body'->>'wizard_answers')::jsonb
+ WHEN partial.p->'edit_fields_parsed'->'body'->>'wizard_answers' IS NOT NULL
+ THEN (partial.p->'edit_fields_parsed'->'body'->>'wizard_answers')::jsonb
+ WHEN partial.p->>'wizard_answers' IS NOT NULL
+ THEN (partial.p->>'wizard_answers')::jsonb
+ WHEN partial.p->'wizard_answers' IS NOT NULL
+ AND jsonb_typeof(partial.p->'wizard_answers') = 'object'
+ THEN partial.p->'wizard_answers'
+ ELSE '{}'::jsonb
+ END AS answers
+ FROM partial
+),
+
+-- Парсим wizard_plan (или берём из существующей записи)
+wizard_plan_parsed AS (
+ SELECT
+ CASE
+ WHEN partial.p->'edit_fields_parsed'->'wizard_plan_parsed' IS NOT NULL
+ AND jsonb_typeof(partial.p->'edit_fields_parsed'->'wizard_plan_parsed') = 'object'
+ THEN partial.p->'edit_fields_parsed'->'wizard_plan_parsed'
+ WHEN partial.p->>'wizard_plan' IS NOT NULL
+ THEN (partial.p->>'wizard_plan')::jsonb
+ WHEN partial.p->'wizard_plan' IS NOT NULL
+ AND jsonb_typeof(partial.p->'wizard_plan') = 'object'
+ THEN partial.p->'wizard_plan'
+ WHEN partial.p->'edit_fields_raw'->'body'->>'wizard_plan' IS NOT NULL
+ THEN (partial.p->'edit_fields_raw'->'body'->>'wizard_plan')::jsonb
+ WHEN EXISTS (SELECT 1 FROM existing_claim WHERE payload->'wizard_plan' IS NOT NULL)
+ THEN (SELECT payload->'wizard_plan' FROM existing_claim)
+ ELSE NULL
+ END AS wizard_plan
+ FROM partial
+),
+
+-- Парсим problem_description (или берём из БД)
+problem_description_parsed AS (
+ SELECT
+ CASE
+ WHEN partial.p->>'problem_description' IS NOT NULL
+ THEN partial.p->>'problem_description'
+ WHEN EXISTS (SELECT 1 FROM existing_claim WHERE payload->>'problem_description' IS NOT NULL)
+ THEN (SELECT payload->>'problem_description' FROM existing_claim)
+ ELSE NULL
+ END AS problem_description
+ FROM partial
+),
+
+-- Определяем правильный статус
+status_code_resolved AS (
+ SELECT
+ CASE
+ -- Если есть documents_required и документы загружаются - новый флоу
+ WHEN (SELECT jsonb_array_length(documents_required) FROM documents_required_parsed) > 0
+ THEN CASE
+ -- Все документы загружены или пропущены
+ WHEN (SELECT jsonb_array_length(documents_uploaded) FROM documents_uploaded_parsed) +
+ (SELECT jsonb_array_length(documents_skipped) FROM documents_skipped_parsed) >=
+ (SELECT jsonb_array_length(documents_required) FROM documents_required_parsed)
+ THEN 'draft_docs_complete'
+ -- Документы загружаются
+ WHEN (SELECT jsonb_array_length(documents_uploaded) FROM documents_uploaded_parsed) > 0
+ THEN 'draft_docs_progress'
+ -- Только описание
+ ELSE 'draft_new'
+ END
+ -- Старый флоу: проверяем wizard_answers
+ WHEN (SELECT answers->>'docs_exist' FROM wizard_answers_parsed) = 'true'
+ THEN 'in_work'
+ -- Сохраняем существующий статус, если он новый
+ WHEN EXISTS (SELECT 1 FROM existing_claim
+ WHERE status_code IN ('draft_new', 'draft_docs_progress', 'draft_docs_complete', 'draft_claim_ready'))
+ THEN (SELECT status_code FROM existing_claim)
+ -- По умолчанию
+ ELSE 'draft'
+ END AS status_code
+ FROM partial
+),
+
+-- UPSERT claim
+claim_upsert AS (
+ INSERT INTO clpr_claims (
+ id,
+ session_token,
+ unified_id,
+ contact_id,
+ phone,
+ channel,
+ type_code,
+ status_code,
+ payload,
+ created_at,
+ updated_at,
+ expires_at
+ )
+ SELECT
+ COALESCE((SELECT id FROM existing_claim), partial.claim_id_str::uuid),
+ COALESCE(
+ partial.p->>'session_id',
+ partial.p->'edit_fields_parsed'->'body'->>'session_id',
+ partial.p->'edit_fields_raw'->'body'->>'session_id',
+ 'sess-unknown'
+ ),
+ COALESCE(
+ partial.p->>'unified_id',
+ partial.p->'edit_fields_parsed'->'body'->>'unified_id',
+ partial.p->'edit_fields_raw'->'body'->>'unified_id'
+ ),
+ COALESCE(
+ partial.p->>'contact_id',
+ partial.p->'edit_fields_parsed'->'body'->>'contact_id',
+ partial.p->'edit_fields_raw'->'body'->>'contact_id'
+ ),
+ COALESCE(
+ partial.p->>'phone',
+ partial.p->'edit_fields_parsed'->'body'->>'phone',
+ partial.p->'edit_fields_raw'->'body'->>'phone'
+ ),
+ 'web_form',
+ COALESCE(partial.p->>'type_code', 'consumer'),
+ (SELECT status_code FROM status_code_resolved),
+ jsonb_build_object(
+ 'claim_id', partial.claim_id_str,
+ 'problem_description', (SELECT problem_description FROM problem_description_parsed),
+ 'answers', (SELECT answers FROM wizard_answers_parsed),
+ -- ✅ ДЕДУПЛИЦИРОВАННЫЙ documents_meta
+ 'documents_meta', (SELECT documents_meta FROM documents_meta_dedup),
+ -- ✅ НОВЫЙ ФЛОУ: Сохраняем documents_required и связанные поля
+ 'documents_required', (SELECT documents_required FROM documents_required_parsed),
+ 'documents_uploaded', (SELECT documents_uploaded FROM documents_uploaded_parsed),
+ 'documents_skipped', (SELECT documents_skipped FROM documents_skipped_parsed),
+ 'current_doc_index', (SELECT current_doc_index FROM current_doc_index_parsed),
+ 'wizard_plan', (SELECT wizard_plan FROM wizard_plan_parsed),
+ 'phone', COALESCE(partial.p->>'phone', (SELECT payload->>'phone' FROM existing_claim)),
+ 'email', COALESCE(partial.p->>'email', (SELECT payload->>'email' FROM existing_claim))
+ ),
+ COALESCE((SELECT created_at FROM existing_claim), now()),
+ now(),
+ now() + interval '14 days'
+ FROM partial
+ ON CONFLICT (id) DO UPDATE SET
+ session_token = EXCLUDED.session_token,
+ unified_id = COALESCE(EXCLUDED.unified_id, clpr_claims.unified_id),
+ contact_id = COALESCE(EXCLUDED.contact_id, clpr_claims.contact_id),
+ phone = COALESCE(EXCLUDED.phone, clpr_claims.phone),
+ -- ✅ НЕ перезаписываем статус, если он новый (сохраняем существующий)
+ status_code = CASE
+ WHEN clpr_claims.status_code IN ('draft_new', 'draft_docs_progress', 'draft_docs_complete', 'draft_claim_ready')
+ THEN clpr_claims.status_code -- Сохраняем существующий новый статус
+ ELSE EXCLUDED.status_code -- Используем новый статус
+ END,
+ -- ✅ ИСПРАВЛЕНО: Дедуплицированное объединение documents_meta
+ payload = jsonb_set(
+ jsonb_set(
+ jsonb_set(
+ jsonb_set(
+ jsonb_set(
+ -- Сначала берём существующий payload и объединяем с новым (без критичных полей)
+ COALESCE(clpr_claims.payload, '{}'::jsonb) ||
+ (EXCLUDED.payload - 'documents_meta' - 'documents_required' - 'documents_uploaded' - 'documents_skipped' - 'current_doc_index'),
+ '{documents_meta}',
+ -- ✅ ДЕДУПЛИКАЦИЯ: новые записи перезаписывают старые с тем же field_name
+ (
+ SELECT COALESCE(jsonb_agg(doc), '[]'::jsonb)
+ FROM (
+ SELECT DISTINCT ON (doc->>'field_name') doc
+ FROM (
+ -- Новые записи (приоритет)
+ SELECT jsonb_array_elements(COALESCE(EXCLUDED.payload->'documents_meta', '[]'::jsonb)) AS doc, 1 AS priority
+ UNION ALL
+ -- Существующие записи
+ SELECT jsonb_array_elements(COALESCE(clpr_claims.payload->'documents_meta', '[]'::jsonb)) AS doc, 2 AS priority
+ ) all_docs
+ ORDER BY doc->>'field_name', priority, (doc->>'uploaded_at') DESC NULLS LAST
+ ) unique_docs
+ ),
+ true
+ ),
+ '{documents_required}',
+ COALESCE(
+ EXCLUDED.payload->'documents_required',
+ clpr_claims.payload->'documents_required',
+ '[]'::jsonb
+ ),
+ true
+ ),
+ '{documents_uploaded}',
+ COALESCE(
+ EXCLUDED.payload->'documents_uploaded',
+ clpr_claims.payload->'documents_uploaded',
+ '[]'::jsonb
+ ),
+ true
+ ),
+ '{documents_skipped}',
+ COALESCE(
+ EXCLUDED.payload->'documents_skipped',
+ clpr_claims.payload->'documents_skipped',
+ '[]'::jsonb
+ ),
+ true
+ ),
+ '{current_doc_index}',
+ COALESCE(
+ EXCLUDED.payload->'current_doc_index',
+ clpr_claims.payload->'current_doc_index',
+ to_jsonb(0)
+ ),
+ true
+ ),
+ updated_at = now(),
+ expires_at = now() + interval '14 days'
+ RETURNING id, status_code, payload, unified_id, contact_id, phone, session_token
+),
+
+-- UPSERT documents (если есть)
+docs_upsert AS (
+ INSERT INTO clpr_claim_documents (
+ claim_id,
+ field_name,
+ file_id,
+ uploaded_at,
+ file_name,
+ original_file_name
+ )
+ SELECT
+ partial.claim_id_str AS claim_id,
+ doc.field_name,
+ doc.file_id,
+ COALESCE((doc.uploaded_at)::timestamptz, now()),
+ doc.file_name,
+ doc.original_file_name
+ FROM partial
+ CROSS JOIN LATERAL jsonb_to_recordset(
+ COALESCE(partial.p->'documents_meta', '[]'::jsonb)
+ ) AS doc(
+ field_name text,
+ file_id text,
+ file_name text,
+ original_file_name text,
+ uploaded_at text
+ )
+ WHERE partial.p->'documents_meta' IS NOT NULL
+ AND jsonb_array_length(partial.p->'documents_meta') > 0
+ ON CONFLICT (claim_id, field_name) DO UPDATE SET
+ file_id = EXCLUDED.file_id,
+ uploaded_at = EXCLUDED.uploaded_at,
+ file_name = EXCLUDED.file_name,
+ original_file_name = EXCLUDED.original_file_name
+ RETURNING id, claim_id, field_name, file_id, file_name, original_file_name
+)
+
+-- Возвращаем результат
+SELECT
+ (SELECT jsonb_build_object(
+ 'claim_id', cu.id::text,
+ 'claim_id_str', (cu.payload->>'claim_id'),
+ 'status_code', cu.status_code,
+ 'unified_id', cu.unified_id,
+ 'contact_id', cu.contact_id,
+ 'phone', cu.phone,
+ 'session_token', cu.session_token,
+ 'payload', cu.payload
+ ) FROM claim_upsert cu) AS claim,
+
+ (SELECT jsonb_agg(jsonb_build_object(
+ 'id', id,
+ 'field_name', field_name,
+ 'file_id', file_id,
+ 'file_name', file_name,
+ 'original_file_name', original_file_name
+ )) FROM docs_upsert) AS documents;
+
diff --git a/docs/SQL_CLEANUP_DOCUMENTS_META_DUPLICATES.sql b/docs/SQL_CLEANUP_DOCUMENTS_META_DUPLICATES.sql
new file mode 100644
index 0000000..e51ba0c
--- /dev/null
+++ b/docs/SQL_CLEANUP_DOCUMENTS_META_DUPLICATES.sql
@@ -0,0 +1,36 @@
+-- ============================================================================
+-- SQL для очистки дубликатов в documents_meta
+-- ============================================================================
+-- Удаляет дубликаты, оставляя только самую новую запись для каждого field_name
+-- ============================================================================
+
+-- $1 = claim_id (UUID)
+
+UPDATE clpr_claims
+SET payload = jsonb_set(
+ payload,
+ '{documents_meta}',
+ (
+ SELECT COALESCE(jsonb_agg(doc ORDER BY (doc->>'uploaded_at') DESC NULLS LAST), '[]'::jsonb)
+ FROM (
+ SELECT DISTINCT ON (doc->>'field_name') doc
+ FROM jsonb_array_elements(COALESCE(payload->'documents_meta', '[]'::jsonb)) AS doc
+ ORDER BY
+ doc->>'field_name',
+ -- Приоритет: записи с file_url важнее, потом по дате
+ CASE WHEN doc->>'file_url' IS NOT NULL AND doc->>'file_url' <> '' THEN 0 ELSE 1 END,
+ (doc->>'uploaded_at') DESC NULLS LAST
+ ) unique_docs
+ ),
+ true
+),
+updated_at = now()
+WHERE id = $1
+RETURNING
+ id,
+ jsonb_array_length(payload->'documents_meta') AS documents_meta_count,
+ (
+ SELECT jsonb_agg(doc->>'field_name')
+ FROM jsonb_array_elements(payload->'documents_meta') AS doc
+ ) AS field_names;
+
diff --git a/docs/SQL_DOCUMENT_ID_RETURN.md b/docs/SQL_DOCUMENT_ID_RETURN.md
new file mode 100644
index 0000000..5a0637a
--- /dev/null
+++ b/docs/SQL_DOCUMENT_ID_RETURN.md
@@ -0,0 +1,104 @@
+# Возврат claim_document_id при сохранении документов
+
+## Когда возникает claim_document_id?
+
+`claim_document_id` (поле `id` в таблице `clpr_claim_documents`) возникает в момент INSERT или UPDATE записи в таблицу `clpr_claim_documents`.
+
+## Где это происходит?
+
+### 1. При загрузке документа (SQL_CLAIMSAVE_FIXED_NEW_FLOW.sql)
+
+В CTE `docs_upsert` происходит INSERT/UPDATE:
+
+```sql
+docs_upsert AS (
+ INSERT INTO clpr_claim_documents (
+ claim_id,
+ field_name,
+ file_id,
+ uploaded_at,
+ file_name,
+ original_file_name
+ )
+ SELECT
+ partial.claim_id_str AS claim_id,
+ doc.field_name,
+ doc.file_id,
+ COALESCE((doc.uploaded_at)::timestamptz, now()),
+ doc.file_name,
+ doc.original_file_name
+ FROM partial
+ CROSS JOIN LATERAL jsonb_to_recordset(
+ COALESCE(partial.p->'documents_meta', '[]'::jsonb)
+ ) AS doc(...)
+ WHERE partial.p->'documents_meta' IS NOT NULL
+ AND jsonb_array_length(partial.p->'documents_meta') > 0
+ ON CONFLICT (claim_id, field_name) DO UPDATE SET
+ file_id = EXCLUDED.file_id,
+ uploaded_at = EXCLUDED.uploaded_at,
+ file_name = EXCLUDED.file_name,
+ original_file_name = EXCLUDED.original_file_name
+ RETURNING id, claim_id, field_name, file_id, file_name, original_file_name -- ✅ Возвращаем id
+)
+```
+
+### 2. В финальном SELECT
+
+```sql
+SELECT
+ (SELECT jsonb_build_object(...) FROM claim_upsert cu) AS claim,
+
+ (SELECT jsonb_agg(jsonb_build_object(
+ 'id', id, -- ✅ Это и есть claim_document_id
+ 'field_name', field_name,
+ 'file_id', file_id,
+ 'file_name', file_name,
+ 'original_file_name', original_file_name
+ )) FROM docs_upsert) AS documents;
+```
+
+## Структура ответа
+
+После выполнения SQL запроса возвращается:
+
+```json
+{
+ "claim": {
+ "claim_id": "...",
+ "status_code": "...",
+ ...
+ },
+ "documents": [
+ {
+ "id": "16fa625e-1da3-4097-895a-75a8904c702a", // ← Это claim_document_id
+ "field_name": "uploads[1][0]",
+ "file_id": "...",
+ "file_name": "...",
+ "original_file_name": "..."
+ },
+ ...
+ ]
+}
+```
+
+## Когда это нужно?
+
+`claim_document_id` нужен для:
+1. **Связи с другими таблицами** - если нужно связать документ с другими сущностями
+2. **Обновления документа** - для UPDATE конкретной записи по ID
+3. **Удаления документа** - для DELETE конкретной записи по ID
+4. **Отслеживания** - для логирования и аудита
+
+## Важно
+
+- `claim_document_id` генерируется автоматически PostgreSQL при INSERT (если `id` имеет тип UUID с DEFAULT)
+- При UPDATE (ON CONFLICT) возвращается существующий `id`
+- `RETURNING id` в SQL запросе обязательно должен быть, чтобы получить `id` обратно
+
+## Проверка
+
+Чтобы убедиться, что `claim_document_id` возвращается, проверьте:
+1. SQL запрос содержит `RETURNING id` в INSERT/UPDATE для `clpr_claim_documents`
+2. Финальный SELECT включает `'id', id` из CTE с документами
+3. n8n получает массив `documents` с полем `id` в каждом элементе
+
diff --git a/docs/SQL_FIX_DOCUMENT_ID_RETURN.sql b/docs/SQL_FIX_DOCUMENT_ID_RETURN.sql
new file mode 100644
index 0000000..9896248
--- /dev/null
+++ b/docs/SQL_FIX_DOCUMENT_ID_RETURN.sql
@@ -0,0 +1,41 @@
+-- Исправление: возврат правильного claim_document_id из таблицы clpr_claim_documents
+-- Проблема: возвращается не id из таблицы, а что-то другое (возможно из documents_meta)
+
+-- В SQL запросе должно быть:
+-- 1. В CTE upsert_docs: RETURNING id (это claim_document_id из таблицы)
+-- 2. В финальном SELECT: использовать u.id из upsert_docs, а НЕ из documents_meta
+
+-- ПРАВИЛЬНЫЙ ВАРИАНТ:
+SELECT
+ (SELECT jsonb_build_object(...) FROM upd_claim u) AS claim,
+
+ (
+ SELECT jsonb_agg(
+ jsonb_build_object(
+ 'id', u.id, -- ✅ Это claim_document_id из таблицы clpr_claim_documents
+ 'claim_document_id', u.id, -- ✅ Явно указываем, что это claim_document_id
+ 'field_name', u.field_name,
+ 'file_id', u.file_id,
+ 'file_url', d.file_url,
+ 'file_name', d.file_name,
+ 'original_file_name', d.original_file_name,
+ 'uploaded_at', d.uploaded_at,
+ 'filename_for_upload',
+ COALESCE(
+ NULLIF(d.original_file_name, ''),
+ NULLIF(d.file_name, ''),
+ regexp_replace(d.file_id, '^.*/', '')
+ )
+ )
+ )
+ FROM upsert_docs u -- ✅ Используем u.id из upsert_docs (таблица clpr_claim_documents)
+ JOIN docs d ON d.claim_id = u.claim_id AND d.field_name = u.field_name
+ WHERE d.file_url IS NOT NULL AND d.file_url <> ''
+ ) AS documents;
+
+-- ❌ НЕПРАВИЛЬНО (если используется id из documents_meta):
+-- 'id', d.id -- Это НЕ claim_document_id из таблицы!
+
+-- ✅ ПРАВИЛЬНО:
+-- 'id', u.id -- Это claim_document_id из таблицы clpr_claim_documents
+
diff --git a/docs/SQL_GET_CONTACT_DATA_FROM_CRM.sql b/docs/SQL_GET_CONTACT_DATA_FROM_CRM.sql
new file mode 100644
index 0000000..67f9f4b
--- /dev/null
+++ b/docs/SQL_GET_CONTACT_DATA_FROM_CRM.sql
@@ -0,0 +1,146 @@
+-- ============================================================================
+-- SQL запрос для получения данных контакта из CRM (через n8n)
+-- ============================================================================
+-- Назначение: Получить актуальные данные контакта из CRM для отображения
+-- в форме подтверждения (если данные уже подтверждены)
+--
+-- Использование: В n8n workflow после проверки флага contact_data_confirmed_at
+-- ============================================================================
+
+-- ВАЖНО: Этот запрос выполняется через n8n HTTP Request к CRM webservice,
+-- а не напрямую в PostgreSQL, т.к. CRM в MySQL
+
+-- Пример запроса к CRM через n8n:
+-- POST https://crm.clientright.ru/webservice.php
+-- Body: operation=retrieve&sessionName={{sessionName}}&id=12x{{contact_id}}
+
+-- ============================================================================
+-- Альтернатива: Хранить кэш данных контакта в PostgreSQL
+-- ============================================================================
+
+-- Создаём таблицу для кэширования данных контакта из CRM
+CREATE TABLE IF NOT EXISTS clpr_contact_data_cache (
+ unified_id VARCHAR NOT NULL PRIMARY KEY,
+ contact_id INTEGER,
+ firstname VARCHAR,
+ lastname VARCHAR,
+ middle_name VARCHAR,
+ inn VARCHAR,
+ birthday DATE,
+ birthplace VARCHAR,
+ mailingstreet VARCHAR,
+ email VARCHAR,
+ mobile VARCHAR,
+ -- Дополнительные поля из CRM
+ data_json JSONB, -- Полные данные из CRM для расширяемости
+ synced_at TIMESTAMPTZ DEFAULT NOW(),
+ created_at TIMESTAMPTZ DEFAULT NOW(),
+ updated_at TIMESTAMPTZ DEFAULT NOW(),
+
+ CONSTRAINT fk_unified_id FOREIGN KEY (unified_id)
+ REFERENCES clpr_users(unified_id) ON DELETE CASCADE
+);
+
+-- Индекс для быстрого поиска
+CREATE INDEX IF NOT EXISTS idx_clpr_contact_data_cache_contact_id
+ON clpr_contact_data_cache(contact_id);
+
+-- Комментарий
+COMMENT ON TABLE clpr_contact_data_cache IS
+'Кэш данных контакта из CRM. Обновляется при синхронизации через n8n.';
+
+-- ============================================================================
+-- Функция: Получить данные контакта (из кэша или NULL)
+-- ============================================================================
+CREATE OR REPLACE FUNCTION clpr_get_contact_data(p_unified_id VARCHAR)
+RETURNS TABLE(
+ contact_id INTEGER,
+ firstname VARCHAR,
+ lastname VARCHAR,
+ middle_name VARCHAR,
+ inn VARCHAR,
+ birthday DATE,
+ birthplace VARCHAR,
+ mailingstreet VARCHAR,
+ email VARCHAR,
+ mobile VARCHAR,
+ data_json JSONB
+) AS $$
+BEGIN
+ RETURN QUERY
+ SELECT
+ c.contact_id,
+ c.firstname,
+ c.lastname,
+ c.middle_name,
+ c.inn,
+ c.birthday,
+ c.birthplace,
+ c.mailingstreet,
+ c.email,
+ c.mobile,
+ c.data_json
+ FROM clpr_contact_data_cache c
+ WHERE c.unified_id = p_unified_id;
+END;
+$$ LANGUAGE plpgsql;
+
+-- ============================================================================
+-- Функция: Обновить кэш данных контакта
+-- ============================================================================
+CREATE OR REPLACE FUNCTION clpr_update_contact_data_cache(
+ p_unified_id VARCHAR,
+ p_contact_id INTEGER,
+ p_data JSONB
+)
+RETURNS VOID AS $$
+BEGIN
+ INSERT INTO clpr_contact_data_cache (
+ unified_id,
+ contact_id,
+ firstname,
+ lastname,
+ middle_name,
+ inn,
+ birthday,
+ birthplace,
+ mailingstreet,
+ email,
+ mobile,
+ data_json,
+ synced_at,
+ updated_at
+ ) VALUES (
+ p_unified_id,
+ p_contact_id,
+ p_data->>'firstname',
+ p_data->>'lastname',
+ p_data->>'cf_1157', -- middle_name
+ p_data->>'cf_1257', -- inn
+ (p_data->>'birthday')::DATE,
+ p_data->>'cf_1263', -- birthplace
+ p_data->>'mailingstreet',
+ p_data->>'email',
+ p_data->>'mobile',
+ p_data,
+ NOW(),
+ NOW()
+ )
+ ON CONFLICT (unified_id) DO UPDATE
+ SET
+ contact_id = EXCLUDED.contact_id,
+ firstname = EXCLUDED.firstname,
+ lastname = EXCLUDED.lastname,
+ middle_name = EXCLUDED.middle_name,
+ inn = EXCLUDED.inn,
+ birthday = EXCLUDED.birthday,
+ birthplace = EXCLUDED.birthplace,
+ mailingstreet = EXCLUDED.mailingstreet,
+ email = EXCLUDED.email,
+ mobile = EXCLUDED.mobile,
+ data_json = EXCLUDED.data_json,
+ synced_at = NOW(),
+ updated_at = NOW();
+END;
+$$ LANGUAGE plpgsql;
+
diff --git a/docs/SQL_GET_DOCUMENT_BY_ID.sql b/docs/SQL_GET_DOCUMENT_BY_ID.sql
new file mode 100644
index 0000000..badf455
--- /dev/null
+++ b/docs/SQL_GET_DOCUMENT_BY_ID.sql
@@ -0,0 +1,127 @@
+-- ============================================================================
+-- SQL запрос для получения документа по claim_document_id
+-- ============================================================================
+-- Параметры:
+-- $1 :: uuid -- claim_id (ID жалобы)
+-- $2 :: uuid -- claim_document_id (ID документа из таблицы clpr_claim_documents)
+-- ============================================================================
+
+WITH c AS (
+ SELECT id, id::text AS claim_id_text, payload
+ FROM clpr_claims
+ WHERE id = $1
+)
+
+SELECT
+ cd.id AS claim_document_id,
+ cd.claim_id::text AS claim_id,
+ cd.field_name,
+ cd.file_id,
+ cd.uploaded_at,
+ cd.file_name,
+ cd.original_file_name,
+ cd.file_hash, -- ✅ SHA-256 хеш файла (для дедупликации)
+ m.file_url,
+ m.file_name AS meta_file_name,
+ m.original_file_name AS meta_original_file_name,
+ -- ✅ Название документа: сначала из field_label в documents_meta, потом из documents_uploaded, потом из documents_required
+ COALESCE(
+ NULLIF(m.field_label, ''),
+ NULLIF(du_name.document_name_from_uploaded, ''),
+ NULLIF(dr_name.document_name_from_required, ''),
+ cd.field_name,
+ 'Документ'
+ ) AS document_name,
+ COALESCE(
+ NULLIF(m.original_file_name, ''),
+ NULLIF(m.file_name, ''),
+ NULLIF(cd.original_file_name, ''),
+ NULLIF(cd.file_name, ''),
+ NULLIF(regexp_replace(cd.file_id, '^.*/', ''), ''),
+ 'document.pdf'
+ ) AS filename_for_upload,
+ /* описание: сначала из массива edit_fields_parsed.uploads_descriptions[i],
+ потом — из payload.body['uploads_descriptions[i]'],
+ потом — из payload['uploads_descriptions[i]'] */
+ NULLIF(
+ COALESCE(ud_arr.upload_description, ud_body.upload_description, ud_root.upload_description),
+ ''
+ ) AS upload_description
+
+FROM clpr_claim_documents cd
+JOIN c ON c.claim_id_text = cd.claim_id::text
+
+-- достаём i из uploads[i][j]
+JOIN LATERAL (
+ SELECT NULLIF((regexp_match(cd.field_name, 'uploads\[(\d+)\]'))[1], '')::int AS i1
+) idx ON TRUE
+
+-- мета по файлу (валидный URL) + название документа (field_label)
+LEFT JOIN LATERAL (
+ SELECT
+ x.file_url::text,
+ x.file_name::text,
+ x.original_file_name::text,
+ x.field_label::text
+ FROM jsonb_to_recordset(COALESCE(c.payload->'documents_meta','[]'::jsonb))
+ AS x(field_name text, file_id text, file_url text, file_name text, original_file_name text, field_label text)
+ WHERE x.field_name = cd.field_name
+ AND x.file_id = cd.file_id
+ AND x.file_url ~* '^https?://'
+ AND x.file_url <> ''
+ LIMIT 1
+) m ON TRUE
+
+-- название документа из documents_uploaded (fallback, если нет field_label в documents_meta)
+LEFT JOIN LATERAL (
+ SELECT du.name::text AS document_name_from_uploaded
+ FROM jsonb_to_recordset(COALESCE(c.payload->'documents_uploaded','[]'::jsonb))
+ AS du(id text, name text, field_name text, file_id text, type text)
+ WHERE du.field_name = cd.field_name
+ AND (du.file_id = cd.file_id OR du.file_id IS NULL)
+ LIMIT 1
+) du_name ON TRUE
+
+-- название документа из documents_required (fallback через тип документа из documents_uploaded)
+LEFT JOIN LATERAL (
+ SELECT dr.name::text AS document_name_from_required
+ FROM jsonb_to_recordset(COALESCE(c.payload->'documents_required','[]'::jsonb))
+ AS dr(id text, name text, required boolean, priority int)
+ -- Находим тип документа через documents_uploaded по field_name
+ WHERE EXISTS (
+ SELECT 1
+ FROM jsonb_to_recordset(COALESCE(c.payload->'documents_uploaded','[]'::jsonb))
+ AS du(id text, field_name text)
+ WHERE du.field_name = cd.field_name
+ AND du.id = dr.id
+ LIMIT 1
+ )
+ LIMIT 1
+) dr_name ON TRUE
+
+-- 1) массив: payload.edit_fields_parsed.uploads_descriptions[i]
+LEFT JOIN LATERAL (
+ SELECT (c.payload->'edit_fields_parsed'->'uploads_descriptions')->>idx.i1 AS upload_description
+) ud_arr ON TRUE
+
+-- 2) плоские ключи в payload.body: 'uploads_descriptions[i]'
+LEFT JOIN LATERAL (
+ SELECT b.v AS upload_description
+ FROM jsonb_each_text(COALESCE(c.payload->'body','{}'::jsonb)) AS b(k, v)
+ WHERE b.k = 'uploads_descriptions[' || idx.i1::text || ']'
+ LIMIT 1
+) ud_body ON TRUE
+
+-- 3) плоские ключи на корне payload: 'uploads_descriptions[i]'
+LEFT JOIN LATERAL (
+ SELECT r.v AS upload_description
+ FROM jsonb_each_text(COALESCE(c.payload,'{}'::jsonb)) AS r(k, v)
+ WHERE r.k = 'uploads_descriptions[' || idx.i1::text || ']'
+ LIMIT 1
+) ud_root ON TRUE
+
+-- ✅ ФИЛЬТР: ищем конкретный документ по claim_document_id (после всех JOIN)
+WHERE cd.id = $2
+
+LIMIT 1; -- ✅ Возвращаем только один документ
+
diff --git a/docs/SQL_MARK_FORM_APPROVED.sql b/docs/SQL_MARK_FORM_APPROVED.sql
index e981e9a..01c5b25 100644
--- a/docs/SQL_MARK_FORM_APPROVED.sql
+++ b/docs/SQL_MARK_FORM_APPROVED.sql
@@ -3,10 +3,17 @@
--
-- Параметры:
-- $1 = claim_id (UUID или текст)
+-- $2 = approved_form (JSONB - полные данные формы после апрува)
+-- $3 = sms_code (текст - код подтверждения)
+-- $4 = phone (текст - телефон)
--
-- Обновляет:
-- - status_code = 'approved' (отмечает форму как подтвержденную)
-- - is_confirmed = true (дополнительный флаг подтверждения)
+-- - payload.approved_form = полные данные формы
+-- - payload.sms_verified = true
+-- - payload.sms_code = код подтверждения
+-- - payload.approved_at = время подтверждения
-- - updated_at = now() (время обновления)
--
-- После этого запись больше не будет показываться в списке черновиков
@@ -29,6 +36,14 @@ UPDATE clpr_claims c
SET
status_code = 'approved',
is_confirmed = true,
+ payload = c.payload
+ || jsonb_build_object(
+ 'approved_form', $2::jsonb,
+ 'sms_verified', true,
+ 'sms_code', $3::text,
+ 'approved_phone', $4::text,
+ 'approved_at', now()::text
+ ),
updated_at = now()
FROM claim_lookup cl
WHERE c.id = cl.id
@@ -37,5 +52,5 @@ RETURNING
c.payload->>'claim_id' AS claim_id,
c.status_code,
c.is_confirmed,
- c.updated_at;
-
+ c.updated_at,
+ c.payload->'approved_form' IS NOT NULL AS has_approved_form;
diff --git a/docs/SQL_UPDATE_DOCUMENT_DESCRIPTION.sql b/docs/SQL_UPDATE_DOCUMENT_DESCRIPTION.sql
new file mode 100644
index 0000000..8084dc8
--- /dev/null
+++ b/docs/SQL_UPDATE_DOCUMENT_DESCRIPTION.sql
@@ -0,0 +1,91 @@
+-- ============================================================================
+-- SQL запрос для обновления upload_description документа по claim_document_id
+-- ============================================================================
+-- Параметры:
+-- $1 :: uuid -- claim_document_id (ID документа из таблицы clpr_claim_documents)
+-- $2 :: text -- upload_description (новое описание документа)
+-- ============================================================================
+
+WITH
+-- Находим документ и извлекаем нужные данные
+doc_info AS (
+ SELECT
+ cd.id AS claim_document_id,
+ cd.claim_id::text AS claim_id_text,
+ cd.field_name,
+ -- Извлекаем индекс i из field_name (например, uploads[0][0] -> 0)
+ NULLIF((regexp_match(cd.field_name, 'uploads\[(\d+)\]'))[1], '')::int AS upload_index,
+ c.id AS claim_uuid,
+ c.payload
+ FROM clpr_claim_documents cd
+ JOIN clpr_claims c ON c.id::text = cd.claim_id::text
+ WHERE cd.id = $1
+ LIMIT 1
+),
+
+-- Обновляем payload: приоритет обновления в следующем порядке:
+-- 1. payload.body['uploads_descriptions[i]'] (самый приоритетный)
+-- 2. payload['uploads_descriptions[i]'] (если нет body)
+-- 3. payload.edit_fields_parsed.uploads_descriptions[i] (массив, если нет плоских ключей)
+updated_claim AS (
+ UPDATE clpr_claims c
+ SET
+ payload = CASE
+ -- Если есть payload.body, обновляем там
+ WHEN c.payload->'body' IS NOT NULL THEN
+ jsonb_set(
+ c.payload,
+ ARRAY['body', 'uploads_descriptions[' || di.upload_index::text || ']'],
+ to_jsonb($2::text),
+ true
+ )
+ -- Если нет body, но есть корневой ключ, обновляем там
+ WHEN c.payload ? ('uploads_descriptions[' || di.upload_index::text || ']') THEN
+ jsonb_set(
+ c.payload,
+ ARRAY['uploads_descriptions[' || di.upload_index::text || ']'],
+ to_jsonb($2::text),
+ true
+ )
+ -- Если есть edit_fields_parsed.uploads_descriptions (массив), обновляем там
+ WHEN c.payload->'edit_fields_parsed'->'uploads_descriptions' IS NOT NULL
+ AND jsonb_typeof(c.payload->'edit_fields_parsed'->'uploads_descriptions') = 'array' THEN
+ -- Для массива используем jsonb_set с числовым индексом
+ jsonb_set(
+ c.payload,
+ ARRAY['edit_fields_parsed', 'uploads_descriptions', di.upload_index::text],
+ to_jsonb($2::text),
+ true
+ )
+ -- Если ничего нет, создаём в body
+ ELSE
+ jsonb_set(
+ COALESCE(c.payload, '{}'::jsonb),
+ ARRAY['body', 'uploads_descriptions[' || di.upload_index::text || ']'],
+ to_jsonb($2::text),
+ true
+ )
+ END,
+ updated_at = now()
+ FROM doc_info di
+ WHERE c.id = di.claim_uuid
+ RETURNING c.id, c.payload
+)
+
+-- Возвращаем обновлённые данные
+SELECT
+ di.claim_document_id,
+ di.claim_id_text AS claim_id,
+ di.field_name,
+ di.upload_index,
+ -- Проверяем, где сохранилось описание
+ COALESCE(
+ uc.payload->'body'->>('uploads_descriptions[' || di.upload_index::text || ']'),
+ uc.payload->>('uploads_descriptions[' || di.upload_index::text || ']'),
+ uc.payload->'edit_fields_parsed'->'uploads_descriptions'->>di.upload_index::text,
+ NULL
+ ) AS upload_description,
+ uc.payload
+FROM doc_info di
+JOIN updated_claim uc ON uc.id = di.claim_uuid;
+
diff --git a/docs/SQL_UPDATE_DOCUMENT_DESCRIPTION_SIMPLE.sql b/docs/SQL_UPDATE_DOCUMENT_DESCRIPTION_SIMPLE.sql
new file mode 100644
index 0000000..b95cc2e
--- /dev/null
+++ b/docs/SQL_UPDATE_DOCUMENT_DESCRIPTION_SIMPLE.sql
@@ -0,0 +1,86 @@
+-- ============================================================================
+-- Упрощённый SQL запрос для обновления upload_description
+-- ============================================================================
+-- Параметры:
+-- $1 :: uuid -- claim_document_id (ID документа из таблицы clpr_claim_documents)
+-- $2 :: text -- upload_description (новое описание документа)
+-- ============================================================================
+-- Этот запрос обновляет описание в payload.body['uploads_descriptions[i]']
+-- Это самый приоритетный способ хранения, который проверяется первым при чтении
+-- ============================================================================
+
+WITH
+-- Находим документ и извлекаем нужные данные
+doc_info AS (
+ SELECT
+ cd.id AS claim_document_id,
+ cd.claim_id::text AS claim_id_text,
+ cd.field_name,
+ -- Извлекаем индекс i из field_name (например, uploads[0][0] -> 0)
+ NULLIF((regexp_match(cd.field_name, 'uploads\[(\d+)\]'))[1], '')::int AS upload_index,
+ c.id AS claim_uuid,
+ c.payload
+ FROM clpr_claim_documents cd
+ JOIN clpr_claims c ON c.id::text = cd.claim_id::text
+ WHERE cd.id = $1
+ LIMIT 1
+),
+
+-- Обновляем payload: обновляем описание в body (самый приоритетный и надёжный способ)
+updated_claim AS (
+ UPDATE clpr_claims c
+ SET
+ payload = (
+ -- Сохраняем весь payload кроме body
+ COALESCE(c.payload, '{}'::jsonb) - 'body'
+ ) || jsonb_build_object(
+ -- Обновляем body: берём существующий body (или пустой объект) и добавляем/обновляем ключ
+ 'body',
+ COALESCE(c.payload->'body', '{}'::jsonb) ||
+ jsonb_build_object('uploads_descriptions[' || di.upload_index::text || ']', $2::text)
+ ),
+ updated_at = now()
+ FROM doc_info di
+ WHERE c.id = di.claim_uuid
+ RETURNING c.id, c.payload
+)
+
+-- Возвращаем обновлённые данные
+SELECT
+ di.claim_document_id,
+ di.claim_id_text AS claim_id,
+ di.field_name,
+ di.upload_index,
+ -- Проверяем, где сохранилось описание (приоритет: body > корень > массив)
+ COALESCE(
+ uc.payload->'body'->>('uploads_descriptions[' || di.upload_index::text || ']'),
+ uc.payload->>('uploads_descriptions[' || di.upload_index::text || ']'),
+ uc.payload->'edit_fields_parsed'->'uploads_descriptions'->>di.upload_index::text,
+ NULL
+ ) AS upload_description,
+ -- ✅ Диагностика: длина сохранённого значения
+ length(
+ COALESCE(
+ uc.payload->'body'->>('uploads_descriptions[' || di.upload_index::text || ']'),
+ uc.payload->>('uploads_descriptions[' || di.upload_index::text || ']'),
+ uc.payload->'edit_fields_parsed'->'uploads_descriptions'->>di.upload_index::text,
+ ''
+ )
+ ) AS description_length,
+ -- ✅ Диагностика: длина переданного значения
+ length($2::text) AS input_length,
+ -- ✅ Диагностика: проверяем, что значение сохранилось полностью
+ CASE
+ WHEN length($2::text) = length(
+ COALESCE(
+ uc.payload->'body'->>('uploads_descriptions[' || di.upload_index::text || ']'),
+ uc.payload->>('uploads_descriptions[' || di.upload_index::text || ']'),
+ uc.payload->'edit_fields_parsed'->'uploads_descriptions'->>di.upload_index::text,
+ ''
+ )
+ ) THEN 'OK'
+ ELSE 'TRUNCATED'
+ END AS status
+FROM doc_info di
+JOIN updated_claim uc ON uc.id = di.claim_uuid;
+
diff --git a/docs/SQL_UPDATE_DOCUMENT_HASH.sql b/docs/SQL_UPDATE_DOCUMENT_HASH.sql
new file mode 100644
index 0000000..20d5ada
--- /dev/null
+++ b/docs/SQL_UPDATE_DOCUMENT_HASH.sql
@@ -0,0 +1,21 @@
+-- ============================================================================
+-- SQL запрос для записи file_hash в clpr_claim_documents
+-- ============================================================================
+-- Параметры:
+-- $1 :: uuid -- claim_document_id (ID документа)
+-- $2 :: varchar -- file_hash (SHA-256 хеш, 64 символа hex)
+-- ============================================================================
+
+UPDATE clpr_claim_documents
+SET file_hash = $2
+WHERE id = $1
+RETURNING
+ id AS claim_document_id,
+ claim_id,
+ field_name,
+ file_id,
+ file_hash,
+ file_name,
+ original_file_name;
+
+
diff --git a/docs/migrations/001_add_ocr_status.sql b/docs/migrations/001_add_ocr_status.sql
new file mode 100644
index 0000000..4f118ac
--- /dev/null
+++ b/docs/migrations/001_add_ocr_status.sql
@@ -0,0 +1,64 @@
+-- ============================================================
+-- Миграция: Добавление статуса OCR обработки для документов
+-- Дата: 2025-11-28
+-- Описание: Добавляет колонки для отслеживания статуса OCR
+-- обработки документов в заявках
+-- ============================================================
+
+-- 1. Добавляем колонки в clpr_claim_documents
+ALTER TABLE clpr_claim_documents
+ADD COLUMN IF NOT EXISTS ocr_status VARCHAR(20) DEFAULT 'pending',
+ADD COLUMN IF NOT EXISTS ocr_processed_at TIMESTAMP,
+ADD COLUMN IF NOT EXISTS ocr_error TEXT;
+
+-- 2. Комментарии к колонкам
+COMMENT ON COLUMN clpr_claim_documents.ocr_status IS 'Статус OCR обработки: pending, processing, ready, error';
+COMMENT ON COLUMN clpr_claim_documents.ocr_processed_at IS 'Время завершения OCR обработки';
+COMMENT ON COLUMN clpr_claim_documents.ocr_error IS 'Текст ошибки при неудачной OCR обработке';
+
+-- 3. Индекс для быстрого поиска по статусу
+CREATE INDEX IF NOT EXISTS idx_claim_docs_ocr_status
+ON clpr_claim_documents(claim_id, ocr_status);
+
+-- 4. Индекс для поиска необработанных документов
+CREATE INDEX IF NOT EXISTS idx_claim_docs_pending
+ON clpr_claim_documents(ocr_status)
+WHERE ocr_status = 'pending';
+
+-- 5. Проставляем 'ready' для уже обработанных документов
+-- (те, что уже есть в document_texts по file_hash)
+UPDATE clpr_claim_documents cd
+SET
+ ocr_status = 'ready',
+ ocr_processed_at = NOW()
+WHERE cd.file_hash IS NOT NULL
+ AND EXISTS (
+ SELECT 1 FROM document_texts dt
+ WHERE dt.file_hash = cd.file_hash
+ )
+ AND (cd.ocr_status IS NULL OR cd.ocr_status = 'pending');
+
+-- 6. Статистика после миграции
+DO $$
+DECLARE
+ total_docs INT;
+ ready_docs INT;
+ pending_docs INT;
+BEGIN
+ SELECT COUNT(*) INTO total_docs FROM clpr_claim_documents;
+ SELECT COUNT(*) INTO ready_docs FROM clpr_claim_documents WHERE ocr_status = 'ready';
+ SELECT COUNT(*) INTO pending_docs FROM clpr_claim_documents WHERE ocr_status = 'pending';
+
+ RAISE NOTICE '=== OCR Status Migration Complete ===';
+ RAISE NOTICE 'Total documents: %', total_docs;
+ RAISE NOTICE 'Ready (already processed): %', ready_docs;
+ RAISE NOTICE 'Pending (need OCR): %', pending_docs;
+END $$;
+
+
+
+
+
+
+
+
diff --git a/docs/migrations/002_add_document_match.sql b/docs/migrations/002_add_document_match.sql
new file mode 100644
index 0000000..7a81cb5
--- /dev/null
+++ b/docs/migrations/002_add_document_match.sql
@@ -0,0 +1,72 @@
+-- ============================================================================
+-- Миграция 002: Добавление проверки соответствия документов
+-- ============================================================================
+-- Цель: Хранить результат проверки AI — соответствует ли загруженный документ
+-- запрошенному типу (договор, чек, переписка и т.д.)
+-- ============================================================================
+
+-- Добавляем колонки в clpr_claim_documents
+ALTER TABLE clpr_claim_documents
+ADD COLUMN IF NOT EXISTS document_type VARCHAR(50), -- ожидаемый тип: contract, payment, correspondence, evidence_photo
+ADD COLUMN IF NOT EXISTS document_label VARCHAR(255), -- читаемое название: "Договор или заказ"
+ADD COLUMN IF NOT EXISTS match_score INT, -- процент соответствия 0-100
+ADD COLUMN IF NOT EXISTS match_status VARCHAR(20) DEFAULT 'pending', -- pending/passed/failed/skipped
+ADD COLUMN IF NOT EXISTS match_reason TEXT, -- пояснение от AI почему такой score
+ADD COLUMN IF NOT EXISTS match_checked_at TIMESTAMP; -- когда проверено
+
+-- Комментарии к колонкам
+COMMENT ON COLUMN clpr_claim_documents.document_type IS 'Ожидаемый тип документа: contract, payment, correspondence, evidence_photo, other';
+COMMENT ON COLUMN clpr_claim_documents.document_label IS 'Читаемое название типа документа: "Договор или заказ", "Чек", "Переписка"';
+COMMENT ON COLUMN clpr_claim_documents.match_score IS 'Процент соответствия документа ожидаемому типу (0-100). NULL = не проверено';
+COMMENT ON COLUMN clpr_claim_documents.match_status IS 'Статус проверки: pending (ждёт), passed (ок), failed (не соответствует), skipped (пропущено)';
+COMMENT ON COLUMN clpr_claim_documents.match_reason IS 'Пояснение от AI: почему документ соответствует/не соответствует';
+COMMENT ON COLUMN clpr_claim_documents.match_checked_at IS 'Когда была выполнена проверка соответствия';
+
+-- Индекс для быстрого поиска непроверенных и проблемных документов
+CREATE INDEX IF NOT EXISTS idx_claim_docs_match_status
+ON clpr_claim_documents(claim_id, match_status);
+
+-- Заполняем document_type и document_label из payload для существующих документов
+UPDATE clpr_claim_documents cd
+SET
+ document_type = du.doc_type,
+ document_label = dm.field_label
+FROM clpr_claims c,
+LATERAL (
+ SELECT x->>'field_label' AS field_label
+ FROM jsonb_array_elements(COALESCE(c.payload->'documents_meta', '[]'::jsonb)) x
+ WHERE x->>'field_name' = cd.field_name
+ LIMIT 1
+) dm,
+LATERAL (
+ SELECT x->>'type' AS doc_type
+ FROM jsonb_array_elements(COALESCE(c.payload->'documents_uploaded', '[]'::jsonb)) x
+ WHERE x->>'file_name' = cd.file_name OR x->>'file_id' LIKE '%' || cd.file_name
+ LIMIT 1
+) du
+WHERE cd.claim_id::text = c.id::text
+ AND cd.document_type IS NULL;
+
+-- Статистика после миграции
+DO $$
+DECLARE
+ total_docs INT;
+ with_type INT;
+ with_label INT;
+BEGIN
+ SELECT COUNT(*) INTO total_docs FROM clpr_claim_documents;
+ SELECT COUNT(*) INTO with_type FROM clpr_claim_documents WHERE document_type IS NOT NULL;
+ SELECT COUNT(*) INTO with_label FROM clpr_claim_documents WHERE document_label IS NOT NULL;
+
+ RAISE NOTICE '=== Document Match Migration Complete ===';
+ RAISE NOTICE 'Total documents: %', total_docs;
+ RAISE NOTICE 'With document_type: %', with_type;
+ RAISE NOTICE 'With document_label: %', with_label;
+END $$;
+
+
+
+
+
+
+
diff --git a/docs/n8n_nodes/README_SETUP.md b/docs/n8n_nodes/README_SETUP.md
new file mode 100644
index 0000000..7069901
--- /dev/null
+++ b/docs/n8n_nodes/README_SETUP.md
@@ -0,0 +1,104 @@
+# Настройка OCR Status Tracking в n8n
+
+## Шаги для добавления нод в workflow `fnSo3FTTbQcMjwt3`
+
+### 1. Открой workflow в n8n
+https://n8n.clientright.pro/workflow/fnSo3FTTbQcMjwt3
+
+### 2. Добавь ноды в следующем порядке:
+
+#### Нода 1: `update_ocr_status` (PostgreSQL)
+**Расположение:** После `Postgres PGVector Store1`
+**Позиция:** [3850, 1664]
+
+**SQL запрос:**
+```sql
+UPDATE clpr_claim_documents
+SET
+ ocr_status = 'ready',
+ ocr_processed_at = NOW()
+WHERE id = '{{ $('files').item.json.claim_document_id }}'::uuid
+RETURNING
+ id AS doc_id,
+ claim_id,
+ ocr_status,
+ (SELECT COUNT(*) FROM clpr_claim_documents WHERE claim_id = clpr_claim_documents.claim_id) AS total_docs,
+ (SELECT COUNT(*) FROM clpr_claim_documents WHERE claim_id = clpr_claim_documents.claim_id AND ocr_status = 'ready') AS ready_docs;
+```
+
+**Credentials:** `timeweb_bd` (Postgres account 2)
+
+---
+
+#### Нода 2: `redis_incr_ready` (Redis)
+**Расположение:** После `update_ocr_status`
+**Позиция:** [4100, 1664]
+
+**Параметры:**
+- Operation: `incr`
+- Key: `claim:ocr:ready:{{ $json.claim_id }}`
+
+**Credentials:** `Redis account`
+
+---
+
+#### Нода 3: `check_all_ready` (IF)
+**Расположение:** После `redis_incr_ready`
+**Позиция:** [4350, 1664]
+
+**Условие:**
+```
+{{ $('update_ocr_status').item.json.total_docs }} == {{ $('update_ocr_status').item.json.ready_docs }}
+```
+
+---
+
+#### Нода 4: `publish_docs_ready` (Redis)
+**Расположение:** TRUE выход из `check_all_ready`
+**Позиция:** [4600, 1550]
+
+**Параметры:**
+- Operation: `publish`
+- Channel: `clpr:claim:docs_ready`
+- Message:
+```javascript
+{{ JSON.stringify({
+ claim_id: $('update_ocr_status').item.json.claim_id,
+ total_docs: $('update_ocr_status').item.json.total_docs,
+ ready_docs: $('update_ocr_status').item.json.ready_docs,
+ timestamp: new Date().toISOString()
+}) }}
+```
+
+**Credentials:** `Redis account`
+
+---
+
+### 3. Соединения (Connections)
+
+```
+Postgres PGVector Store1 → update_ocr_status
+update_ocr_status → redis_incr_ready
+redis_incr_ready → check_all_ready
+check_all_ready (TRUE) → publish_docs_ready
+check_all_ready (FALSE) → (конец)
+```
+
+### 4. Сохрани и активируй workflow
+
+---
+
+## Проверка
+
+После добавления нод, при обработке документа:
+1. Статус в `clpr_claim_documents.ocr_status` будет меняться на `'ready'`
+2. Счётчик в Redis `claim:ocr:ready:{claim_id}` будет инкрементиться
+3. Когда все документы готовы, событие `clpr:claim:docs_ready` будет опубликовано в Redis
+
+
+
+
+
+
+
+
diff --git a/docs/n8n_nodes/check_all_ready.json b/docs/n8n_nodes/check_all_ready.json
new file mode 100644
index 0000000..19c2533
--- /dev/null
+++ b/docs/n8n_nodes/check_all_ready.json
@@ -0,0 +1,37 @@
+{
+ "name": "check_all_ready",
+ "type": "n8n-nodes-base.if",
+ "typeVersion": 2.2,
+ "position": [4350, 1664],
+ "parameters": {
+ "conditions": {
+ "options": {
+ "caseSensitive": true,
+ "leftValue": "",
+ "typeValidation": "strict",
+ "version": 2
+ },
+ "conditions": [
+ {
+ "id": "ocr-ready-check-001",
+ "leftValue": "={{ $('update_ocr_status').item.json.total_docs }}",
+ "rightValue": "={{ $('update_ocr_status').item.json.ready_docs }}",
+ "operator": {
+ "type": "number",
+ "operation": "equals"
+ }
+ }
+ ],
+ "combinator": "and"
+ },
+ "options": {}
+ }
+}
+
+
+
+
+
+
+
+
diff --git a/docs/n8n_nodes/publish_docs_ready.json b/docs/n8n_nodes/publish_docs_ready.json
new file mode 100644
index 0000000..47a4f27
--- /dev/null
+++ b/docs/n8n_nodes/publish_docs_ready.json
@@ -0,0 +1,25 @@
+{
+ "name": "publish_docs_ready",
+ "type": "n8n-nodes-base.redis",
+ "typeVersion": 1,
+ "position": [4600, 1550],
+ "parameters": {
+ "operation": "publish",
+ "channel": "clpr:claim:docs_ready",
+ "messageData": "={{ JSON.stringify({ claim_id: $('update_ocr_status').item.json.claim_id, total_docs: $('update_ocr_status').item.json.total_docs, ready_docs: $('update_ocr_status').item.json.ready_docs, timestamp: new Date().toISOString() }) }}"
+ },
+ "credentials": {
+ "redis": {
+ "id": "F2IkIEYT9O7UTtz9",
+ "name": "Redis account"
+ }
+ }
+}
+
+
+
+
+
+
+
+
diff --git a/docs/n8n_nodes/redis_incr_ready.json b/docs/n8n_nodes/redis_incr_ready.json
new file mode 100644
index 0000000..8cbffb3
--- /dev/null
+++ b/docs/n8n_nodes/redis_incr_ready.json
@@ -0,0 +1,24 @@
+{
+ "name": "redis_incr_ready",
+ "type": "n8n-nodes-base.redis",
+ "typeVersion": 1,
+ "position": [4100, 1664],
+ "parameters": {
+ "operation": "incr",
+ "key": "=claim:ocr:ready:{{ $json.claim_id }}"
+ },
+ "credentials": {
+ "redis": {
+ "id": "F2IkIEYT9O7UTtz9",
+ "name": "Redis account"
+ }
+ }
+}
+
+
+
+
+
+
+
+
diff --git a/docs/n8n_nodes/update_ocr_error.json b/docs/n8n_nodes/update_ocr_error.json
new file mode 100644
index 0000000..a4f123c
--- /dev/null
+++ b/docs/n8n_nodes/update_ocr_error.json
@@ -0,0 +1,26 @@
+{
+ "name": "update_ocr_error",
+ "type": "n8n-nodes-base.postgres",
+ "typeVersion": 2.6,
+ "position": [3850, 1850],
+ "parameters": {
+ "operation": "executeQuery",
+ "query": "-- Обновляем статус OCR при ошибке\nUPDATE clpr_claim_documents\nSET \n ocr_status = 'error',\n ocr_error = '{{ $json.error || \"OCR processing failed\" }}',\n ocr_processed_at = NOW()\nWHERE id = '{{ $('files').item.json.claim_document_id }}'::uuid\nRETURNING id, claim_id, ocr_status, ocr_error;",
+ "options": {}
+ },
+ "credentials": {
+ "postgres": {
+ "id": "sGJ0fJhU8rz88w3k",
+ "name": "timeweb_bd"
+ }
+ },
+ "onError": "continueRegularOutput"
+}
+
+
+
+
+
+
+
+
diff --git a/docs/n8n_nodes/update_ocr_status.json b/docs/n8n_nodes/update_ocr_status.json
new file mode 100644
index 0000000..909656d
--- /dev/null
+++ b/docs/n8n_nodes/update_ocr_status.json
@@ -0,0 +1,25 @@
+{
+ "name": "update_ocr_status",
+ "type": "n8n-nodes-base.postgres",
+ "typeVersion": 2.6,
+ "position": [3850, 1664],
+ "parameters": {
+ "operation": "executeQuery",
+ "query": "-- Обновляем статус OCR для документа и возвращаем счётчики\nUPDATE clpr_claim_documents\nSET \n ocr_status = 'ready',\n ocr_processed_at = NOW()\nWHERE id = '{{ $('files').item.json.claim_document_id }}'::uuid\nRETURNING \n id AS doc_id,\n claim_id,\n ocr_status,\n (SELECT COUNT(*) FROM clpr_claim_documents WHERE claim_id = clpr_claim_documents.claim_id) AS total_docs,\n (SELECT COUNT(*) FROM clpr_claim_documents WHERE claim_id = clpr_claim_documents.claim_id AND ocr_status = 'ready') AS ready_docs;",
+ "options": {}
+ },
+ "credentials": {
+ "postgres": {
+ "id": "sGJ0fJhU8rz88w3k",
+ "name": "timeweb_bd"
+ }
+ }
+}
+
+
+
+
+
+
+
+
diff --git a/frontend/index.html b/frontend/index.html
index 05d1228..9ba69bc 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -4,7 +4,7 @@
- ERV Insurance Platform
+ Clientright — защита прав потребителей
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
new file mode 100644
index 0000000..c76312e
--- /dev/null
+++ b/frontend/package-lock.json
@@ -0,0 +1,8927 @@
+{
+ "name": "ticket-form-intake-frontend",
+ "version": "1.0.0",
+ "lockfileVersion": 2,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "ticket-form-intake-frontend",
+ "version": "1.0.0",
+ "dependencies": {
+ "@ant-design/icons": "^5.5.1",
+ "@tanstack/react-query": "^5.59.16",
+ "antd": "^5.21.6",
+ "axios": "^1.7.7",
+ "browser-image-compression": "^2.0.2",
+ "dayjs": "^1.11.13",
+ "imask": "^7.6.1",
+ "jspdf": "^2.5.2",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "react-dropzone": "^14.3.5",
+ "react-router-dom": "^6.26.2",
+ "serve": "^14.2.1",
+ "socket.io-client": "^4.8.1",
+ "zustand": "^5.0.1"
+ },
+ "devDependencies": {
+ "@types/react": "^18.3.11",
+ "@types/react-dom": "^18.3.1",
+ "@typescript-eslint/eslint-plugin": "^8.11.0",
+ "@typescript-eslint/parser": "^8.11.0",
+ "@vitejs/plugin-react": "^4.3.3",
+ "eslint": "^9.13.0",
+ "eslint-plugin-react-hooks": "^5.0.0",
+ "eslint-plugin-react-refresh": "^0.4.13",
+ "typescript": "^5.6.3",
+ "vite": "^5.4.10"
+ }
+ },
+ "node_modules/@ant-design/colors": {
+ "version": "7.2.1",
+ "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-7.2.1.tgz",
+ "integrity": "sha512-lCHDcEzieu4GA3n8ELeZ5VQ8pKQAWcGGLRTQ50aQM2iqPpq2evTxER84jfdPvsPAtEcZ7m44NI45edFMo8oOYQ==",
+ "dependencies": {
+ "@ant-design/fast-color": "^2.0.6"
+ }
+ },
+ "node_modules/@ant-design/cssinjs": {
+ "version": "1.24.0",
+ "resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-1.24.0.tgz",
+ "integrity": "sha512-K4cYrJBsgvL+IoozUXYjbT6LHHNt+19a9zkvpBPxLjFHas1UpPM2A5MlhROb0BT8N8WoavM5VsP9MeSeNK/3mg==",
+ "dependencies": {
+ "@babel/runtime": "^7.11.1",
+ "@emotion/hash": "^0.8.0",
+ "@emotion/unitless": "^0.7.5",
+ "classnames": "^2.3.1",
+ "csstype": "^3.1.3",
+ "rc-util": "^5.35.0",
+ "stylis": "^4.3.4"
+ },
+ "peerDependencies": {
+ "react": ">=16.0.0",
+ "react-dom": ">=16.0.0"
+ }
+ },
+ "node_modules/@ant-design/cssinjs-utils": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@ant-design/cssinjs-utils/-/cssinjs-utils-1.1.3.tgz",
+ "integrity": "sha512-nOoQMLW1l+xR1Co8NFVYiP8pZp3VjIIzqV6D6ShYF2ljtdwWJn5WSsH+7kvCktXL/yhEtWURKOfH5Xz/gzlwsg==",
+ "dependencies": {
+ "@ant-design/cssinjs": "^1.21.0",
+ "@babel/runtime": "^7.23.2",
+ "rc-util": "^5.38.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@ant-design/fast-color": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/@ant-design/fast-color/-/fast-color-2.0.6.tgz",
+ "integrity": "sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==",
+ "dependencies": {
+ "@babel/runtime": "^7.24.7"
+ },
+ "engines": {
+ "node": ">=8.x"
+ }
+ },
+ "node_modules/@ant-design/icons": {
+ "version": "5.6.1",
+ "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.6.1.tgz",
+ "integrity": "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==",
+ "dependencies": {
+ "@ant-design/colors": "^7.0.0",
+ "@ant-design/icons-svg": "^4.4.0",
+ "@babel/runtime": "^7.24.8",
+ "classnames": "^2.2.6",
+ "rc-util": "^5.31.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "peerDependencies": {
+ "react": ">=16.0.0",
+ "react-dom": ">=16.0.0"
+ }
+ },
+ "node_modules/@ant-design/icons-svg": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz",
+ "integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA=="
+ },
+ "node_modules/@ant-design/react-slick": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-1.1.2.tgz",
+ "integrity": "sha512-EzlvzE6xQUBrZuuhSAFTdsr4P2bBBHGZwKFemEfq8gIGyIQCxalYfZW/T2ORbtQx5rU69o+WycP3exY/7T1hGA==",
+ "dependencies": {
+ "@babel/runtime": "^7.10.4",
+ "classnames": "^2.2.5",
+ "json2mq": "^0.2.0",
+ "resize-observer-polyfill": "^1.5.1",
+ "throttle-debounce": "^5.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
+ "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz",
+ "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
+ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.28.5",
+ "@babel/helper-compilation-targets": "^7.27.2",
+ "@babel/helper-module-transforms": "^7.28.3",
+ "@babel/helpers": "^7.28.4",
+ "@babel/parser": "^7.28.5",
+ "@babel/template": "^7.27.2",
+ "@babel/traverse": "^7.28.5",
+ "@babel/types": "^7.28.5",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/core/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz",
+ "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/parser": "^7.28.5",
+ "@babel/types": "^7.28.5",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
+ "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/compat-data": "^7.27.2",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
+ "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
+ "dev": true,
+ "dependencies": {
+ "@babel/traverse": "^7.27.1",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.28.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz",
+ "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "@babel/traverse": "^7.28.3"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
+ "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz",
+ "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==",
+ "dev": true,
+ "dependencies": {
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.28.4"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
+ "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.28.5"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
+ "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
+ "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/runtime": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
+ "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/runtime-corejs3": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.28.4.tgz",
+ "integrity": "sha512-h7iEYiW4HebClDEhtvFObtPmIvrd1SSfpI9EhOeKk4CtIK/ngBWFpuhCzhdmRKtg71ylcue+9I6dv54XYO1epQ==",
+ "dependencies": {
+ "core-js-pure": "^3.43.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
+ "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/parser": "^7.27.2",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz",
+ "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.28.5",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.28.5",
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.28.5",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
+ "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@emotion/hash": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
+ "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow=="
+ },
+ "node_modules/@emotion/unitless": {
+ "version": "0.7.5",
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
+ "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg=="
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.9.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz",
+ "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==",
+ "dev": true,
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
+ "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
+ "dev": true,
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/config-array": {
+ "version": "0.21.1",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz",
+ "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==",
+ "dev": true,
+ "dependencies": {
+ "@eslint/object-schema": "^2.1.7",
+ "debug": "^4.3.1",
+ "minimatch": "^3.1.2"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/config-array/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/@eslint/config-array/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/@eslint/config-helpers": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz",
+ "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==",
+ "dev": true,
+ "dependencies": {
+ "@eslint/core": "^0.17.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/core": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
+ "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/json-schema": "^7.0.15"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz",
+ "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==",
+ "dev": true,
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^10.0.1",
+ "globals": "^14.0.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.1",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "9.39.1",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz",
+ "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==",
+ "dev": true,
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ }
+ },
+ "node_modules/@eslint/object-schema": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
+ "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
+ "dev": true,
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/plugin-kit": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
+ "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
+ "dev": true,
+ "dependencies": {
+ "@eslint/core": "^0.17.0",
+ "levn": "^0.4.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@humanfs/core": {
+ "version": "0.19.1",
+ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
+ "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+ "dev": true,
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node": {
+ "version": "0.16.7",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
+ "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
+ "dev": true,
+ "dependencies": {
+ "@humanfs/core": "^0.19.1",
+ "@humanwhocodes/retry": "^0.4.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/retry": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@rc-component/async-validator": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-5.0.4.tgz",
+ "integrity": "sha512-qgGdcVIF604M9EqjNF0hbUTz42bz/RDtxWdWuU5EQe3hi7M8ob54B6B35rOsvX5eSvIHIzT9iH1R3n+hk3CGfg==",
+ "dependencies": {
+ "@babel/runtime": "^7.24.4"
+ },
+ "engines": {
+ "node": ">=14.x"
+ }
+ },
+ "node_modules/@rc-component/color-picker": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@rc-component/color-picker/-/color-picker-2.0.1.tgz",
+ "integrity": "sha512-WcZYwAThV/b2GISQ8F+7650r5ZZJ043E57aVBFkQ+kSY4C6wdofXgB0hBx+GPGpIU0Z81eETNoDUJMr7oy/P8Q==",
+ "dependencies": {
+ "@ant-design/fast-color": "^2.0.6",
+ "@babel/runtime": "^7.23.6",
+ "classnames": "^2.2.6",
+ "rc-util": "^5.38.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/context": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/context/-/context-1.4.0.tgz",
+ "integrity": "sha512-kFcNxg9oLRMoL3qki0OMxK+7g5mypjgaaJp/pkOis/6rVxma9nJBF/8kCIuTYHUQNr0ii7MxqE33wirPZLJQ2w==",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "rc-util": "^5.27.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/mini-decimal": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/mini-decimal/-/mini-decimal-1.1.0.tgz",
+ "integrity": "sha512-jS4E7T9Li2GuYwI6PyiVXmxTiM6b07rlD9Ge8uGZSCz3WlzcG5ZK7g5bbuKNeZ9pgUuPK/5guV781ujdVpm4HQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.18.0"
+ },
+ "engines": {
+ "node": ">=8.x"
+ }
+ },
+ "node_modules/@rc-component/mutate-observer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/mutate-observer/-/mutate-observer-1.1.0.tgz",
+ "integrity": "sha512-QjrOsDXQusNwGZPf4/qRQasg7UFEj06XiCJ8iuiq/Io7CrHrgVi6Uuetw60WAMG1799v+aM8kyc+1L/GBbHSlw==",
+ "dependencies": {
+ "@babel/runtime": "^7.18.0",
+ "classnames": "^2.3.2",
+ "rc-util": "^5.24.4"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/portal": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@rc-component/portal/-/portal-1.1.2.tgz",
+ "integrity": "sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg==",
+ "dependencies": {
+ "@babel/runtime": "^7.18.0",
+ "classnames": "^2.3.2",
+ "rc-util": "^5.24.4"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/qrcode": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@rc-component/qrcode/-/qrcode-1.1.1.tgz",
+ "integrity": "sha512-LfLGNymzKdUPjXUbRP+xOhIWY4jQ+YMj5MmWAcgcAq1Ij8XP7tRmAXqyuv96XvLUBE/5cA8hLFl9eO1JQMujrA==",
+ "dependencies": {
+ "@babel/runtime": "^7.24.7"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/tour": {
+ "version": "1.15.1",
+ "resolved": "https://registry.npmjs.org/@rc-component/tour/-/tour-1.15.1.tgz",
+ "integrity": "sha512-Tr2t7J1DKZUpfJuDZWHxyxWpfmj8EZrqSgyMZ+BCdvKZ6r1UDsfU46M/iWAAFBy961Ssfom2kv5f3UcjIL2CmQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.18.0",
+ "@rc-component/portal": "^1.0.0-9",
+ "@rc-component/trigger": "^2.0.0",
+ "classnames": "^2.3.2",
+ "rc-util": "^5.24.4"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@rc-component/trigger": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-2.3.0.tgz",
+ "integrity": "sha512-iwaxZyzOuK0D7lS+0AQEtW52zUWxoGqTGkke3dRyb8pYiShmRpCjB/8TzPI4R6YySCH7Vm9BZj/31VPiiQTLBg==",
+ "dependencies": {
+ "@babel/runtime": "^7.23.2",
+ "@rc-component/portal": "^1.1.0",
+ "classnames": "^2.3.2",
+ "rc-motion": "^2.0.0",
+ "rc-resize-observer": "^1.3.1",
+ "rc-util": "^5.44.0"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/@remix-run/router": {
+ "version": "1.23.1",
+ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.1.tgz",
+ "integrity": "sha512-vDbaOzF7yT2Qs4vO6XV1MHcJv+3dgR1sT+l3B8xxOVhUC336prMvqrvsLL/9Dnw2xr6Qhz4J0dmS0llNAbnUmQ==",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-beta.27",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
+ "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
+ "dev": true
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz",
+ "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz",
+ "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz",
+ "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz",
+ "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz",
+ "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz",
+ "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz",
+ "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz",
+ "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz",
+ "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz",
+ "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz",
+ "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz",
+ "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz",
+ "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz",
+ "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz",
+ "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz",
+ "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz",
+ "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz",
+ "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz",
+ "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz",
+ "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz",
+ "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz",
+ "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@socket.io/component-emitter": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
+ "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="
+ },
+ "node_modules/@tanstack/query-core": {
+ "version": "5.90.11",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.11.tgz",
+ "integrity": "sha512-f9z/nXhCgWDF4lHqgIE30jxLe4sYv15QodfdPDKYAk7nAEjNcndy4dHz3ezhdUaR23BpWa4I2EH4/DZ0//Uf8A==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/react-query": {
+ "version": "5.90.11",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.11.tgz",
+ "integrity": "sha512-3uyzz01D1fkTLXuxF3JfoJoHQMU2fxsfJwE+6N5hHy0dVNoZOvwKP8Z2k7k1KDeD54N20apcJnG75TBAStIrBA==",
+ "dependencies": {
+ "@tanstack/query-core": "5.90.11"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": "^18 || ^19"
+ }
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true
+ },
+ "node_modules/@types/prop-types": {
+ "version": "15.7.15",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
+ "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
+ "devOptional": true
+ },
+ "node_modules/@types/raf": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz",
+ "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==",
+ "optional": true
+ },
+ "node_modules/@types/react": {
+ "version": "18.3.27",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz",
+ "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==",
+ "devOptional": true,
+ "dependencies": {
+ "@types/prop-types": "*",
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "18.3.7",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
+ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
+ "dev": true,
+ "peerDependencies": {
+ "@types/react": "^18.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "8.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.48.0.tgz",
+ "integrity": "sha512-XxXP5tL1txl13YFtrECECQYeZjBZad4fyd3cFV4a19LkAY/bIp9fev3US4S5fDVV2JaYFiKAZ/GRTOLer+mbyQ==",
+ "dev": true,
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.10.0",
+ "@typescript-eslint/scope-manager": "8.48.0",
+ "@typescript-eslint/type-utils": "8.48.0",
+ "@typescript-eslint/utils": "8.48.0",
+ "@typescript-eslint/visitor-keys": "8.48.0",
+ "graphemer": "^1.4.0",
+ "ignore": "^7.0.0",
+ "natural-compare": "^1.4.0",
+ "ts-api-utils": "^2.1.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^8.48.0",
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "8.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.48.0.tgz",
+ "integrity": "sha512-jCzKdm/QK0Kg4V4IK/oMlRZlY+QOcdjv89U2NgKHZk1CYTj82/RVSx1mV/0gqCVMJ/DA+Zf/S4NBWNF8GQ+eqQ==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "8.48.0",
+ "@typescript-eslint/types": "8.48.0",
+ "@typescript-eslint/typescript-estree": "8.48.0",
+ "@typescript-eslint/visitor-keys": "8.48.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/project-service": {
+ "version": "8.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.48.0.tgz",
+ "integrity": "sha512-Ne4CTZyRh1BecBf84siv42wv5vQvVmgtk8AuiEffKTUo3DrBaGYZueJSxxBZ8fjk/N3DrgChH4TOdIOwOwiqqw==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/tsconfig-utils": "^8.48.0",
+ "@typescript-eslint/types": "^8.48.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "8.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.48.0.tgz",
+ "integrity": "sha512-uGSSsbrtJrLduti0Q1Q9+BF1/iFKaxGoQwjWOIVNJv0o6omrdyR8ct37m4xIl5Zzpkp69Kkmvom7QFTtue89YQ==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "8.48.0",
+ "@typescript-eslint/visitor-keys": "8.48.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/tsconfig-utils": {
+ "version": "8.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.48.0.tgz",
+ "integrity": "sha512-WNebjBdFdyu10sR1M4OXTt2OkMd5KWIL+LLfeH9KhgP+jzfDV/LI3eXzwJ1s9+Yc0Kzo2fQCdY/OpdusCMmh6w==",
+ "dev": true,
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "8.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.48.0.tgz",
+ "integrity": "sha512-zbeVaVqeXhhab6QNEKfK96Xyc7UQuoFWERhEnj3mLVnUWrQnv15cJNseUni7f3g557gm0e46LZ6IJ4NJVOgOpw==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "8.48.0",
+ "@typescript-eslint/typescript-estree": "8.48.0",
+ "@typescript-eslint/utils": "8.48.0",
+ "debug": "^4.3.4",
+ "ts-api-utils": "^2.1.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "8.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.48.0.tgz",
+ "integrity": "sha512-cQMcGQQH7kwKoVswD1xdOytxQR60MWKM1di26xSUtxehaDs/32Zpqsu5WJlXTtTTqyAVK8R7hvsUnIXRS+bjvA==",
+ "dev": true,
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "8.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.48.0.tgz",
+ "integrity": "sha512-ljHab1CSO4rGrQIAyizUS6UGHHCiAYhbfcIZ1zVJr5nMryxlXMVWS3duFPSKvSUbFPwkXMFk1k0EMIjub4sRRQ==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/project-service": "8.48.0",
+ "@typescript-eslint/tsconfig-utils": "8.48.0",
+ "@typescript-eslint/types": "8.48.0",
+ "@typescript-eslint/visitor-keys": "8.48.0",
+ "debug": "^4.3.4",
+ "minimatch": "^9.0.4",
+ "semver": "^7.6.0",
+ "tinyglobby": "^0.2.15",
+ "ts-api-utils": "^2.1.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "8.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.48.0.tgz",
+ "integrity": "sha512-yTJO1XuGxCsSfIVt1+1UrLHtue8xz16V8apzPYI06W0HbEbEWHxHXgZaAgavIkoh+GeV6hKKd5jm0sS6OYxWXQ==",
+ "dev": true,
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.7.0",
+ "@typescript-eslint/scope-manager": "8.48.0",
+ "@typescript-eslint/types": "8.48.0",
+ "@typescript-eslint/typescript-estree": "8.48.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "8.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.48.0.tgz",
+ "integrity": "sha512-T0XJMaRPOH3+LBbAfzR2jalckP1MSG/L9eUtY0DEzUyVaXJ/t6zN0nR7co5kz0Jko/nkSYCBRkz1djvjajVTTg==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "8.48.0",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "dev": true,
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
+ "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/core": "^7.28.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-beta.27",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.17.0"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+ }
+ },
+ "node_modules/@zeit/schemas": {
+ "version": "2.36.0",
+ "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.36.0.tgz",
+ "integrity": "sha512-7kjMwcChYEzMKjeex9ZFXkt1AyNov9R5HZtjBKVsmVpw7pa7ZtlCGvCBC2vnnXctaYN+aRI61HjIqeetZW5ROg=="
+ },
+ "node_modules/acorn": {
+ "version": "8.15.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "dev": true,
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-align": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz",
+ "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==",
+ "dependencies": {
+ "string-width": "^4.1.0"
+ }
+ },
+ "node_modules/ansi-align/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-align/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+ },
+ "node_modules/ansi-align/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-align/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/antd": {
+ "version": "5.29.1",
+ "resolved": "https://registry.npmjs.org/antd/-/antd-5.29.1.tgz",
+ "integrity": "sha512-TTFVbpKbyL6cPfEoKq6Ya3BIjTUr7uDW9+7Z+1oysRv1gpcN7kQ4luH8r/+rXXwz4n6BIz1iBJ1ezKCdsdNW0w==",
+ "dependencies": {
+ "@ant-design/colors": "^7.2.1",
+ "@ant-design/cssinjs": "^1.23.0",
+ "@ant-design/cssinjs-utils": "^1.1.3",
+ "@ant-design/fast-color": "^2.0.6",
+ "@ant-design/icons": "^5.6.1",
+ "@ant-design/react-slick": "~1.1.2",
+ "@babel/runtime": "^7.26.0",
+ "@rc-component/color-picker": "~2.0.1",
+ "@rc-component/mutate-observer": "^1.1.0",
+ "@rc-component/qrcode": "~1.1.0",
+ "@rc-component/tour": "~1.15.1",
+ "@rc-component/trigger": "^2.3.0",
+ "classnames": "^2.5.1",
+ "copy-to-clipboard": "^3.3.3",
+ "dayjs": "^1.11.11",
+ "rc-cascader": "~3.34.0",
+ "rc-checkbox": "~3.5.0",
+ "rc-collapse": "~3.9.0",
+ "rc-dialog": "~9.6.0",
+ "rc-drawer": "~7.3.0",
+ "rc-dropdown": "~4.2.1",
+ "rc-field-form": "~2.7.1",
+ "rc-image": "~7.12.0",
+ "rc-input": "~1.8.0",
+ "rc-input-number": "~9.5.0",
+ "rc-mentions": "~2.20.0",
+ "rc-menu": "~9.16.1",
+ "rc-motion": "^2.9.5",
+ "rc-notification": "~5.6.4",
+ "rc-pagination": "~5.1.0",
+ "rc-picker": "~4.11.3",
+ "rc-progress": "~4.0.0",
+ "rc-rate": "~2.13.1",
+ "rc-resize-observer": "^1.4.3",
+ "rc-segmented": "~2.7.0",
+ "rc-select": "~14.16.8",
+ "rc-slider": "~11.1.9",
+ "rc-steps": "~6.0.1",
+ "rc-switch": "~4.1.0",
+ "rc-table": "~7.54.0",
+ "rc-tabs": "~15.7.0",
+ "rc-textarea": "~1.10.2",
+ "rc-tooltip": "~6.4.0",
+ "rc-tree": "~5.13.1",
+ "rc-tree-select": "~5.27.0",
+ "rc-upload": "~4.11.0",
+ "rc-util": "^5.44.4",
+ "scroll-into-view-if-needed": "^3.1.0",
+ "throttle-debounce": "^5.0.2"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/ant-design"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/arch": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz",
+ "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/arg": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
+ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+ },
+ "node_modules/atob": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
+ "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
+ "bin": {
+ "atob": "bin/atob.js"
+ },
+ "engines": {
+ "node": ">= 4.5.0"
+ }
+ },
+ "node_modules/attr-accept": {
+ "version": "2.2.5",
+ "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.5.tgz",
+ "integrity": "sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/axios": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
+ "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
+ "dependencies": {
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.4",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
+ },
+ "node_modules/base64-arraybuffer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
+ "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
+ "optional": true,
+ "engines": {
+ "node": ">= 0.6.0"
+ }
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.8.32",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.32.tgz",
+ "integrity": "sha512-OPz5aBThlyLFgxyhdwf/s2+8ab3OvT7AdTNvKHBwpXomIYeXqpUUuT8LrdtxZSsWJ4R4CU1un4XGh5Ez3nlTpw==",
+ "dev": true,
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.js"
+ }
+ },
+ "node_modules/boxen": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.0.0.tgz",
+ "integrity": "sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==",
+ "dependencies": {
+ "ansi-align": "^3.0.1",
+ "camelcase": "^7.0.0",
+ "chalk": "^5.0.1",
+ "cli-boxes": "^3.0.0",
+ "string-width": "^5.1.2",
+ "type-fest": "^2.13.0",
+ "widest-line": "^4.0.1",
+ "wrap-ansi": "^8.0.1"
+ },
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/boxen/node_modules/chalk": {
+ "version": "5.6.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
+ "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
+ "engines": {
+ "node": "^12.17.0 || ^14.13 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/browser-image-compression": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/browser-image-compression/-/browser-image-compression-2.0.2.tgz",
+ "integrity": "sha512-pBLlQyUf6yB8SmmngrcOw3EoS4RpQ1BcylI3T9Yqn7+4nrQTXJD4sJDe5ODnJdrvNMaio5OicFo75rDyJD2Ucw==",
+ "dependencies": {
+ "uzip": "0.20201231.0"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.0",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz",
+ "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "baseline-browser-mapping": "^2.8.25",
+ "caniuse-lite": "^1.0.30001754",
+ "electron-to-chromium": "^1.5.249",
+ "node-releases": "^2.0.27",
+ "update-browserslist-db": "^1.1.4"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/btoa": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz",
+ "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==",
+ "bin": {
+ "btoa": "bin/btoa.js"
+ },
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/camelcase": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz",
+ "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==",
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001757",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz",
+ "integrity": "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ]
+ },
+ "node_modules/canvg": {
+ "version": "3.0.11",
+ "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz",
+ "integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==",
+ "optional": true,
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "@types/raf": "^3.4.0",
+ "core-js": "^3.8.3",
+ "raf": "^3.4.1",
+ "regenerator-runtime": "^0.13.7",
+ "rgbcolor": "^1.0.1",
+ "stackblur-canvas": "^2.0.0",
+ "svg-pathdata": "^6.0.3"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/chalk-template": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz",
+ "integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==",
+ "dependencies": {
+ "chalk": "^4.1.2"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk-template?sponsor=1"
+ }
+ },
+ "node_modules/classnames": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
+ "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="
+ },
+ "node_modules/cli-boxes": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz",
+ "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/clipboardy": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-3.0.0.tgz",
+ "integrity": "sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==",
+ "dependencies": {
+ "arch": "^2.2.0",
+ "execa": "^5.1.1",
+ "is-wsl": "^2.2.0"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/compressible": {
+ "version": "2.0.18",
+ "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
+ "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==",
+ "dependencies": {
+ "mime-db": ">= 1.43.0 < 2"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/compression": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz",
+ "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "compressible": "~2.0.18",
+ "debug": "2.6.9",
+ "negotiator": "~0.6.4",
+ "on-headers": "~1.1.0",
+ "safe-buffer": "5.2.1",
+ "vary": "~1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/compression/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/compression/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+ },
+ "node_modules/compute-scroll-into-view": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz",
+ "integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw=="
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
+ },
+ "node_modules/content-disposition": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
+ "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true
+ },
+ "node_modules/copy-to-clipboard": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz",
+ "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==",
+ "dependencies": {
+ "toggle-selection": "^1.0.6"
+ }
+ },
+ "node_modules/core-js": {
+ "version": "3.47.0",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.47.0.tgz",
+ "integrity": "sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==",
+ "hasInstallScript": true,
+ "optional": true,
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/core-js"
+ }
+ },
+ "node_modules/core-js-pure": {
+ "version": "3.47.0",
+ "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.47.0.tgz",
+ "integrity": "sha512-BcxeDbzUrRnXGYIVAGFtcGQVNpFcUhVjr6W7F8XktvQW2iJP9e66GP6xdKotCRFlrxBvNIBrhwKteRXqMV86Nw==",
+ "hasInstallScript": true,
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/core-js"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/css-line-break": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
+ "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
+ "optional": true,
+ "dependencies": {
+ "utrie": "^1.0.2"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="
+ },
+ "node_modules/dayjs": {
+ "version": "1.11.19",
+ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz",
+ "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw=="
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deep-extend": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
+ "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/dompurify": {
+ "version": "2.5.8",
+ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.8.tgz",
+ "integrity": "sha512-o1vSNgrmYMQObbSSvF/1brBYEQPHhV1+gsmrusO7/GXtp1T9rCS8cXFqVxK/9crT1jA6Ccv+5MTSjBNqr7Sovw==",
+ "optional": true
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/eastasianwidth": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.262",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.262.tgz",
+ "integrity": "sha512-NlAsMteRHek05jRUxUR0a5jpjYq9ykk6+kO0yRaMi5moe7u0fVIOeQ3Y30A8dIiWFBNUoQGi1ljb1i5VtS9WQQ==",
+ "dev": true
+ },
+ "node_modules/emoji-regex": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
+ },
+ "node_modules/engine.io-client": {
+ "version": "6.6.3",
+ "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz",
+ "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==",
+ "dependencies": {
+ "@socket.io/component-emitter": "~3.1.0",
+ "debug": "~4.3.1",
+ "engine.io-parser": "~5.2.1",
+ "ws": "~8.17.1",
+ "xmlhttprequest-ssl": "~2.1.1"
+ }
+ },
+ "node_modules/engine.io-client/node_modules/debug": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+ "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/engine.io-parser": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
+ "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "9.39.1",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz",
+ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
+ "dev": true,
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.8.0",
+ "@eslint-community/regexpp": "^4.12.1",
+ "@eslint/config-array": "^0.21.1",
+ "@eslint/config-helpers": "^0.4.2",
+ "@eslint/core": "^0.17.0",
+ "@eslint/eslintrc": "^3.3.1",
+ "@eslint/js": "9.39.1",
+ "@eslint/plugin-kit": "^0.4.1",
+ "@humanfs/node": "^0.16.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@humanwhocodes/retry": "^0.4.2",
+ "@types/estree": "^1.0.6",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.6",
+ "debug": "^4.3.2",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^8.4.0",
+ "eslint-visitor-keys": "^4.2.1",
+ "espree": "^10.4.0",
+ "esquery": "^1.5.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^8.0.0",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "jiti": "*"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-plugin-react-hooks": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz",
+ "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
+ }
+ },
+ "node_modules/eslint-plugin-react-refresh": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.24.tgz",
+ "integrity": "sha512-nLHIW7TEq3aLrEYWpVaJ1dRgFR+wLDPN8e8FpYAql/bMV2oBEfC37K0gLEGgv9fy66juNShSMV8OkTqzltcG/w==",
+ "dev": true,
+ "peerDependencies": {
+ "eslint": ">=8.40"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "8.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
+ "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
+ "dev": true,
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/eslint/node_modules/eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "dev": true,
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint/node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/eslint/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/espree": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
+ "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+ "dev": true,
+ "dependencies": {
+ "acorn": "^8.15.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/espree/node_modules/eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "dev": true,
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
+ "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+ "dev": true,
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/execa": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
+ "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
+ "dependencies": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^6.0.0",
+ "human-signals": "^2.1.0",
+ "is-stream": "^2.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^4.0.1",
+ "onetime": "^5.1.2",
+ "signal-exit": "^3.0.3",
+ "strip-final-newline": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fflate": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
+ "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="
+ },
+ "node_modules/file-entry-cache": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+ "dev": true,
+ "dependencies": {
+ "flat-cache": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/file-selector": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-2.1.2.tgz",
+ "integrity": "sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==",
+ "dependencies": {
+ "tslib": "^2.7.0"
+ },
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+ "dev": true,
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.4"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
+ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
+ "dev": true
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.15.11",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
+ "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
+ "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/get-stream": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+ "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/graphemer": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+ "dev": true
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/html2canvas": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
+ "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
+ "optional": true,
+ "dependencies": {
+ "css-line-break": "^2.1.0",
+ "text-segmentation": "^1.0.3"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/human-signals": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
+ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
+ "engines": {
+ "node": ">=10.17.0"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
+ "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/imask": {
+ "version": "7.6.1",
+ "resolved": "https://registry.npmjs.org/imask/-/imask-7.6.1.tgz",
+ "integrity": "sha512-sJlIFM7eathUEMChTh9Mrfw/IgiWgJqBKq2VNbyXvBZ7ev/IlO6/KQTKlV/Fm+viQMLrFLG/zCuudrLIwgK2dg==",
+ "dependencies": {
+ "@babel/runtime-corejs3": "^7.24.4"
+ },
+ "engines": {
+ "npm": ">=4.0.0"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "dev": true,
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/ini": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
+ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="
+ },
+ "node_modules/is-docker": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
+ "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
+ "bin": {
+ "is-docker": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-port-reachable": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-port-reachable/-/is-port-reachable-4.0.0.tgz",
+ "integrity": "sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==",
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-stream": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-wsl": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
+ "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
+ "dependencies": {
+ "is-docker": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
+ "dev": true,
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true
+ },
+ "node_modules/json2mq": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz",
+ "integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==",
+ "dependencies": {
+ "string-convert": "^0.2.0"
+ }
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/jspdf": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.2.tgz",
+ "integrity": "sha512-myeX9c+p7znDWPk0eTrujCzNjT+CXdXyk7YmJq5nD5V7uLLKmSXnlQ/Jn/kuo3X09Op70Apm0rQSnFWyGK8uEQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.23.2",
+ "atob": "^2.1.2",
+ "btoa": "^1.2.1",
+ "fflate": "^0.8.1"
+ },
+ "optionalDependencies": {
+ "canvg": "^3.0.6",
+ "core-js": "^3.6.0",
+ "dompurify": "^2.5.4",
+ "html2canvas": "^1.0.0-rc.5"
+ }
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true
+ },
+ "node_modules/negotiator": {
+ "version": "0.6.4",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz",
+ "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.27",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
+ "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
+ "dev": true
+ },
+ "node_modules/npm-run-path": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
+ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
+ "dependencies": {
+ "path-key": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/on-headers": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
+ "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "dependencies": {
+ "mimic-fn": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-is-inside": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
+ "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w=="
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz",
+ "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw=="
+ },
+ "node_modules/performance-now": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+ "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
+ "optional": true
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/prop-types": {
+ "version": "15.8.1",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
+ "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
+ "dependencies": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.13.1"
+ }
+ },
+ "node_modules/prop-types/node_modules/react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+ },
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/raf": {
+ "version": "3.4.1",
+ "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
+ "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==",
+ "optional": true,
+ "dependencies": {
+ "performance-now": "^2.1.0"
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
+ "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/rc": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
+ "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
+ "dependencies": {
+ "deep-extend": "^0.6.0",
+ "ini": "~1.3.0",
+ "minimist": "^1.2.0",
+ "strip-json-comments": "~2.0.1"
+ },
+ "bin": {
+ "rc": "cli.js"
+ }
+ },
+ "node_modules/rc-cascader": {
+ "version": "3.34.0",
+ "resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-3.34.0.tgz",
+ "integrity": "sha512-KpXypcvju9ptjW9FaN2NFcA2QH9E9LHKq169Y0eWtH4e/wHQ5Wh5qZakAgvb8EKZ736WZ3B0zLLOBsrsja5Dag==",
+ "dependencies": {
+ "@babel/runtime": "^7.25.7",
+ "classnames": "^2.3.1",
+ "rc-select": "~14.16.2",
+ "rc-tree": "~5.13.0",
+ "rc-util": "^5.43.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-checkbox": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/rc-checkbox/-/rc-checkbox-3.5.0.tgz",
+ "integrity": "sha512-aOAQc3E98HteIIsSqm6Xk2FPKIER6+5vyEFMZfo73TqM+VVAIqOkHoPjgKLqSNtVLWScoaM7vY2ZrGEheI79yg==",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "classnames": "^2.3.2",
+ "rc-util": "^5.25.2"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-collapse": {
+ "version": "3.9.0",
+ "resolved": "https://registry.npmjs.org/rc-collapse/-/rc-collapse-3.9.0.tgz",
+ "integrity": "sha512-swDdz4QZ4dFTo4RAUMLL50qP0EY62N2kvmk2We5xYdRwcRn8WcYtuetCJpwpaCbUfUt5+huLpVxhvmnK+PHrkA==",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "classnames": "2.x",
+ "rc-motion": "^2.3.4",
+ "rc-util": "^5.27.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-dialog": {
+ "version": "9.6.0",
+ "resolved": "https://registry.npmjs.org/rc-dialog/-/rc-dialog-9.6.0.tgz",
+ "integrity": "sha512-ApoVi9Z8PaCQg6FsUzS8yvBEQy0ZL2PkuvAgrmohPkN3okps5WZ5WQWPc1RNuiOKaAYv8B97ACdsFU5LizzCqg==",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "@rc-component/portal": "^1.0.0-8",
+ "classnames": "^2.2.6",
+ "rc-motion": "^2.3.0",
+ "rc-util": "^5.21.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-drawer": {
+ "version": "7.3.0",
+ "resolved": "https://registry.npmjs.org/rc-drawer/-/rc-drawer-7.3.0.tgz",
+ "integrity": "sha512-DX6CIgiBWNpJIMGFO8BAISFkxiuKitoizooj4BDyee8/SnBn0zwO2FHrNDpqqepj0E/TFTDpmEBCyFuTgC7MOg==",
+ "dependencies": {
+ "@babel/runtime": "^7.23.9",
+ "@rc-component/portal": "^1.1.1",
+ "classnames": "^2.2.6",
+ "rc-motion": "^2.6.1",
+ "rc-util": "^5.38.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-dropdown": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/rc-dropdown/-/rc-dropdown-4.2.1.tgz",
+ "integrity": "sha512-YDAlXsPv3I1n42dv1JpdM7wJ+gSUBfeyPK59ZpBD9jQhK9jVuxpjj3NmWQHOBceA1zEPVX84T2wbdb2SD0UjmA==",
+ "dependencies": {
+ "@babel/runtime": "^7.18.3",
+ "@rc-component/trigger": "^2.0.0",
+ "classnames": "^2.2.6",
+ "rc-util": "^5.44.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.11.0",
+ "react-dom": ">=16.11.0"
+ }
+ },
+ "node_modules/rc-field-form": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/rc-field-form/-/rc-field-form-2.7.1.tgz",
+ "integrity": "sha512-vKeSifSJ6HoLaAB+B8aq/Qgm8a3dyxROzCtKNCsBQgiverpc4kWDQihoUwzUj+zNWJOykwSY4dNX3QrGwtVb9A==",
+ "dependencies": {
+ "@babel/runtime": "^7.18.0",
+ "@rc-component/async-validator": "^5.0.3",
+ "rc-util": "^5.32.2"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-image": {
+ "version": "7.12.0",
+ "resolved": "https://registry.npmjs.org/rc-image/-/rc-image-7.12.0.tgz",
+ "integrity": "sha512-cZ3HTyyckPnNnUb9/DRqduqzLfrQRyi+CdHjdqgsyDpI3Ln5UX1kXnAhPBSJj9pVRzwRFgqkN7p9b6HBDjmu/Q==",
+ "dependencies": {
+ "@babel/runtime": "^7.11.2",
+ "@rc-component/portal": "^1.0.2",
+ "classnames": "^2.2.6",
+ "rc-dialog": "~9.6.0",
+ "rc-motion": "^2.6.2",
+ "rc-util": "^5.34.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-input": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/rc-input/-/rc-input-1.8.0.tgz",
+ "integrity": "sha512-KXvaTbX+7ha8a/k+eg6SYRVERK0NddX8QX7a7AnRvUa/rEH0CNMlpcBzBkhI0wp2C8C4HlMoYl8TImSN+fuHKA==",
+ "dependencies": {
+ "@babel/runtime": "^7.11.1",
+ "classnames": "^2.2.1",
+ "rc-util": "^5.18.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.0.0",
+ "react-dom": ">=16.0.0"
+ }
+ },
+ "node_modules/rc-input-number": {
+ "version": "9.5.0",
+ "resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-9.5.0.tgz",
+ "integrity": "sha512-bKaEvB5tHebUURAEXw35LDcnRZLq3x1k7GxfAqBMzmpHkDGzjAtnUL8y4y5N15rIFIg5IJgwr211jInl3cipag==",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "@rc-component/mini-decimal": "^1.0.1",
+ "classnames": "^2.2.5",
+ "rc-input": "~1.8.0",
+ "rc-util": "^5.40.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-mentions": {
+ "version": "2.20.0",
+ "resolved": "https://registry.npmjs.org/rc-mentions/-/rc-mentions-2.20.0.tgz",
+ "integrity": "sha512-w8HCMZEh3f0nR8ZEd466ATqmXFCMGMN5UFCzEUL0bM/nGw/wOS2GgRzKBcm19K++jDyuWCOJOdgcKGXU3fXfbQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.22.5",
+ "@rc-component/trigger": "^2.0.0",
+ "classnames": "^2.2.6",
+ "rc-input": "~1.8.0",
+ "rc-menu": "~9.16.0",
+ "rc-textarea": "~1.10.0",
+ "rc-util": "^5.34.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-menu": {
+ "version": "9.16.1",
+ "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-9.16.1.tgz",
+ "integrity": "sha512-ghHx6/6Dvp+fw8CJhDUHFHDJ84hJE3BXNCzSgLdmNiFErWSOaZNsihDAsKq9ByTALo/xkNIwtDFGIl6r+RPXBg==",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "@rc-component/trigger": "^2.0.0",
+ "classnames": "2.x",
+ "rc-motion": "^2.4.3",
+ "rc-overflow": "^1.3.1",
+ "rc-util": "^5.27.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-motion": {
+ "version": "2.9.5",
+ "resolved": "https://registry.npmjs.org/rc-motion/-/rc-motion-2.9.5.tgz",
+ "integrity": "sha512-w+XTUrfh7ArbYEd2582uDrEhmBHwK1ZENJiSJVb7uRxdE7qJSYjbO2eksRXmndqyKqKoYPc9ClpPh5242mV1vA==",
+ "dependencies": {
+ "@babel/runtime": "^7.11.1",
+ "classnames": "^2.2.1",
+ "rc-util": "^5.44.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-notification": {
+ "version": "5.6.4",
+ "resolved": "https://registry.npmjs.org/rc-notification/-/rc-notification-5.6.4.tgz",
+ "integrity": "sha512-KcS4O6B4qzM3KH7lkwOB7ooLPZ4b6J+VMmQgT51VZCeEcmghdeR4IrMcFq0LG+RPdnbe/ArT086tGM8Snimgiw==",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "classnames": "2.x",
+ "rc-motion": "^2.9.0",
+ "rc-util": "^5.20.1"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-overflow": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/rc-overflow/-/rc-overflow-1.5.0.tgz",
+ "integrity": "sha512-Lm/v9h0LymeUYJf0x39OveU52InkdRXqnn2aYXfWmo8WdOonIKB2kfau+GF0fWq6jPgtdO9yMqveGcK6aIhJmg==",
+ "dependencies": {
+ "@babel/runtime": "^7.11.1",
+ "classnames": "^2.2.1",
+ "rc-resize-observer": "^1.0.0",
+ "rc-util": "^5.37.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-pagination": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/rc-pagination/-/rc-pagination-5.1.0.tgz",
+ "integrity": "sha512-8416Yip/+eclTFdHXLKTxZvn70duYVGTvUUWbckCCZoIl3jagqke3GLsFrMs0bsQBikiYpZLD9206Ej4SOdOXQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "classnames": "^2.3.2",
+ "rc-util": "^5.38.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-picker": {
+ "version": "4.11.3",
+ "resolved": "https://registry.npmjs.org/rc-picker/-/rc-picker-4.11.3.tgz",
+ "integrity": "sha512-MJ5teb7FlNE0NFHTncxXQ62Y5lytq6sh5nUw0iH8OkHL/TjARSEvSHpr940pWgjGANpjCwyMdvsEV55l5tYNSg==",
+ "dependencies": {
+ "@babel/runtime": "^7.24.7",
+ "@rc-component/trigger": "^2.0.0",
+ "classnames": "^2.2.1",
+ "rc-overflow": "^1.3.2",
+ "rc-resize-observer": "^1.4.0",
+ "rc-util": "^5.43.0"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "date-fns": ">= 2.x",
+ "dayjs": ">= 1.x",
+ "luxon": ">= 3.x",
+ "moment": ">= 2.x",
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ },
+ "peerDependenciesMeta": {
+ "date-fns": {
+ "optional": true
+ },
+ "dayjs": {
+ "optional": true
+ },
+ "luxon": {
+ "optional": true
+ },
+ "moment": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/rc-progress": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-4.0.0.tgz",
+ "integrity": "sha512-oofVMMafOCokIUIBnZLNcOZFsABaUw8PPrf1/y0ZBvKZNpOiu5h4AO9vv11Sw0p4Hb3D0yGWuEattcQGtNJ/aw==",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "classnames": "^2.2.6",
+ "rc-util": "^5.16.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-rate": {
+ "version": "2.13.1",
+ "resolved": "https://registry.npmjs.org/rc-rate/-/rc-rate-2.13.1.tgz",
+ "integrity": "sha512-QUhQ9ivQ8Gy7mtMZPAjLbxBt5y9GRp65VcUyGUMF3N3fhiftivPHdpuDIaWIMOTEprAjZPC08bls1dQB+I1F2Q==",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "classnames": "^2.2.5",
+ "rc-util": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-resize-observer": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/rc-resize-observer/-/rc-resize-observer-1.4.3.tgz",
+ "integrity": "sha512-YZLjUbyIWox8E9i9C3Tm7ia+W7euPItNWSPX5sCcQTYbnwDb5uNpnLHQCG1f22oZWUhLw4Mv2tFmeWe68CDQRQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.20.7",
+ "classnames": "^2.2.1",
+ "rc-util": "^5.44.1",
+ "resize-observer-polyfill": "^1.5.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-segmented": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/rc-segmented/-/rc-segmented-2.7.0.tgz",
+ "integrity": "sha512-liijAjXz+KnTRVnxxXG2sYDGd6iLL7VpGGdR8gwoxAXy2KglviKCxLWZdjKYJzYzGSUwKDSTdYk8brj54Bn5BA==",
+ "dependencies": {
+ "@babel/runtime": "^7.11.1",
+ "classnames": "^2.2.1",
+ "rc-motion": "^2.4.4",
+ "rc-util": "^5.17.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.0.0",
+ "react-dom": ">=16.0.0"
+ }
+ },
+ "node_modules/rc-select": {
+ "version": "14.16.8",
+ "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-14.16.8.tgz",
+ "integrity": "sha512-NOV5BZa1wZrsdkKaiK7LHRuo5ZjZYMDxPP6/1+09+FB4KoNi8jcG1ZqLE3AVCxEsYMBe65OBx71wFoHRTP3LRg==",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "@rc-component/trigger": "^2.1.1",
+ "classnames": "2.x",
+ "rc-motion": "^2.0.1",
+ "rc-overflow": "^1.3.1",
+ "rc-util": "^5.16.1",
+ "rc-virtual-list": "^3.5.2"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-dom": "*"
+ }
+ },
+ "node_modules/rc-slider": {
+ "version": "11.1.9",
+ "resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-11.1.9.tgz",
+ "integrity": "sha512-h8IknhzSh3FEM9u8ivkskh+Ef4Yo4JRIY2nj7MrH6GQmrwV6mcpJf5/4KgH5JaVI1H3E52yCdpOlVyGZIeph5A==",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "classnames": "^2.2.5",
+ "rc-util": "^5.36.0"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-steps": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/rc-steps/-/rc-steps-6.0.1.tgz",
+ "integrity": "sha512-lKHL+Sny0SeHkQKKDJlAjV5oZ8DwCdS2hFhAkIjuQt1/pB81M0cA0ErVFdHq9+jmPmFw1vJB2F5NBzFXLJxV+g==",
+ "dependencies": {
+ "@babel/runtime": "^7.16.7",
+ "classnames": "^2.2.3",
+ "rc-util": "^5.16.1"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-switch": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/rc-switch/-/rc-switch-4.1.0.tgz",
+ "integrity": "sha512-TI8ufP2Az9oEbvyCeVE4+90PDSljGyuwix3fV58p7HV2o4wBnVToEyomJRVyTaZeqNPAp+vqeo4Wnj5u0ZZQBg==",
+ "dependencies": {
+ "@babel/runtime": "^7.21.0",
+ "classnames": "^2.2.1",
+ "rc-util": "^5.30.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-table": {
+ "version": "7.54.0",
+ "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.54.0.tgz",
+ "integrity": "sha512-/wDTkki6wBTjwylwAGjpLKYklKo9YgjZwAU77+7ME5mBoS32Q4nAwoqhA2lSge6fobLW3Tap6uc5xfwaL2p0Sw==",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "@rc-component/context": "^1.4.0",
+ "classnames": "^2.2.5",
+ "rc-resize-observer": "^1.1.0",
+ "rc-util": "^5.44.3",
+ "rc-virtual-list": "^3.14.2"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-tabs": {
+ "version": "15.7.0",
+ "resolved": "https://registry.npmjs.org/rc-tabs/-/rc-tabs-15.7.0.tgz",
+ "integrity": "sha512-ZepiE+6fmozYdWf/9gVp7k56PKHB1YYoDsKeQA1CBlJ/POIhjkcYiv0AGP0w2Jhzftd3AVvZP/K+V+Lpi2ankA==",
+ "dependencies": {
+ "@babel/runtime": "^7.11.2",
+ "classnames": "2.x",
+ "rc-dropdown": "~4.2.0",
+ "rc-menu": "~9.16.0",
+ "rc-motion": "^2.6.2",
+ "rc-resize-observer": "^1.0.0",
+ "rc-util": "^5.34.1"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-textarea": {
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/rc-textarea/-/rc-textarea-1.10.2.tgz",
+ "integrity": "sha512-HfaeXiaSlpiSp0I/pvWpecFEHpVysZ9tpDLNkxQbMvMz6gsr7aVZ7FpWP9kt4t7DB+jJXesYS0us1uPZnlRnwQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "classnames": "^2.2.1",
+ "rc-input": "~1.8.0",
+ "rc-resize-observer": "^1.0.0",
+ "rc-util": "^5.27.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-tooltip": {
+ "version": "6.4.0",
+ "resolved": "https://registry.npmjs.org/rc-tooltip/-/rc-tooltip-6.4.0.tgz",
+ "integrity": "sha512-kqyivim5cp8I5RkHmpsp1Nn/Wk+1oeloMv9c7LXNgDxUpGm+RbXJGL+OPvDlcRnx9DBeOe4wyOIl4OKUERyH1g==",
+ "dependencies": {
+ "@babel/runtime": "^7.11.2",
+ "@rc-component/trigger": "^2.0.0",
+ "classnames": "^2.3.1",
+ "rc-util": "^5.44.3"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-tree": {
+ "version": "5.13.1",
+ "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-5.13.1.tgz",
+ "integrity": "sha512-FNhIefhftobCdUJshO7M8uZTA9F4OPGVXqGfZkkD/5soDeOhwO06T/aKTrg0WD8gRg/pyfq+ql3aMymLHCTC4A==",
+ "dependencies": {
+ "@babel/runtime": "^7.10.1",
+ "classnames": "2.x",
+ "rc-motion": "^2.0.1",
+ "rc-util": "^5.16.1",
+ "rc-virtual-list": "^3.5.1"
+ },
+ "engines": {
+ "node": ">=10.x"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-dom": "*"
+ }
+ },
+ "node_modules/rc-tree-select": {
+ "version": "5.27.0",
+ "resolved": "https://registry.npmjs.org/rc-tree-select/-/rc-tree-select-5.27.0.tgz",
+ "integrity": "sha512-2qTBTzwIT7LRI1o7zLyrCzmo5tQanmyGbSaGTIf7sYimCklAToVVfpMC6OAldSKolcnjorBYPNSKQqJmN3TCww==",
+ "dependencies": {
+ "@babel/runtime": "^7.25.7",
+ "classnames": "2.x",
+ "rc-select": "~14.16.2",
+ "rc-tree": "~5.13.0",
+ "rc-util": "^5.43.0"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-dom": "*"
+ }
+ },
+ "node_modules/rc-upload": {
+ "version": "4.11.0",
+ "resolved": "https://registry.npmjs.org/rc-upload/-/rc-upload-4.11.0.tgz",
+ "integrity": "sha512-ZUyT//2JAehfHzjWowqROcwYJKnZkIUGWaTE/VogVrepSl7AFNbQf4+zGfX4zl9Vrj/Jm8scLO0R6UlPDKK4wA==",
+ "dependencies": {
+ "@babel/runtime": "^7.18.3",
+ "classnames": "^2.2.5",
+ "rc-util": "^5.2.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-util": {
+ "version": "5.44.4",
+ "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.44.4.tgz",
+ "integrity": "sha512-resueRJzmHG9Q6rI/DfK6Kdv9/Lfls05vzMs1Sk3M2P+3cJa+MakaZyWY8IPfehVuhPJFKrIY1IK4GqbiaiY5w==",
+ "dependencies": {
+ "@babel/runtime": "^7.18.3",
+ "react-is": "^18.2.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc-virtual-list": {
+ "version": "3.19.2",
+ "resolved": "https://registry.npmjs.org/rc-virtual-list/-/rc-virtual-list-3.19.2.tgz",
+ "integrity": "sha512-Ys6NcjwGkuwkeaWBDqfI3xWuZ7rDiQXlH1o2zLfFzATfEgXcqpk8CkgMfbJD81McqjcJVez25a3kPxCR807evA==",
+ "dependencies": {
+ "@babel/runtime": "^7.20.0",
+ "classnames": "^2.2.6",
+ "rc-resize-observer": "^1.0.0",
+ "rc-util": "^5.36.0"
+ },
+ "engines": {
+ "node": ">=8.x"
+ },
+ "peerDependencies": {
+ "react": ">=16.9.0",
+ "react-dom": ">=16.9.0"
+ }
+ },
+ "node_modules/rc/node_modules/strip-json-comments": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+ "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
+ },
+ "peerDependencies": {
+ "react": "^18.3.1"
+ }
+ },
+ "node_modules/react-dropzone": {
+ "version": "14.3.8",
+ "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.3.8.tgz",
+ "integrity": "sha512-sBgODnq+lcA4P296DY4wacOZz3JFpD99fp+hb//iBO2HHnyeZU3FwWyXJ6salNpqQdsZrgMrotuko/BdJMV8Ug==",
+ "dependencies": {
+ "attr-accept": "^2.2.4",
+ "file-selector": "^2.1.0",
+ "prop-types": "^15.8.1"
+ },
+ "engines": {
+ "node": ">= 10.13"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8 || 18.0.0"
+ }
+ },
+ "node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="
+ },
+ "node_modules/react-refresh": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
+ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-router": {
+ "version": "6.30.2",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.2.tgz",
+ "integrity": "sha512-H2Bm38Zu1bm8KUE5NVWRMzuIyAV8p/JrOaBJAwVmp37AXG72+CZJlEBw6pdn9i5TBgLMhNDgijS4ZlblpHyWTA==",
+ "dependencies": {
+ "@remix-run/router": "1.23.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8"
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "6.30.2",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.2.tgz",
+ "integrity": "sha512-l2OwHn3UUnEVUqc6/1VMmR1cvZryZ3j3NzapC2eUXO1dB0sYp5mvwdjiXhpUbRb21eFow3qSxpP8Yv6oAU824Q==",
+ "dependencies": {
+ "@remix-run/router": "1.23.1",
+ "react-router": "6.30.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8",
+ "react-dom": ">=16.8"
+ }
+ },
+ "node_modules/regenerator-runtime": {
+ "version": "0.13.11",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
+ "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
+ "optional": true
+ },
+ "node_modules/registry-auth-token": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz",
+ "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==",
+ "dependencies": {
+ "rc": "^1.1.6",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/registry-url": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz",
+ "integrity": "sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==",
+ "dependencies": {
+ "rc": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/resize-observer-polyfill": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
+ "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/rgbcolor": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz",
+ "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==",
+ "optional": true,
+ "engines": {
+ "node": ">= 0.8.15"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz",
+ "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==",
+ "dev": true,
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.53.3",
+ "@rollup/rollup-android-arm64": "4.53.3",
+ "@rollup/rollup-darwin-arm64": "4.53.3",
+ "@rollup/rollup-darwin-x64": "4.53.3",
+ "@rollup/rollup-freebsd-arm64": "4.53.3",
+ "@rollup/rollup-freebsd-x64": "4.53.3",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.53.3",
+ "@rollup/rollup-linux-arm-musleabihf": "4.53.3",
+ "@rollup/rollup-linux-arm64-gnu": "4.53.3",
+ "@rollup/rollup-linux-arm64-musl": "4.53.3",
+ "@rollup/rollup-linux-loong64-gnu": "4.53.3",
+ "@rollup/rollup-linux-ppc64-gnu": "4.53.3",
+ "@rollup/rollup-linux-riscv64-gnu": "4.53.3",
+ "@rollup/rollup-linux-riscv64-musl": "4.53.3",
+ "@rollup/rollup-linux-s390x-gnu": "4.53.3",
+ "@rollup/rollup-linux-x64-gnu": "4.53.3",
+ "@rollup/rollup-linux-x64-musl": "4.53.3",
+ "@rollup/rollup-openharmony-arm64": "4.53.3",
+ "@rollup/rollup-win32-arm64-msvc": "4.53.3",
+ "@rollup/rollup-win32-ia32-msvc": "4.53.3",
+ "@rollup/rollup-win32-x64-gnu": "4.53.3",
+ "@rollup/rollup-win32-x64-msvc": "4.53.3",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/scheduler": {
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "node_modules/scroll-into-view-if-needed": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz",
+ "integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==",
+ "dependencies": {
+ "compute-scroll-into-view": "^3.0.2"
+ }
+ },
+ "node_modules/semver": {
+ "version": "7.7.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
+ "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/serve": {
+ "version": "14.2.5",
+ "resolved": "https://registry.npmjs.org/serve/-/serve-14.2.5.tgz",
+ "integrity": "sha512-Qn/qMkzCcMFVPb60E/hQy+iRLpiU8PamOfOSYoAHmmF+fFFmpPpqa6Oci2iWYpTdOUM3VF+TINud7CfbQnsZbA==",
+ "dependencies": {
+ "@zeit/schemas": "2.36.0",
+ "ajv": "8.12.0",
+ "arg": "5.0.2",
+ "boxen": "7.0.0",
+ "chalk": "5.0.1",
+ "chalk-template": "0.4.0",
+ "clipboardy": "3.0.0",
+ "compression": "1.8.1",
+ "is-port-reachable": "4.0.0",
+ "serve-handler": "6.1.6",
+ "update-check": "1.5.4"
+ },
+ "bin": {
+ "serve": "build/main.js"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/serve-handler": {
+ "version": "6.1.6",
+ "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.6.tgz",
+ "integrity": "sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==",
+ "dependencies": {
+ "bytes": "3.0.0",
+ "content-disposition": "0.5.2",
+ "mime-types": "2.1.18",
+ "minimatch": "3.1.2",
+ "path-is-inside": "1.0.2",
+ "path-to-regexp": "3.3.0",
+ "range-parser": "1.2.0"
+ }
+ },
+ "node_modules/serve-handler/node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/serve-handler/node_modules/bytes": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
+ "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/serve-handler/node_modules/mime-db": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz",
+ "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/serve-handler/node_modules/mime-types": {
+ "version": "2.1.18",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz",
+ "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==",
+ "dependencies": {
+ "mime-db": "~1.33.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/serve-handler/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/serve/node_modules/ajv": {
+ "version": "8.12.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
+ "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/serve/node_modules/chalk": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz",
+ "integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==",
+ "engines": {
+ "node": "^12.17.0 || ^14.13 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/serve/node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
+ },
+ "node_modules/socket.io-client": {
+ "version": "4.8.1",
+ "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz",
+ "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==",
+ "dependencies": {
+ "@socket.io/component-emitter": "~3.1.0",
+ "debug": "~4.3.2",
+ "engine.io-client": "~6.6.1",
+ "socket.io-parser": "~4.2.4"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/socket.io-client/node_modules/debug": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+ "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/socket.io-parser": {
+ "version": "4.2.4",
+ "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
+ "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
+ "dependencies": {
+ "@socket.io/component-emitter": "~3.1.0",
+ "debug": "~4.3.1"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/socket.io-parser/node_modules/debug": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+ "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/stackblur-canvas": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz",
+ "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==",
+ "optional": true,
+ "engines": {
+ "node": ">=0.1.14"
+ }
+ },
+ "node_modules/string-convert": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz",
+ "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A=="
+ },
+ "node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "dependencies": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
+ "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/strip-final-newline": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
+ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/stylis": {
+ "version": "4.3.6",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz",
+ "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ=="
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/svg-pathdata": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz",
+ "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==",
+ "optional": true,
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/text-segmentation": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
+ "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
+ "optional": true,
+ "dependencies": {
+ "utrie": "^1.0.2"
+ }
+ },
+ "node_modules/throttle-debounce": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz",
+ "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==",
+ "engines": {
+ "node": ">=12.22"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "dev": true,
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/toggle-selection": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
+ "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ=="
+ },
+ "node_modules/ts-api-utils": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
+ "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=18.12"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "2.19.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
+ "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
+ "engines": {
+ "node": ">=12.20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz",
+ "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/update-check": {
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.4.tgz",
+ "integrity": "sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==",
+ "dependencies": {
+ "registry-auth-token": "3.3.2",
+ "registry-url": "3.1.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/utrie": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
+ "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
+ "optional": true,
+ "dependencies": {
+ "base64-arraybuffer": "^1.0.2"
+ }
+ },
+ "node_modules/uzip": {
+ "version": "0.20201231.0",
+ "resolved": "https://registry.npmjs.org/uzip/-/uzip-0.20201231.0.tgz",
+ "integrity": "sha512-OZeJfZP+R0z9D6TmBgLq2LHzSSptGMGDGigGiEe0pr8UBe/7fdflgHlHBNDASTXB5jnFuxHpNaJywSg8YFeGng=="
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/vite": {
+ "version": "5.4.21",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
+ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
+ "dev": true,
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/widest-line": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz",
+ "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==",
+ "dependencies": {
+ "string-width": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/wrap-ansi": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "dependencies": {
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-styles": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/ws": {
+ "version": "8.17.1",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
+ "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/xmlhttprequest-ssl": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
+ "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/zustand": {
+ "version": "5.0.9",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.9.tgz",
+ "integrity": "sha512-ALBtUj0AfjJt3uNRQoL1tL2tMvj6Gp/6e39dnfT6uzpelGru8v1tPOGBzayOWbPJvujM8JojDk3E1LxeFisBNg==",
+ "engines": {
+ "node": ">=12.20.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=18.0.0",
+ "immer": ">=9.0.6",
+ "react": ">=18.0.0",
+ "use-sync-external-store": ">=1.2.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "immer": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "use-sync-external-store": {
+ "optional": true
+ }
+ }
+ }
+ },
+ "dependencies": {
+ "@ant-design/colors": {
+ "version": "7.2.1",
+ "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-7.2.1.tgz",
+ "integrity": "sha512-lCHDcEzieu4GA3n8ELeZ5VQ8pKQAWcGGLRTQ50aQM2iqPpq2evTxER84jfdPvsPAtEcZ7m44NI45edFMo8oOYQ==",
+ "requires": {
+ "@ant-design/fast-color": "^2.0.6"
+ }
+ },
+ "@ant-design/cssinjs": {
+ "version": "1.24.0",
+ "resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-1.24.0.tgz",
+ "integrity": "sha512-K4cYrJBsgvL+IoozUXYjbT6LHHNt+19a9zkvpBPxLjFHas1UpPM2A5MlhROb0BT8N8WoavM5VsP9MeSeNK/3mg==",
+ "requires": {
+ "@babel/runtime": "^7.11.1",
+ "@emotion/hash": "^0.8.0",
+ "@emotion/unitless": "^0.7.5",
+ "classnames": "^2.3.1",
+ "csstype": "^3.1.3",
+ "rc-util": "^5.35.0",
+ "stylis": "^4.3.4"
+ }
+ },
+ "@ant-design/cssinjs-utils": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@ant-design/cssinjs-utils/-/cssinjs-utils-1.1.3.tgz",
+ "integrity": "sha512-nOoQMLW1l+xR1Co8NFVYiP8pZp3VjIIzqV6D6ShYF2ljtdwWJn5WSsH+7kvCktXL/yhEtWURKOfH5Xz/gzlwsg==",
+ "requires": {
+ "@ant-design/cssinjs": "^1.21.0",
+ "@babel/runtime": "^7.23.2",
+ "rc-util": "^5.38.0"
+ }
+ },
+ "@ant-design/fast-color": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/@ant-design/fast-color/-/fast-color-2.0.6.tgz",
+ "integrity": "sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==",
+ "requires": {
+ "@babel/runtime": "^7.24.7"
+ }
+ },
+ "@ant-design/icons": {
+ "version": "5.6.1",
+ "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.6.1.tgz",
+ "integrity": "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==",
+ "requires": {
+ "@ant-design/colors": "^7.0.0",
+ "@ant-design/icons-svg": "^4.4.0",
+ "@babel/runtime": "^7.24.8",
+ "classnames": "^2.2.6",
+ "rc-util": "^5.31.1"
+ }
+ },
+ "@ant-design/icons-svg": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz",
+ "integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA=="
+ },
+ "@ant-design/react-slick": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-1.1.2.tgz",
+ "integrity": "sha512-EzlvzE6xQUBrZuuhSAFTdsr4P2bBBHGZwKFemEfq8gIGyIQCxalYfZW/T2ORbtQx5rU69o+WycP3exY/7T1hGA==",
+ "requires": {
+ "@babel/runtime": "^7.10.4",
+ "classnames": "^2.2.5",
+ "json2mq": "^0.2.0",
+ "resize-observer-polyfill": "^1.5.1",
+ "throttle-debounce": "^5.0.0"
+ }
+ },
+ "@babel/code-frame": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
+ "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ }
+ },
+ "@babel/compat-data": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz",
+ "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==",
+ "dev": true
+ },
+ "@babel/core": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
+ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.28.5",
+ "@babel/helper-compilation-targets": "^7.27.2",
+ "@babel/helper-module-transforms": "^7.28.3",
+ "@babel/helpers": "^7.28.4",
+ "@babel/parser": "^7.28.5",
+ "@babel/template": "^7.27.2",
+ "@babel/traverse": "^7.28.5",
+ "@babel/types": "^7.28.5",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "dependencies": {
+ "semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true
+ }
+ }
+ },
+ "@babel/generator": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz",
+ "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==",
+ "dev": true,
+ "requires": {
+ "@babel/parser": "^7.28.5",
+ "@babel/types": "^7.28.5",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ }
+ },
+ "@babel/helper-compilation-targets": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
+ "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
+ "dev": true,
+ "requires": {
+ "@babel/compat-data": "^7.27.2",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "dependencies": {
+ "semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true
+ }
+ }
+ },
+ "@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "dev": true
+ },
+ "@babel/helper-module-imports": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
+ "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
+ "dev": true,
+ "requires": {
+ "@babel/traverse": "^7.27.1",
+ "@babel/types": "^7.27.1"
+ }
+ },
+ "@babel/helper-module-transforms": {
+ "version": "7.28.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz",
+ "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-module-imports": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "@babel/traverse": "^7.28.3"
+ }
+ },
+ "@babel/helper-plugin-utils": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
+ "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
+ "dev": true
+ },
+ "@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "dev": true
+ },
+ "@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "dev": true
+ },
+ "@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "dev": true
+ },
+ "@babel/helpers": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz",
+ "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==",
+ "dev": true,
+ "requires": {
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.28.4"
+ }
+ },
+ "@babel/parser": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
+ "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.28.5"
+ }
+ },
+ "@babel/plugin-transform-react-jsx-self": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
+ "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ }
+ },
+ "@babel/plugin-transform-react-jsx-source": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
+ "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ }
+ },
+ "@babel/runtime": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
+ "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ=="
+ },
+ "@babel/runtime-corejs3": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.28.4.tgz",
+ "integrity": "sha512-h7iEYiW4HebClDEhtvFObtPmIvrd1SSfpI9EhOeKk4CtIK/ngBWFpuhCzhdmRKtg71ylcue+9I6dv54XYO1epQ==",
+ "requires": {
+ "core-js-pure": "^3.43.0"
+ }
+ },
+ "@babel/template": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
+ "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/parser": "^7.27.2",
+ "@babel/types": "^7.27.1"
+ }
+ },
+ "@babel/traverse": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz",
+ "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.28.5",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.28.5",
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.28.5",
+ "debug": "^4.3.1"
+ }
+ },
+ "@babel/types": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
+ "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
+ "dev": true,
+ "requires": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ }
+ },
+ "@emotion/hash": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
+ "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow=="
+ },
+ "@emotion/unitless": {
+ "version": "0.7.5",
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
+ "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg=="
+ },
+ "@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "dev": true,
+ "optional": true
+ },
+ "@eslint-community/eslint-utils": {
+ "version": "4.9.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz",
+ "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==",
+ "dev": true,
+ "requires": {
+ "eslint-visitor-keys": "^3.4.3"
+ }
+ },
+ "@eslint-community/regexpp": {
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
+ "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
+ "dev": true
+ },
+ "@eslint/config-array": {
+ "version": "0.21.1",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz",
+ "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==",
+ "dev": true,
+ "requires": {
+ "@eslint/object-schema": "^2.1.7",
+ "debug": "^4.3.1",
+ "minimatch": "^3.1.2"
+ },
+ "dependencies": {
+ "brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ }
+ }
+ },
+ "@eslint/config-helpers": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz",
+ "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==",
+ "dev": true,
+ "requires": {
+ "@eslint/core": "^0.17.0"
+ }
+ },
+ "@eslint/core": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
+ "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
+ "dev": true,
+ "requires": {
+ "@types/json-schema": "^7.0.15"
+ }
+ },
+ "@eslint/eslintrc": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz",
+ "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==",
+ "dev": true,
+ "requires": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^10.0.1",
+ "globals": "^14.0.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.1",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "dependencies": {
+ "brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true
+ },
+ "minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ }
+ }
+ },
+ "@eslint/js": {
+ "version": "9.39.1",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz",
+ "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==",
+ "dev": true
+ },
+ "@eslint/object-schema": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
+ "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
+ "dev": true
+ },
+ "@eslint/plugin-kit": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
+ "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
+ "dev": true,
+ "requires": {
+ "@eslint/core": "^0.17.0",
+ "levn": "^0.4.1"
+ }
+ },
+ "@humanfs/core": {
+ "version": "0.19.1",
+ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
+ "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+ "dev": true
+ },
+ "@humanfs/node": {
+ "version": "0.16.7",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
+ "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
+ "dev": true,
+ "requires": {
+ "@humanfs/core": "^0.19.1",
+ "@humanwhocodes/retry": "^0.4.0"
+ }
+ },
+ "@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true
+ },
+ "@humanwhocodes/retry": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
+ "dev": true
+ },
+ "@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "requires": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "requires": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true
+ },
+ "@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true
+ },
+ "@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "requires": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "@rc-component/async-validator": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-5.0.4.tgz",
+ "integrity": "sha512-qgGdcVIF604M9EqjNF0hbUTz42bz/RDtxWdWuU5EQe3hi7M8ob54B6B35rOsvX5eSvIHIzT9iH1R3n+hk3CGfg==",
+ "requires": {
+ "@babel/runtime": "^7.24.4"
+ }
+ },
+ "@rc-component/color-picker": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@rc-component/color-picker/-/color-picker-2.0.1.tgz",
+ "integrity": "sha512-WcZYwAThV/b2GISQ8F+7650r5ZZJ043E57aVBFkQ+kSY4C6wdofXgB0hBx+GPGpIU0Z81eETNoDUJMr7oy/P8Q==",
+ "requires": {
+ "@ant-design/fast-color": "^2.0.6",
+ "@babel/runtime": "^7.23.6",
+ "classnames": "^2.2.6",
+ "rc-util": "^5.38.1"
+ }
+ },
+ "@rc-component/context": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/context/-/context-1.4.0.tgz",
+ "integrity": "sha512-kFcNxg9oLRMoL3qki0OMxK+7g5mypjgaaJp/pkOis/6rVxma9nJBF/8kCIuTYHUQNr0ii7MxqE33wirPZLJQ2w==",
+ "requires": {
+ "@babel/runtime": "^7.10.1",
+ "rc-util": "^5.27.0"
+ }
+ },
+ "@rc-component/mini-decimal": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/mini-decimal/-/mini-decimal-1.1.0.tgz",
+ "integrity": "sha512-jS4E7T9Li2GuYwI6PyiVXmxTiM6b07rlD9Ge8uGZSCz3WlzcG5ZK7g5bbuKNeZ9pgUuPK/5guV781ujdVpm4HQ==",
+ "requires": {
+ "@babel/runtime": "^7.18.0"
+ }
+ },
+ "@rc-component/mutate-observer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/mutate-observer/-/mutate-observer-1.1.0.tgz",
+ "integrity": "sha512-QjrOsDXQusNwGZPf4/qRQasg7UFEj06XiCJ8iuiq/Io7CrHrgVi6Uuetw60WAMG1799v+aM8kyc+1L/GBbHSlw==",
+ "requires": {
+ "@babel/runtime": "^7.18.0",
+ "classnames": "^2.3.2",
+ "rc-util": "^5.24.4"
+ }
+ },
+ "@rc-component/portal": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@rc-component/portal/-/portal-1.1.2.tgz",
+ "integrity": "sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg==",
+ "requires": {
+ "@babel/runtime": "^7.18.0",
+ "classnames": "^2.3.2",
+ "rc-util": "^5.24.4"
+ }
+ },
+ "@rc-component/qrcode": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@rc-component/qrcode/-/qrcode-1.1.1.tgz",
+ "integrity": "sha512-LfLGNymzKdUPjXUbRP+xOhIWY4jQ+YMj5MmWAcgcAq1Ij8XP7tRmAXqyuv96XvLUBE/5cA8hLFl9eO1JQMujrA==",
+ "requires": {
+ "@babel/runtime": "^7.24.7"
+ }
+ },
+ "@rc-component/tour": {
+ "version": "1.15.1",
+ "resolved": "https://registry.npmjs.org/@rc-component/tour/-/tour-1.15.1.tgz",
+ "integrity": "sha512-Tr2t7J1DKZUpfJuDZWHxyxWpfmj8EZrqSgyMZ+BCdvKZ6r1UDsfU46M/iWAAFBy961Ssfom2kv5f3UcjIL2CmQ==",
+ "requires": {
+ "@babel/runtime": "^7.18.0",
+ "@rc-component/portal": "^1.0.0-9",
+ "@rc-component/trigger": "^2.0.0",
+ "classnames": "^2.3.2",
+ "rc-util": "^5.24.4"
+ }
+ },
+ "@rc-component/trigger": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-2.3.0.tgz",
+ "integrity": "sha512-iwaxZyzOuK0D7lS+0AQEtW52zUWxoGqTGkke3dRyb8pYiShmRpCjB/8TzPI4R6YySCH7Vm9BZj/31VPiiQTLBg==",
+ "requires": {
+ "@babel/runtime": "^7.23.2",
+ "@rc-component/portal": "^1.1.0",
+ "classnames": "^2.3.2",
+ "rc-motion": "^2.0.0",
+ "rc-resize-observer": "^1.3.1",
+ "rc-util": "^5.44.0"
+ }
+ },
+ "@remix-run/router": {
+ "version": "1.23.1",
+ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.1.tgz",
+ "integrity": "sha512-vDbaOzF7yT2Qs4vO6XV1MHcJv+3dgR1sT+l3B8xxOVhUC336prMvqrvsLL/9Dnw2xr6Qhz4J0dmS0llNAbnUmQ=="
+ },
+ "@rolldown/pluginutils": {
+ "version": "1.0.0-beta.27",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
+ "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
+ "dev": true
+ },
+ "@rollup/rollup-android-arm-eabi": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz",
+ "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==",
+ "dev": true,
+ "optional": true
+ },
+ "@rollup/rollup-android-arm64": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz",
+ "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==",
+ "dev": true,
+ "optional": true
+ },
+ "@rollup/rollup-darwin-arm64": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz",
+ "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==",
+ "dev": true,
+ "optional": true
+ },
+ "@rollup/rollup-darwin-x64": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz",
+ "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==",
+ "dev": true,
+ "optional": true
+ },
+ "@rollup/rollup-freebsd-arm64": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz",
+ "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==",
+ "dev": true,
+ "optional": true
+ },
+ "@rollup/rollup-freebsd-x64": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz",
+ "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==",
+ "dev": true,
+ "optional": true
+ },
+ "@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz",
+ "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==",
+ "dev": true,
+ "optional": true
+ },
+ "@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz",
+ "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==",
+ "dev": true,
+ "optional": true
+ },
+ "@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz",
+ "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==",
+ "dev": true,
+ "optional": true
+ },
+ "@rollup/rollup-linux-arm64-musl": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz",
+ "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==",
+ "dev": true,
+ "optional": true
+ },
+ "@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz",
+ "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==",
+ "dev": true,
+ "optional": true
+ },
+ "@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz",
+ "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==",
+ "dev": true,
+ "optional": true
+ },
+ "@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz",
+ "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==",
+ "dev": true,
+ "optional": true
+ },
+ "@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz",
+ "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==",
+ "dev": true,
+ "optional": true
+ },
+ "@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz",
+ "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==",
+ "dev": true,
+ "optional": true
+ },
+ "@rollup/rollup-linux-x64-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz",
+ "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==",
+ "dev": true,
+ "optional": true
+ },
+ "@rollup/rollup-linux-x64-musl": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz",
+ "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==",
+ "dev": true,
+ "optional": true
+ },
+ "@rollup/rollup-openharmony-arm64": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz",
+ "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==",
+ "dev": true,
+ "optional": true
+ },
+ "@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz",
+ "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==",
+ "dev": true,
+ "optional": true
+ },
+ "@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz",
+ "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==",
+ "dev": true,
+ "optional": true
+ },
+ "@rollup/rollup-win32-x64-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz",
+ "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==",
+ "dev": true,
+ "optional": true
+ },
+ "@rollup/rollup-win32-x64-msvc": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz",
+ "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==",
+ "dev": true,
+ "optional": true
+ },
+ "@socket.io/component-emitter": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
+ "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="
+ },
+ "@tanstack/query-core": {
+ "version": "5.90.11",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.11.tgz",
+ "integrity": "sha512-f9z/nXhCgWDF4lHqgIE30jxLe4sYv15QodfdPDKYAk7nAEjNcndy4dHz3ezhdUaR23BpWa4I2EH4/DZ0//Uf8A=="
+ },
+ "@tanstack/react-query": {
+ "version": "5.90.11",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.11.tgz",
+ "integrity": "sha512-3uyzz01D1fkTLXuxF3JfoJoHQMU2fxsfJwE+6N5hHy0dVNoZOvwKP8Z2k7k1KDeD54N20apcJnG75TBAStIrBA==",
+ "requires": {
+ "@tanstack/query-core": "5.90.11"
+ }
+ },
+ "@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "requires": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "requires": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "requires": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true
+ },
+ "@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true
+ },
+ "@types/prop-types": {
+ "version": "15.7.15",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
+ "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
+ "devOptional": true
+ },
+ "@types/raf": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz",
+ "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==",
+ "optional": true
+ },
+ "@types/react": {
+ "version": "18.3.27",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz",
+ "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==",
+ "devOptional": true,
+ "requires": {
+ "@types/prop-types": "*",
+ "csstype": "^3.2.2"
+ }
+ },
+ "@types/react-dom": {
+ "version": "18.3.7",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
+ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
+ "dev": true,
+ "requires": {}
+ },
+ "@typescript-eslint/eslint-plugin": {
+ "version": "8.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.48.0.tgz",
+ "integrity": "sha512-XxXP5tL1txl13YFtrECECQYeZjBZad4fyd3cFV4a19LkAY/bIp9fev3US4S5fDVV2JaYFiKAZ/GRTOLer+mbyQ==",
+ "dev": true,
+ "requires": {
+ "@eslint-community/regexpp": "^4.10.0",
+ "@typescript-eslint/scope-manager": "8.48.0",
+ "@typescript-eslint/type-utils": "8.48.0",
+ "@typescript-eslint/utils": "8.48.0",
+ "@typescript-eslint/visitor-keys": "8.48.0",
+ "graphemer": "^1.4.0",
+ "ignore": "^7.0.0",
+ "natural-compare": "^1.4.0",
+ "ts-api-utils": "^2.1.0"
+ }
+ },
+ "@typescript-eslint/parser": {
+ "version": "8.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.48.0.tgz",
+ "integrity": "sha512-jCzKdm/QK0Kg4V4IK/oMlRZlY+QOcdjv89U2NgKHZk1CYTj82/RVSx1mV/0gqCVMJ/DA+Zf/S4NBWNF8GQ+eqQ==",
+ "dev": true,
+ "requires": {
+ "@typescript-eslint/scope-manager": "8.48.0",
+ "@typescript-eslint/types": "8.48.0",
+ "@typescript-eslint/typescript-estree": "8.48.0",
+ "@typescript-eslint/visitor-keys": "8.48.0",
+ "debug": "^4.3.4"
+ }
+ },
+ "@typescript-eslint/project-service": {
+ "version": "8.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.48.0.tgz",
+ "integrity": "sha512-Ne4CTZyRh1BecBf84siv42wv5vQvVmgtk8AuiEffKTUo3DrBaGYZueJSxxBZ8fjk/N3DrgChH4TOdIOwOwiqqw==",
+ "dev": true,
+ "requires": {
+ "@typescript-eslint/tsconfig-utils": "^8.48.0",
+ "@typescript-eslint/types": "^8.48.0",
+ "debug": "^4.3.4"
+ }
+ },
+ "@typescript-eslint/scope-manager": {
+ "version": "8.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.48.0.tgz",
+ "integrity": "sha512-uGSSsbrtJrLduti0Q1Q9+BF1/iFKaxGoQwjWOIVNJv0o6omrdyR8ct37m4xIl5Zzpkp69Kkmvom7QFTtue89YQ==",
+ "dev": true,
+ "requires": {
+ "@typescript-eslint/types": "8.48.0",
+ "@typescript-eslint/visitor-keys": "8.48.0"
+ }
+ },
+ "@typescript-eslint/tsconfig-utils": {
+ "version": "8.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.48.0.tgz",
+ "integrity": "sha512-WNebjBdFdyu10sR1M4OXTt2OkMd5KWIL+LLfeH9KhgP+jzfDV/LI3eXzwJ1s9+Yc0Kzo2fQCdY/OpdusCMmh6w==",
+ "dev": true,
+ "requires": {}
+ },
+ "@typescript-eslint/type-utils": {
+ "version": "8.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.48.0.tgz",
+ "integrity": "sha512-zbeVaVqeXhhab6QNEKfK96Xyc7UQuoFWERhEnj3mLVnUWrQnv15cJNseUni7f3g557gm0e46LZ6IJ4NJVOgOpw==",
+ "dev": true,
+ "requires": {
+ "@typescript-eslint/types": "8.48.0",
+ "@typescript-eslint/typescript-estree": "8.48.0",
+ "@typescript-eslint/utils": "8.48.0",
+ "debug": "^4.3.4",
+ "ts-api-utils": "^2.1.0"
+ }
+ },
+ "@typescript-eslint/types": {
+ "version": "8.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.48.0.tgz",
+ "integrity": "sha512-cQMcGQQH7kwKoVswD1xdOytxQR60MWKM1di26xSUtxehaDs/32Zpqsu5WJlXTtTTqyAVK8R7hvsUnIXRS+bjvA==",
+ "dev": true
+ },
+ "@typescript-eslint/typescript-estree": {
+ "version": "8.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.48.0.tgz",
+ "integrity": "sha512-ljHab1CSO4rGrQIAyizUS6UGHHCiAYhbfcIZ1zVJr5nMryxlXMVWS3duFPSKvSUbFPwkXMFk1k0EMIjub4sRRQ==",
+ "dev": true,
+ "requires": {
+ "@typescript-eslint/project-service": "8.48.0",
+ "@typescript-eslint/tsconfig-utils": "8.48.0",
+ "@typescript-eslint/types": "8.48.0",
+ "@typescript-eslint/visitor-keys": "8.48.0",
+ "debug": "^4.3.4",
+ "minimatch": "^9.0.4",
+ "semver": "^7.6.0",
+ "tinyglobby": "^0.2.15",
+ "ts-api-utils": "^2.1.0"
+ }
+ },
+ "@typescript-eslint/utils": {
+ "version": "8.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.48.0.tgz",
+ "integrity": "sha512-yTJO1XuGxCsSfIVt1+1UrLHtue8xz16V8apzPYI06W0HbEbEWHxHXgZaAgavIkoh+GeV6hKKd5jm0sS6OYxWXQ==",
+ "dev": true,
+ "requires": {
+ "@eslint-community/eslint-utils": "^4.7.0",
+ "@typescript-eslint/scope-manager": "8.48.0",
+ "@typescript-eslint/types": "8.48.0",
+ "@typescript-eslint/typescript-estree": "8.48.0"
+ }
+ },
+ "@typescript-eslint/visitor-keys": {
+ "version": "8.48.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.48.0.tgz",
+ "integrity": "sha512-T0XJMaRPOH3+LBbAfzR2jalckP1MSG/L9eUtY0DEzUyVaXJ/t6zN0nR7co5kz0Jko/nkSYCBRkz1djvjajVTTg==",
+ "dev": true,
+ "requires": {
+ "@typescript-eslint/types": "8.48.0",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "dependencies": {
+ "eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "dev": true
+ }
+ }
+ },
+ "@vitejs/plugin-react": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
+ "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
+ "dev": true,
+ "requires": {
+ "@babel/core": "^7.28.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-beta.27",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.17.0"
+ }
+ },
+ "@zeit/schemas": {
+ "version": "2.36.0",
+ "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.36.0.tgz",
+ "integrity": "sha512-7kjMwcChYEzMKjeex9ZFXkt1AyNov9R5HZtjBKVsmVpw7pa7ZtlCGvCBC2vnnXctaYN+aRI61HjIqeetZW5ROg=="
+ },
+ "acorn": {
+ "version": "8.15.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "dev": true
+ },
+ "acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "requires": {}
+ },
+ "ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "requires": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ }
+ },
+ "ansi-align": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz",
+ "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==",
+ "requires": {
+ "string-width": "^4.1.0"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
+ },
+ "emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+ },
+ "string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "requires": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ }
+ },
+ "strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "requires": {
+ "ansi-regex": "^5.0.1"
+ }
+ }
+ }
+ },
+ "ansi-regex": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="
+ },
+ "ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "antd": {
+ "version": "5.29.1",
+ "resolved": "https://registry.npmjs.org/antd/-/antd-5.29.1.tgz",
+ "integrity": "sha512-TTFVbpKbyL6cPfEoKq6Ya3BIjTUr7uDW9+7Z+1oysRv1gpcN7kQ4luH8r/+rXXwz4n6BIz1iBJ1ezKCdsdNW0w==",
+ "requires": {
+ "@ant-design/colors": "^7.2.1",
+ "@ant-design/cssinjs": "^1.23.0",
+ "@ant-design/cssinjs-utils": "^1.1.3",
+ "@ant-design/fast-color": "^2.0.6",
+ "@ant-design/icons": "^5.6.1",
+ "@ant-design/react-slick": "~1.1.2",
+ "@babel/runtime": "^7.26.0",
+ "@rc-component/color-picker": "~2.0.1",
+ "@rc-component/mutate-observer": "^1.1.0",
+ "@rc-component/qrcode": "~1.1.0",
+ "@rc-component/tour": "~1.15.1",
+ "@rc-component/trigger": "^2.3.0",
+ "classnames": "^2.5.1",
+ "copy-to-clipboard": "^3.3.3",
+ "dayjs": "^1.11.11",
+ "rc-cascader": "~3.34.0",
+ "rc-checkbox": "~3.5.0",
+ "rc-collapse": "~3.9.0",
+ "rc-dialog": "~9.6.0",
+ "rc-drawer": "~7.3.0",
+ "rc-dropdown": "~4.2.1",
+ "rc-field-form": "~2.7.1",
+ "rc-image": "~7.12.0",
+ "rc-input": "~1.8.0",
+ "rc-input-number": "~9.5.0",
+ "rc-mentions": "~2.20.0",
+ "rc-menu": "~9.16.1",
+ "rc-motion": "^2.9.5",
+ "rc-notification": "~5.6.4",
+ "rc-pagination": "~5.1.0",
+ "rc-picker": "~4.11.3",
+ "rc-progress": "~4.0.0",
+ "rc-rate": "~2.13.1",
+ "rc-resize-observer": "^1.4.3",
+ "rc-segmented": "~2.7.0",
+ "rc-select": "~14.16.8",
+ "rc-slider": "~11.1.9",
+ "rc-steps": "~6.0.1",
+ "rc-switch": "~4.1.0",
+ "rc-table": "~7.54.0",
+ "rc-tabs": "~15.7.0",
+ "rc-textarea": "~1.10.2",
+ "rc-tooltip": "~6.4.0",
+ "rc-tree": "~5.13.1",
+ "rc-tree-select": "~5.27.0",
+ "rc-upload": "~4.11.0",
+ "rc-util": "^5.44.4",
+ "scroll-into-view-if-needed": "^3.1.0",
+ "throttle-debounce": "^5.0.2"
+ }
+ },
+ "arch": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz",
+ "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ=="
+ },
+ "arg": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
+ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="
+ },
+ "argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true
+ },
+ "asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+ },
+ "atob": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
+ "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg=="
+ },
+ "attr-accept": {
+ "version": "2.2.5",
+ "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.5.tgz",
+ "integrity": "sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ=="
+ },
+ "axios": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
+ "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
+ "requires": {
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.4",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
+ "balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
+ },
+ "base64-arraybuffer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
+ "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
+ "optional": true
+ },
+ "baseline-browser-mapping": {
+ "version": "2.8.32",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.32.tgz",
+ "integrity": "sha512-OPz5aBThlyLFgxyhdwf/s2+8ab3OvT7AdTNvKHBwpXomIYeXqpUUuT8LrdtxZSsWJ4R4CU1un4XGh5Ez3nlTpw==",
+ "dev": true
+ },
+ "boxen": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.0.0.tgz",
+ "integrity": "sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==",
+ "requires": {
+ "ansi-align": "^3.0.1",
+ "camelcase": "^7.0.0",
+ "chalk": "^5.0.1",
+ "cli-boxes": "^3.0.0",
+ "string-width": "^5.1.2",
+ "type-fest": "^2.13.0",
+ "widest-line": "^4.0.1",
+ "wrap-ansi": "^8.0.1"
+ },
+ "dependencies": {
+ "chalk": {
+ "version": "5.6.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
+ "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="
+ }
+ }
+ },
+ "brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "dev": true,
+ "requires": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "browser-image-compression": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/browser-image-compression/-/browser-image-compression-2.0.2.tgz",
+ "integrity": "sha512-pBLlQyUf6yB8SmmngrcOw3EoS4RpQ1BcylI3T9Yqn7+4nrQTXJD4sJDe5ODnJdrvNMaio5OicFo75rDyJD2Ucw==",
+ "requires": {
+ "uzip": "0.20201231.0"
+ }
+ },
+ "browserslist": {
+ "version": "4.28.0",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz",
+ "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==",
+ "dev": true,
+ "requires": {
+ "baseline-browser-mapping": "^2.8.25",
+ "caniuse-lite": "^1.0.30001754",
+ "electron-to-chromium": "^1.5.249",
+ "node-releases": "^2.0.27",
+ "update-browserslist-db": "^1.1.4"
+ }
+ },
+ "btoa": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz",
+ "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g=="
+ },
+ "bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="
+ },
+ "call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "requires": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ }
+ },
+ "callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true
+ },
+ "camelcase": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz",
+ "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw=="
+ },
+ "caniuse-lite": {
+ "version": "1.0.30001757",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz",
+ "integrity": "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==",
+ "dev": true
+ },
+ "canvg": {
+ "version": "3.0.11",
+ "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz",
+ "integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==",
+ "optional": true,
+ "requires": {
+ "@babel/runtime": "^7.12.5",
+ "@types/raf": "^3.4.0",
+ "core-js": "^3.8.3",
+ "raf": "^3.4.1",
+ "regenerator-runtime": "^0.13.7",
+ "rgbcolor": "^1.0.1",
+ "stackblur-canvas": "^2.0.0",
+ "svg-pathdata": "^6.0.3"
+ }
+ },
+ "chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "requires": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ }
+ },
+ "chalk-template": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz",
+ "integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==",
+ "requires": {
+ "chalk": "^4.1.2"
+ }
+ },
+ "classnames": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
+ "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="
+ },
+ "cli-boxes": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz",
+ "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="
+ },
+ "clipboardy": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-3.0.0.tgz",
+ "integrity": "sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==",
+ "requires": {
+ "arch": "^2.2.0",
+ "execa": "^5.1.1",
+ "is-wsl": "^2.2.0"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+ },
+ "combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "requires": {
+ "delayed-stream": "~1.0.0"
+ }
+ },
+ "compressible": {
+ "version": "2.0.18",
+ "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
+ "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==",
+ "requires": {
+ "mime-db": ">= 1.43.0 < 2"
+ }
+ },
+ "compression": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz",
+ "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==",
+ "requires": {
+ "bytes": "3.1.2",
+ "compressible": "~2.0.18",
+ "debug": "2.6.9",
+ "negotiator": "~0.6.4",
+ "on-headers": "~1.1.0",
+ "safe-buffer": "5.2.1",
+ "vary": "~1.1.2"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+ }
+ }
+ },
+ "compute-scroll-into-view": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz",
+ "integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw=="
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
+ },
+ "content-disposition": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
+ "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA=="
+ },
+ "convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true
+ },
+ "copy-to-clipboard": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz",
+ "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==",
+ "requires": {
+ "toggle-selection": "^1.0.6"
+ }
+ },
+ "core-js": {
+ "version": "3.47.0",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.47.0.tgz",
+ "integrity": "sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==",
+ "optional": true
+ },
+ "core-js-pure": {
+ "version": "3.47.0",
+ "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.47.0.tgz",
+ "integrity": "sha512-BcxeDbzUrRnXGYIVAGFtcGQVNpFcUhVjr6W7F8XktvQW2iJP9e66GP6xdKotCRFlrxBvNIBrhwKteRXqMV86Nw=="
+ },
+ "cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "requires": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ }
+ },
+ "css-line-break": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
+ "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
+ "optional": true,
+ "requires": {
+ "utrie": "^1.0.2"
+ }
+ },
+ "csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="
+ },
+ "dayjs": {
+ "version": "1.11.19",
+ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz",
+ "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw=="
+ },
+ "debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "requires": {
+ "ms": "^2.1.3"
+ }
+ },
+ "deep-extend": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
+ "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="
+ },
+ "deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true
+ },
+ "delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
+ },
+ "dompurify": {
+ "version": "2.5.8",
+ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.8.tgz",
+ "integrity": "sha512-o1vSNgrmYMQObbSSvF/1brBYEQPHhV1+gsmrusO7/GXtp1T9rCS8cXFqVxK/9crT1jA6Ccv+5MTSjBNqr7Sovw==",
+ "optional": true
+ },
+ "dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "requires": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ }
+ },
+ "eastasianwidth": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
+ },
+ "electron-to-chromium": {
+ "version": "1.5.262",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.262.tgz",
+ "integrity": "sha512-NlAsMteRHek05jRUxUR0a5jpjYq9ykk6+kO0yRaMi5moe7u0fVIOeQ3Y30A8dIiWFBNUoQGi1ljb1i5VtS9WQQ==",
+ "dev": true
+ },
+ "emoji-regex": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
+ },
+ "engine.io-client": {
+ "version": "6.6.3",
+ "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz",
+ "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==",
+ "requires": {
+ "@socket.io/component-emitter": "~3.1.0",
+ "debug": "~4.3.1",
+ "engine.io-parser": "~5.2.1",
+ "ws": "~8.17.1",
+ "xmlhttprequest-ssl": "~2.1.1"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+ "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+ "requires": {
+ "ms": "^2.1.3"
+ }
+ }
+ }
+ },
+ "engine.io-parser": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
+ "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q=="
+ },
+ "es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="
+ },
+ "es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="
+ },
+ "es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "requires": {
+ "es-errors": "^1.3.0"
+ }
+ },
+ "es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "requires": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ }
+ },
+ "esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "requires": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true
+ },
+ "escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true
+ },
+ "eslint": {
+ "version": "9.39.1",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz",
+ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
+ "dev": true,
+ "requires": {
+ "@eslint-community/eslint-utils": "^4.8.0",
+ "@eslint-community/regexpp": "^4.12.1",
+ "@eslint/config-array": "^0.21.1",
+ "@eslint/config-helpers": "^0.4.2",
+ "@eslint/core": "^0.17.0",
+ "@eslint/eslintrc": "^3.3.1",
+ "@eslint/js": "9.39.1",
+ "@eslint/plugin-kit": "^0.4.1",
+ "@humanfs/node": "^0.16.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@humanwhocodes/retry": "^0.4.2",
+ "@types/estree": "^1.0.6",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.6",
+ "debug": "^4.3.2",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^8.4.0",
+ "eslint-visitor-keys": "^4.2.1",
+ "espree": "^10.4.0",
+ "esquery": "^1.5.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^8.0.0",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3"
+ },
+ "dependencies": {
+ "brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "dev": true
+ },
+ "ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true
+ },
+ "minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ }
+ }
+ },
+ "eslint-plugin-react-hooks": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz",
+ "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==",
+ "dev": true,
+ "requires": {}
+ },
+ "eslint-plugin-react-refresh": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.24.tgz",
+ "integrity": "sha512-nLHIW7TEq3aLrEYWpVaJ1dRgFR+wLDPN8e8FpYAql/bMV2oBEfC37K0gLEGgv9fy66juNShSMV8OkTqzltcG/w==",
+ "dev": true,
+ "requires": {}
+ },
+ "eslint-scope": {
+ "version": "8.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
+ "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
+ "dev": true,
+ "requires": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ }
+ },
+ "eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true
+ },
+ "espree": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
+ "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+ "dev": true,
+ "requires": {
+ "acorn": "^8.15.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "dependencies": {
+ "eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "dev": true
+ }
+ }
+ },
+ "esquery": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
+ "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+ "dev": true,
+ "requires": {
+ "estraverse": "^5.1.0"
+ }
+ },
+ "esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "requires": {
+ "estraverse": "^5.2.0"
+ }
+ },
+ "estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true
+ },
+ "esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true
+ },
+ "execa": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
+ "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
+ "requires": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^6.0.0",
+ "human-signals": "^2.1.0",
+ "is-stream": "^2.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^4.0.1",
+ "onetime": "^5.1.2",
+ "signal-exit": "^3.0.3",
+ "strip-final-newline": "^2.0.0"
+ }
+ },
+ "fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
+ },
+ "fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true
+ },
+ "fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true
+ },
+ "fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "requires": {}
+ },
+ "fflate": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
+ "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="
+ },
+ "file-entry-cache": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+ "dev": true,
+ "requires": {
+ "flat-cache": "^4.0.0"
+ }
+ },
+ "file-selector": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-2.1.2.tgz",
+ "integrity": "sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==",
+ "requires": {
+ "tslib": "^2.7.0"
+ }
+ },
+ "find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "requires": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ }
+ },
+ "flat-cache": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+ "dev": true,
+ "requires": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.4"
+ }
+ },
+ "flatted": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
+ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
+ "dev": true
+ },
+ "follow-redirects": {
+ "version": "1.15.11",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
+ "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="
+ },
+ "form-data": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
+ "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
+ "requires": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
+ "mime-types": "^2.1.12"
+ }
+ },
+ "fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "optional": true
+ },
+ "function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="
+ },
+ "gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true
+ },
+ "get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "requires": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ }
+ },
+ "get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "requires": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ }
+ },
+ "get-stream": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="
+ },
+ "glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "requires": {
+ "is-glob": "^4.0.3"
+ }
+ },
+ "globals": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+ "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+ "dev": true
+ },
+ "gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="
+ },
+ "graphemer": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+ "dev": true
+ },
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
+ },
+ "has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="
+ },
+ "has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "requires": {
+ "has-symbols": "^1.0.3"
+ }
+ },
+ "hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "requires": {
+ "function-bind": "^1.1.2"
+ }
+ },
+ "html2canvas": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
+ "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
+ "optional": true,
+ "requires": {
+ "css-line-break": "^2.1.0",
+ "text-segmentation": "^1.0.3"
+ }
+ },
+ "human-signals": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
+ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="
+ },
+ "ignore": {
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
+ "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
+ "dev": true
+ },
+ "imask": {
+ "version": "7.6.1",
+ "resolved": "https://registry.npmjs.org/imask/-/imask-7.6.1.tgz",
+ "integrity": "sha512-sJlIFM7eathUEMChTh9Mrfw/IgiWgJqBKq2VNbyXvBZ7ev/IlO6/KQTKlV/Fm+viQMLrFLG/zCuudrLIwgK2dg==",
+ "requires": {
+ "@babel/runtime-corejs3": "^7.24.4"
+ }
+ },
+ "import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "dev": true,
+ "requires": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ }
+ },
+ "imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true
+ },
+ "ini": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
+ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="
+ },
+ "is-docker": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
+ "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="
+ },
+ "is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true
+ },
+ "is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
+ },
+ "is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "requires": {
+ "is-extglob": "^2.1.1"
+ }
+ },
+ "is-port-reachable": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-port-reachable/-/is-port-reachable-4.0.0.tgz",
+ "integrity": "sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig=="
+ },
+ "is-stream": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="
+ },
+ "is-wsl": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
+ "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
+ "requires": {
+ "is-docker": "^2.0.0"
+ }
+ },
+ "isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
+ },
+ "js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
+ },
+ "js-yaml": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
+ "dev": true,
+ "requires": {
+ "argparse": "^2.0.1"
+ }
+ },
+ "jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true
+ },
+ "json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true
+ },
+ "json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true
+ },
+ "json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true
+ },
+ "json2mq": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz",
+ "integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==",
+ "requires": {
+ "string-convert": "^0.2.0"
+ }
+ },
+ "json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true
+ },
+ "jspdf": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.2.tgz",
+ "integrity": "sha512-myeX9c+p7znDWPk0eTrujCzNjT+CXdXyk7YmJq5nD5V7uLLKmSXnlQ/Jn/kuo3X09Op70Apm0rQSnFWyGK8uEQ==",
+ "requires": {
+ "@babel/runtime": "^7.23.2",
+ "atob": "^2.1.2",
+ "btoa": "^1.2.1",
+ "canvg": "^3.0.6",
+ "core-js": "^3.6.0",
+ "dompurify": "^2.5.4",
+ "fflate": "^0.8.1",
+ "html2canvas": "^1.0.0-rc.5"
+ }
+ },
+ "keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "requires": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "requires": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ }
+ },
+ "locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "requires": {
+ "p-locate": "^5.0.0"
+ }
+ },
+ "lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true
+ },
+ "loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "requires": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ }
+ },
+ "lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "requires": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="
+ },
+ "merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="
+ },
+ "mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
+ },
+ "mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "requires": {
+ "mime-db": "1.52.0"
+ }
+ },
+ "mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
+ },
+ "minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
+ "requires": {
+ "brace-expansion": "^2.0.1"
+ }
+ },
+ "minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="
+ },
+ "ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+ },
+ "nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "dev": true
+ },
+ "natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true
+ },
+ "negotiator": {
+ "version": "0.6.4",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz",
+ "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w=="
+ },
+ "node-releases": {
+ "version": "2.0.27",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
+ "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
+ "dev": true
+ },
+ "npm-run-path": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
+ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
+ "requires": {
+ "path-key": "^3.0.0"
+ }
+ },
+ "object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="
+ },
+ "on-headers": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
+ "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A=="
+ },
+ "onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "requires": {
+ "mimic-fn": "^2.1.0"
+ }
+ },
+ "optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "requires": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ }
+ },
+ "p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "requires": {
+ "yocto-queue": "^0.1.0"
+ }
+ },
+ "p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "requires": {
+ "p-limit": "^3.0.2"
+ }
+ },
+ "parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "requires": {
+ "callsites": "^3.0.0"
+ }
+ },
+ "path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true
+ },
+ "path-is-inside": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
+ "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w=="
+ },
+ "path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="
+ },
+ "path-to-regexp": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz",
+ "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw=="
+ },
+ "performance-now": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+ "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
+ "optional": true
+ },
+ "picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true
+ },
+ "picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true
+ },
+ "postcss": {
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "dev": true,
+ "requires": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ }
+ },
+ "prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true
+ },
+ "prop-types": {
+ "version": "15.8.1",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
+ "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
+ "requires": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.13.1"
+ },
+ "dependencies": {
+ "react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+ }
+ }
+ },
+ "proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+ },
+ "punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="
+ },
+ "raf": {
+ "version": "3.4.1",
+ "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
+ "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==",
+ "optional": true,
+ "requires": {
+ "performance-now": "^2.1.0"
+ }
+ },
+ "range-parser": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
+ "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A=="
+ },
+ "rc": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
+ "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
+ "requires": {
+ "deep-extend": "^0.6.0",
+ "ini": "~1.3.0",
+ "minimist": "^1.2.0",
+ "strip-json-comments": "~2.0.1"
+ },
+ "dependencies": {
+ "strip-json-comments": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+ "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="
+ }
+ }
+ },
+ "rc-cascader": {
+ "version": "3.34.0",
+ "resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-3.34.0.tgz",
+ "integrity": "sha512-KpXypcvju9ptjW9FaN2NFcA2QH9E9LHKq169Y0eWtH4e/wHQ5Wh5qZakAgvb8EKZ736WZ3B0zLLOBsrsja5Dag==",
+ "requires": {
+ "@babel/runtime": "^7.25.7",
+ "classnames": "^2.3.1",
+ "rc-select": "~14.16.2",
+ "rc-tree": "~5.13.0",
+ "rc-util": "^5.43.0"
+ }
+ },
+ "rc-checkbox": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/rc-checkbox/-/rc-checkbox-3.5.0.tgz",
+ "integrity": "sha512-aOAQc3E98HteIIsSqm6Xk2FPKIER6+5vyEFMZfo73TqM+VVAIqOkHoPjgKLqSNtVLWScoaM7vY2ZrGEheI79yg==",
+ "requires": {
+ "@babel/runtime": "^7.10.1",
+ "classnames": "^2.3.2",
+ "rc-util": "^5.25.2"
+ }
+ },
+ "rc-collapse": {
+ "version": "3.9.0",
+ "resolved": "https://registry.npmjs.org/rc-collapse/-/rc-collapse-3.9.0.tgz",
+ "integrity": "sha512-swDdz4QZ4dFTo4RAUMLL50qP0EY62N2kvmk2We5xYdRwcRn8WcYtuetCJpwpaCbUfUt5+huLpVxhvmnK+PHrkA==",
+ "requires": {
+ "@babel/runtime": "^7.10.1",
+ "classnames": "2.x",
+ "rc-motion": "^2.3.4",
+ "rc-util": "^5.27.0"
+ }
+ },
+ "rc-dialog": {
+ "version": "9.6.0",
+ "resolved": "https://registry.npmjs.org/rc-dialog/-/rc-dialog-9.6.0.tgz",
+ "integrity": "sha512-ApoVi9Z8PaCQg6FsUzS8yvBEQy0ZL2PkuvAgrmohPkN3okps5WZ5WQWPc1RNuiOKaAYv8B97ACdsFU5LizzCqg==",
+ "requires": {
+ "@babel/runtime": "^7.10.1",
+ "@rc-component/portal": "^1.0.0-8",
+ "classnames": "^2.2.6",
+ "rc-motion": "^2.3.0",
+ "rc-util": "^5.21.0"
+ }
+ },
+ "rc-drawer": {
+ "version": "7.3.0",
+ "resolved": "https://registry.npmjs.org/rc-drawer/-/rc-drawer-7.3.0.tgz",
+ "integrity": "sha512-DX6CIgiBWNpJIMGFO8BAISFkxiuKitoizooj4BDyee8/SnBn0zwO2FHrNDpqqepj0E/TFTDpmEBCyFuTgC7MOg==",
+ "requires": {
+ "@babel/runtime": "^7.23.9",
+ "@rc-component/portal": "^1.1.1",
+ "classnames": "^2.2.6",
+ "rc-motion": "^2.6.1",
+ "rc-util": "^5.38.1"
+ }
+ },
+ "rc-dropdown": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/rc-dropdown/-/rc-dropdown-4.2.1.tgz",
+ "integrity": "sha512-YDAlXsPv3I1n42dv1JpdM7wJ+gSUBfeyPK59ZpBD9jQhK9jVuxpjj3NmWQHOBceA1zEPVX84T2wbdb2SD0UjmA==",
+ "requires": {
+ "@babel/runtime": "^7.18.3",
+ "@rc-component/trigger": "^2.0.0",
+ "classnames": "^2.2.6",
+ "rc-util": "^5.44.1"
+ }
+ },
+ "rc-field-form": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/rc-field-form/-/rc-field-form-2.7.1.tgz",
+ "integrity": "sha512-vKeSifSJ6HoLaAB+B8aq/Qgm8a3dyxROzCtKNCsBQgiverpc4kWDQihoUwzUj+zNWJOykwSY4dNX3QrGwtVb9A==",
+ "requires": {
+ "@babel/runtime": "^7.18.0",
+ "@rc-component/async-validator": "^5.0.3",
+ "rc-util": "^5.32.2"
+ }
+ },
+ "rc-image": {
+ "version": "7.12.0",
+ "resolved": "https://registry.npmjs.org/rc-image/-/rc-image-7.12.0.tgz",
+ "integrity": "sha512-cZ3HTyyckPnNnUb9/DRqduqzLfrQRyi+CdHjdqgsyDpI3Ln5UX1kXnAhPBSJj9pVRzwRFgqkN7p9b6HBDjmu/Q==",
+ "requires": {
+ "@babel/runtime": "^7.11.2",
+ "@rc-component/portal": "^1.0.2",
+ "classnames": "^2.2.6",
+ "rc-dialog": "~9.6.0",
+ "rc-motion": "^2.6.2",
+ "rc-util": "^5.34.1"
+ }
+ },
+ "rc-input": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/rc-input/-/rc-input-1.8.0.tgz",
+ "integrity": "sha512-KXvaTbX+7ha8a/k+eg6SYRVERK0NddX8QX7a7AnRvUa/rEH0CNMlpcBzBkhI0wp2C8C4HlMoYl8TImSN+fuHKA==",
+ "requires": {
+ "@babel/runtime": "^7.11.1",
+ "classnames": "^2.2.1",
+ "rc-util": "^5.18.1"
+ }
+ },
+ "rc-input-number": {
+ "version": "9.5.0",
+ "resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-9.5.0.tgz",
+ "integrity": "sha512-bKaEvB5tHebUURAEXw35LDcnRZLq3x1k7GxfAqBMzmpHkDGzjAtnUL8y4y5N15rIFIg5IJgwr211jInl3cipag==",
+ "requires": {
+ "@babel/runtime": "^7.10.1",
+ "@rc-component/mini-decimal": "^1.0.1",
+ "classnames": "^2.2.5",
+ "rc-input": "~1.8.0",
+ "rc-util": "^5.40.1"
+ }
+ },
+ "rc-mentions": {
+ "version": "2.20.0",
+ "resolved": "https://registry.npmjs.org/rc-mentions/-/rc-mentions-2.20.0.tgz",
+ "integrity": "sha512-w8HCMZEh3f0nR8ZEd466ATqmXFCMGMN5UFCzEUL0bM/nGw/wOS2GgRzKBcm19K++jDyuWCOJOdgcKGXU3fXfbQ==",
+ "requires": {
+ "@babel/runtime": "^7.22.5",
+ "@rc-component/trigger": "^2.0.0",
+ "classnames": "^2.2.6",
+ "rc-input": "~1.8.0",
+ "rc-menu": "~9.16.0",
+ "rc-textarea": "~1.10.0",
+ "rc-util": "^5.34.1"
+ }
+ },
+ "rc-menu": {
+ "version": "9.16.1",
+ "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-9.16.1.tgz",
+ "integrity": "sha512-ghHx6/6Dvp+fw8CJhDUHFHDJ84hJE3BXNCzSgLdmNiFErWSOaZNsihDAsKq9ByTALo/xkNIwtDFGIl6r+RPXBg==",
+ "requires": {
+ "@babel/runtime": "^7.10.1",
+ "@rc-component/trigger": "^2.0.0",
+ "classnames": "2.x",
+ "rc-motion": "^2.4.3",
+ "rc-overflow": "^1.3.1",
+ "rc-util": "^5.27.0"
+ }
+ },
+ "rc-motion": {
+ "version": "2.9.5",
+ "resolved": "https://registry.npmjs.org/rc-motion/-/rc-motion-2.9.5.tgz",
+ "integrity": "sha512-w+XTUrfh7ArbYEd2582uDrEhmBHwK1ZENJiSJVb7uRxdE7qJSYjbO2eksRXmndqyKqKoYPc9ClpPh5242mV1vA==",
+ "requires": {
+ "@babel/runtime": "^7.11.1",
+ "classnames": "^2.2.1",
+ "rc-util": "^5.44.0"
+ }
+ },
+ "rc-notification": {
+ "version": "5.6.4",
+ "resolved": "https://registry.npmjs.org/rc-notification/-/rc-notification-5.6.4.tgz",
+ "integrity": "sha512-KcS4O6B4qzM3KH7lkwOB7ooLPZ4b6J+VMmQgT51VZCeEcmghdeR4IrMcFq0LG+RPdnbe/ArT086tGM8Snimgiw==",
+ "requires": {
+ "@babel/runtime": "^7.10.1",
+ "classnames": "2.x",
+ "rc-motion": "^2.9.0",
+ "rc-util": "^5.20.1"
+ }
+ },
+ "rc-overflow": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/rc-overflow/-/rc-overflow-1.5.0.tgz",
+ "integrity": "sha512-Lm/v9h0LymeUYJf0x39OveU52InkdRXqnn2aYXfWmo8WdOonIKB2kfau+GF0fWq6jPgtdO9yMqveGcK6aIhJmg==",
+ "requires": {
+ "@babel/runtime": "^7.11.1",
+ "classnames": "^2.2.1",
+ "rc-resize-observer": "^1.0.0",
+ "rc-util": "^5.37.0"
+ }
+ },
+ "rc-pagination": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/rc-pagination/-/rc-pagination-5.1.0.tgz",
+ "integrity": "sha512-8416Yip/+eclTFdHXLKTxZvn70duYVGTvUUWbckCCZoIl3jagqke3GLsFrMs0bsQBikiYpZLD9206Ej4SOdOXQ==",
+ "requires": {
+ "@babel/runtime": "^7.10.1",
+ "classnames": "^2.3.2",
+ "rc-util": "^5.38.0"
+ }
+ },
+ "rc-picker": {
+ "version": "4.11.3",
+ "resolved": "https://registry.npmjs.org/rc-picker/-/rc-picker-4.11.3.tgz",
+ "integrity": "sha512-MJ5teb7FlNE0NFHTncxXQ62Y5lytq6sh5nUw0iH8OkHL/TjARSEvSHpr940pWgjGANpjCwyMdvsEV55l5tYNSg==",
+ "requires": {
+ "@babel/runtime": "^7.24.7",
+ "@rc-component/trigger": "^2.0.0",
+ "classnames": "^2.2.1",
+ "rc-overflow": "^1.3.2",
+ "rc-resize-observer": "^1.4.0",
+ "rc-util": "^5.43.0"
+ }
+ },
+ "rc-progress": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-4.0.0.tgz",
+ "integrity": "sha512-oofVMMafOCokIUIBnZLNcOZFsABaUw8PPrf1/y0ZBvKZNpOiu5h4AO9vv11Sw0p4Hb3D0yGWuEattcQGtNJ/aw==",
+ "requires": {
+ "@babel/runtime": "^7.10.1",
+ "classnames": "^2.2.6",
+ "rc-util": "^5.16.1"
+ }
+ },
+ "rc-rate": {
+ "version": "2.13.1",
+ "resolved": "https://registry.npmjs.org/rc-rate/-/rc-rate-2.13.1.tgz",
+ "integrity": "sha512-QUhQ9ivQ8Gy7mtMZPAjLbxBt5y9GRp65VcUyGUMF3N3fhiftivPHdpuDIaWIMOTEprAjZPC08bls1dQB+I1F2Q==",
+ "requires": {
+ "@babel/runtime": "^7.10.1",
+ "classnames": "^2.2.5",
+ "rc-util": "^5.0.1"
+ }
+ },
+ "rc-resize-observer": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/rc-resize-observer/-/rc-resize-observer-1.4.3.tgz",
+ "integrity": "sha512-YZLjUbyIWox8E9i9C3Tm7ia+W7euPItNWSPX5sCcQTYbnwDb5uNpnLHQCG1f22oZWUhLw4Mv2tFmeWe68CDQRQ==",
+ "requires": {
+ "@babel/runtime": "^7.20.7",
+ "classnames": "^2.2.1",
+ "rc-util": "^5.44.1",
+ "resize-observer-polyfill": "^1.5.1"
+ }
+ },
+ "rc-segmented": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/rc-segmented/-/rc-segmented-2.7.0.tgz",
+ "integrity": "sha512-liijAjXz+KnTRVnxxXG2sYDGd6iLL7VpGGdR8gwoxAXy2KglviKCxLWZdjKYJzYzGSUwKDSTdYk8brj54Bn5BA==",
+ "requires": {
+ "@babel/runtime": "^7.11.1",
+ "classnames": "^2.2.1",
+ "rc-motion": "^2.4.4",
+ "rc-util": "^5.17.0"
+ }
+ },
+ "rc-select": {
+ "version": "14.16.8",
+ "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-14.16.8.tgz",
+ "integrity": "sha512-NOV5BZa1wZrsdkKaiK7LHRuo5ZjZYMDxPP6/1+09+FB4KoNi8jcG1ZqLE3AVCxEsYMBe65OBx71wFoHRTP3LRg==",
+ "requires": {
+ "@babel/runtime": "^7.10.1",
+ "@rc-component/trigger": "^2.1.1",
+ "classnames": "2.x",
+ "rc-motion": "^2.0.1",
+ "rc-overflow": "^1.3.1",
+ "rc-util": "^5.16.1",
+ "rc-virtual-list": "^3.5.2"
+ }
+ },
+ "rc-slider": {
+ "version": "11.1.9",
+ "resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-11.1.9.tgz",
+ "integrity": "sha512-h8IknhzSh3FEM9u8ivkskh+Ef4Yo4JRIY2nj7MrH6GQmrwV6mcpJf5/4KgH5JaVI1H3E52yCdpOlVyGZIeph5A==",
+ "requires": {
+ "@babel/runtime": "^7.10.1",
+ "classnames": "^2.2.5",
+ "rc-util": "^5.36.0"
+ }
+ },
+ "rc-steps": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/rc-steps/-/rc-steps-6.0.1.tgz",
+ "integrity": "sha512-lKHL+Sny0SeHkQKKDJlAjV5oZ8DwCdS2hFhAkIjuQt1/pB81M0cA0ErVFdHq9+jmPmFw1vJB2F5NBzFXLJxV+g==",
+ "requires": {
+ "@babel/runtime": "^7.16.7",
+ "classnames": "^2.2.3",
+ "rc-util": "^5.16.1"
+ }
+ },
+ "rc-switch": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/rc-switch/-/rc-switch-4.1.0.tgz",
+ "integrity": "sha512-TI8ufP2Az9oEbvyCeVE4+90PDSljGyuwix3fV58p7HV2o4wBnVToEyomJRVyTaZeqNPAp+vqeo4Wnj5u0ZZQBg==",
+ "requires": {
+ "@babel/runtime": "^7.21.0",
+ "classnames": "^2.2.1",
+ "rc-util": "^5.30.0"
+ }
+ },
+ "rc-table": {
+ "version": "7.54.0",
+ "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.54.0.tgz",
+ "integrity": "sha512-/wDTkki6wBTjwylwAGjpLKYklKo9YgjZwAU77+7ME5mBoS32Q4nAwoqhA2lSge6fobLW3Tap6uc5xfwaL2p0Sw==",
+ "requires": {
+ "@babel/runtime": "^7.10.1",
+ "@rc-component/context": "^1.4.0",
+ "classnames": "^2.2.5",
+ "rc-resize-observer": "^1.1.0",
+ "rc-util": "^5.44.3",
+ "rc-virtual-list": "^3.14.2"
+ }
+ },
+ "rc-tabs": {
+ "version": "15.7.0",
+ "resolved": "https://registry.npmjs.org/rc-tabs/-/rc-tabs-15.7.0.tgz",
+ "integrity": "sha512-ZepiE+6fmozYdWf/9gVp7k56PKHB1YYoDsKeQA1CBlJ/POIhjkcYiv0AGP0w2Jhzftd3AVvZP/K+V+Lpi2ankA==",
+ "requires": {
+ "@babel/runtime": "^7.11.2",
+ "classnames": "2.x",
+ "rc-dropdown": "~4.2.0",
+ "rc-menu": "~9.16.0",
+ "rc-motion": "^2.6.2",
+ "rc-resize-observer": "^1.0.0",
+ "rc-util": "^5.34.1"
+ }
+ },
+ "rc-textarea": {
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/rc-textarea/-/rc-textarea-1.10.2.tgz",
+ "integrity": "sha512-HfaeXiaSlpiSp0I/pvWpecFEHpVysZ9tpDLNkxQbMvMz6gsr7aVZ7FpWP9kt4t7DB+jJXesYS0us1uPZnlRnwQ==",
+ "requires": {
+ "@babel/runtime": "^7.10.1",
+ "classnames": "^2.2.1",
+ "rc-input": "~1.8.0",
+ "rc-resize-observer": "^1.0.0",
+ "rc-util": "^5.27.0"
+ }
+ },
+ "rc-tooltip": {
+ "version": "6.4.0",
+ "resolved": "https://registry.npmjs.org/rc-tooltip/-/rc-tooltip-6.4.0.tgz",
+ "integrity": "sha512-kqyivim5cp8I5RkHmpsp1Nn/Wk+1oeloMv9c7LXNgDxUpGm+RbXJGL+OPvDlcRnx9DBeOe4wyOIl4OKUERyH1g==",
+ "requires": {
+ "@babel/runtime": "^7.11.2",
+ "@rc-component/trigger": "^2.0.0",
+ "classnames": "^2.3.1",
+ "rc-util": "^5.44.3"
+ }
+ },
+ "rc-tree": {
+ "version": "5.13.1",
+ "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-5.13.1.tgz",
+ "integrity": "sha512-FNhIefhftobCdUJshO7M8uZTA9F4OPGVXqGfZkkD/5soDeOhwO06T/aKTrg0WD8gRg/pyfq+ql3aMymLHCTC4A==",
+ "requires": {
+ "@babel/runtime": "^7.10.1",
+ "classnames": "2.x",
+ "rc-motion": "^2.0.1",
+ "rc-util": "^5.16.1",
+ "rc-virtual-list": "^3.5.1"
+ }
+ },
+ "rc-tree-select": {
+ "version": "5.27.0",
+ "resolved": "https://registry.npmjs.org/rc-tree-select/-/rc-tree-select-5.27.0.tgz",
+ "integrity": "sha512-2qTBTzwIT7LRI1o7zLyrCzmo5tQanmyGbSaGTIf7sYimCklAToVVfpMC6OAldSKolcnjorBYPNSKQqJmN3TCww==",
+ "requires": {
+ "@babel/runtime": "^7.25.7",
+ "classnames": "2.x",
+ "rc-select": "~14.16.2",
+ "rc-tree": "~5.13.0",
+ "rc-util": "^5.43.0"
+ }
+ },
+ "rc-upload": {
+ "version": "4.11.0",
+ "resolved": "https://registry.npmjs.org/rc-upload/-/rc-upload-4.11.0.tgz",
+ "integrity": "sha512-ZUyT//2JAehfHzjWowqROcwYJKnZkIUGWaTE/VogVrepSl7AFNbQf4+zGfX4zl9Vrj/Jm8scLO0R6UlPDKK4wA==",
+ "requires": {
+ "@babel/runtime": "^7.18.3",
+ "classnames": "^2.2.5",
+ "rc-util": "^5.2.0"
+ }
+ },
+ "rc-util": {
+ "version": "5.44.4",
+ "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.44.4.tgz",
+ "integrity": "sha512-resueRJzmHG9Q6rI/DfK6Kdv9/Lfls05vzMs1Sk3M2P+3cJa+MakaZyWY8IPfehVuhPJFKrIY1IK4GqbiaiY5w==",
+ "requires": {
+ "@babel/runtime": "^7.18.3",
+ "react-is": "^18.2.0"
+ }
+ },
+ "rc-virtual-list": {
+ "version": "3.19.2",
+ "resolved": "https://registry.npmjs.org/rc-virtual-list/-/rc-virtual-list-3.19.2.tgz",
+ "integrity": "sha512-Ys6NcjwGkuwkeaWBDqfI3xWuZ7rDiQXlH1o2zLfFzATfEgXcqpk8CkgMfbJD81McqjcJVez25a3kPxCR807evA==",
+ "requires": {
+ "@babel/runtime": "^7.20.0",
+ "classnames": "^2.2.6",
+ "rc-resize-observer": "^1.0.0",
+ "rc-util": "^5.36.0"
+ }
+ },
+ "react": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+ "requires": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "react-dom": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+ "requires": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
+ }
+ },
+ "react-dropzone": {
+ "version": "14.3.8",
+ "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.3.8.tgz",
+ "integrity": "sha512-sBgODnq+lcA4P296DY4wacOZz3JFpD99fp+hb//iBO2HHnyeZU3FwWyXJ6salNpqQdsZrgMrotuko/BdJMV8Ug==",
+ "requires": {
+ "attr-accept": "^2.2.4",
+ "file-selector": "^2.1.0",
+ "prop-types": "^15.8.1"
+ }
+ },
+ "react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="
+ },
+ "react-refresh": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
+ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
+ "dev": true
+ },
+ "react-router": {
+ "version": "6.30.2",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.2.tgz",
+ "integrity": "sha512-H2Bm38Zu1bm8KUE5NVWRMzuIyAV8p/JrOaBJAwVmp37AXG72+CZJlEBw6pdn9i5TBgLMhNDgijS4ZlblpHyWTA==",
+ "requires": {
+ "@remix-run/router": "1.23.1"
+ }
+ },
+ "react-router-dom": {
+ "version": "6.30.2",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.2.tgz",
+ "integrity": "sha512-l2OwHn3UUnEVUqc6/1VMmR1cvZryZ3j3NzapC2eUXO1dB0sYp5mvwdjiXhpUbRb21eFow3qSxpP8Yv6oAU824Q==",
+ "requires": {
+ "@remix-run/router": "1.23.1",
+ "react-router": "6.30.2"
+ }
+ },
+ "regenerator-runtime": {
+ "version": "0.13.11",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
+ "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
+ "optional": true
+ },
+ "registry-auth-token": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz",
+ "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==",
+ "requires": {
+ "rc": "^1.1.6",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "registry-url": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz",
+ "integrity": "sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==",
+ "requires": {
+ "rc": "^1.0.1"
+ }
+ },
+ "require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="
+ },
+ "resize-observer-polyfill": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
+ "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="
+ },
+ "resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true
+ },
+ "rgbcolor": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz",
+ "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==",
+ "optional": true
+ },
+ "rollup": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz",
+ "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==",
+ "dev": true,
+ "requires": {
+ "@rollup/rollup-android-arm-eabi": "4.53.3",
+ "@rollup/rollup-android-arm64": "4.53.3",
+ "@rollup/rollup-darwin-arm64": "4.53.3",
+ "@rollup/rollup-darwin-x64": "4.53.3",
+ "@rollup/rollup-freebsd-arm64": "4.53.3",
+ "@rollup/rollup-freebsd-x64": "4.53.3",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.53.3",
+ "@rollup/rollup-linux-arm-musleabihf": "4.53.3",
+ "@rollup/rollup-linux-arm64-gnu": "4.53.3",
+ "@rollup/rollup-linux-arm64-musl": "4.53.3",
+ "@rollup/rollup-linux-loong64-gnu": "4.53.3",
+ "@rollup/rollup-linux-ppc64-gnu": "4.53.3",
+ "@rollup/rollup-linux-riscv64-gnu": "4.53.3",
+ "@rollup/rollup-linux-riscv64-musl": "4.53.3",
+ "@rollup/rollup-linux-s390x-gnu": "4.53.3",
+ "@rollup/rollup-linux-x64-gnu": "4.53.3",
+ "@rollup/rollup-linux-x64-musl": "4.53.3",
+ "@rollup/rollup-openharmony-arm64": "4.53.3",
+ "@rollup/rollup-win32-arm64-msvc": "4.53.3",
+ "@rollup/rollup-win32-ia32-msvc": "4.53.3",
+ "@rollup/rollup-win32-x64-gnu": "4.53.3",
+ "@rollup/rollup-win32-x64-msvc": "4.53.3",
+ "@types/estree": "1.0.8",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
+ },
+ "scheduler": {
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+ "requires": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "scroll-into-view-if-needed": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz",
+ "integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==",
+ "requires": {
+ "compute-scroll-into-view": "^3.0.2"
+ }
+ },
+ "semver": {
+ "version": "7.7.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
+ "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "dev": true
+ },
+ "serve": {
+ "version": "14.2.5",
+ "resolved": "https://registry.npmjs.org/serve/-/serve-14.2.5.tgz",
+ "integrity": "sha512-Qn/qMkzCcMFVPb60E/hQy+iRLpiU8PamOfOSYoAHmmF+fFFmpPpqa6Oci2iWYpTdOUM3VF+TINud7CfbQnsZbA==",
+ "requires": {
+ "@zeit/schemas": "2.36.0",
+ "ajv": "8.12.0",
+ "arg": "5.0.2",
+ "boxen": "7.0.0",
+ "chalk": "5.0.1",
+ "chalk-template": "0.4.0",
+ "clipboardy": "3.0.0",
+ "compression": "1.8.1",
+ "is-port-reachable": "4.0.0",
+ "serve-handler": "6.1.6",
+ "update-check": "1.5.4"
+ },
+ "dependencies": {
+ "ajv": {
+ "version": "8.12.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
+ "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
+ "requires": {
+ "fast-deep-equal": "^3.1.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2",
+ "uri-js": "^4.2.2"
+ }
+ },
+ "chalk": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz",
+ "integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w=="
+ },
+ "json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
+ }
+ }
+ },
+ "serve-handler": {
+ "version": "6.1.6",
+ "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.6.tgz",
+ "integrity": "sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==",
+ "requires": {
+ "bytes": "3.0.0",
+ "content-disposition": "0.5.2",
+ "mime-types": "2.1.18",
+ "minimatch": "3.1.2",
+ "path-is-inside": "1.0.2",
+ "path-to-regexp": "3.3.0",
+ "range-parser": "1.2.0"
+ },
+ "dependencies": {
+ "brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "bytes": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
+ "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw=="
+ },
+ "mime-db": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz",
+ "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ=="
+ },
+ "mime-types": {
+ "version": "2.1.18",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz",
+ "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==",
+ "requires": {
+ "mime-db": "~1.33.0"
+ }
+ },
+ "minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ }
+ }
+ },
+ "shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "requires": {
+ "shebang-regex": "^3.0.0"
+ }
+ },
+ "shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="
+ },
+ "signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
+ },
+ "socket.io-client": {
+ "version": "4.8.1",
+ "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz",
+ "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==",
+ "requires": {
+ "@socket.io/component-emitter": "~3.1.0",
+ "debug": "~4.3.2",
+ "engine.io-client": "~6.6.1",
+ "socket.io-parser": "~4.2.4"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+ "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+ "requires": {
+ "ms": "^2.1.3"
+ }
+ }
+ }
+ },
+ "socket.io-parser": {
+ "version": "4.2.4",
+ "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
+ "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
+ "requires": {
+ "@socket.io/component-emitter": "~3.1.0",
+ "debug": "~4.3.1"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+ "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+ "requires": {
+ "ms": "^2.1.3"
+ }
+ }
+ }
+ },
+ "source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true
+ },
+ "stackblur-canvas": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz",
+ "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==",
+ "optional": true
+ },
+ "string-convert": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz",
+ "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A=="
+ },
+ "string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "requires": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ }
+ },
+ "strip-ansi": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
+ "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
+ "requires": {
+ "ansi-regex": "^6.0.1"
+ }
+ },
+ "strip-final-newline": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
+ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="
+ },
+ "strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true
+ },
+ "stylis": {
+ "version": "4.3.6",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz",
+ "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ=="
+ },
+ "supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ },
+ "svg-pathdata": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz",
+ "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==",
+ "optional": true
+ },
+ "text-segmentation": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
+ "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
+ "optional": true,
+ "requires": {
+ "utrie": "^1.0.2"
+ }
+ },
+ "throttle-debounce": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz",
+ "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A=="
+ },
+ "tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "dev": true,
+ "requires": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ }
+ },
+ "toggle-selection": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
+ "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ=="
+ },
+ "ts-api-utils": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
+ "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
+ "dev": true,
+ "requires": {}
+ },
+ "tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
+ },
+ "type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "requires": {
+ "prelude-ls": "^1.2.1"
+ }
+ },
+ "type-fest": {
+ "version": "2.19.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
+ "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA=="
+ },
+ "typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true
+ },
+ "update-browserslist-db": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz",
+ "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==",
+ "dev": true,
+ "requires": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ }
+ },
+ "update-check": {
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.4.tgz",
+ "integrity": "sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==",
+ "requires": {
+ "registry-auth-token": "3.3.2",
+ "registry-url": "3.1.0"
+ }
+ },
+ "uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "requires": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "utrie": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
+ "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
+ "optional": true,
+ "requires": {
+ "base64-arraybuffer": "^1.0.2"
+ }
+ },
+ "uzip": {
+ "version": "0.20201231.0",
+ "resolved": "https://registry.npmjs.org/uzip/-/uzip-0.20201231.0.tgz",
+ "integrity": "sha512-OZeJfZP+R0z9D6TmBgLq2LHzSSptGMGDGigGiEe0pr8UBe/7fdflgHlHBNDASTXB5jnFuxHpNaJywSg8YFeGng=="
+ },
+ "vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="
+ },
+ "vite": {
+ "version": "5.4.21",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
+ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
+ "dev": true,
+ "requires": {
+ "esbuild": "^0.21.3",
+ "fsevents": "~2.3.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
+ }
+ },
+ "which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "requires": {
+ "isexe": "^2.0.0"
+ }
+ },
+ "widest-line": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz",
+ "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==",
+ "requires": {
+ "string-width": "^5.0.1"
+ }
+ },
+ "word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true
+ },
+ "wrap-ansi": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "requires": {
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="
+ }
+ }
+ },
+ "ws": {
+ "version": "8.17.1",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
+ "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
+ "requires": {}
+ },
+ "xmlhttprequest-ssl": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
+ "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ=="
+ },
+ "yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true
+ },
+ "yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true
+ },
+ "zustand": {
+ "version": "5.0.9",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.9.tgz",
+ "integrity": "sha512-ALBtUj0AfjJt3uNRQoL1tL2tMvj6Gp/6e39dnfT6uzpelGru8v1tPOGBzayOWbPJvujM8JojDk3E1LxeFisBNg==",
+ "requires": {}
+ }
+ }
+}
diff --git a/frontend/public/index.html b/frontend/public/index.html
index 05d1228..9ba69bc 100644
--- a/frontend/public/index.html
+++ b/frontend/public/index.html
@@ -4,7 +4,7 @@
- ERV Insurance Platform
+ Clientright — защита прав потребителей
diff --git a/frontend/src/components/form/Step1Phone.tsx b/frontend/src/components/form/Step1Phone.tsx
index 41c65a9..bfdc0f2 100644
--- a/frontend/src/components/form/Step1Phone.tsx
+++ b/frontend/src/components/form/Step1Phone.tsx
@@ -278,6 +278,37 @@ export default function Step1Phone({
maxLength={10}
size="large"
style={{ flex: 1 }}
+ onPaste={(e) => {
+ // Обработка вставки: очищаем от +7, пробелов и других символов
+ e.preventDefault();
+ const pastedText = (e.clipboardData || (window as any).clipboardData).getData('text');
+ // Убираем все нецифровые символы
+ let cleanText = pastedText.replace(/\D/g, '');
+ // Если начинается с 7 или 8, убираем первую цифру (код страны)
+ if (cleanText.length === 11 && (cleanText.startsWith('7') || cleanText.startsWith('8'))) {
+ cleanText = cleanText.substring(1);
+ }
+ // Оставляем только первые 10 цифр
+ cleanText = cleanText.substring(0, 10);
+
+ // ✅ Устанавливаем значение напрямую в input, затем синхронизируем с формой
+ const target = e.target as HTMLInputElement;
+ if (target) {
+ target.value = cleanText;
+ // Триггерим событие input для синхронизации с формой
+ const inputEvent = new Event('input', { bubbles: true });
+ target.dispatchEvent(inputEvent);
+ }
+
+ // ✅ Синхронизируем с формой через requestAnimationFrame для избежания циклических ссылок
+ requestAnimationFrame(() => {
+ form.setFieldValue('phone', cleanText);
+ // Показываем предупреждение, если номер был обрезан
+ if (pastedText.replace(/\D/g, '').length > 10) {
+ message.warning('Номер автоматически обрезан до 10 цифр');
+ }
+ });
+ }}
/>
diff --git a/frontend/src/components/form/Step3Payment.tsx b/frontend/src/components/form/Step3Payment.tsx
index 8048114..c125bd5 100644
--- a/frontend/src/components/form/Step3Payment.tsx
+++ b/frontend/src/components/form/Step3Payment.tsx
@@ -1,10 +1,14 @@
-import { useState } from 'react';
-import { Form, Input, Button, Select, message, Space, Divider } from 'antd';
+import { useState, useEffect } from 'react';
+import { Form, Input, Button, AutoComplete, message, Space, Divider } from 'antd';
import { PhoneOutlined, SafetyOutlined, QrcodeOutlined, MailOutlined, CopyOutlined } from '@ant-design/icons';
const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8200';
+const NSPK_BANKS_API = 'http://212.193.27.93/api/payouts/dictionaries/nspk-banks';
-const { Option } = Select;
+interface Bank {
+ bankid: string;
+ bankname: string;
+}
interface Props {
formData: any;
@@ -31,6 +35,72 @@ export default function Step3Payment({
const [verifyLoading, setVerifyLoading] = useState(false);
const [submitting, setSubmitting] = useState(false);
const [debugCode, setDebugCode] = useState(formData.smsDebugCode ?? null);
+ const [banks, setBanks] = useState([]);
+ const [banksLoading, setBanksLoading] = useState(false);
+
+ // Загрузка списка банков при монтировании компонента
+ useEffect(() => {
+ const loadBanks = async () => {
+ try {
+ setBanksLoading(true);
+ addDebugEvent?.('banks', 'pending', '📋 Загружаю список банков СБП...');
+
+ const response = await fetch(NSPK_BANKS_API);
+
+ if (!response.ok) {
+ throw new Error(`HTTP ${response.status}`);
+ }
+
+ const banksData: Bank[] = await response.json();
+
+ // Сортируем по названию для удобства
+ banksData.sort((a, b) => a.bankname.localeCompare(b.bankname, 'ru'));
+
+ setBanks(banksData);
+ addDebugEvent?.('banks', 'success', `✅ Загружено ${banksData.length} банков`, { count: banksData.length });
+
+ // Если есть сохранённый bankName или bankId - восстанавливаем значения
+ if (formData.bankName) {
+ const foundBank = banksData.find(b =>
+ b.bankname.toLowerCase() === formData.bankName.toLowerCase() ||
+ b.bankname.toLowerCase().includes(formData.bankName.toLowerCase())
+ );
+ if (foundBank) {
+ updateFormData({
+ bankId: foundBank.bankid,
+ bankName: foundBank.bankname
+ });
+ form.setFieldsValue({
+ bankId: foundBank.bankid,
+ bankName: foundBank.bankname
+ });
+ }
+ } else if (formData.bankId) {
+ // Если есть только bankId, находим по ID
+ const foundBank = banksData.find(b => b.bankid === formData.bankId);
+ if (foundBank) {
+ updateFormData({
+ bankId: foundBank.bankid,
+ bankName: foundBank.bankname
+ });
+ form.setFieldsValue({
+ bankId: foundBank.bankid,
+ bankName: foundBank.bankname
+ });
+ }
+ }
+ } catch (error: any) {
+ console.error('Ошибка загрузки банков:', error);
+ addDebugEvent?.('banks', 'error', `❌ Ошибка загрузки банков: ${error.message}`, { error: error.message });
+ message.error('Не удалось загрузить список банков. Попробуйте обновить страницу.');
+ } finally {
+ setBanksLoading(false);
+ }
+ };
+
+ loadBanks();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []); // Загружаем банки только при монтировании
const sendCode = async () => {
try {
@@ -136,11 +206,25 @@ export default function Step3Payment({
}
};
+ // Инициализация формы с bankId и bankName если есть
+ useEffect(() => {
+ if (formData.bankId || formData.bankName) {
+ form.setFieldsValue({
+ bankId: formData.bankId,
+ bankName: formData.bankName
+ });
+ }
+ }, [formData.bankId, formData.bankName, form]);
+
return (
+
+
+
- {
- const children = option?.children;
- if (typeof children === 'string') {
- return children.toLowerCase().includes(input.toLowerCase());
+ rules={[
+ { required: true, message: 'Выберите банк для получения выплаты' },
+ {
+ validator: (_, value) => {
+ if (!value) {
+ return Promise.resolve();
+ }
+ const foundBank = banks.find(b =>
+ b.bankname.toLowerCase() === value.toLowerCase()
+ );
+ if (!foundBank) {
+ return Promise.reject(new Error('Выберите банк из списка'));
+ }
+ return Promise.resolve();
}
- return false;
+ }
+ ]}
+ >
+ ({
+ value: bank.bankname,
+ label: bank.bankname,
+ }))}
+ filterOption={(inputValue, option) => {
+ if (!option?.label) return false;
+ return option.label.toLowerCase().includes(inputValue.toLowerCase());
}}
- >
- 🟢 Сбербанк
- 🟡 Тинькофф
- 🔵 ВТБ
- 🔴 Альфа-Банк
- 🟡 Райффайзенбанк
- 🔵 Газпромбанк
- 🔴 Росбанк
- 🟢 Совкомбанк
- 🔵 Открытие
- 💳 Другой банк
-
+ onSelect={(value) => {
+ // При выборе из списка находим банк и сохраняем оба поля
+ const selectedBank = banks.find(b => b.bankname === value);
+ if (selectedBank) {
+ updateFormData({
+ bankId: selectedBank.bankid,
+ bankName: selectedBank.bankname
+ });
+ // Устанавливаем bankId в скрытое поле
+ form.setFieldsValue({ bankId: selectedBank.bankid });
+ }
+ }}
+ onChange={(value) => {
+ // При вводе текста ищем точное совпадение по названию
+ if (typeof value === 'string') {
+ const foundBank = banks.find(b =>
+ b.bankname.toLowerCase() === value.toLowerCase()
+ );
+ if (foundBank) {
+ updateFormData({
+ bankId: foundBank.bankid,
+ bankName: foundBank.bankname
+ });
+ form.setFieldsValue({ bankId: foundBank.bankid });
+ } else if (value === '') {
+ // Если поле очищено, очищаем и bankId
+ updateFormData({ bankId: undefined, bankName: undefined });
+ form.setFieldsValue({ bankId: undefined });
+ }
+ }
+ }}
+ style={{ width: '100%' }}
+ />
@@ -387,7 +515,8 @@ export default function Step3Payment({
email: 'test@test.ru',
phone: '+79991234567',
paymentMethod: 'sbp',
- bankName: 'sberbank',
+ bankId: banks.length > 0 ? banks[0].bankid : '100000000111', // Сбербанк по умолчанию
+ bankName: banks.length > 0 ? banks[0].bankname : 'Сбербанк',
};
updateFormData(devData);
message.success('DEV: Телефон автоматически подтверждён');
@@ -407,7 +536,8 @@ export default function Step3Payment({
email: 'test@test.ru',
phone: '+79991234567',
paymentMethod: 'sbp',
- bankName: 'sberbank',
+ bankId: banks.length > 0 ? banks[0].bankid : '100000000111', // Сбербанк по умолчанию
+ bankName: banks.length > 0 ? banks[0].bankname : 'Сбербанк',
};
updateFormData(devData);
onSubmit();
diff --git a/frontend/src/components/form/StepClaimConfirmation.tsx b/frontend/src/components/form/StepClaimConfirmation.tsx
index 572f36d..428f8cf 100644
--- a/frontend/src/components/form/StepClaimConfirmation.tsx
+++ b/frontend/src/components/form/StepClaimConfirmation.tsx
@@ -4,14 +4,18 @@ import { generateConfirmationFormHTML } from './generateConfirmationFormHTML';
interface Props {
claimPlanData: any; // Данные заявления от n8n
+ contact_data_confirmed?: boolean; // ✅ Флаг подтверждения данных контакта
onNext: () => void;
onPrev: () => void;
+ onSubmitted?: () => void; // ✅ Callback после успешной отправки
}
export default function StepClaimConfirmation({
claimPlanData,
+ contact_data_confirmed: prop_contact_data_confirmed,
onNext,
onPrev,
+ onSubmitted,
}: Props) {
const [loading, setLoading] = useState(true);
const iframeRef = useRef(null);
@@ -86,8 +90,15 @@ export default function StepClaimConfirmation({
console.log('📋 formData.propertyName:', formData.propertyName);
console.log('📋 formData.propertyName?.meta:', formData.propertyName?.meta);
+ // ✅ Получаем флаги подтверждения данных из props, claimPlanData или formData
+ const contact_data_confirmed =
+ prop_contact_data_confirmed !== undefined ? prop_contact_data_confirmed :
+ claimPlanData?.contact_data_confirmed ||
+ claimPlanData?.propertyName?.meta?.contact_data_confirmed ||
+ false;
+
// Генерируем HTML форму здесь, на нашей стороне
- const html = generateConfirmationFormHTML(formData);
+ const html = generateConfirmationFormHTML(formData, contact_data_confirmed);
setHtmlContent(html);
setLoading(false);
}, [claimPlanData]);
@@ -114,6 +125,17 @@ export default function StepClaimConfirmation({
claimPlanData?.propertyName?.user?.mobile ||
claimPlanData?.phone || '';
+ // ✅ Получаем флаг подтверждения данных контакта
+ const contact_data_confirmed =
+ prop_contact_data_confirmed !== undefined ? prop_contact_data_confirmed :
+ claimPlanData?.contact_data_confirmed ||
+ claimPlanData?.propertyName?.meta?.contact_data_confirmed ||
+ false;
+
+ // ✅ Получаем данные банка (ID и название)
+ const bankId = formData?.user?.bank_id || '';
+ const bankName = formData?.user?.bank_name || '';
+
// Формируем payload для Redis канала
const payload = {
claim_id: claimId,
@@ -124,6 +146,14 @@ export default function StepClaimConfirmation({
phone: phone,
sms_code: smsCode || '', // SMS код для верификации
+ // ✅ Флаг редактирования перс данных (cf_2624)
+ contact_data_confirmed: contact_data_confirmed,
+ cf_2624: contact_data_confirmed ? "1" : "0", // Значение для CRM
+
+ // ✅ Данные банка для СБП выплаты
+ bank_id: bankId,
+ bank_name: bankName,
+
// Данные формы подтверждения
form_data: formData,
user: formData?.user || {},
@@ -214,10 +244,15 @@ export default function StepClaimConfirmation({
saveFormData(pendingFormData, code);
// Показываем сообщение об успешной отправке
- message.success('Ваше заявление отправлено!');
+ message.success('Поздравляем! Ваше обращение направлено в Клиентправ.');
- // Переходим дальше
- onNext();
+ // ✅ Вызываем callback для показа сообщения об успехе вместо формы
+ if (onSubmitted) {
+ onSubmitted();
+ } else {
+ // Fallback: переходим дальше
+ onNext();
+ }
} else {
message.error(result.detail || 'Неверный код');
}
diff --git a/frontend/src/components/form/StepDocumentsNew.tsx b/frontend/src/components/form/StepDocumentsNew.tsx
index 00f429f..7518fad 100644
--- a/frontend/src/components/form/StepDocumentsNew.tsx
+++ b/frontend/src/components/form/StepDocumentsNew.tsx
@@ -359,367 +359,3 @@ export default function StepDocumentsNew({
);
}
-
-
- * StepDocumentsNew.tsx
- *
- * Поэкранная загрузка документов.
- * Один документ на экран с возможностью пропуска.
- *
- * @version 1.0
- * @date 2025-11-26
- */
-
-import { useState, useCallback, useEffect, useRef } from 'react';
-import {
- Button,
- Card,
- Upload,
- Progress,
- Alert,
- Typography,
- Space,
- Spin,
- message,
- Result
-} from 'antd';
-import {
- UploadOutlined,
- FileTextOutlined,
- ExclamationCircleOutlined,
- CheckCircleOutlined,
- LoadingOutlined,
- InboxOutlined
-} from '@ant-design/icons';
-import type { UploadFile, UploadProps } from 'antd/es/upload/interface';
-
-const { Title, Text, Paragraph } = Typography;
-const { Dragger } = Upload;
-
-// === Типы ===
-export interface DocumentConfig {
- type: string; // Идентификатор: contract, payment, correspondence
- name: string; // Название: "Договор или оферта"
- critical: boolean; // Обязательный документ?
- hints?: string; // Подсказка: "Скриншот или PDF договора"
- accept?: string[]; // Допустимые форматы: ['pdf', 'jpg', 'png']
-}
-
-interface Props {
- formData: any;
- updateFormData: (data: any) => void;
- documents: DocumentConfig[];
- currentIndex: number;
- onDocumentUploaded: (docType: string, fileData: any) => void;
- onDocumentSkipped: (docType: string) => void;
- onAllDocumentsComplete: () => void;
- onPrev: () => void;
- addDebugEvent?: (type: string, status: string, message: string, data?: any) => void;
-}
-
-// === Компонент ===
-export default function StepDocumentsNew({
- formData,
- updateFormData,
- documents,
- currentIndex,
- onDocumentUploaded,
- onDocumentSkipped,
- onAllDocumentsComplete,
- onPrev,
- addDebugEvent,
-}: Props) {
- const [fileList, setFileList] = useState([]);
- const [uploading, setUploading] = useState(false);
- const [uploadProgress, setUploadProgress] = useState(0);
-
- // Текущий документ
- const currentDoc = documents[currentIndex];
- const isLastDocument = currentIndex === documents.length - 1;
- const totalDocs = documents.length;
-
- // Сбрасываем файлы при смене документа
- useEffect(() => {
- setFileList([]);
- setUploadProgress(0);
- }, [currentIndex]);
-
- // === Handlers ===
-
- const handleUpload = useCallback(async () => {
- if (fileList.length === 0) {
- message.error('Выберите файл для загрузки');
- return;
- }
-
- const file = fileList[0];
- if (!file.originFileObj) {
- message.error('Ошибка: файл не найден');
- return;
- }
-
- setUploading(true);
- setUploadProgress(0);
-
- try {
- addDebugEvent?.('documents', 'info', `📤 Загрузка документа: ${currentDoc.name}`, {
- document_type: currentDoc.type,
- file_name: file.name,
- file_size: file.size,
- });
-
- const formDataToSend = new FormData();
- formDataToSend.append('claim_id', formData.claim_id || '');
- formDataToSend.append('session_id', formData.session_id || '');
- formDataToSend.append('document_type', currentDoc.type);
- formDataToSend.append('file', file.originFileObj, file.name);
-
- // Симуляция прогресса (реальный прогресс будет через XHR)
- const progressInterval = setInterval(() => {
- setUploadProgress(prev => Math.min(prev + 10, 90));
- }, 200);
-
- const response = await fetch('/api/v1/documents/upload', {
- method: 'POST',
- body: formDataToSend,
- });
-
- clearInterval(progressInterval);
- setUploadProgress(100);
-
- if (!response.ok) {
- const errorText = await response.text();
- throw new Error(`Ошибка загрузки: ${response.status} ${errorText}`);
- }
-
- const result = await response.json();
-
- addDebugEvent?.('documents', 'success', `✅ Документ загружен: ${currentDoc.name}`, {
- document_type: currentDoc.type,
- file_id: result.file_id,
- });
-
- message.success(`${currentDoc.name} загружен!`);
-
- // Сохраняем в formData
- const uploadedDocs = formData.documents_uploaded || [];
- uploadedDocs.push({
- type: currentDoc.type,
- file_id: result.file_id,
- file_name: file.name,
- ocr_status: 'processing',
- });
-
- updateFormData({
- documents_uploaded: uploadedDocs,
- current_doc_index: currentIndex + 1,
- });
-
- // Callback
- onDocumentUploaded(currentDoc.type, result);
-
- // Переходим к следующему или завершаем
- if (isLastDocument) {
- onAllDocumentsComplete();
- }
-
- } catch (error) {
- console.error('❌ Upload error:', error);
- message.error('Ошибка загрузки файла. Попробуйте ещё раз.');
- addDebugEvent?.('documents', 'error', `❌ Ошибка загрузки: ${currentDoc.name}`, {
- error: String(error),
- });
- } finally {
- setUploading(false);
- }
- }, [fileList, currentDoc, formData, updateFormData, currentIndex, isLastDocument, onDocumentUploaded, onAllDocumentsComplete, addDebugEvent]);
-
- const handleSkip = useCallback(() => {
- if (currentDoc.critical) {
- // Показываем предупреждение, но всё равно разрешаем пропустить
- message.warning(`⚠️ Документ "${currentDoc.name}" важен для рассмотрения заявки`);
- }
-
- addDebugEvent?.('documents', 'info', `⏭️ Документ пропущен: ${currentDoc.name}`, {
- document_type: currentDoc.type,
- was_critical: currentDoc.critical,
- });
-
- // Сохраняем в список пропущенных
- const skippedDocs = formData.documents_skipped || [];
- if (!skippedDocs.includes(currentDoc.type)) {
- skippedDocs.push(currentDoc.type);
- }
-
- updateFormData({
- documents_skipped: skippedDocs,
- current_doc_index: currentIndex + 1,
- });
-
- // Callback
- onDocumentSkipped(currentDoc.type);
-
- // Переходим к следующему или завершаем
- if (isLastDocument) {
- onAllDocumentsComplete();
- }
- }, [currentDoc, formData, updateFormData, currentIndex, isLastDocument, onDocumentSkipped, onAllDocumentsComplete, addDebugEvent]);
-
- // === Upload Props ===
- const uploadProps: UploadProps = {
- fileList,
- onChange: ({ fileList: newFileList }) => setFileList(newFileList.slice(-1)), // Только один файл
- beforeUpload: () => false, // Не загружаем автоматически
- maxCount: 1,
- accept: currentDoc?.accept
- ? currentDoc.accept.map(ext => `.${ext}`).join(',')
- : '.pdf,.jpg,.jpeg,.png,.heic,.doc,.docx',
- disabled: uploading,
- };
-
- // === Render ===
-
- if (!currentDoc) {
- return (
- }
- />
- );
- }
-
- return (
-
-
- {/* === Прогресс === */}
-
-
-
- Документ {currentIndex + 1} из {totalDocs}
-
-
- {Math.round((currentIndex / totalDocs) * 100)}% завершено
-
-
-
-
-
- {/* === Заголовок === */}
-
-
-
- {currentDoc.name}
- {currentDoc.critical && (
-
- )}
-
-
- {currentDoc.hints && (
-
- {currentDoc.hints}
-
- )}
-
-
- {/* === Алерт для критичных документов === */}
- {currentDoc.critical && (
- }
- style={{ marginBottom: 24 }}
- />
- )}
-
- {/* === Загрузка файла === */}
-
-
- {uploading ? (
-
- ) : (
-
- )}
-
-
- {uploading
- ? 'Загружаем документ...'
- : 'Перетащите файл сюда или нажмите для выбора'
- }
-
-
- Поддерживаются: PDF, JPG, PNG, HEIC, DOC (до 20 МБ)
-
-
-
- {/* === Прогресс загрузки === */}
- {uploading && (
-
- )}
-
- {/* === Кнопки === */}
-
-
- ← Назад
-
-
-
-
- Пропустить
-
-
- }
- >
- {isLastDocument ? 'Загрузить и продолжить' : 'Загрузить'}
-
-
-
-
- {/* === Уже загруженные документы === */}
- {formData.documents_uploaded && formData.documents_uploaded.length > 0 && (
-
-
Загруженные документы:
-
- {formData.documents_uploaded.map((doc: any, idx: number) => (
-
-
- {documents.find(d => d.type === doc.type)?.name || doc.type}
-
- ))}
-
-
- )}
-
-
- );
-}
-
-
diff --git a/frontend/src/components/form/StepDraftSelection.tsx b/frontend/src/components/form/StepDraftSelection.tsx
index f69704e..a805c12 100644
--- a/frontend/src/components/form/StepDraftSelection.tsx
+++ b/frontend/src/components/form/StepDraftSelection.tsx
@@ -66,6 +66,12 @@ const getRelativeTime = (dateStr: string) => {
}
};
+interface DocumentStatus {
+ name: string;
+ required: boolean;
+ uploaded: boolean;
+}
+
interface Draft {
id: string;
claim_id: string;
@@ -74,7 +80,9 @@ interface Draft {
channel: string;
created_at: string;
updated_at: string;
+ problem_title?: string; // Краткое описание (заголовок)
problem_description?: string;
+ category?: string; // Категория проблемы
wizard_plan: boolean;
wizard_answers: boolean;
has_documents: boolean;
@@ -82,6 +90,7 @@ interface Draft {
documents_total?: number;
documents_uploaded?: number;
documents_skipped?: number;
+ documents_list?: DocumentStatus[]; // Список документов со статусами
wizard_ready?: boolean;
claim_ready?: boolean;
is_legacy?: boolean; // Старый формат без documents_required
@@ -127,10 +136,10 @@ const STATUS_CONFIG: Record ,
- label: 'Обработка',
- description: 'Формируется заявление...',
- action: 'Ожидайте',
+ icon: ,
+ label: 'Документы загружены',
+ description: 'Все документы обработаны',
+ action: 'Продолжить',
},
draft_claim_ready: {
color: 'green',
@@ -274,11 +283,8 @@ export default function StepDraftSelection({
if (draft.is_legacy && onRestartDraft) {
// Legacy черновик - предлагаем начать заново с тем же описанием
onRestartDraft(draftId, draft.problem_description || '');
- } else if (draft.status_code === 'draft_docs_complete') {
- // Всё ещё обрабатывается - показываем сообщение
- message.info('Заявление формируется. Пожалуйста, подождите.');
} else {
- // Обычный переход
+ // ✅ Разрешаем переход на любом этапе до апрува по SMS
onSelectDraft(draftId);
}
};
@@ -286,15 +292,12 @@ export default function StepDraftSelection({
// Кнопка действия
const getActionButton = (draft: Draft) => {
const config = getStatusConfig(draft);
- const isProcessing = draft.status_code === 'draft_docs_complete';
return (
handleDraftAction(draft)}
icon={config.icon}
- disabled={isProcessing}
- loading={isProcessing}
>
{config.action}
@@ -320,19 +323,26 @@ export default function StepDraftSelection({
+ {/* Кнопка создания новой заявки - всегда вверху */}
+ }
+ onClick={onNewClaim}
+ size="large"
+ style={{ width: '100%' }}
+ >
+ Создать новую заявку
+
+
{loading ? (
) : drafts.length === 0 ? (
- } onClick={onNewClaim} size="large">
- Создать новую заявку
-
-
+ />
) : (
<>
handleDelete(draft.claim_id || draft.id)}
- okText="Да, удалить"
- cancelText="Отмена"
- >
- }
- loading={deletingId === (draft.claim_id || draft.id)}
- disabled={deletingId === (draft.claim_id || draft.id)}
- >
- Удалить
-
- ,
- ]}
>
{config.label}
+ {draft.category && (
+ {draft.category}
+ )}
}
description={
- {/* Описание проблемы */}
+ {/* Заголовок - краткое описание проблемы */}
+ {draft.problem_title && (
+
+ {draft.problem_title}
+
+ )}
+
+ {/* Полное описание проблемы */}
{draft.problem_description && (
-
- {draft.problem_description.length > 60
- ? draft.problem_description.substring(0, 60) + '...'
+ {draft.problem_description.length > 250
+ ? draft.problem_description.substring(0, 250) + '...'
: draft.problem_description
}
-
+
)}
{/* Время обновления */}
@@ -436,62 +446,115 @@ export default function StepDraftSelection({
/>
)}
- {/* Прогресс документов */}
- {docsProgress && (
-
-
- 📎 Документы: {docsProgress.uploaded} из {docsProgress.total} загружено
- {docsProgress.skipped > 0 && ` (${docsProgress.skipped} пропущено)`}
-
+ {/* Список документов со статусами */}
+ {draft.documents_list && draft.documents_list.length > 0 && (
+
+
+
+ 📄 Документы
+
+
+ {draft.documents_uploaded || 0} / {draft.documents_total || 0}
+
+
+
+ {draft.documents_list.map((doc, idx) => (
+
+ {doc.uploaded ? (
+
+ ) : (
+
+ )}
+
+ {doc.name}
+ {doc.required && !doc.uploaded && * }
+
+
+ ))}
+
+
+ )}
+
+ {/* Прогрессбар (если нет списка) */}
+ {(!draft.documents_list || draft.documents_list.length === 0) && docsProgress && docsProgress.total > 0 && (
+
)}
- {/* Старые теги прогресса (для обратной совместимости) */}
- {!docsProgress && !draft.is_legacy && (
-
-
- {draft.problem_description ? '✓ Описание' : 'Описание'}
-
-
- {draft.wizard_plan ? '✓ План' : 'План'}
-
-
- {draft.has_documents ? '✓ Документы' : 'Документы'}
-
-
- )}
-
{/* Описание статуса */}
{config.description}
-
- }
- />
-
+
+ {/* Кнопки действий */}
+
+ {getActionButton(draft)}
+
handleDelete(draft.claim_id || draft.id)}
+ okText="Да, удалить"
+ cancelText="Отмена"
+ >
+ }
+ loading={deletingId === (draft.claim_id || draft.id)}
+ disabled={deletingId === (draft.claim_id || draft.id)}
+ >
+ Удалить
+
+
+
+
+ }
+ />
+
);
}}
/>
-
- }
- onClick={onNewClaim}
- size="large"
- style={{ width: '100%' }}
- >
- Создать новую заявку
-
-
-
-
+
}
diff --git a/frontend/src/components/form/StepWaitingClaim.tsx b/frontend/src/components/form/StepWaitingClaim.tsx
index 8e5ce97..2483fef 100644
--- a/frontend/src/components/form/StepWaitingClaim.tsx
+++ b/frontend/src/components/form/StepWaitingClaim.tsx
@@ -336,344 +336,3 @@ export default function StepWaitingClaim({
);
}
-
-
- * StepWaitingClaim.tsx
- *
- * Экран ожидания формирования заявления.
- * Показывает прогресс: OCR → Анализ → Формирование заявления.
- * Подписывается на SSE для получения claim_ready.
- *
- * @version 1.0
- * @date 2025-11-26
- */
-
-import { useState, useEffect, useRef, useCallback } from 'react';
-import { Card, Typography, Progress, Space, Button, Spin, Result, Steps } from 'antd';
-import {
- LoadingOutlined,
- CheckCircleOutlined,
- FileSearchOutlined,
- RobotOutlined,
- FileTextOutlined,
- ClockCircleOutlined
-} from '@ant-design/icons';
-import AiWorkingIllustration from '../../assets/ai-working.svg';
-
-const { Title, Paragraph, Text } = Typography;
-const { Step } = Steps;
-
-interface Props {
- sessionId: string;
- claimId?: string;
- documentsCount: number;
- onClaimReady: (claimData: any) => void;
- onTimeout: () => void;
- onError: (error: string) => void;
- addDebugEvent?: (type: string, status: string, message: string, data?: any) => void;
-}
-
-type ProcessingStep = 'ocr' | 'analysis' | 'generation' | 'ready';
-
-interface ProcessingState {
- currentStep: ProcessingStep;
- ocrCompleted: number;
- ocrTotal: number;
- message: string;
-}
-
-export default function StepWaitingClaim({
- sessionId,
- claimId,
- documentsCount,
- onClaimReady,
- onTimeout,
- onError,
- addDebugEvent,
-}: Props) {
- const eventSourceRef = useRef
(null);
- const timeoutRef = useRef(null);
-
- const [state, setState] = useState({
- currentStep: 'ocr',
- ocrCompleted: 0,
- ocrTotal: documentsCount,
- message: 'Распознаём документы...',
- });
-
- const [elapsedTime, setElapsedTime] = useState(0);
- const [error, setError] = useState(null);
-
- // Таймер для отображения времени
- useEffect(() => {
- const interval = setInterval(() => {
- setElapsedTime(prev => prev + 1);
- }, 1000);
-
- return () => clearInterval(interval);
- }, []);
-
- // SSE подписка
- useEffect(() => {
- if (!sessionId) {
- setError('Отсутствует session_id');
- return;
- }
-
- console.log('🔌 StepWaitingClaim: подписываемся на SSE', { sessionId, claimId });
-
- const eventSource = new EventSource(`/api/v1/events/${sessionId}`);
- eventSourceRef.current = eventSource;
-
- addDebugEvent?.('waiting', 'info', '🔌 Подписка на SSE для ожидания заявления', {
- session_id: sessionId,
- claim_id: claimId,
- });
-
- // Таймаут 5 минут
- timeoutRef.current = setTimeout(() => {
- console.warn('⏰ Timeout ожидания заявления');
- setError('Превышено время ожидания. Попробуйте обновить страницу.');
- addDebugEvent?.('waiting', 'warning', '⏰ Таймаут ожидания заявления');
- eventSource.close();
- onTimeout();
- }, 300000); // 5 минут
-
- eventSource.onopen = () => {
- console.log('✅ SSE соединение открыто (waiting)');
- addDebugEvent?.('waiting', 'info', '✅ SSE соединение открыто');
- };
-
- eventSource.onmessage = (event) => {
- try {
- const data = JSON.parse(event.data);
- console.log('📥 SSE event (waiting):', data);
-
- const eventType = data.event_type || data.type;
-
- // OCR документа завершён
- if (eventType === 'document_ocr_completed') {
- setState(prev => ({
- ...prev,
- ocrCompleted: prev.ocrCompleted + 1,
- message: `Распознано ${prev.ocrCompleted + 1} из ${prev.ocrTotal} документов`,
- }));
- addDebugEvent?.('waiting', 'info', `📄 OCR завершён: ${data.document_type}`);
- }
-
- // Все документы распознаны, начинаем анализ
- if (eventType === 'ocr_all_completed' || eventType === 'analysis_started') {
- setState(prev => ({
- ...prev,
- currentStep: 'analysis',
- message: 'Анализируем данные...',
- }));
- addDebugEvent?.('waiting', 'info', '🔍 Начат анализ данных');
- }
-
- // Генерация заявления
- if (eventType === 'claim_generation_started') {
- setState(prev => ({
- ...prev,
- currentStep: 'generation',
- message: 'Формируем заявление...',
- }));
- addDebugEvent?.('waiting', 'info', '📝 Начато формирование заявления');
- }
-
- // Заявление готово!
- if (eventType === 'claim_ready' || eventType === 'claim_plan_ready') {
- console.log('🎉 Заявление готово!', data);
-
- // Очищаем таймаут
- if (timeoutRef.current) {
- clearTimeout(timeoutRef.current);
- timeoutRef.current = null;
- }
-
- setState(prev => ({
- ...prev,
- currentStep: 'ready',
- message: 'Заявление готово!',
- }));
-
- addDebugEvent?.('waiting', 'success', '✅ Заявление готово');
-
- // Закрываем SSE
- eventSource.close();
- eventSourceRef.current = null;
-
- // Callback с данными
- setTimeout(() => {
- onClaimReady(data.data || data.claim_data || data);
- }, 500);
- }
-
- // Ошибка
- if (eventType === 'claim_error' || data.status === 'error') {
- setError(data.message || 'Произошла ошибка при формировании заявления');
- addDebugEvent?.('waiting', 'error', `❌ Ошибка: ${data.message}`);
- eventSource.close();
- onError(data.message);
- }
-
- } catch (err) {
- console.error('❌ Ошибка парсинга SSE:', err);
- }
- };
-
- eventSource.onerror = (err) => {
- console.error('❌ SSE error (waiting):', err);
- // Не показываем ошибку сразу — SSE может переподключиться
- };
-
- return () => {
- if (timeoutRef.current) {
- clearTimeout(timeoutRef.current);
- timeoutRef.current = null;
- }
- if (eventSourceRef.current) {
- eventSourceRef.current.close();
- eventSourceRef.current = null;
- }
- };
- }, [sessionId, claimId, onClaimReady, onTimeout, onError, addDebugEvent]);
-
- // Форматирование времени
- const formatTime = (seconds: number) => {
- const mins = Math.floor(seconds / 60);
- const secs = seconds % 60;
- return `${mins}:${secs.toString().padStart(2, '0')}`;
- };
-
- // Вычисляем процент прогресса
- const getProgress = (): number => {
- switch (state.currentStep) {
- case 'ocr':
- // OCR: 0-50%
- return state.ocrTotal > 0
- ? Math.round((state.ocrCompleted / state.ocrTotal) * 50)
- : 25;
- case 'analysis':
- return 60;
- case 'generation':
- return 85;
- case 'ready':
- return 100;
- default:
- return 0;
- }
- };
-
- // Индекс текущего шага для Steps
- const getStepIndex = (): number => {
- switch (state.currentStep) {
- case 'ocr': return 0;
- case 'analysis': return 1;
- case 'generation': return 2;
- case 'ready': return 3;
- default: return 0;
- }
- };
-
- // === Render ===
-
- if (error) {
- return (
- window.location.reload()}>
- Обновить страницу
-
- }
- />
- );
- }
-
- if (state.currentStep === 'ready') {
- return (
- }
- extra={ }
- />
- );
- }
-
- return (
-
-
- {/* === Иллюстрация === */}
-
-
- {/* === Заголовок === */}
- {state.message}
-
-
- Наш AI-ассистент обрабатывает ваши документы и формирует заявление.
- Это займёт 1-2 минуты.
-
-
- {/* === Прогресс === */}
-
-
- {/* === Шаги обработки === */}
-
- 0 ? `${state.ocrCompleted}/${state.ocrTotal}` : ''}
- icon={state.currentStep === 'ocr' ? : }
- />
- : }
- />
- : }
- />
- }
- />
-
-
- {/* === Таймер === */}
-
-
-
- Время ожидания: {formatTime(elapsedTime)}
-
-
-
- {/* === Подсказка === */}
-
- Не закрывайте эту страницу. Обработка происходит на сервере.
-
-
-
- );
-}
-
-
diff --git a/frontend/src/components/form/StepWizardPlan.tsx b/frontend/src/components/form/StepWizardPlan.tsx
index 66e76b7..b673111 100644
--- a/frontend/src/components/form/StepWizardPlan.tsx
+++ b/frontend/src/components/form/StepWizardPlan.tsx
@@ -1,6 +1,6 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Button, Card, Checkbox, Form, Input, Radio, Result, Select, Skeleton, Space, Tag, Typography, Upload, message, Progress } from 'antd';
-import { LoadingOutlined, PlusOutlined, ThunderboltOutlined, InboxOutlined } from '@ant-design/icons';
+import { LoadingOutlined, PlusOutlined, ThunderboltOutlined, InboxOutlined, FileTextOutlined } from '@ant-design/icons';
import AiWorkingIllustration from '../../assets/ai-working.svg';
import type { UploadFile } from 'antd/es/upload/interface';
@@ -133,6 +133,8 @@ export default function StepWizardPlan({
new Set(formData.wizardSkippedDocuments || [])
);
const [submitting, setSubmitting] = useState(false);
+ const [isFormingClaim, setIsFormingClaim] = useState(false); // Состояние ожидания формирования заявления
+ const [ragError, setRagError] = useState(null); // Ошибка RAG
const [progressState, setProgressState] = useState<{ done: number; total: number }>({
done: 0,
total: 0,
@@ -1070,6 +1072,8 @@ export default function StepWizardPlan({
onChange={(e) =>
updateDocumentBlock(docId, block.id, { description: e.target.value })
}
+ maxLength={500}
+ showCount
/>
)}
@@ -1146,27 +1150,61 @@ export default function StepWizardPlan({
const renderCustomUploads = () => (
}>
- Добавить блок
-
+ style={{
+ marginTop: 24,
+ borderRadius: 8,
+ border: '2px dashed #d9d9d9',
+ background: '#fafafa'
+ }}
+ title={
+
+
+
Дополнительные документы
+
}
>
{customFileBlocks.length === 0 && (
-
- Можно добавить произвольные группы документов — например, переписку, дополнительные акты
- или фото.
-
+
+
+ Есть ещё документы, которые могут помочь?
+
+
+ Если у вас есть дополнительные документы, которые не указаны в списке выше,
+ вы можете загрузить их здесь. Например:
+
+
+ Дополнительная переписка
+ Скриншоты переговоров
+ Дополнительные чеки или акты
+ Любые другие документы, которые могут быть полезны
+
+
}
+ onClick={addCustomBlock}
+ block
+ size="large"
+ >
+ Добавить документ
+
+
)}
{customFileBlocks.map((block, idx) => (
+
+ Дополнительный документ #{idx + 1}
+
+ }
extra={
removeCustomBlock(block.id)}>
Удалить
@@ -1174,43 +1212,74 @@ export default function StepWizardPlan({
}
>
- updateCustomBlock(block.id, { category: value })}
- allowClear
- >
- {customCategoryOptions.map((option) => (
-
- {option.label}
-
- ))}
-
-
))}
{customFileBlocks.length > 0 && (
- }>
- Добавить ещё документы
+ }
+ block
+ style={{ marginTop: 8 }}
+ >
+ Добавить ещё документ
)}
@@ -1591,6 +1660,27 @@ export default function StepWizardPlan({
const newSkipped = [...skippedDocs, currentDoc.id];
setSkippedDocs(newSkipped);
+ // ✅ ЛОГИРОВАНИЕ: Пропуск документа
+ console.log('⏭️ Документ пропущен:', {
+ document_id: currentDoc.id,
+ document_name: currentDoc.name,
+ document_type: currentDoc.type || currentDoc.id,
+ was_required: currentDoc.required || false,
+ claim_id: formData.claim_id,
+ session_id: formData.session_id,
+ skipped_documents_count: newSkipped.length,
+ skipped_documents: newSkipped,
+ });
+
+ // ✅ ЛОГИРОВАНИЕ: Отправка события для отладки
+ addDebugEvent?.('documents', 'info', `⏭️ Документ пропущен: ${currentDoc.name}`, {
+ document_type: currentDoc.type || currentDoc.id,
+ document_id: currentDoc.id,
+ was_required: currentDoc.required || false,
+ claim_id: formData.claim_id,
+ skipped_documents: newSkipped,
+ });
+
// Находим следующий незагруженный документ (используем обновлённый список)
const findNextUnprocessed = (startIndex: number) => {
for (let i = startIndex; i < documentsRequired.length; i++) {
@@ -1604,11 +1694,52 @@ export default function StepWizardPlan({
};
const nextIndex = findNextUnprocessed(currentDocIndex + 1);
+ // ✅ Сохраняем в formData (это сохранится в БД через n8n при следующем сохранении черновика)
updateFormData({
documents_skipped: newSkipped,
current_doc_index: nextIndex,
});
+ // ✅ ЯВНОЕ СОХРАНЕНИЕ: Отправляем событие в n8n для сохранения documents_skipped в БД
+ // Используем тот же webhook, что и при загрузке документа
+ if (formData.claim_id && formData.session_id) {
+ try {
+ const formDataToSend = new FormData();
+ formDataToSend.append('claim_id', formData.claim_id);
+ formDataToSend.append('session_id', formData.session_id);
+ formDataToSend.append('document_type', currentDoc.type || currentDoc.id);
+ formDataToSend.append('document_name', currentDoc.name || currentDoc.id);
+ formDataToSend.append('group_index', String(currentDocIndex)); // ✅ Индекс документа
+ if (formData.unified_id) formDataToSend.append('unified_id', formData.unified_id);
+ if (formData.contact_id) formDataToSend.append('contact_id', formData.contact_id);
+ if (formData.phone) formDataToSend.append('phone', formData.phone);
+
+ console.log('💾 Отправка пропущенного документа в n8n:', {
+ claim_id: formData.claim_id,
+ document_type: currentDoc.type || currentDoc.id,
+ document_name: currentDoc.name,
+ group_index: currentDocIndex,
+ });
+
+ const response = await fetch('/api/v1/documents/skip', {
+ method: 'POST',
+ body: formDataToSend,
+ });
+
+ if (!response.ok) {
+ const errorData = await response.json().catch(() => ({}));
+ console.error('❌ Ошибка отправки пропущенного документа в n8n:', errorData);
+ // Не блокируем пользователя - данные сохранятся при следующем сохранении черновика
+ } else {
+ const result = await response.json();
+ console.log('✅ Пропущенный документ отправлен в n8n:', result);
+ }
+ } catch (error) {
+ console.error('❌ Ошибка отправки пропущенного документа в n8n:', error);
+ // Не блокируем пользователя - данные сохранятся при следующем сохранении черновика
+ }
+ }
+
// Переход к следующему незагруженному документу
setCurrentDocIndex(nextIndex);
return;
@@ -1714,10 +1845,295 @@ export default function StepWizardPlan({
}, [currentDocIndex]);
// Все документы загружены — переход к ожиданию заявления
- const handleAllDocsComplete = () => {
- message.loading('Формируем заявление...', 0);
- // TODO: Переход к StepWaitingClaim или показ loader
- onNext();
+ const handleAllDocsComplete = async () => {
+ // ✅ Отправляем кастомные документы, если они есть
+ const customBlocksWithFiles = customFileBlocks.filter(block => block.files.length > 0);
+
+ if (customBlocksWithFiles.length > 0) {
+ try {
+ message.loading('Отправляем дополнительные документы...', 0);
+
+ // Отправляем каждый кастомный блок отдельно
+ for (const block of customBlocksWithFiles) {
+ if (!block.description?.trim()) {
+ message.warning('Пропущен документ без названия');
+ continue;
+ }
+
+ const formDataToSend = new FormData();
+ formDataToSend.append('claim_id', formData.claim_id || '');
+ formDataToSend.append('session_id', formData.session_id || '');
+ formDataToSend.append('unified_id', formData.unified_id || '');
+ formDataToSend.append('contact_id', formData.contact_id || '');
+ formDataToSend.append('phone', formData.phone || '');
+ formDataToSend.append('document_type', 'custom');
+ formDataToSend.append('document_name', block.description);
+ formDataToSend.append('document_description', block.description);
+
+ // Добавляем все файлы блока
+ block.files.forEach((file) => {
+ if (file.originFileObj) {
+ formDataToSend.append('files', file.originFileObj, file.name);
+ }
+ });
+
+ const response = await fetch('/api/v1/documents/upload-multiple', {
+ method: 'POST',
+ body: formDataToSend,
+ });
+
+ if (!response.ok) {
+ console.error('❌ Ошибка отправки кастомного документа:', await response.text());
+ } else {
+ console.log('✅ Кастомный документ отправлен:', block.description);
+ }
+ }
+
+ message.destroy();
+ message.success(`Отправлено ${customBlocksWithFiles.length} дополнительных документов`);
+ } catch (error) {
+ console.error('❌ Ошибка отправки кастомных документов:', error);
+ message.destroy();
+ message.warning('Не удалось отправить некоторые дополнительные документы');
+ }
+ }
+
+ // ✅ Показываем экран ожидания
+ setIsFormingClaim(true);
+ setRagError(null); // Сбрасываем предыдущую ошибку
+
+ // ✅ Запускаем RAG через check-ocr-status
+ try {
+ const response = await fetch('/api/v1/documents/check-ocr-status', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ claim_id: formData.claim_id,
+ session_id: formData.session_id,
+ }),
+ });
+
+ if (response.ok) {
+ const data = await response.json();
+ console.log('✅ OCR status check:', data);
+
+ // Если есть кэш — сразу переходим
+ if (data.from_cache && data.form_draft) {
+ console.log('✅ Используем кэшированные данные:', data.form_draft);
+ const formDraft = data.form_draft;
+ const user = formDraft.user || {};
+ const project = formDraft.project || {};
+
+ // ✅ Используем тот же маппинг что и в ClaimForm.tsx
+ const claimPlanData = {
+ propertyName: {
+ applicant: {
+ first_name: user.firstname || '',
+ middle_name: user.secondname || '',
+ last_name: user.lastname || '',
+ phone: user.mobile || formData.phone || '',
+ email: user.email || '',
+ birth_date: user.birthday || '',
+ birth_place: user.birthplace || '',
+ address: user.mailingstreet || '',
+ inn: user.inn || '',
+ },
+ case: {
+ category: project.category || '',
+ direction: project.direction || '',
+ },
+ contract_or_service: {
+ subject: project.subject || '',
+ amount_paid: project.agrprice || '',
+ agreement_date: project.agrdate || '',
+ period_start: project.startdate || '',
+ period_end: project.finishdate || '',
+ country: project.country || '',
+ hotel: project.hotel || '',
+ },
+ offenders: (formDraft.offenders || []).map((o: any) => ({
+ name: o.accountname || '',
+ accountname: o.accountname || '',
+ address: o.address || '',
+ email: o.email || '',
+ website: o.website || '',
+ phone: o.phone || '',
+ inn: o.inn || '',
+ ogrn: o.ogrn || '',
+ role: o.role || '',
+ })),
+ claim: {
+ description: project.description || formData.problem_description || '',
+ reason: project.category || '',
+ },
+ meta: {
+ claim_id: formData.claim_id,
+ unified_id: formData.unified_id || '',
+ session_token: formData.session_id,
+ },
+ attachments_names: Array.isArray(data.documents_meta)
+ ? [...new Set(data.documents_meta.map((d: any) =>
+ d.field_label || d.original_file_name || d.file_name || 'Документ'
+ ))]
+ : [],
+ },
+ session_token: formData.session_id,
+ claim_id: formData.claim_id,
+ prefix: 'clpr_',
+ };
+
+ updateFormData({
+ form_draft: formDraft,
+ claimPlanData: claimPlanData,
+ showClaimConfirmation: true,
+ claim_ready: true,
+ });
+
+ setIsFormingClaim(false);
+ message.success('Данные загружены из кэша');
+ onNext();
+ return;
+ }
+
+ // Иначе подключаемся к SSE и ждём результат от n8n
+ const sessionId = formData.session_id;
+ console.log('📡 Подключаемся к SSE:', `/api/v1/events/${sessionId}`);
+
+ const eventSource = new EventSource(`/api/v1/events/${sessionId}`);
+
+ eventSource.onmessage = (event) => {
+ try {
+ const eventData = JSON.parse(event.data);
+ console.log('📥 SSE event:', eventData);
+
+ // Обрабатываем событие ocr_status
+ if (eventData.event_type === 'ocr_status') {
+ if (eventData.status === 'ready') {
+ // ✅ Успех — данные готовы
+ console.log('✅ Заявление готово:', eventData.data);
+ const formDraft = eventData.data?.form_draft;
+
+ // Формируем claimPlanData для StepClaimConfirmation
+ if (formDraft) {
+ const user = formDraft.user || {};
+ const project = formDraft.project || {};
+
+ // ✅ Используем тот же маппинг что и в ClaimForm.tsx
+ const claimPlanData = {
+ propertyName: {
+ applicant: {
+ first_name: user.firstname || '',
+ middle_name: user.secondname || '',
+ last_name: user.lastname || '',
+ phone: user.mobile || formData.phone || '',
+ email: user.email || '',
+ birth_date: user.birthday || '',
+ birth_place: user.birthplace || '',
+ address: user.mailingstreet || '',
+ inn: user.inn || '',
+ },
+ case: {
+ category: project.category || '',
+ direction: project.direction || '',
+ },
+ contract_or_service: {
+ subject: project.subject || '',
+ amount_paid: project.agrprice || '',
+ agreement_date: project.agrdate || '',
+ period_start: project.startdate || '',
+ period_end: project.finishdate || '',
+ country: project.country || '',
+ hotel: project.hotel || '',
+ },
+ offenders: (formDraft.offenders || []).map((o: any) => ({
+ name: o.accountname || '',
+ accountname: o.accountname || '',
+ address: o.address || '',
+ email: o.email || '',
+ website: o.website || '',
+ phone: o.phone || '',
+ inn: o.inn || '',
+ ogrn: o.ogrn || '',
+ role: o.role || '',
+ })),
+ claim: {
+ description: project.description || formData.problem_description || '',
+ reason: project.category || '',
+ },
+ meta: {
+ claim_id: formData.claim_id,
+ unified_id: formData.unified_id || '',
+ session_token: formData.session_id,
+ },
+ attachments_names: Array.isArray(eventData.data?.documents_meta)
+ ? [...new Set(eventData.data.documents_meta.map((d: any) =>
+ d.field_label || d.original_file_name || d.file_name || 'Документ'
+ ))]
+ : [],
+ },
+ session_token: formData.session_id,
+ claim_id: formData.claim_id,
+ prefix: 'clpr_',
+ };
+
+ updateFormData({
+ form_draft: formDraft,
+ claimPlanData: claimPlanData,
+ showClaimConfirmation: true,
+ claim_ready: true,
+ });
+ } else {
+ updateFormData({
+ claim_ready: true,
+ });
+ }
+
+ setIsFormingClaim(false);
+ message.success(eventData.message || 'Заявление сформировано!');
+ eventSource.close();
+ onNext();
+ } else if (eventData.status === 'error' || eventData.status === 'timeout') {
+ // ❌ Ошибка — показываем кнопку повторить
+ console.error('❌ Ошибка RAG:', eventData.message);
+ setIsFormingClaim(false);
+ setRagError(eventData.message || 'Ошибка формирования заявления');
+ eventSource.close();
+ }
+ }
+ } catch (e) {
+ console.error('❌ Ошибка парсинга SSE:', e);
+ }
+ };
+
+ eventSource.onerror = (error) => {
+ console.error('❌ SSE error:', error);
+ message.destroy();
+ setIsFormingClaim(false);
+ setRagError('Потеряно соединение с сервером');
+ eventSource.close();
+ };
+
+ // Таймаут 3 минуты (RAG может занять время)
+ setTimeout(() => {
+ if (eventSource.readyState !== EventSource.CLOSED) {
+ console.warn('⏰ SSE timeout');
+ message.destroy();
+ setIsFormingClaim(false);
+ setRagError('Превышено время ожидания. Попробуйте ещё раз.');
+ eventSource.close();
+ }
+ }, 180000); // 3 минуты для RAG
+
+ } else {
+ console.warn('⚠️ OCR status check failed:', await response.text());
+ message.destroy();
+ onNext();
+ }
+ } catch (error) {
+ console.error('❌ Error calling check-ocr-status:', error);
+ message.destroy();
+ onNext();
+ }
};
return (
@@ -1833,7 +2249,7 @@ export default function StepWizardPlan({
{/* Кнопки */}
- ← Назад
+ ← К списку заявок
) : null}
+ {/* ✅ НОВЫЙ ФЛОУ: Формируем заявление (экран ожидания) */}
+ {hasNewFlowDocs && allDocsProcessed && isFormingClaim && (
+
+
+
📝 Формируем заявление...
+
+ Анализируем документы и собираем данные для вашего заявления.
+ Это займёт до 1-2 минут.
+
+
+
+ )}
+
+ {/* ❌ ОШИБКА: Показываем кнопку повторить */}
+ {hasNewFlowDocs && allDocsProcessed && ragError && !isFormingClaim && (
+
+ {
+ setRagError(null);
+ handleAllDocsComplete();
+ }}
+ >
+ 🔄 Повторить
+ ,
+ {
+ setRagError(null);
+ onNext();
+ }}
+ >
+ Пропустить и продолжить
+ ,
+ ]}
+ />
+
+ )}
+
{/* ✅ НОВЫЙ ФЛОУ: Все документы загружены */}
- {hasNewFlowDocs && allDocsProcessed && (() => {
+ {hasNewFlowDocs && allDocsProcessed && !isFormingClaim && !ragError && (() => {
// Правильно считаем загруженные и пропущенные документы из documentsRequired
const uploadedCount = documentsRequired.filter((doc: any) => {
const docId = doc.id || doc.name;
@@ -1893,15 +2358,23 @@ export default function StepWizardPlan({
}).length;
return (
-
-
✅ Все документы обработаны!
-
- Загружено: {uploadedCount} из {documentsRequired.length}, пропущено: {skippedCount}
-
-
- Продолжить →
-
-
+ <>
+
+
✅ Все обязательные документы обработаны!
+
+ Загружено: {uploadedCount} из {documentsRequired.length}, пропущено: {skippedCount}
+
+
+
+ {/* ✅ Дополнительные документы */}
+ {renderCustomUploads()}
+
+
+
+ Продолжить →
+
+
+ >
);
})()}
diff --git a/frontend/src/components/form/generateConfirmationFormHTML.ts b/frontend/src/components/form/generateConfirmationFormHTML.ts
index 07e55f1..aa1fd14 100644
--- a/frontend/src/components/form/generateConfirmationFormHTML.ts
+++ b/frontend/src/components/form/generateConfirmationFormHTML.ts
@@ -1,7 +1,7 @@
// Функция генерации HTML формы подтверждения заявления
// Основана на структуре из n8n Code node "Mini-app Подтверждение данных"
-export function generateConfirmationFormHTML(data: any): string {
+export function generateConfirmationFormHTML(data: any, contact_data_confirmed: boolean = false): string {
// Извлекаем SMS данные (до нормализации, так как структура может быть разной)
const smsInputData = {
prefix: data.sms_meta?.prefix || data.prefix || '',
@@ -290,6 +290,7 @@ export function generateConfirmationFormHTML(data: any): string {
telegram_id: telegramId,
token: data.token || '',
sms_meta: smsMetaData,
+ contact_data_confirmed: contact_data_confirmed || false, // ✅ Флаг подтверждения данных контакта
});
caseJson = caseJson.replace(/';
+ ' id="' + id + '" ' + nameAttr + ' value="' + esc(value || '') + '" placeholder="' + esc(placeholder || '') + '"' + extra + ' />';
return fieldHtml;
}
function createReadonlyField(root, key, value) {
var id = 'field_' + root + '_' + key + '_readonly_' + Math.random().toString(36).slice(2);
- return ' ';
+ // ✅ Добавляем name атрибут для правильной работы форм
+ var nameAttr = 'name="' + esc(root) + '_' + esc(key) + '"';
+ return ' ';
}
function createDateField(root, key, value) {
@@ -740,14 +837,26 @@ export function generateConfirmationFormHTML(data: any): string {
dateValue = parts[2] + '-' + parts[1] + '-' + parts[0];
}
}
- return ' ';
+
+ // ✅ Добавляем name атрибут для правильной работы форм
+ var nameAttr = 'name="' + esc(root) + '_' + esc(key) + '"';
+
+ // ✅ Проверяем, нужно ли блокировать поле (для подтверждённых данных)
+ var isLockedField = contact_data_confirmed && root === 'user' && key === 'birthday';
+ if (isLockedField) {
+ return ' ';
+ }
+
+ return ' ';
}
function createMoneyField(root, key, value) {
var id = 'field_' + root + '_' + key + '_' + Math.random().toString(36).slice(2);
+ // ✅ Добавляем name атрибут для правильной работы форм
+ var nameAttr = 'name="' + esc(root) + '_' + esc(key) + '"';
return '' +
' ' +
'рублей ' +
'
';
@@ -755,16 +864,43 @@ export function generateConfirmationFormHTML(data: any): string {
function createTextarea(root, key, value) {
var id = 'field_' + root + '_' + key + '_' + Math.random().toString(36).slice(2);
- return '';
+ // ✅ Добавляем name атрибут для правильной работы форм
+ var nameAttr = 'name="' + esc(root) + '_' + esc(key) + '"';
+ return '';
+ }
+
+ function createBankSelect(root, key, value) {
+ var id = 'field_' + root + '_' + key + '_' + Math.random().toString(36).slice(2);
+ var datalistId = 'bank-datalist-' + id;
+ // ✅ Добавляем name атрибут для правильной работы форм
+ var nameAttr = 'name="' + esc(root) + '_' + esc(key) + '"';
+ // Создаём input с datalist для автоподстановки
+ var inputHtml = ' ';
+ inputHtml += '';
+ inputHtml += 'Загрузка списка банков... ';
+ inputHtml += ' ';
+ // Скрытое поле для bank_id
+ var hiddenId = id + '_id';
+ var hiddenNameAttr = 'name="' + esc(root) + '_bank_id"';
+ inputHtml += ' ';
+ return inputHtml;
}
function createCheckbox(root, key, checked, labelText, required) {
- var id = 'field_' + root + '_' + key + '_' + Math.random().toString(36).slice(2);
+ // ✅ Генерируем безопасный id (только буквы, цифры, подчёркивание, дефис)
+ var safeRoot = String(root || '').replace(/[^a-zA-Z0-9_-]/g, '_');
+ var safeKey = String(key || '').replace(/[^a-zA-Z0-9_-]/g, '_');
+ var randomPart = Math.random().toString(36).slice(2);
+ var id = 'field_' + safeRoot + '_' + safeKey + '_' + randomPart;
var checkedAttr = checked ? ' checked' : '';
var requiredClass = required ? ' required-checkbox' : '';
+ // ✅ Добавляем name атрибут для правильной работы форм
+ var nameAttr = 'name="' + esc(root) + '_' + esc(key) + '"';
- var checkboxHtml = '';
- checkboxHtml += ' ';
+ // ✅ Label правильно связан с input через for/id (экранируем id для безопасности)
+ var escapedId = esc(id);
+ var checkboxHtml = '';
+ checkboxHtml += ' ';
checkboxHtml += ' ';
checkboxHtml += '' + labelText + ' ';
checkboxHtml += ' ';
@@ -779,6 +915,9 @@ export function generateConfirmationFormHTML(data: any): string {
console.log('injected.case:', injected.case);
console.log('injected.propertyName:', injected.propertyName);
+ // ✅ Извлекаем флаг подтверждения данных из injected
+ var contact_data_confirmed = injected.contact_data_confirmed || false;
+
// Достаём объект кейса из «типичных» мест
var dataCandidate = null;
@@ -843,34 +982,45 @@ export function generateConfirmationFormHTML(data: any): string {
var html = '';
+ // ✅ Предупреждение о заблокированных данных (если данные подтверждены)
+ if (contact_data_confirmed) {
+ html += '';
+ html += '
';
+ html += '⚠️ Данные подтверждены ';
+ html += 'Ваши персональные данные (ФИО, ИНН, дата рождения, адрес) заблокированы для редактирования. ';
+ html += 'Для изменения данных обратитесь в поддержку.';
+ html += '
';
+ html += '
';
+ }
+
html += '';
html += '
В МОО «Клиентправ» ';
html += '
help@clientright.ru
';
html += '
';
html += 'Заявитель: ';
- html += createField('user', 'lastname', u.lastname, 'Фамилия (обязательно)');
+ html += createField('user', 'lastname', u.lastname, 'Фамилия') + '* ';
html += ' ';
- html += createField('user', 'firstname', u.firstname, 'Имя (обязательно)');
+ html += createField('user', 'firstname', u.firstname, 'Имя') + '* ';
html += ' ';
html += createField('user', 'secondname', u.secondname, 'Отчество');
html += '
';
html += 'Дата рождения: ';
html += createDateField('user', 'birthday', u.birthday);
- html += '
';
+ html += '* ';
html += 'Место рождения: ';
- html += createField('user', 'birthplace', u.birthplace, 'Место рождения (обязательно)');
- html += '
';
+ html += createField('user', 'birthplace', u.birthplace, 'Место рождения');
+ html += '* ';
html += 'ИНН: ';
- html += createField('user', 'inn', u.inn, '12-значный ИНН (обязательно)');
- html += '
';
+ html += createField('user', 'inn', u.inn, '12-значный ИНН');
+ html += '* ';
html += 'Адрес: ';
- html += createField('user', 'mailingstreet', u.mailingstreet, 'Адрес регистрации как в паспорте (обязательно)');
- html += '
';
+ html += createField('user', 'mailingstreet', u.mailingstreet, 'Адрес регистрации как в паспорте');
+ html += '* ';
html += 'Телефон: ';
html += createReadonlyField('user', 'mobile', u.mobile);
@@ -885,6 +1035,9 @@ export function generateConfirmationFormHTML(data: any): string {
// Возмещение
html += '
Возмещение: ';
html += 'Выплата возмещения возможна по системе быстрых платежей (СБП) по номеру телефона заявителя: ' + esc(u.mobile || '') + '
';
+ html += 'Банк для получения выплаты: ';
+ html += createBankSelect('user', 'bank_id', u.bank_id || '');
+ html += '*
';
html += '
';
@@ -904,12 +1057,12 @@ export function generateConfirmationFormHTML(data: any): string {
// Дата события / заключения договора
html += 'Дата события / заключения договора: ';
html += createDateField('project', 'agrdate', p.agrdate);
- html += '
';
+ html += '* ';
// Сумма оплаты / стоимость
html += 'Сумма оплаты / стоимость: ';
html += createMoneyField('project', 'agrprice', p.agrprice);
- html += '
';
+ html += '* ';
// Период
html += 'Период: ';
@@ -934,23 +1087,19 @@ export function generateConfirmationFormHTML(data: any): string {
html += '
Наименование: ';
html += createField('offender', 'accountname', offender.accountname, 'Название организации', i);
- html += '
';
+ html += '* ';
html += 'ИНН: ';
html += createField('offender', 'inn', offender.inn, 'ИНН организации (10 или 12 цифр)', i);
- html += '
';
-
- html += 'ОГРН: ';
- html += createField('offender', 'ogrn', offender.ogrn, 'ОГРН', i);
- html += '
';
+ html += '* ';
html += 'Адрес: ';
html += createField('offender', 'address', offender.address, 'Адрес', i);
- html += '
';
+ html += '* ';
html += 'E-mail: ';
html += createField('offender', 'email', offender.email, 'email@example.com', i);
- html += '
';
+ html += '* ';
html += 'Телефон: ';
html += createField('offender', 'phone', offender.phone, '+7 (___) ___-__-__', i);
@@ -968,15 +1117,18 @@ export function generateConfirmationFormHTML(data: any): string {
// Причина обращения (редактируемая)
html += '
Причина обращения: ';
html += createField('project', 'reason', p.reason, 'Можете уточнить или изменить причину обращения');
- html += '
';
+ html += '* ';
- html += 'Описание проблемы:
';
+ html += 'Описание проблемы: *
';
html += createTextarea('project', 'description', p.description);
html += 'На основании вышеизложенного и руководствуясь ст. 45 Закона «О защите прав потребителей», ст. 3, ч. 1 ст. 46 ГПК РФ, прошу вас защитить мои потребительские права, обратиться в суд с заявлением о защите моих потребительских прав и/или с коллективным иском о защите группы потребителей, и представлять мои интересы во всех судебных органах РФ, а также обращаться с заявлениями во все госорганы, подавать претензии, письма и жалобы.
';
html += '
';
+ // Блок с предупреждением о незаполненных полях (будет обновляться динамически)
+ html += '
';
+
// Согласие на обработку персональных данных
html += '';
html += createCheckbox('meta', 'privacyConsent', state.meta && state.meta.privacyConsent,
@@ -1108,36 +1260,62 @@ export function generateConfirmationFormHTML(data: any): string {
return parseFloat(s) > 0;
}
+ // Список обязательных полей
+ var requiredFieldsList = [
+ { root: 'user', key: 'lastname', name: 'Фамилия' },
+ { root: 'user', key: 'firstname', name: 'Имя' },
+ { root: 'user', key: 'birthday', name: 'Дата рождения' },
+ { root: 'user', key: 'birthplace', name: 'Место рождения' },
+ { root: 'user', key: 'mailingstreet', name: 'Адрес' },
+ { root: 'user', key: 'inn', name: 'ИНН' },
+ { root: 'project', key: 'agrdate', name: 'Дата договора' },
+ { root: 'project', key: 'agrprice', name: 'Сумма' },
+ { root: 'project', key: 'reason', name: 'Причина обращения' },
+ { root: 'project', key: 'description', name: 'Описание проблемы' },
+ { root: 'offender', key: 'accountname', name: 'Название организации' },
+ { root: 'offender', key: 'inn', name: 'ИНН организации' },
+ { root: 'offender', key: 'address', name: 'Адрес организации' },
+ { root: 'offender', key: 'email', name: 'E-mail организации' },
+ { root: 'user', key: 'bank_id', name: 'Банк для получения выплаты' }
+ ];
+
+ // Функция проверки, является ли поле обязательным
+ function isRequiredField(root, key) {
+ return requiredFieldsList.some(function(f) {
+ return f.root === root && f.key === key;
+ });
+ }
+
+ // Функция получения значения поля
+ function getFieldValue(root, key, index) {
+ if (root === 'user') {
+ return state.user[key] || '';
+ } else if (root === 'project') {
+ return state.project[key] || '';
+ } else if (root === 'offender') {
+ var offender = state.offenders[index || 0];
+ return (offender && offender[key]) || '';
+ }
+ return '';
+ }
+
+ // Функция проверки, заполнено ли поле
+ function isFieldFilled(root, key, index) {
+ var value = getFieldValue(root, key, index);
+ if (value === null || value === undefined) return false;
+ if (typeof value === 'string') {
+ return value.trim().length > 0;
+ }
+ return !!value;
+ }
+
// Функция проверки всех обязательных полей
function validateAllFields() {
- var requiredFields = [
- { root: 'user', key: 'lastname', name: 'Фамилия' },
- { root: 'user', key: 'firstname', name: 'Имя' },
- { root: 'user', key: 'birthday', name: 'Дата рождения' },
- { root: 'user', key: 'birthplace', name: 'Место рождения' },
- { root: 'user', key: 'mailingstreet', name: 'Адрес' },
- { root: 'user', key: 'inn', name: 'ИНН' },
- { root: 'project', key: 'agrdate', name: 'Дата договора' },
- { root: 'project', key: 'agrprice', name: 'Сумма' },
- { root: 'project', key: 'reason', name: 'Причина обращения' },
- { root: 'project', key: 'description', name: 'Описание проблемы' },
- { root: 'offender', key: 'accountname', name: 'Название организации' },
- { root: 'offender', key: 'address', name: 'Адрес организации' }
- ];
-
var errors = [];
- for (var i = 0; i < requiredFields.length; i++) {
- var field = requiredFields[i];
- var value = '';
- if (field.root === 'user') {
- value = state.user[field.key] || '';
- } else if (field.root === 'project') {
- value = state.project[field.key] || '';
- } else if (field.root === 'offender') {
- value = (state.offenders[0] && state.offenders[0][field.key]) || '';
- }
- if (!value || (typeof value === 'string' && value.trim() === '')) {
+ for (var i = 0; i < requiredFieldsList.length; i++) {
+ var field = requiredFieldsList[i];
+ if (!isFieldFilled(field.root, field.key, field.root === 'offender' ? 0 : undefined)) {
errors.push(field.name);
}
}
@@ -1145,6 +1323,34 @@ export function generateConfirmationFormHTML(data: any): string {
return errors;
}
+ // Функция обновления блока с предупреждением о незаполненных полях
+ function updateValidationWarning() {
+ var warningBlock = document.getElementById('validation-warning-block');
+ if (!warningBlock) return;
+
+ var validationErrors = validateAllFields();
+
+ if (validationErrors.length === 0) {
+ warningBlock.style.display = 'none';
+ return;
+ }
+
+ // Формируем HTML для предупреждения
+ var warningHtml = '
';
+ warningHtml += '
';
+ warningHtml += '⚠️ Пожалуйста, заполните все обязательные поля (' + validationErrors.length + '):';
+ warningHtml += '
';
+ warningHtml += '
';
+ for (var i = 0; i < validationErrors.length; i++) {
+ warningHtml += '' + esc(validationErrors[i]) + ' ';
+ }
+ warningHtml += ' ';
+ warningHtml += '
';
+
+ warningBlock.innerHTML = warningHtml;
+ warningBlock.style.display = 'block';
+ }
+
// Функция обновления состояния кнопки отправки
function updateSubmitButton() {
var confirmBtn = document.getElementById('confirmBtn');
@@ -1153,6 +1359,15 @@ export function generateConfirmationFormHTML(data: any): string {
var isConsentGiven = state.meta && state.meta.privacyConsent === true;
var validationErrors = validateAllFields();
+ // Обновляем блок с предупреждением
+ updateValidationWarning();
+
+ // Обновляем стили всех полей
+ var fields = document.querySelectorAll('.bind');
+ Array.prototype.forEach.call(fields, function(field) {
+ updateFieldStyle(field);
+ });
+
if (!isConsentGiven) {
confirmBtn.disabled = true;
confirmBtn.style.opacity = '0.6';
@@ -1167,20 +1382,60 @@ export function generateConfirmationFormHTML(data: any): string {
confirmBtn.disabled = true;
confirmBtn.style.opacity = '0.6';
confirmBtn.style.cursor = 'not-allowed';
- confirmBtn.textContent = '❌ Заполните все поля (' + validationErrors.length + ')';
+ confirmBtn.textContent = '❌ Заполните все обязательные поля (' + validationErrors.length + ')';
confirmBtn.title = 'Не заполнены: ' + validationErrors.join(', ');
}
}
- // ✅ Функция для обновления стиля заполненных полей
+ // ✅ Функция для обновления стиля заполненных полей с валидацией
function updateFieldStyle(field) {
var value = field.type === 'checkbox' ? field.checked : (field.value || '').trim();
var hasValue = field.type === 'checkbox' ? value : value.length > 0;
+ var key = field.getAttribute('data-key');
+ var root = field.getAttribute('data-root');
+ var index = field.getAttribute('data-index');
+ var fieldIndex = index !== null ? parseInt(index, 10) : undefined;
+
+ // Убираем все классы сначала
+ field.classList.remove('filled');
+ field.classList.remove('invalid');
+ field.classList.remove('required-empty');
+
+ // Проверяем, является ли поле обязательным
+ var isRequired = isRequiredField(root, key);
if (hasValue) {
- field.classList.add('filled');
+ // Проверяем валидность для телефона и email
+ var isValid = true;
+
+ if (key === 'mobile' || key === 'phone') {
+ // Валидация телефона: только цифры, 10-11 символов
+ var cleanPhone = value.replace(/\D/g, '');
+ isValid = cleanPhone.length >= 10 && cleanPhone.length <= 11;
+ } else if (key === 'email') {
+ // Валидация email: должен содержать @ и .
+ isValid = value.includes('@') && value.includes('.') && value.length > 5;
+ } else if (key === 'inn') {
+ // Валидация ИНН: 10 или 12 цифр
+ var cleanInn = value.replace(/\D/g, '');
+ if (root === 'user') {
+ isValid = cleanInn.length === 12;
+ } else {
+ isValid = cleanInn.length === 10 || cleanInn.length === 12;
+ }
+ }
+
+ if (isValid) {
+ field.classList.add('filled');
+ } else {
+ field.classList.add('invalid');
+ }
} else {
- field.classList.remove('filled');
+ // Поле не заполнено
+ if (isRequired) {
+ // Обязательное поле не заполнено - подсвечиваем жёлтым
+ field.classList.add('required-empty');
+ }
}
}
@@ -1190,14 +1445,99 @@ export function generateConfirmationFormHTML(data: any): string {
var fields = document.querySelectorAll('.bind');
console.log('Found fields:', fields.length);
- // ✅ Устанавливаем начальный стиль для всех полей
+ // Обработка скрытых полей bank_id
+ var bankIdFields = document.querySelectorAll('.bank-id-field');
+ Array.prototype.forEach.call(bankIdFields, function(field) {
+ field.addEventListener('change', function() {
+ var root = this.getAttribute('data-root');
+ var value = this.value;
+ if (root === 'user') {
+ state.user = state.user || {};
+ state.user.bank_id = value;
+ }
+ });
+ });
+
+ // ✅ Устанавливаем начальный стиль для всех полей и форматируем телефоны
Array.prototype.forEach.call(fields, function(field) {
+ var key = field.getAttribute('data-key');
+ // Автоформатирование телефона при загрузке: убираем + и нецифровые символы
+ if ((key === 'mobile' || key === 'phone') && field.value) {
+ field.value = field.value.replace(/\D/g, '');
+ }
updateFieldStyle(field);
});
Array.prototype.forEach.call(fields, function(field) {
+ var fieldKey = field.getAttribute('data-key');
+
+ // ✅ Блокируем ввод нецифровых символов для телефона
+ if (fieldKey === 'mobile' || fieldKey === 'phone') {
+ field.addEventListener('keypress', function(e) {
+ // Разрешаем: цифры, Backspace, Delete, Tab, стрелки
+ if (!/[0-9]/.test(e.key) && !['Backspace', 'Delete', 'Tab', 'ArrowLeft', 'ArrowRight'].includes(e.key)) {
+ e.preventDefault();
+ }
+ });
+
+ // Блокируем вставку нецифровых символов
+ field.addEventListener('paste', function(e) {
+ e.preventDefault();
+ var pastedText = (e.clipboardData || window.clipboardData).getData('text');
+ var cleanText = pastedText.replace(/\D/g, '').slice(0, 11);
+ document.execCommand('insertText', false, cleanText);
+ });
+ }
+
+ // ✅ Блокируем ввод кириллицы для email (только латиница, цифры и @._-)
+ if (fieldKey === 'email') {
+ field.addEventListener('keypress', function(e) {
+ // Разрешаем: латиница, цифры, @, ., _, -, служебные клавиши
+ if (!/[a-zA-Z0-9@._\-]/.test(e.key) && !['Backspace', 'Delete', 'Tab', 'ArrowLeft', 'ArrowRight'].includes(e.key)) {
+ e.preventDefault();
+ }
+ });
+
+ // Блокируем вставку кириллицы
+ field.addEventListener('paste', function(e) {
+ e.preventDefault();
+ var pastedText = (e.clipboardData || window.clipboardData).getData('text');
+ var cleanText = pastedText.replace(/[^a-zA-Z0-9@._\-]/g, ''); // Только латиница и допустимые символы
+ document.execCommand('insertText', false, cleanText);
+ });
+
+ // Автоочистка при вводе
+ field.addEventListener('input', function() {
+ var cursorPos = this.selectionStart;
+ var oldLen = this.value.length;
+ this.value = this.value.replace(/[^a-zA-Z0-9@._\-]/g, '');
+ var newLen = this.value.length;
+ // ✅ Для полей типа email setSelectionRange может не работать, используем try-catch
+ try {
+ if (this.type !== 'email') {
+ this.setSelectionRange(Math.min(cursorPos, newLen), Math.min(cursorPos, newLen));
+ }
+ } catch (e) {
+ // Игнорируем ошибку для полей типа email
+ console.debug('setSelectionRange not supported for email field');
+ }
+ });
+ }
+
// Обработка ввода
field.addEventListener('input', function() {
+ var key = this.getAttribute('data-key');
+
+ // ✅ Автоформатирование телефона: убираем + и нецифровые символы (на всякий случай)
+ if (key === 'mobile' || key === 'phone') {
+ var cursorPos = this.selectionStart;
+ var oldLen = this.value.length;
+ this.value = this.value.replace(/\D/g, '').slice(0, 11); // Оставляем только цифры, макс 11
+ var newLen = this.value.length;
+ // Корректируем позицию курсора
+ this.setSelectionRange(Math.min(cursorPos, newLen), Math.min(cursorPos, newLen));
+ }
+
// ✅ Обновляем стиль при изменении
updateFieldStyle(this);
// Автозамена запятой на точку для денежных полей
@@ -1206,7 +1546,6 @@ export function generateConfirmationFormHTML(data: any): string {
}
var root = this.getAttribute('data-root');
- var key = this.getAttribute('data-key');
var value = this.type === 'checkbox' ? this.checked : this.value;
// Для полей дат конвертируем YYYY-MM-DD в DD.MM.YYYY для сохранения
@@ -1222,6 +1561,12 @@ export function generateConfirmationFormHTML(data: any): string {
// Обновляем состояние
if (root === 'user') {
state.user = state.user || {};
+ // Для bank_id не сохраняем название банка, только ID из скрытого поля
+ if (key === 'bank_id' && this.classList.contains('bank-select')) {
+ // Это текстовое поле для названия банка - не сохраняем в state
+ // bank_id будет сохранён из скрытого поля
+ return;
+ }
state.user[key] = value;
// Обновляем телефон в СБП
@@ -1322,6 +1667,142 @@ export function generateConfirmationFormHTML(data: any): string {
});
}
+ // Загрузка списка банков СБП
+ function loadBanks() {
+ var bankInputs = document.querySelectorAll('.bank-select');
+ if (bankInputs.length === 0) {
+ console.log('Bank select fields not found');
+ return;
+ }
+
+ console.log('Loading NSPK banks...');
+
+ fetch('http://212.193.27.93/api/payouts/dictionaries/nspk-banks')
+ .then(function(response) {
+ if (!response.ok) throw new Error('HTTP ' + response.status);
+ return response.json();
+ })
+ .then(function(banks) {
+ console.log('Loaded ' + banks.length + ' banks');
+
+ // Сортируем по названию
+ banks.sort(function(a, b) {
+ return a.bankname.localeCompare(b.bankname, 'ru');
+ });
+
+ // Сохраняем список банков глобально для поиска
+ window.__banksList = banks;
+
+ // Заполняем все datalist элементы
+ Array.prototype.forEach.call(bankInputs, function(input) {
+ var datalistId = input.getAttribute('list');
+ var datalist = document.getElementById(datalistId);
+ var hiddenId = input.id + '_id';
+ var hiddenField = document.getElementById(hiddenId);
+ var currentBankId = state.user?.bank_id || '';
+ var currentBankName = '';
+
+ if (!datalist) {
+ console.error('Datalist not found for input:', input.id);
+ return;
+ }
+
+ // Очищаем datalist
+ datalist.innerHTML = '';
+
+ // Заполняем datalist опциями
+ banks.forEach(function(bank) {
+ var option = document.createElement('option');
+ option.value = bank.bankname;
+ option.setAttribute('data-bank-id', bank.bankid);
+ datalist.appendChild(option);
+
+ // Если это текущий банк, устанавливаем значение
+ if (bank.bankid === currentBankId) {
+ currentBankName = bank.bankname;
+ }
+ });
+
+ // Устанавливаем текущее значение если есть
+ if (currentBankName) {
+ input.value = currentBankName;
+ if (hiddenField) {
+ hiddenField.value = currentBankId;
+ }
+ // ✅ Сохраняем название банка в state
+ state.user = state.user || {};
+ state.user.bank_name = currentBankName;
+ input.classList.add('filled');
+ updateFieldStyle(input);
+ }
+
+ // Обработчик изменения для поиска банка по названию
+ input.addEventListener('input', function() {
+ var inputValue = this.value.trim();
+ var foundBank = null;
+
+ // Ищем точное совпадение
+ if (inputValue) {
+ foundBank = banks.find(function(b) {
+ return b.bankname.toLowerCase() === inputValue.toLowerCase();
+ });
+ }
+
+ if (foundBank) {
+ // Найден банк - сохраняем ID и название
+ if (hiddenField) {
+ hiddenField.value = foundBank.bankid;
+ }
+ state.user = state.user || {};
+ state.user.bank_id = foundBank.bankid;
+ state.user.bank_name = foundBank.bankname; // ✅ Сохраняем название банка
+ this.classList.add('filled');
+ } else {
+ // Банк не найден - очищаем ID и название
+ if (hiddenField) {
+ hiddenField.value = '';
+ }
+ state.user = state.user || {};
+ state.user.bank_id = '';
+ state.user.bank_name = ''; // ✅ Очищаем название банка
+ this.classList.remove('filled');
+ }
+ updateFieldStyle(this);
+ updateSubmitButton();
+ });
+
+ // Обработчик выбора из списка
+ input.addEventListener('change', function() {
+ var inputValue = this.value.trim();
+ var foundBank = banks.find(function(b) {
+ return b.bankname.toLowerCase() === inputValue.toLowerCase();
+ });
+
+ if (foundBank) {
+ if (hiddenField) {
+ hiddenField.value = foundBank.bankid;
+ }
+ state.user = state.user || {};
+ state.user.bank_id = foundBank.bankid;
+ state.user.bank_name = foundBank.bankname; // ✅ Сохраняем название банка
+ this.classList.add('filled');
+ updateFieldStyle(this);
+ }
+ });
+ });
+ })
+ .catch(function(error) {
+ console.error('Error loading banks:', error);
+ Array.prototype.forEach.call(bankInputs, function(input) {
+ var datalistId = input.getAttribute('list');
+ var datalist = document.getElementById(datalistId);
+ if (datalist) {
+ datalist.innerHTML = '
Ошибка загрузки банков. Обновите страницу. ';
+ }
+ });
+ });
+ }
+
function initialize() {
try {
console.log('=== НАЧАЛО ИНИЦИАЛИЗАЦИИ ===');
@@ -1349,6 +1830,12 @@ export function generateConfirmationFormHTML(data: any): string {
renderStatement();
console.log('renderStatement completed');
+ // Загружаем список банков СБП
+ console.log('Loading banks...');
+ setTimeout(function() {
+ loadBanks();
+ }, 100);
+
// Валидируем уже заполненные поля
setTimeout(function(){
console.log('Starting field validation...');
@@ -1380,6 +1867,17 @@ export function generateConfirmationFormHTML(data: any): string {
return;
}
+ // Собираем bank_id из скрытых полей перед отправкой
+ var bankIdFields = document.querySelectorAll('.bank-id-field');
+ Array.prototype.forEach.call(bankIdFields, function(field) {
+ var root = field.getAttribute('data-root');
+ var bankId = field.value;
+ if (root === 'user' && bankId) {
+ state.user = state.user || {};
+ state.user.bank_id = bankId;
+ }
+ });
+
window.parent.postMessage({
type: 'claim_confirmed',
data: {
diff --git a/frontend/src/pages/ClaimForm.tsx b/frontend/src/pages/ClaimForm.tsx
index acb9180..a7d12ed 100644
--- a/frontend/src/pages/ClaimForm.tsx
+++ b/frontend/src/pages/ClaimForm.tsx
@@ -76,7 +76,8 @@ interface FormData {
fullName?: string;
email?: string;
paymentMethod?: string;
- bankName?: string;
+ bankId?: string; // ID банка из NSPK API
+ bankName?: string; // Название банка для отображения
cardNumber?: string;
accountNumber?: string;
}
@@ -107,7 +108,7 @@ export default function ClaimForm() {
const [hasDrafts, setHasDrafts] = useState(false);
useEffect(() => {
- // 🔥 VERSION CHECK: Если видишь это в консоли - фронт обновился!
+ // 🔥 VERSION CHECK: Если видишь это в консоли - фронт обновился!
console.log('🔥 ClaimForm v3.8 - 2025-11-20 15:10 - Fix session_id priority in loadDraft');
}, []);
@@ -559,6 +560,19 @@ export default function ClaimForm() {
const claim = data.claim;
const payload = claim.payload || {};
+ // ✅ Сохраняем флаги подтверждения данных контакта
+ const contact_data_confirmed = data.contact_data_confirmed || false;
+ const contact_data_can_edit = data.contact_data_can_edit !== false; // По умолчанию true
+ const contact_data_confirmed_at = data.contact_data_confirmed_at || null;
+ const contact_data_from_crm = data.contact_data_from_crm || null;
+
+ console.log('🔒 Статус данных контакта:', {
+ contact_data_confirmed,
+ contact_data_can_edit,
+ contact_data_confirmed_at,
+ has_crm_data: !!contact_data_from_crm
+ });
+
// ✅ Для telegram черновиков данные могут быть в payload.body
const body = payload.body || {};
const isTelegramFormat = !!payload.body;
@@ -613,8 +627,13 @@ export default function ClaimForm() {
const hasDocuments = Array.isArray(documentsMeta) && documentsMeta.length > 0;
const isDraft = claim.status_code === 'draft';
+ // ✅ НОВОЕ: Проверяем наличие form_draft (собранные данные из RAG)
+ const formDraft = payload.form_draft;
+ const hasFormDraft = !!(formDraft && formDraft.user && formDraft.offenders);
+ const isDraftDocsComplete = claim.status_code === 'draft_docs_complete';
+
const allStepsFilled = hasDescription && hasWizardPlan && hasAnswers && hasDocuments;
- const isReadyForConfirmation = allStepsFilled && isDraft;
+ const isReadyForConfirmation = (allStepsFilled && isDraft) || (hasFormDraft && isDraftDocsComplete);
console.log('🔍 Проверка полноты черновика:', {
hasDescription,
@@ -622,6 +641,8 @@ export default function ClaimForm() {
hasAnswers,
hasDocuments,
isDraft,
+ hasFormDraft,
+ isDraftDocsComplete,
allStepsFilled,
isReadyForConfirmation,
problemDescriptionFound: !!problemDescription,
@@ -707,24 +728,124 @@ export default function ClaimForm() {
// ✅ Если все шаги заполнены и статус = draft → переходим к форме подтверждения
if (isReadyForConfirmation) {
console.log('✅ Все шаги заполнены, преобразуем данные для формы подтверждения');
+ console.log('✅ hasFormDraft:', hasFormDraft, 'isDraftDocsComplete:', isDraftDocsComplete);
setIsPhoneVerified(true);
- // Преобразуем данные из БД в формат propertyName для формы подтверждения
- const claimPlanData = transformDraftToClaimPlanFormat({
- claim,
- payload,
- body,
- isTelegramFormat,
- finalClaimId,
- actualSessionId,
- currentFormData: formData,
- });
+ let claimPlanData;
+
+ // ✅ НОВОЕ: Если есть form_draft — используем его!
+ if (hasFormDraft && formDraft) {
+ console.log('✅ Используем form_draft из БД:', formDraft);
+ console.log('✅ project.description:', formDraft.project?.description);
+ console.log('✅ offenders:', formDraft.offenders);
+ console.log('✅ documentsMeta:', documentsMeta);
+ console.log('✅ documentsMeta[0]?.field_label:', documentsMeta[0]?.field_label);
+
+ const user = formDraft.user || {};
+ const project = formDraft.project || {};
+
+ // Преобразуем form_draft в формат propertyName (с правильными именами полей!)
+ claimPlanData = {
+ propertyName: {
+ applicant: {
+ // Маппинг полей user → applicant
+ first_name: user.firstname || '',
+ middle_name: user.secondname || '',
+ last_name: user.lastname || '',
+ phone: user.mobile || '',
+ email: user.email || '',
+ birth_date: user.birthday || '',
+ birth_place: user.birthplace || '',
+ address: user.mailingstreet || '',
+ inn: user.inn || '',
+ },
+ case: {
+ category: project.category || '',
+ direction: project.direction || '',
+ },
+ contract_or_service: {
+ subject: project.subject || '',
+ amount_paid: project.agrprice || '',
+ agreement_date: project.agrdate || '',
+ period_start: project.startdate || '',
+ period_end: project.finishdate || '',
+ country: project.country || '',
+ hotel: project.hotel || '',
+ },
+ offenders: (formDraft.offenders || []).map((o: any) => ({
+ name: o.accountname || '', // ✅ Форма ожидает 'name', а не 'accountname'
+ accountname: o.accountname || '', // Дублируем для совместимости
+ address: o.address || '',
+ email: o.email || '',
+ website: o.website || '',
+ phone: o.phone || '',
+ inn: o.inn || '',
+ ogrn: o.ogrn || '',
+ role: o.role || '',
+ })),
+ claim: {
+ description: project.description || problemDescription || '', // ✅ Описание проблемы
+ reason: project.category || '', // ✅ Причина обращения
+ },
+ meta: {
+ claim_id: finalClaimId,
+ unified_id: formData.unified_id || '',
+ session_token: actualSessionId,
+ },
+ // ✅ Используем field_label (человекочитаемые названия) вместо имён файлов
+ attachments_names: documentsMeta.map((d: any) => d.field_label || d.original_file_name || d.file_name || 'Документ'),
+ },
+ session_token: actualSessionId,
+ claim_id: finalClaimId,
+ prefix: 'clpr_',
+ };
+
+ console.log('✅ claimPlanData сформирован:', claimPlanData);
+ console.log('✅ claimPlanData.propertyName.claim.description:', claimPlanData.propertyName.claim.description);
+ console.log('✅ claimPlanData.propertyName.offenders:', claimPlanData.propertyName.offenders);
+ } else {
+ // Старый способ: преобразуем данные из БД
+ claimPlanData = transformDraftToClaimPlanFormat({
+ claim,
+ payload,
+ body,
+ isTelegramFormat,
+ finalClaimId,
+ actualSessionId,
+ currentFormData: formData,
+ });
+ }
+
+ console.log('✅ claimPlanData для формы подтверждения:', claimPlanData);
+
+ // ✅ Если данные подтверждены и есть данные из CRM - используем их
+ if (contact_data_confirmed && contact_data_from_crm) {
+ // Обновляем applicant данные из CRM
+ if (claimPlanData?.propertyName?.applicant) {
+ claimPlanData.propertyName.applicant = {
+ ...claimPlanData.propertyName.applicant,
+ first_name: contact_data_from_crm.firstname || claimPlanData.propertyName.applicant.first_name,
+ last_name: contact_data_from_crm.lastname || claimPlanData.propertyName.applicant.last_name,
+ middle_name: contact_data_from_crm.cf_1157 || claimPlanData.propertyName.applicant.middle_name,
+ inn: contact_data_from_crm.cf_1257 || claimPlanData.propertyName.applicant.inn,
+ birth_date: contact_data_from_crm.birthday || claimPlanData.propertyName.applicant.birth_date,
+ birth_place: contact_data_from_crm.cf_1263 || claimPlanData.propertyName.applicant.birth_place,
+ address: contact_data_from_crm.mailingstreet || claimPlanData.propertyName.applicant.address,
+ email: contact_data_from_crm.email || claimPlanData.propertyName.applicant.email,
+ phone: contact_data_from_crm.mobile || claimPlanData.propertyName.applicant.phone,
+ };
+ }
+ }
// Сохраняем данные заявления в formData
updateFormData({
claimPlanData: claimPlanData,
showClaimConfirmation: true,
+ // ✅ Флаги подтверждения данных
+ contact_data_confirmed: contact_data_confirmed,
+ contact_data_can_edit: contact_data_can_edit,
+ contact_data_confirmed_at: contact_data_confirmed_at,
});
// Переход к шагу подтверждения произойдёт автоматически через useEffect
@@ -900,17 +1021,18 @@ export default function ClaimForm() {
is_new_project: formData.is_new_project,
// Основные поля формы (для удобства в n8n)
- voucher: formData.voucher,
- phone: formData.phone,
+ voucher: formData.voucher,
+ phone: formData.phone,
email: formData.email,
- event_type: formData.eventType,
- payment_method: formData.paymentMethod,
- bank_name: formData.bankName,
- card_number: formData.cardNumber,
- account_number: formData.accountNumber,
+ event_type: formData.eventType,
+ payment_method: formData.paymentMethod,
+ bank_id: formData.bankId, // ID банка из NSPK API
+ bank_name: formData.bankName, // Название банка для отображения
+ card_number: formData.cardNumber,
+ account_number: formData.accountNumber,
// Старый блок документов + новые загрузки визарда (пока как есть)
- documents: formData.documents || {},
+ documents: formData.documents || {},
wizard_uploads: formData.wizardUploads || {},
// Всё состояние формы целиком — на всякий случай
@@ -947,7 +1069,7 @@ export default function ClaimForm() {
});
// Помечаем, что заявка отправлена, и показываем заглушку.
setIsSubmitted(true);
- message.success('Данные отправлены, заявка принята в обработку.');
+ message.success('Поздравляем! Ваше обращение направлено в Клиентправ.');
} catch (error) {
message.error('Ошибка соединения с сервером');
addDebugEvent('form', 'error', '❌ Ошибка соединения', { error: String(error) });
@@ -980,8 +1102,8 @@ export default function ClaimForm() {
// Шаг 1: Phone (телефон + SMS верификация)
stepsArray.push({
- title: 'Телефон',
- description: 'Подтверждение по SMS',
+ title: 'Вход',
+ description: 'Подтверждение телефона',
content: (
{
+ // Возвращаемся к списку заявок
+ setShowDraftSelection(true);
+ setSelectedDraftId(null);
+ setCurrentStep(0);
+ }}
onNext={nextStep}
addDebugEvent={addDebugEvent}
/>
@@ -1099,44 +1226,51 @@ export default function ClaimForm() {
content: (
setIsSubmitted(true)}
/>
),
});
}
- // Шаг 3: Policy (всегда)
- stepsArray.push({
- title: 'Проверка полиса',
- description: 'Полис ERV',
- content: (
-
- ),
- });
+ // Шаги для СТАРОГО флоу (страхование ERV) — НЕ показываем для нового флоу защиты прав
+ const isNewClaimFlow = formData.documents_required && formData.documents_required.length > 0;
+
+ if (!isNewClaimFlow) {
+ // Шаг 3: Policy (только для старого флоу)
+ stepsArray.push({
+ title: 'Проверка полиса',
+ description: 'Полис ERV',
+ content: (
+
+ ),
+ });
- // Шаг 4: Event Type Selection (всегда)
- stepsArray.push({
- title: 'Тип события',
- description: 'Выбор случая',
- content: (
-
- ),
- });
+ // Шаг 4: Event Type Selection (только для старого флоу)
+ stepsArray.push({
+ title: 'Тип события',
+ description: 'Выбор случая',
+ content: (
+
+ ),
+ });
+ }
- // Шаги 3+: Document Upload (динамически, если выбран eventType)
- if (formData.eventType && documentConfigs.length > 0) {
+ // Шаги Document Upload (только для старого флоу — если выбран eventType)
+ if (!isNewClaimFlow && formData.eventType && documentConfigs.length > 0) {
documentConfigs.forEach((docConfig, index) => {
stepsArray.push({
title: `Документ ${index + 1}`,
@@ -1160,8 +1294,8 @@ export default function ClaimForm() {
// Последний шаг: Payment (всегда)
stepsArray.push({
- title: 'Оплата',
- description: 'Контакты и выплата',
+ title: 'Заявление',
+ description: 'Подтверждение',
content: (
0 && (
-
- 🔄 Начать заново
-
+
+ 🔄 Начать заново
+
)}
)
@@ -1277,22 +1411,22 @@ export default function ClaimForm() {
>
{isSubmitted ? (
-
Мы изучаем ваш вопрос и документы
+
Поздравляем! Ваше обращение направлено в Клиентправ.
- Заявка отправлена в работу. Юристы проверят информацию и свяжутся с вами по указанным контактам.
+ В ближайшее время на указанную Вами электронную почту поступит письмо, подтверждающее регистрацию вашего обращения.
) : (
<>
-
- {steps.map((item, index) => (
-
- ))}
-
+
+ {steps.map((item, index) => (
+
+ ))}
+
{steps[currentStep] ? steps[currentStep].content : (
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
index a80cc30..184500a 100644
--- a/frontend/vite.config.ts
+++ b/frontend/vite.config.ts
@@ -9,7 +9,15 @@ export default defineConfig({
proxy: {
'/api': {
target: 'http://host.docker.internal:8200',
- changeOrigin: true
+ changeOrigin: true,
+ // SSE support
+ configure: (proxy) => {
+ proxy.on('proxyRes', (proxyRes) => {
+ // Disable buffering for SSE
+ proxyRes.headers['cache-control'] = 'no-cache';
+ proxyRes.headers['x-accel-buffering'] = 'no';
+ });
+ }
},
'/events': {
target: 'http://host.docker.internal:8200',