Files
aiform_prod/frontend/src/components/form/generateConfirmationFormHTML.ts

1460 lines
57 KiB
TypeScript
Raw Normal View History

// Функция генерации HTML формы подтверждения заявления
// Основана на структуре из n8n Code node "Mini-app Подтверждение данных"
export function generateConfirmationFormHTML(data: any): string {
// Извлекаем SMS данные (до нормализации, так как структура может быть разной)
const smsInputData = {
prefix: data.sms_meta?.prefix || data.prefix || '',
session_token: data.session_token || '',
telegram_id: data.telegram_id || '',
claim_id: data.case?.meta?.claim_id || data.claim_id || data.propertyName?.meta?.claim_id || '',
unified_id: data.case?.meta?.unified_id || data.propertyName?.meta?.unified_id || '',
user_id: data.case?.meta?.user_id || data.propertyName?.meta?.user_id || '',
status: data.case?.meta?.status || data.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, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
// Нормализация денежной суммы
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,
};
// Функция нормализации данных из формата propertyName в формат case
function normalizeData(data: any): any {
console.log('=== НОРМАЛИЗАЦИЯ ДАННЫХ ===');
console.log('Input data:', data);
console.log('Has propertyName:', !!data.propertyName);
console.log('Has applicant in propertyName:', !!(data.propertyName && data.propertyName.applicant));
// Если данные приходят в новом формате (с propertyName)
if (data.propertyName && data.propertyName.applicant) {
console.log('Using NEW format with propertyName');
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 || {};
console.log('=== ОТЛАДКА КОНТРАКТА ===');
console.log('contract_or_service:', contract);
console.log('subject:', contract.subject);
console.log('agreement_date_fmt:', contract.agreement_date_fmt);
console.log('agreement_date:', contract.agreement_date);
console.log('period_start_fmt:', contract.period_start_fmt);
console.log('period_end_fmt:', contract.period_end_fmt);
// Получаем список приложенных документов
const attachments = props.attachments_names || [];
console.log('=== ОТЛАДКА ПРИЛОЖЕНИЙ ===');
console.log('attachments_names:', attachments);
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: Object.assign({}, 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,
}),
};
}
// Если данные приходят в старом формате (прямо applicant, case, etc)
if (data.applicant || data.case || data.contract_or_service) {
const applicant = data.applicant || {};
const caseData = data.case || {};
const contract = data.contract_or_service || {};
const offenders = data.offenders || [];
const claim = data.claim || {};
console.log('=== ОТЛАДКА КОНТРАКТА (старый формат) ===');
console.log('contract_or_service:', contract);
console.log('subject:', contract.subject);
console.log('agreement_date_fmt:', contract.agreement_date_fmt);
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: data.attachments_names || [],
offenders: offenders.map((o: any) => Object.assign({}, baseOffender, o || {})),
meta: data.meta || {},
};
}
// Если данные уже в формате case (наша текущая структура)
if (data.case) {
return data.case;
}
// Старый формат (обратная совместимость)
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 || {}),
attachments: data.attachments || [],
};
}
// Нормализуем данные
let normalizedCaseObj: any;
// Если данные приходят в формате propertyName (как из n8n)
if (data.propertyName) {
normalizedCaseObj = normalizeData(data);
} else if (data.case) {
// Данные уже в формате case
normalizedCaseObj = data.case;
} else {
// Пытаемся нормализовать из любого формата
normalizedCaseObj = normalizeData(data);
}
// Убеждаемся, что есть базовые структуры
if (!normalizedCaseObj.offenders || !normalizedCaseObj.offenders.length) {
normalizedCaseObj.offenders = [Object.assign({}, baseOffender)];
}
if (!normalizedCaseObj.user) normalizedCaseObj.user = Object.assign({}, baseUser, normalizedCaseObj.user || {});
if (!normalizedCaseObj.project) normalizedCaseObj.project = Object.assign({}, baseProject, normalizedCaseObj.project || {});
if (!normalizedCaseObj.meta) normalizedCaseObj.meta = {};
if (!normalizedCaseObj.attachments) normalizedCaseObj.attachments = [];
// Нормализуем сумму, если она пришла в виде строки
if (normalizedCaseObj.project && normalizedCaseObj.project.agrprice && typeof normalizedCaseObj.project.agrprice === 'string') {
const normalized = normalizeMoney(normalizedCaseObj.project.agrprice);
if (normalized !== null) {
normalizedCaseObj.project.agrprice = normalized;
}
}
// Используем нормализованные данные
const caseObj = normalizedCaseObj;
// Сервисные поля
const sessionToken = String(safeGet(caseObj.meta?.session_token, data.session_token, ''));
const telegramId = String(safeGet(caseObj.user?.tgid, data.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
// ✅ ВАЖНО: Передаем и case (нормализованный), и propertyName (оригинальный), чтобы JavaScript мог использовать оба формата
let caseJson = JSON.stringify({
case: caseObj,
propertyName: data.propertyName || null, // ✅ Добавляем propertyName для JavaScript кода
session_token: sessionToken,
telegram_id: telegramId,
token: data.token || '',
sms_meta: smsMetaData,
});
caseJson = caseJson.replace(/</g, '\\u003c');
// HTML шаблон (используем упрощенную версию из предоставленного кода)
// Полный код слишком большой, поэтому используем ключевые части
const html = `<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<title>Подтверждение данных</title>
<script src="https://telegram.org/js/telegram-web-app.js"></script>
<style>
*{box-sizing:border-box}
body{
font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;
background:linear-gradient(135deg,#667eea 0%,#764ba2 100%);
margin:0;padding:0;min-height:100vh;
color:#1f2937;
}
.wrap{
max-width:1400px;margin:0 auto;padding:20px;
min-height:100vh;display:flex;flex-direction:column;
}
.header{
text-align:center;margin-bottom:30px;color:white;
}
.header h1{
font-size:28px;font-weight:700;margin:0 0 8px;
text-shadow:0 2px 4px rgba(0,0,0,0.3);
}
.header p{
font-size:16px;opacity:0.9;margin:0;
}
.statement-container{
background:#fff;border-radius:16px;padding:32px;
box-shadow:0 4px 20px rgba(0,0,0,0.08);
line-height:1.8;font-size:15px;
max-width:800px;margin:0 auto;
}
.statement-text{
font-family:'Times New Roman',serif;
text-align:justify;
}
.inline-field{
display:inline-block;min-width:120px;max-width:300px;
border:2px solid #e5e7eb;border-radius:6px;
padding:4px 8px;margin:0 2px;background:#fff;
font-size:inherit;font-family:inherit;
transition:all 0.2s ease;
vertical-align:baseline;
}
.inline-field:focus{
outline:none;border-color:#667eea;
box-shadow:0 0 0 2px rgba(102,126,234,0.1);
background:#f8fafc;
}
.inline-field:hover{border-color:#d1d5db}
.inline-field.large{
min-width:200px;max-width:500px;
}
.inline-field.full-width{
display:block;width:100%;min-width:auto;max-width:none;
margin:8px 0;padding:8px 12px;
}
.readonly-field{
background-color:#f9fafb !important;
border-color:#d1d5db !important;
color:#6b7280 !important;
cursor:not-allowed !important;
font-weight:500;
}
.date-field{
min-width:140px !important;
max-width:160px !important;
cursor:pointer;
}
.section-break{
margin:24px 0;border-top:1px solid #e5e7eb;
padding-top:16px;
}
.checkbox-container{
display:flex;
align-items:flex-start;
gap:8px;
cursor:pointer;
padding:12px;
margin:8px 0;
border-radius:8px;
transition:background-color 0.2s ease;
}
.checkbox-container:hover{
background-color:rgba(102,126,234,0.05);
}
.checkbox-container.required-checkbox{
border:2px solid #e5e7eb;
}
.checkbox-container.required-checkbox.error{
border-color:#ef4444;
background-color:#fef2f2;
}
.checkbox-field{
width:18px;
height:18px;
margin:0;
cursor:pointer;
accent-color:#667eea;
}
.checkmark{
width:18px;
height:18px;
border:2px solid #d1d5db;
border-radius:4px;
position:relative;
transition:all 0.2s ease;
flex-shrink:0;
}
.checkbox-field:checked + .checkmark{
background-color:#667eea;
border-color:#667eea;
}
.checkbox-field:checked + .checkmark::after{
content:'✓';
position:absolute;
color:white;
font-size:12px;
font-weight:bold;
left:50%;
top:50%;
transform:translate(-50%, -50%);
}
.checkbox-field{
position:absolute;
opacity:0;
cursor:pointer;
}
.checkbox-label{
font-size:14px;
line-height:1.5;
color:#374151;
cursor:pointer;
}
.checkbox-label a{
color:#667eea;
text-decoration:none;
}
.checkbox-label a:hover{
text-decoration:underline;
}
.fade-in{
animation:fadeIn 0.3s ease;
}
@keyframes fadeIn{
from{opacity:0;transform:translateY(10px)}
to{opacity:1;transform:translateY(0)}
}
.buttons{
display:flex;gap:12px;margin-top:24px;
flex-wrap:wrap;
justify-content:center;
}
.btn{
appearance:none;border:0;border-radius:12px;
padding:12px 24px;font-weight:600;cursor:pointer;
font-size:14px;transition:all 0.2s ease;
display:flex;align-items:center;gap:8px;
text-decoration:none;justify-content:center;
min-width:120px;
}
.btn:disabled{
opacity:0.6;cursor:not-allowed;
}
.btn-primary{
background:linear-gradient(135deg,#667eea,#764ba2);
color:white;box-shadow:0 4px 12px rgba(102,126,234,0.4);
}
.btn-primary:hover:not(:disabled){
transform:translateY(-1px);
box-shadow:0 6px 16px rgba(102,126,234,0.5);
}
.btn-secondary{
background:#f3f4f6;color:#374151;
border:1px solid #d1d5db;
}
.btn-secondary:hover:not(:disabled){
background:#e5e7eb;transform:translateY(-1px);
}
@media(max-width:768px){
.wrap{padding:16px}
.buttons{flex-direction:column}
.btn{width:100%}
.header h1{font-size:24px}
}
</style>
</head>
<body>
<div class="wrap">
<div class="header">
<h1>📋 Редактирование заявления</h1>
<p>Проверьте и при необходимости отредактируйте все поля</p>
</div>
<div class="statement-container fade-in">
<div id="statement" class="statement-text">Загрузка</div>
<div class="buttons" style="margin-top:32px;justify-content:center">
<button id="confirmBtn" class="btn btn-primary">
Подтвердить и отправить
</button>
</div>
<div id="status" style="margin-top:16px"></div>
</div>
</div>
<script id="case-data" type="application/json">${caseJson}</script>
<script>
(function(){
console.log('=== СКРИПТ ЗАПУЩЕН ===');
function getData(){
try {
var dataEl = document.getElementById('case-data');
if (!dataEl) {
console.error('Элемент #case-data не найден!');
return {};
}
var textContent = dataEl.textContent || '{}';
var parsed = JSON.parse(textContent);
console.log('Parsed data:', parsed);
return parsed;
} catch(e) {
console.error('ОШИБКА ПАРСИНГА JSON:', e);
return {};
}
}
function tryParseJSON(x) {
try {
return typeof x === 'string' ? JSON.parse(x) : x;
} catch {
return null;
}
}
function normalizeMoney(rawValue) {
if (!rawValue) return null;
console.log('normalizeMoney: входящее значение:', rawValue, 'тип:', typeof rawValue);
var 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(',', '.');
console.log('normalizeMoney: после очистки:', s);
if (!/^-?[0-9]+(\\.[0-9]{1,2})?$/.test(s)) {
console.log('normalizeMoney: невалидный формат после очистки');
return null;
}
var result = parseFloat(s);
console.log('normalizeMoney: результат:', result);
return result > 0 ? result : null;
}
// Функция нормализации данных из формата propertyName в формат case
function normalizeData(data) {
console.log('=== НОРМАЛИЗАЦИЯ ДАННЫХ ===');
console.log('Input data:', data);
// Если данные приходят в новом формате (с propertyName)
if (data.propertyName && data.propertyName.applicant) {
console.log('Using NEW format with propertyName');
var props = data.propertyName;
var applicant = props.applicant || {};
var caseData = props.case || {};
var contract = props.contract_or_service || {};
var offenders = props.offenders || [];
var claim = props.claim || {};
var meta = props.meta || {};
var 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(function(o) {
return {
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: Object.assign({}, 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,
}),
};
}
// Если данные приходят в старом формате (прямо applicant, case, etc)
if (data.applicant || data.case || data.contract_or_service) {
var applicant = data.applicant || {};
var caseData = data.case || {};
var contract = data.contract_or_service || {};
var offenders = data.offenders || [];
var claim = data.claim || {};
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: data.attachments_names || [],
offenders: offenders.map(function(o) {
return {
accountname: o.name || o.accountname || 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: data.meta || {},
};
}
// Если данные уже в формате case (наша текущая структура)
if (data.case) {
return data.case;
}
// Старый формат (обратная совместимость)
return {
user: tryParseJSON(data.user) || data.user || {},
project: tryParseJSON(data.project) || data.project || {},
offenders: Array.isArray(data.offenders) ? data.offenders : [],
meta: data.meta || {},
attachments: data.attachments || [],
};
}
function esc(s){
if (s === null || s === undefined) return '';
return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/"/g,'&quot;');
}
function createField(root, key, value, placeholder, index) {
var id = 'field_' + root + '_' + key + '_' + (index !== undefined ? index + '_' : '') + Math.random().toString(36).slice(2);
var dataIndex = index !== undefined ? ' data-index="' + index + '"' : '';
var extra = '';
if (key === 'inn') {
var isIndividual = (root === 'user');
if (isIndividual) {
extra = ' inputmode="numeric" pattern="[0-9]{12}" maxlength="12" autocomplete="off"';
} else {
extra = ' inputmode="numeric" pattern="[0-9]{10,12}" maxlength="12" autocomplete="off"';
}
}
var fieldHtml = '<input class="inline-field bind" data-root="' + esc(root) + '" data-key="' + esc(key) + '"' + dataIndex +
' id="' + id + '" 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 '<input class="inline-field readonly-field" data-root="' + esc(root) + '" data-key="' + esc(key) + '" id="' + id + '" value="' + esc(value || '') + '" readonly />';
}
function createDateField(root, key, value) {
var id = 'field_' + root + '_' + key + '_' + Math.random().toString(36).slice(2);
var dateValue = '';
if (value) {
var cleanValue = String(value).trim();
if (cleanValue.match(/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/)) {
dateValue = cleanValue;
} else if (cleanValue.match(/^[0-9]{2}\.[0-9]{2}\.[0-9]{4}$/)) {
var parts = cleanValue.split('.');
dateValue = parts[2] + '-' + parts[1] + '-' + parts[0];
}
}
return '<input type="date" class="inline-field bind date-field" data-root="' + esc(root) + '" data-key="' + esc(key) + '" id="' + id + '" value="' + esc(dateValue) + '" />';
}
function createMoneyField(root, key, value) {
var id = 'field_' + root + '_' + key + '_' + Math.random().toString(36).slice(2);
return '<div style="display: flex; align-items: center; gap: 8px;">' +
'<input class="inline-field bind money-field" data-root="' + esc(root) + '" data-key="' + esc(key) + '"' +
' id="' + id + '" value="' + esc(value || '') + '"' +
' inputmode="decimal" pattern="[0-9]*[.,]?[0-9]*" autocomplete="off" />' +
'<span style="color: #6b7280; font-size: 14px;">рублей</span>' +
'</div>';
}
function createTextarea(root, key, value) {
var id = 'field_' + root + '_' + key + '_' + Math.random().toString(36).slice(2);
return '<textarea class="inline-field bind full-width" data-root="' + esc(root) + '" data-key="' + esc(key) + '" id="' + id + '">' + esc(value || '') + '</textarea>';
}
function createCheckbox(root, key, checked, labelText, required) {
var id = 'field_' + root + '_' + key + '_' + Math.random().toString(36).slice(2);
var checkedAttr = checked ? ' checked' : '';
var requiredClass = required ? ' required-checkbox' : '';
var checkboxHtml = '<label class="checkbox-container' + requiredClass + '" for="' + id + '">';
checkboxHtml += '<input type="checkbox" class="checkbox-field bind" data-root="' + esc(root) + '" data-key="' + esc(key) + '" id="' + id + '"' + checkedAttr + ' />';
checkboxHtml += '<span class="checkmark"></span>';
checkboxHtml += '<span class="checkbox-label">' + labelText + '</span>';
checkboxHtml += '</label>';
return checkboxHtml;
}
// Получаем данные
var injected = getData();
console.log('=== ПОЛУЧЕНЫ ДАННЫЕ ===');
console.log('injected:', injected);
console.log('injected.case:', injected.case);
console.log('injected.propertyName:', injected.propertyName);
// Достаём объект кейса из «типичных» мест
var dataCandidate = null;
// Если есть propertyName в корне, используем весь injected объект (он уже в правильном формате)
if (injected.propertyName !== undefined) {
dataCandidate = injected; // Используем весь объект, так как normalizeData ожидает { propertyName: {...} }
} else if (injected.value !== undefined) {
dataCandidate = tryParseJSON(injected.value) || injected;
} else if (injected.user || injected.project || injected.offenders || injected.meta) {
dataCandidate = injected;
} else if (injected.data) {
dataCandidate = injected.data;
} else if (injected.output) {
dataCandidate = tryParseJSON(injected.output) || injected.output;
} else if (injected.case) {
dataCandidate = { case: injected.case };
} else {
dataCandidate = injected;
}
console.log('dataCandidate:', dataCandidate);
console.log('Type of dataCandidate:', typeof dataCandidate);
console.log('Keys of dataCandidate:', Object.keys(dataCandidate || {}));
// Если dataCandidate - массив, берем первый элемент
var dataToNormalize = Array.isArray(dataCandidate) ? dataCandidate[0] : dataCandidate;
console.log('Data to normalize:', dataToNormalize);
// Нормализуем данные
var normalizedCase = normalizeData(dataToNormalize);
console.log('Normalized case:', normalizedCase);
// Формируем state из нормализованных данных
var state = normalizedCase || { user: {}, project: {}, offenders: [{}], meta: {}, attachments: [] };
// Убеждаемся, что есть базовые структуры
if (!state.offenders || !state.offenders.length) {
state.offenders = [{}];
}
if (!state.user) state.user = {};
if (!state.project) state.project = {};
if (!state.meta) state.meta = {};
if (!state.attachments) state.attachments = [];
// Добавляем SMS данные из injected
if (injected.sms_meta) {
state.meta = Object.assign({}, state.meta, injected.sms_meta);
}
if (injected.session_token) {
state.meta.session_token = injected.session_token;
}
if (injected.token) {
state.meta.token = injected.token;
}
console.log('Final state:', state);
function renderStatement() {
var u = state.user || {};
var o = (state.offenders && state.offenders[0]) || {};
var p = state.project || {};
var html = '';
html += '<div style="text-align:center;margin-bottom:32px">';
html += '<h2 style="font-size:20px;margin:0 0 16px;color:#1f2937">В МОО «Клиентправ»</h2>';
html += '<p style="margin:0;color:#6b7280">help@clientright.ru</p>';
html += '</div>';
html += '<p><strong>Заявитель:</strong> ';
html += createField('user', 'lastname', u.lastname, 'Фамилия (обязательно)');
html += ' ';
html += createField('user', 'firstname', u.firstname, 'Имя (обязательно)');
html += ' ';
html += createField('user', 'secondname', u.secondname, 'Отчество');
html += '</p>';
html += '<p><strong>Дата рождения:</strong> ';
html += createDateField('user', 'birthday', u.birthday);
html += '</p>';
html += '<p><strong>Место рождения:</strong> ';
html += createField('user', 'birthplace', u.birthplace, 'Место рождения (обязательно)');
html += '</p>';
html += '<p><strong>ИНН:</strong> ';
html += createField('user', 'inn', u.inn, '12-значный ИНН (обязательно)');
html += '</p>';
html += '<p><strong>Адрес:</strong> ';
html += createField('user', 'mailingstreet', u.mailingstreet, 'Адрес регистрации как в паспорте (обязательно)');
html += '</p>';
html += '<p><strong>Телефон:</strong> ';
html += createReadonlyField('user', 'mobile', u.mobile);
html += '</p>';
html += '<p><strong>E-mail:</strong> ';
html += createField('user', 'email', u.email, 'email@example.com');
html += '</p>';
html += '<div class="section-break"></div>';
// Возмещение
html += '<h3 style="font-size:16px;margin:0 0 16px;color:#1f2937">Возмещение:</h3>';
html += '<p>Выплата возмещения возможна по системе быстрых платежей (СБП) по номеру телефона заявителя: <strong id="phone-display">' + esc(u.mobile || '') + '</strong></p>';
html += '<div class="section-break"></div>';
// Заявление
html += '<h3 style="font-size:16px;margin:0 0 16px;color:#1f2937;text-align:center">ЗАЯВЛЕНИЕ</h3>';
// Тема обращения (только для чтения)
html += '<p><strong>Тема обращения:</strong> ';
html += createReadonlyField('project', 'category', p.category);
html += '</p>';
// Название договора / предмет
html += '<p><strong>Предмет договора:</strong> ';
html += createField('project', 'subject', p.subject, 'Название договора или предмет услуг');
html += '</p>';
// Дата события / заключения договора
html += '<p><strong>Дата события / заключения договора:</strong> ';
html += createDateField('project', 'agrdate', p.agrdate);
html += '</p>';
// Сумма оплаты / стоимость
html += '<p><strong>Сумма оплаты / стоимость:</strong> ';
html += createMoneyField('project', 'agrprice', p.agrprice);
html += '</p>';
// Период
html += '<p><strong>Период:</strong> ';
if (p.startdate || p.finishdate) {
html += 'с ';
html += createDateField('project', 'startdate', p.startdate);
html += ' по ';
html += createDateField('project', 'finishdate', p.finishdate);
} else {
html += createField('project', 'period_text', p.period_text, 'Период действия');
}
html += '</p>';
html += '<div class="section-break"></div>';
// Контрагенты / участники
html += '<h4 style="font-size:14px;margin:16px 0 12px;color:#1f2937">Контрагенты / участники:</h4>';
for (var i = 0; i < state.offenders.length; i++) {
var offender = state.offenders[i];
html += '<div style="margin-bottom:16px;padding:12px;border:1px solid #e5e7eb;border-radius:8px;">';
html += '<p><strong>Наименование:</strong> ';
html += createField('offender', 'accountname', offender.accountname, 'Название организации', i);
html += '</p>';
html += '<p><strong>ИНН:</strong> ';
html += createField('offender', 'inn', offender.inn, 'ИНН организации (10 или 12 цифр)', i);
html += '</p>';
html += '<p><strong>ОГРН:</strong> ';
html += createField('offender', 'ogrn', offender.ogrn, 'ОГРН', i);
html += '</p>';
html += '<p><strong>Адрес:</strong> ';
html += createField('offender', 'address', offender.address, 'Адрес', i);
html += '</p>';
html += '<p><strong>E-mail:</strong> ';
html += createField('offender', 'email', offender.email, 'email@example.com', i);
html += '</p>';
html += '<p><strong>Телефон:</strong> ';
html += createField('offender', 'phone', offender.phone, '+7 (___) ___-__-__', i);
html += '</p>';
html += '<p><strong>Сайт:</strong> ';
html += createField('offender', 'website', offender.website, 'https://example.com', i);
html += '</p>';
html += '</div>';
}
html += '<div class="section-break"></div>';
// Причина обращения (редактируемая)
html += '<p><strong>Причина обращения:</strong> ';
html += createField('project', 'reason', p.reason, 'Можете уточнить или изменить причину обращения');
html += '</p>';
html += '<p><strong>Описание проблемы:</strong></p>';
html += createTextarea('project', 'description', p.description);
html += '<p>На основании вышеизложенного и руководствуясь ст. 45 Закона «О защите прав потребителей», ст. 3, ч. 1 ст. 46 ГПК РФ, прошу вас защитить мои потребительские права, обратиться в суд с заявлением о защите моих потребительских прав и/или с коллективным иском о защите группы потребителей, и представлять мои интересы во всех судебных органах РФ, а также обращаться с заявлениями во все госорганы, подавать претензии, письма и жалобы.</p>';
html += '<div class="section-break"></div>';
// Согласие на обработку персональных данных
html += '<div style="margin:24px 0;">';
html += createCheckbox('meta', 'privacyConsent', state.meta && state.meta.privacyConsent,
'Я ознакомлен(а) и согласен(а) с <a href="https://clientright.ru/person" target="_blank">Политикой обработки персональных данных</a> и даю согласие на обработку моих персональных данных в соответствии с Федеральным законом от 27.07.2006 №152-ФЗ «О персональных данных»',
true);
html += '</div>';
// Список приложенных документов
if (state.attachments && state.attachments.length > 0) {
html += '<div class="section-break"></div>';
html += '<h4 style="font-size:14px;margin:16px 0 12px;color:#1f2937">Приложенные документы:</h4>';
html += '<div style="background:#f9fafb;padding:12px;border-radius:8px;border:1px solid #e5e7eb;">';
for (var i = 0; i < state.attachments.length; i++) {
var fileName = state.attachments[i];
html += '<div style="display:flex;align-items:center;margin-bottom:8px;padding:8px;background:white;border-radius:6px;border:1px solid #e5e7eb;">';
html += '<span style="color:#3b82f6;margin-right:8px;">📎</span>';
html += '<span style="color:#374151;font-size:14px;">' + esc(fileName) + '</span>';
html += '</div>';
}
html += '<p style="margin:8px 0 0;color:#6b7280;font-size:12px;">';
html += 'Всего документов: ' + state.attachments.length;
html += '</p>';
html += '</div>';
}
var statementEl = document.getElementById('statement');
if (statementEl) {
console.log('Setting innerHTML, HTML length:', html.length);
statementEl.innerHTML = html;
console.log('innerHTML set, calling attachBindings...');
attachBindings();
console.log('attachBindings completed');
// Обновляем состояние кнопки отправки
setTimeout(function() {
updateSubmitButton();
}, 100);
console.log('=== РЕНДЕРИНГ ЗАВЕРШЕН УСПЕШНО ===');
} else {
console.error('ОШИБКА: элемент #statement не найден в renderStatement!');
}
}
// Простые функции валидации
function isValidPhone(phone) {
if (!phone) return false;
var clean = phone.replace(/\D/g, '');
return clean.length >= 10 && clean.length <= 11;
}
function isValidEmail(email) {
if (!email) return false;
return email.includes('@') && email.includes('.') && email.length > 5;
}
function isNotEmpty(value) {
return value && value.trim().length > 0;
}
// Преобразования дат
function parseDDMMYYYY(s) {
if (!/^[0-9]{2}\.[0-9]{2}\.[0-9]{4}$/.test(s)) return null;
var [d,m,y] = s.split('.').map(Number);
var dt = new Date(y, m-1, d);
var isValid = (dt.getFullYear()===y && dt.getMonth()===m-1 && dt.getDate()===d);
return isValid ? dt : null;
}
function parseYMD(s) {
var cleanS = s.trim().replace(/[\u2012\u2013\u2014\u2015\u2212\uFF0D]/g, '-');
if (!/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/.test(cleanS)) return null;
var [y,m,d] = cleanS.split('-').map(Number);
var dt = new Date(y, m-1, d);
var isValid = (dt.getFullYear()===y && dt.getMonth()===m-1 && dt.getDate()===d);
return isValid ? dt : null;
}
function toYMD(dt) {
var y = dt.getFullYear();
var m = String(dt.getMonth()+1).padStart(2,'0');
var d = String(dt.getDate()).padStart(2,'0');
return y+'-'+m+'-'+d;
}
function isValidINN(inn, isIndividual) {
if (!inn) return false;
var clean = String(inn).replace(/\D/g, '');
if (isIndividual) {
return clean.length === 12;
} else {
return clean.length === 10 || clean.length === 12;
}
}
function isValidName(name) {
if (!name) return false;
var trimmed = name.trim();
if (trimmed.length < 2) return false;
var hasCyrillic = /[а-яёА-ЯЁ]/.test(trimmed);
var hasInvalidChars = /[a-zA-Z0-9]/.test(trimmed);
return hasCyrillic && !hasInvalidChars;
}
function isValidAddress(address) {
if (!address) return false;
var trimmed = address.trim();
if (trimmed.length < 10) return false;
var hasNumbers = /[0-9]/.test(trimmed);
var cyrillicPattern = new RegExp('[а-яёА-ЯЁ]');
var hasLetters = cyrillicPattern.test(trimmed);
return hasNumbers && hasLetters;
}
function isValidMoney(v) {
if (!v) return false;
var s = String(v).replace(/\s+/g,'').replace(',', '.');
if (!/^[0-9]+(\.[0-9]{1,2})?$/.test(s)) return false;
return parseFloat(s) > 0;
}
// Функция проверки всех обязательных полей
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() === '')) {
errors.push(field.name);
}
}
return errors;
}
// Функция обновления состояния кнопки отправки
function updateSubmitButton() {
var confirmBtn = document.getElementById('confirmBtn');
if (!confirmBtn) return;
var isConsentGiven = state.meta && state.meta.privacyConsent === true;
var validationErrors = validateAllFields();
if (!isConsentGiven) {
confirmBtn.disabled = true;
confirmBtn.style.opacity = '0.6';
confirmBtn.style.cursor = 'not-allowed';
confirmBtn.textContent = '✅ Подтвердить и отправить';
} else if (validationErrors.length === 0) {
confirmBtn.disabled = false;
confirmBtn.style.opacity = '1';
confirmBtn.style.cursor = 'pointer';
confirmBtn.textContent = '✅ Подтвердить и отправить';
} else {
confirmBtn.disabled = true;
confirmBtn.style.opacity = '0.6';
confirmBtn.style.cursor = 'not-allowed';
confirmBtn.textContent = '❌ Заполните все поля (' + validationErrors.length + ')';
confirmBtn.title = 'Не заполнены: ' + validationErrors.join(', ');
}
}
function attachBindings() {
console.log('Attaching bindings...');
var fields = document.querySelectorAll('.bind');
console.log('Found fields:', fields.length);
Array.prototype.forEach.call(fields, function(field) {
// Обработка ввода
field.addEventListener('input', function() {
// Автозамена запятой на точку для денежных полей
if (this.classList.contains('money-field') && this.value.includes(',')) {
this.value = this.value.replace(/,/g, '.');
}
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 для сохранения
if (this.classList.contains('date-field') && value) {
var parts = value.split('-');
if (parts.length === 3) {
value = parts[2] + '.' + parts[1] + '.' + parts[0];
}
}
console.log('Field changed:', root, key, value);
// Обновляем состояние
if (root === 'user') {
state.user = state.user || {};
state.user[key] = value;
// Обновляем телефон в СБП
if (key === 'mobile') {
var phoneDisplay = document.getElementById('phone-display');
if (phoneDisplay) {
phoneDisplay.textContent = value;
}
}
} else if (root === 'project') {
state.project = state.project || {};
state.project[key] = value;
} else if (root === 'offender') {
var index = parseInt(this.getAttribute('data-index') || '0', 10);
state.offenders[index] = state.offenders[index] || {};
state.offenders[index][key] = value;
} else if (root === 'meta') {
state.meta = state.meta || {};
state.meta[key] = value;
// Для чекбокса согласия обновляем состояние кнопки
if (key === 'privacyConsent') {
updateSubmitButton();
}
}
// Обновляем состояние кнопки при изменении любого поля
updateSubmitButton();
});
// Блокировка нечисловых символов для ИНН
field.addEventListener('keydown', function(e) {
var key = this.getAttribute('data-key');
if (key === 'inn') {
var isDigit = (e.key >= '0' && e.key <= '9');
var isServiceKey = ['Backspace', 'Delete', 'Tab', 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End'].includes(e.key);
var isCtrlKey = e.ctrlKey && ['a', 'c', 'v', 'x', 'z'].includes(e.key.toLowerCase());
if (!isDigit && !isServiceKey && !isCtrlKey) {
e.preventDefault();
}
}
});
// Фильтрация при вставке для ИНН
field.addEventListener('paste', function(e) {
var key = this.getAttribute('data-key');
if (key === 'inn') {
e.preventDefault();
var paste = (e.clipboardData || window.clipboardData).getData('text');
var cleanPaste = paste.replace(/\D/g, '');
var root = this.getAttribute('data-root');
var isIndividual = (root === 'user');
var maxLength = 12;
cleanPaste = cleanPaste.slice(0, maxLength);
this.value = cleanPaste;
var inputEvent = new Event('input', { bubbles: true });
this.dispatchEvent(inputEvent);
}
});
// Валидация при потере фокуса
field.addEventListener('blur', function() {
updateSubmitButton();
});
// Дополнительная обработка для чекбоксов
if (field.type === 'checkbox') {
field.addEventListener('change', function() {
var root = this.getAttribute('data-root');
var key = this.getAttribute('data-key');
var value = this.checked;
if (root === 'meta' && key === 'privacyConsent') {
state.meta = state.meta || {};
state.meta[key] = value;
updateSubmitButton();
var container = this.closest('.checkbox-container');
if (container) {
if (value) {
container.classList.remove('error');
} else {
container.classList.add('error');
}
}
}
});
}
});
}
function initialize() {
try {
console.log('=== НАЧАЛО ИНИЦИАЛИЗАЦИИ ===');
console.log('injected data:', injected);
console.log('state before render:', state);
// Проверяем, что элемент statement существует
var statementEl = document.getElementById('statement');
console.log('statement element found:', !!statementEl);
if (!statementEl) {
console.error('КРИТИЧЕСКАЯ ОШИБКА: элемент #statement не найден!');
return;
}
// Инициализируем поля для шаблонов, если их нет
if (!state.project.serviceType) state.project.serviceType = 'education';
if (!state.project.customServiceType) state.project.customServiceType = '';
if (!state.project.customReason) state.project.customReason = '';
// Добавляем поле согласия на обработку персданных
if (state.meta.privacyConsent === undefined) state.meta.privacyConsent = false;
console.log('Calling renderStatement...');
renderStatement();
console.log('renderStatement completed');
// Валидируем уже заполненные поля
setTimeout(function(){
console.log('Starting field validation...');
var fields = document.querySelectorAll('.bind');
console.log('Found fields for validation:', fields.length);
Array.prototype.forEach.call(fields, function(field){
// Валидация будет выполнена при первом взаимодействии
});
console.log('Initial validation completed');
}, 100);
var confirmBtn = document.getElementById('confirmBtn');
console.log('confirmBtn found:', !!confirmBtn);
if (confirmBtn) {
confirmBtn.addEventListener('click', function(e) {
e.preventDefault();
console.log('Confirm button clicked');
// Проверяем согласие
if (!state.meta || !state.meta.privacyConsent) {
alert('Необходимо дать согласие на обработку персональных данных');
return;
}
// Проверяем валидность полей
var validationErrors = validateAllFields();
if (validationErrors.length > 0) {
alert('Заполните все обязательные поля: ' + validationErrors.join(', '));
return;
}
window.parent.postMessage({
type: 'claim_confirmed',
data: {
user: state.user,
project: state.project,
offenders: state.offenders,
meta: state.meta
},
originalData: injected
}, '*');
});
}
// Делегированная фильтрация ИНН
if (!window.__innFilterAttached) {
document.addEventListener('input', function (e) {
var el = e.target;
if (!el || !el.classList) return;
var isInn = el.classList.contains('bind') && el.getAttribute('data-key') === 'inn';
if (!isInn) return;
// Логика из test/index.php - фильтрация по типу ИНН
var root = el.getAttribute('data-root');
var isIndividual = (root === 'user'); // user = физлицо, offender = юрлицо
// Оставляем только цифры
var v = (el.value || '').replace(/\D/g, '');
if (isIndividual) {
// Физлицо: максимум 12 цифр (как js-inn-mask: "999999999999")
v = v.slice(0, 12);
} else {
// Юрлицо: максимум 12 цифр, но валидны 10 или 12 (как js-inn-mask2: "9{10,12}")
v = v.slice(0, 12);
}
if (el.value !== v) el.value = v;
// Синхронизируем state
var root = el.getAttribute('data-root');
if (root === 'user') {
state.user = state.user || {};
state.user.inn = v;
} else if (root === 'offender') {
var idx = parseInt(el.getAttribute('data-index') || '0', 10);
state.offenders = state.offenders || [];
state.offenders[idx] = state.offenders[idx] || {};
state.offenders[idx].inn = v;
}
// Моментальная валидация и обновление кнопки
updateSubmitButton();
}, { passive: true });
window.__innFilterAttached = true;
}
// Функция для отправки размера iframe родителю
function sendIframeSize() {
try {
var height = Math.max(
document.body.scrollHeight,
document.body.offsetHeight,
document.documentElement.clientHeight,
document.documentElement.scrollHeight,
document.documentElement.offsetHeight
);
window.parent.postMessage({
type: 'iframe_resize',
height: height
}, '*');
} catch (e) {
console.warn('Не удалось отправить размер iframe:', e);
}
}
// Отправляем сообщение родителю при загрузке
window.parent.postMessage({type: 'claim_form_loaded'}, '*');
// Отправляем размер iframe после небольшой задержки для полной загрузки
setTimeout(function() {
sendIframeSize();
}, 500);
// Отправляем размер при изменении содержимого
var resizeObserver = null;
if (window.ResizeObserver) {
resizeObserver = new ResizeObserver(function() {
sendIframeSize();
});
resizeObserver.observe(document.body);
}
// Также отправляем размер при изменении размера окна
window.addEventListener('resize', function() {
setTimeout(sendIframeSize, 100);
});
console.log('=== ИНИЦИАЛИЗАЦИЯ ЗАВЕРШЕНА ===');
} catch (e) {
console.error('=== ОШИБКА ИНИЦИАЛИЗАЦИИ ===');
console.error('Error details:', e);
console.error('Error stack:', e.stack);
var statementEl = document.getElementById('statement');
if (statementEl) {
statementEl.innerHTML = '<p style="color:red;padding:20px;">Ошибка загрузки: ' + e.message + '<br><br>Проверьте консоль браузера (F12) для деталей.</p>';
}
}
}
// Запускаем инициализацию когда DOM готов
if (document.readyState === 'loading') {
console.log('DOM еще загружается, ждем события DOMContentLoaded...');
document.addEventListener('DOMContentLoaded', function() {
console.log('DOMContentLoaded fired, starting initialization...');
initialize();
});
} else {
console.log('DOM уже готов, запускаем инициализацию сразу...');
initialize();
}
})();
</script>
</body>
</html>`;
return html;
}