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 || 'не указан')}
-
-
-
-
-
-
-
-
-`;
- };
-
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;
+}
+