diff --git a/frontend/src/components/form/StepClaimConfirmation.tsx b/frontend/src/components/form/StepClaimConfirmation.tsx index 9b9fbd4..223c48d 100644 --- a/frontend/src/components/form/StepClaimConfirmation.tsx +++ b/frontend/src/components/form/StepClaimConfirmation.tsx @@ -1,5 +1,6 @@ import { useEffect, useRef, useState } from 'react'; import { Card, Spin, message } from 'antd'; +import { generateConfirmationFormHTML } from './generateConfirmationFormHTML'; interface Props { claimPlanData: any; // Данные заявления от n8n @@ -103,374 +104,6 @@ export default function StepClaimConfirmation({ setLoading(false); }, [claimPlanData]); - // Функция генерации HTML формы подтверждения заявления - const generateConfirmationFormHTML = (data: any): string => { - const user = data.case?.user || {}; - const project = data.case?.project || {}; - const offenders = data.case?.offenders || []; - const offender = offenders[0] || {}; - const meta = data.case?.meta || {}; - const attachments = data.case?.attachments || []; - - // Экранируем данные для безопасной вставки в HTML - const caseJson = JSON.stringify(data) - .replace(//g, '\\u003e'); - - // Вспомогательная функция для экранирования HTML - const esc = (str: any): string => { - if (str === null || str === undefined) return ''; - return String(str) - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"') - .replace(/'/g, '''); - }; - - return ` - - - - -Подтверждение данных заявления - - - - -
-

📋 Подтверждение данных заявления

-

Проверьте и при необходимости отредактируйте данные перед отправкой

- -
-

Статус: Данные заявления получены

-

Claim ID: ${esc(meta.claim_id || 'не указан')}

-

Unified ID: ${esc(meta.unified_id || 'не указан')}

-
- -
- -
-
Данные заявителя
-
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
-
- - -
-
-
- - -
-
- - -
-
-
- - -
-
Данные дела
-
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
-
- - -
-
- - -
-
- - -
-
Данные нарушителя
-
- - -
-
- - -
-
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
-
- - - ${attachments.length > 0 ? ` -
-
Прикрепленные документы
-
- ${attachments.map(function(att, idx) { - return '
' + (idx + 1) + '. ' + esc(att) + '
'; - }).join('')} -
-
- ` : ''} - -
- - -
-
-
- - - - -`; - }; - useEffect(() => { // Слушаем сообщения от iframe const handleMessage = (event: MessageEvent) => { diff --git a/frontend/src/components/form/generateConfirmationFormHTML.ts b/frontend/src/components/form/generateConfirmationFormHTML.ts new file mode 100644 index 0000000..0f97f1a --- /dev/null +++ b/frontend/src/components/form/generateConfirmationFormHTML.ts @@ -0,0 +1,608 @@ +// Функция генерации HTML формы подтверждения заявления +// Основана на структуре из n8n Code node "Mini-app Подтверждение данных" + +export function generateConfirmationFormHTML(data: any): string { + // Нормализуем данные + const raw = { + propertyName: data.case || {}, + session_token: data.session_token || '', + prefix: data.sms_meta?.prefix || '', + telegram_id: data.telegram_id || '', + claim_id: data.case?.meta?.claim_id || '', + unified_id: data.case?.meta?.unified_id || '', + user_id: data.case?.meta?.user_id || '', + token: data.token || '', + }; + + // Извлекаем SMS данные + const smsInputData = { + prefix: raw.prefix || '', + session_token: raw.session_token || '', + telegram_id: raw.telegram_id || '', + claim_id: raw.claim_id || '', + unified_id: (raw.propertyName && raw.propertyName.meta && raw.propertyName.meta.unified_id) || '', + user_id: (raw.propertyName && raw.propertyName.meta && raw.propertyName.meta.user_id) || '', + status: (raw.propertyName && raw.propertyName.meta && raw.propertyName.meta.status) || '', + }; + + // Утилиты + function safeGet(...keys: any[]): any { + for (const k of keys) { + if (k === undefined || k === null) continue; + if (typeof k === 'object') { + if (Object.keys(k).length) return k; + continue; + } + const s = String(k).trim(); + if (s !== '') return k; + } + return ''; + } + + function tryParseJSON(x: any): any { + try { + return typeof x === 'string' ? JSON.parse(x) : x; + } catch { + return null; + } + } + + function escapeHtml(str: any): string { + if (str === undefined || str === null) return ''; + return String(str) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + } + + // Нормализация денежной суммы + function normalizeMoney(rawValue: any): number | null { + if (!rawValue) return null; + let s = String(rawValue); + s = s.replace(/\s+/g, ''); + s = s.replace(/руб(лей|ль|\.)?/gi, ''); + s = s.replace(/₽|р\.|р$/gi, ''); + s = s.replace(/[^0-9,.-]/g, ''); + s = s.replace(',', '.'); + if (!/^-?[0-9]+(\.[0-9]{1,2})?$/.test(s)) return null; + const result = parseFloat(s); + return result > 0 ? result : null; + } + + // Базовые схемы + const baseUser = { + firstname: null, + secondname: null, + lastname: null, + mobile: null, + email: null, + tgid: null, + birthday: null, + birthplace: null, + mailingstreet: null, + inn: null, + }; + + const baseProject = { + category: null, + direction: null, + agrprice: null, + subject: null, + agrdate: null, + startdate: null, + finishdate: null, + period_text: null, + description: null, + reason: null, + }; + + const baseOffender = { + accountname: null, + address: null, + email: null, + website: null, + phone: null, + inn: null, + ogrn: null, + }; + + // Нормализация данных + function normalizeData(data: any): any { + // Если данные приходят в новом формате (с propertyName) + if (data.propertyName && data.propertyName.applicant) { + const props = data.propertyName; + const applicant = props.applicant || {}; + const caseData = props.case || {}; + const contract = props.contract_or_service || {}; + const offenders = props.offenders || []; + const claim = props.claim || {}; + const meta = props.meta || {}; + const attachments = props.attachments_names || []; + + return { + user: { + firstname: applicant.first_name || null, + secondname: applicant.middle_name || null, + lastname: applicant.last_name || null, + mobile: applicant.phone || null, + email: applicant.email || null, + birthday: applicant.birth_date_fmt || applicant.birth_date || null, + birthplace: applicant.birth_place || null, + mailingstreet: applicant.address || null, + inn: applicant.inn || null, + tgid: null, + }, + project: { + category: caseData.category || null, + direction: caseData.direction || null, + agrprice: normalizeMoney(contract.amount_paid_fmt || contract.amount_paid) || null, + subject: contract.subject || null, + agrdate: contract.agreement_date_fmt || contract.agreement_date || null, + startdate: contract.period_start_fmt || contract.period_start || null, + finishdate: contract.period_end_fmt || contract.period_end || null, + period_text: contract.period_text || null, + description: claim.description || null, + reason: claim.reason || caseData.category || null, + }, + attachments: attachments, + offenders: offenders.map((o: any) => ({ + accountname: o.name || null, + address: o.address || null, + email: o.email || null, + website: o.website || null, + phone: o.phone || null, + inn: o.inn || null, + ogrn: o.ogrn || null, + })), + meta: { + ...meta, + session_token: data.session_token || meta.claim_id || null, + prefix: data.prefix || null, + telegram_id: data.telegram_id || null, + claim_id: data.claim_id || meta.claim_id || null, + unified_id: meta.unified_id || null, + user_id: meta.user_id || null, + }, + }; + } + + // Старый формат (обратная совместимость) + return { + user: Object.assign({}, baseUser, tryParseJSON(data.user) || data.user || {}), + project: Object.assign({}, baseProject, tryParseJSON(data.project) || data.project || {}), + offenders: Array.isArray(data.offenders) + ? data.offenders.map((o: any) => Object.assign({}, baseOffender, o || {})) + : [], + meta: Object.assign({}, data.meta || {}), + }; + } + + // Нормализуем данные + let dataCandidate = null; + if (raw.propertyName !== undefined) { + if (typeof raw.propertyName === 'object' && raw.propertyName !== null) { + dataCandidate = raw.propertyName; + } else if (typeof raw.propertyName === 'string') { + dataCandidate = tryParseJSON(raw.propertyName); + } + } + if (!dataCandidate && raw.value !== undefined) dataCandidate = tryParseJSON(raw.value); + if (!dataCandidate && (raw.user || raw.project || raw.offenders || raw.meta)) + dataCandidate = raw; + if (!dataCandidate && raw.data) dataCandidate = raw.data; + if (!dataCandidate && raw.output) dataCandidate = tryParseJSON(raw.output) || raw.output; + dataCandidate = dataCandidate || {}; + + const dataToNormalize = Array.isArray(dataCandidate) ? dataCandidate[0] : dataCandidate; + const caseObj = normalizeData({ propertyName: dataToNormalize, ...raw }); + if (!caseObj.offenders.length) { + caseObj.offenders = [Object.assign({}, baseOffender)]; + } + + // Сервисные поля + const sessionToken = String(safeGet(caseObj.meta?.session_token, raw.session_token, '')); + const telegramId = String(safeGet(caseObj.user?.tgid, raw.telegram_id, '')); + + const smsMetaData = { + session_token: String(safeGet(smsInputData.session_token, sessionToken, '')), + prefix: String(safeGet(smsInputData.prefix, '')), + telegram_id: String(safeGet(smsInputData.telegram_id, telegramId, '')), + claim_id: String(safeGet(smsInputData.claim_id, '')), + unified_id: String(safeGet(smsInputData.unified_id, '')), + user_id: String(safeGet(smsInputData.user_id, '')), + }; + + // Безопасно встраиваем данные в HTML + let caseJson = JSON.stringify({ + case: caseObj, + session_token: sessionToken, + telegram_id: telegramId, + token: raw.token || '', + sms_meta: smsMetaData, + }); + caseJson = caseJson.replace(/ + + + + +Подтверждение данных + + + + +
+
+

📋 Редактирование заявления

+

Проверьте и при необходимости отредактируйте все поля

+
+ +
+
Загрузка…
+ +
+ +
+ +
+
+
+ + + + +`; + + return html; +} +