2174 lines
92 KiB
JavaScript
2174 lines
92 KiB
JavaScript
// Code node: Mini-app "Подтверждение данных" — фикс без вложенных ${} в шаблоне
|
||
|
||
// --- ВХОД ---
|
||
const raw = $input.all()?.[0]?.json ?? {};
|
||
|
||
// Извлекаем SMS данные из входящих данных n8n
|
||
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) || ''
|
||
};
|
||
|
||
console.log('SMS input data from n8n:', smsInputData);
|
||
|
||
// --- Утилиты ---
|
||
function safeGet(...keys) {
|
||
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) { try { return typeof x === 'string' ? JSON.parse(x) : x; } catch { return null; } }
|
||
function escapeHtml(str){
|
||
if (str === undefined || str === null) return '';
|
||
return String(str).replace(/&/g,'&').replace(/</g,'<')
|
||
.replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,''');
|
||
}
|
||
|
||
// Нормализация денежной суммы из любого формата в число
|
||
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;
|
||
}
|
||
|
||
// --- Достаём объект кейса из «типичных» мест ---
|
||
let dataCandidate = null;
|
||
if (!dataCandidate && raw.propertyName !== undefined) {
|
||
// Если propertyName - это объект (как в вашем случае), берем его напрямую
|
||
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 || {};
|
||
|
||
console.log('Raw dataCandidate:', dataCandidate);
|
||
console.log('Type of dataCandidate:', typeof dataCandidate);
|
||
console.log('Keys of dataCandidate:', Object.keys(dataCandidate || {}));
|
||
|
||
// --- Базовые схемы ---
|
||
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) {
|
||
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');
|
||
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 || {};
|
||
|
||
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);
|
||
|
||
// Получаем список приложенных документов
|
||
var 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(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, {
|
||
// Добавляем SMS данные из корня элемента массива
|
||
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 || {};
|
||
|
||
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(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: data.meta || {}
|
||
};
|
||
}
|
||
|
||
// Старый формат (обратная совместимость)
|
||
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(function(o) {
|
||
return Object.assign({}, baseOffender, o || {});
|
||
}) : [],
|
||
meta: Object.assign({}, data.meta || {})
|
||
};
|
||
}
|
||
|
||
// Если dataCandidate - массив, берем первый элемент
|
||
var dataToNormalize = Array.isArray(dataCandidate) ? dataCandidate[0] : dataCandidate;
|
||
console.log('Data to normalize:', dataToNormalize);
|
||
|
||
const caseObj = normalizeData(dataToNormalize);
|
||
if (!caseObj.offenders.length) caseObj.offenders = [ Object.assign({}, baseOffender) ];
|
||
|
||
console.log('Normalized caseObj:', caseObj);
|
||
|
||
// --- Куда постить подтверждение ---
|
||
const webhookUrl = String(safeGet(
|
||
raw.webhook_url, raw.confirm_url, raw.CONFIRM_URL, raw.WIZARD_POST_URL,
|
||
raw.headers?.['x-webhook-url'], raw.headers?.['x-confirm-url'], ''
|
||
)) || 'https://n8n.clientright.pro/webhook/miniapp/confirm';
|
||
|
||
// --- Сервисные поля (для отправки вместе с формой) ---
|
||
const sessionToken = String(safeGet(caseObj.meta?.session_token, raw.session_token, raw.query?.session_token, ''));
|
||
const telegramId = String(safeGet(caseObj.user?.tgid, raw.telegram_id, raw.query?.telegram_id, raw.tg_id, raw.chat_id, ''));
|
||
|
||
// Дополнительные поля для SMS - берем из входящих данных n8n
|
||
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, ''))
|
||
};
|
||
|
||
console.log('dataToNormalize keys:', Object.keys(dataToNormalize || {}));
|
||
console.log('dataToNormalize.prefix:', dataToNormalize.prefix);
|
||
console.log('dataToNormalize.session_token:', dataToNormalize.session_token);
|
||
|
||
// Читаем дополнительные данные из URL параметров
|
||
try {
|
||
const urlParams = new URLSearchParams(window.location.search);
|
||
var urlMetaData = {
|
||
session_token: urlParams.get('session_token') || '',
|
||
prefix: urlParams.get('prefix') || '',
|
||
telegram_id: urlParams.get('telegram_id') || urlParams.get('tg_id') || '',
|
||
claim_id: urlParams.get('claim_id') || ''
|
||
};
|
||
console.log('URL search string:', window.location.search);
|
||
console.log('URL meta data:', urlMetaData);
|
||
} catch (e) {
|
||
console.error('Error reading URL params:', e);
|
||
var urlMetaData = {};
|
||
}
|
||
console.log('SMS meta data extracted:', smsMetaData);
|
||
|
||
// --- Безопасно встраиваем данные в HTML ---
|
||
let caseJson = JSON.stringify({
|
||
case: caseObj,
|
||
session_token: sessionToken,
|
||
telegram_id: telegramId,
|
||
token: raw.token || '', // Добавляем token для отправки формы
|
||
sms_meta: Object.assign({}, smsMetaData, urlMetaData) // Объединяем данные из JSON и URL
|
||
});
|
||
caseJson = caseJson.replace(/</g, '\\u003c'); // защита </script>
|
||
|
||
// --- 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;
|
||
}
|
||
.grid{
|
||
display:grid;grid-template-columns:1fr;gap:24px;flex:1;
|
||
}
|
||
@media(min-width:1200px){.grid{grid-template-columns:1fr 1fr}}
|
||
.card{
|
||
background:rgba(255,255,255,0.95);
|
||
backdrop-filter:blur(10px);
|
||
border-radius:20px;
|
||
box-shadow:0 20px 40px rgba(0,0,0,0.1);
|
||
padding:24px;
|
||
border:1px solid rgba(255,255,255,0.2);
|
||
transition:all 0.3s ease;
|
||
}
|
||
.card:hover{
|
||
transform:translateY(-2px);
|
||
box-shadow:0 25px 50px rgba(0,0,0,0.15);
|
||
}
|
||
.section{
|
||
margin-bottom:32px;
|
||
}
|
||
.section:last-child{margin-bottom:0}
|
||
.section-title{
|
||
font-size:20px;font-weight:600;margin:0 0 20px;
|
||
color:#1f2937;
|
||
display:flex;align-items:center;gap:8px;
|
||
}
|
||
.section-title::before{
|
||
content:'';width:4px;height:20px;
|
||
background:linear-gradient(135deg,#667eea,#764ba2);
|
||
border-radius:2px;
|
||
}
|
||
.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;
|
||
}
|
||
.inline-field.required-empty{
|
||
border-color:#ef4444 !important;
|
||
background-color:#fef2f2 !important;
|
||
box-shadow:0 0 0 2px rgba(239,68,68,0.1) !important;
|
||
}
|
||
.inline-field.valid{
|
||
border-color:#10b981 !important;
|
||
background-color:#f0fdf4 !important;
|
||
}
|
||
.inline-field.verified{
|
||
border-color:#3b82f6 !important;
|
||
background-color:#eff6ff !important;
|
||
}
|
||
.date-field{
|
||
min-width:140px !important;
|
||
max-width:160px !important;
|
||
cursor:pointer;
|
||
}
|
||
.date-field::-webkit-calendar-picker-indicator{
|
||
cursor:pointer;
|
||
padding:2px;
|
||
border-radius:4px;
|
||
transition:background-color 0.2s ease;
|
||
}
|
||
.date-field::-webkit-calendar-picker-indicator:hover{
|
||
background-color:rgba(102,126,234,0.1);
|
||
}
|
||
.select-field{
|
||
min-width:160px !important;
|
||
max-width:300px !important;
|
||
cursor:pointer;
|
||
background-color:white;
|
||
background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3e%3c/svg%3e");
|
||
background-position:right 8px center;
|
||
background-repeat:no-repeat;
|
||
background-size:16px 16px;
|
||
padding-right:32px !important;
|
||
appearance:none;
|
||
}
|
||
.select-field:focus{
|
||
background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%23667eea' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3e%3c/svg%3e");
|
||
}
|
||
.readonly-field{
|
||
background-color:#f9fafb !important;
|
||
border-color:#d1d5db !important;
|
||
color:#6b7280 !important;
|
||
cursor:not-allowed !important;
|
||
font-weight:500;
|
||
}
|
||
.readonly-field:hover{
|
||
border-color:#d1d5db !important;
|
||
}
|
||
.readonly-field:focus{
|
||
outline:none !important;
|
||
border-color:#d1d5db !important;
|
||
box-shadow:none !important;
|
||
}
|
||
.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;
|
||
}
|
||
.field-hint{
|
||
font-size:11px;color:#6b7280;font-style:italic;
|
||
margin-left:4px;
|
||
}
|
||
.validation-message{
|
||
font-size:12px;margin-top:4px;padding:4px 8px;
|
||
border-radius:6px;display:none;
|
||
}
|
||
.validation-message.error{
|
||
color:#dc2626;background:#fef2f2;border:1px solid #fecaca;
|
||
}
|
||
.validation-message.success{
|
||
color:#059669;background:#ecfdf5;border:1px solid #a7f3d0;
|
||
}
|
||
.section-break{
|
||
margin:24px 0;border-top:1px solid #e5e7eb;
|
||
padding-top:16px;
|
||
}
|
||
.verification-progress{
|
||
position:fixed;top:20px;right:20px;
|
||
background:rgba(255,255,255,0.95);backdrop-filter:blur(10px);
|
||
border-radius:12px;padding:12px 16px;
|
||
box-shadow:0 4px 20px rgba(0,0,0,0.1);
|
||
border:1px solid rgba(255,255,255,0.2);
|
||
z-index:1000;
|
||
}
|
||
.progress-bar{
|
||
width:200px;height:4px;background:#e5e7eb;
|
||
border-radius:2px;margin:8px 0;
|
||
overflow:hidden;
|
||
}
|
||
.progress-fill{
|
||
height:100%;background:linear-gradient(135deg,#667eea,#764ba2);
|
||
border-radius:2px;transition:width 0.3s ease;
|
||
}
|
||
.buttons{
|
||
display:flex;gap:12px;margin-top:24px;
|
||
flex-wrap:wrap;
|
||
}
|
||
.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);
|
||
}
|
||
.preview-container{
|
||
position:relative;
|
||
}
|
||
.preview{
|
||
white-space:pre-wrap;
|
||
font-family:'SF Mono',Monaco,'Cascadia Code','Roboto Mono',Consolas,'Courier New',monospace;
|
||
font-size:12px;line-height:1.6;
|
||
border:2px solid #e5e7eb;border-radius:12px;
|
||
padding:16px;background:#f9fafb;
|
||
max-height:70vh;overflow:auto;
|
||
transition:all 0.2s ease;
|
||
}
|
||
.preview:hover{
|
||
border-color:#d1d5db;
|
||
}
|
||
.error{
|
||
color:#dc2626;font-size:14px;margin-top:8px;
|
||
padding:8px 12px;background:#fef2f2;
|
||
border:1px solid #fecaca;border-radius:8px;
|
||
display:flex;align-items:center;gap:8px;
|
||
}
|
||
.success{
|
||
color:#059669;font-size:14px;margin-top:8px;
|
||
padding:8px 12px;background:#ecfdf5;
|
||
border:1px solid #a7f3d0;border-radius:8px;
|
||
display:flex;align-items:center;gap:8px;
|
||
}
|
||
.meta-info{
|
||
color:#6b7280;font-size:12px;margin-top:12px;
|
||
padding:8px 12px;background:#f3f4f6;
|
||
border-radius:8px;display:flex;gap:16px;
|
||
flex-wrap:wrap;
|
||
}
|
||
.meta-item{
|
||
display:flex;align-items:center;gap:4px;
|
||
}
|
||
.loading{
|
||
display:inline-block;width:16px;height:16px;
|
||
border:2px solid #e5e7eb;border-top:2px solid #667eea;
|
||
border-radius:50%;animation:spin 1s linear infinite;
|
||
}
|
||
@keyframes spin{
|
||
0%{transform:rotate(0deg)}
|
||
100%{transform:rotate(360deg)}
|
||
}
|
||
.fade-in{
|
||
animation:fadeIn 0.3s ease;
|
||
}
|
||
@keyframes fadeIn{
|
||
from{opacity:0;transform:translateY(10px)}
|
||
to{opacity:1;transform:translateY(0)}
|
||
}
|
||
.pulse{
|
||
animation:pulse 2s infinite;
|
||
}
|
||
@keyframes pulse{
|
||
0%,100%{opacity:1}
|
||
50%{opacity:0.7}
|
||
}
|
||
@media(max-width:768px){
|
||
.wrap{padding:16px}
|
||
.buttons{flex-direction:column}
|
||
.btn{width:100%}
|
||
.header h1{font-size:24px}
|
||
.card{padding:20px}
|
||
}
|
||
</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('=== СКРИПТ ЗАПУЩЕН ===');
|
||
console.log('Document ready state:', document.readyState);
|
||
console.log('Current time:', new Date().toISOString());
|
||
|
||
// Получаем данные
|
||
function getData(){
|
||
try {
|
||
console.log('=== ПАРСИНГ ДАННЫХ ===');
|
||
var dataEl = document.getElementById('case-data');
|
||
console.log('case-data element found:', !!dataEl);
|
||
|
||
if (!dataEl) {
|
||
console.error('Элемент #case-data не найден!');
|
||
return {};
|
||
}
|
||
|
||
var textContent = dataEl.textContent || '{}';
|
||
console.log('Raw JSON length:', textContent.length);
|
||
console.log('Raw JSON preview:', textContent.substring(0, 200));
|
||
|
||
var parsed = JSON.parse(textContent);
|
||
console.log('Parsed data:', parsed);
|
||
return parsed;
|
||
} catch(e) {
|
||
console.error('ОШИБКА ПАРСИНГА JSON:', e);
|
||
console.error('Текст для парсинга:', dataEl ? dataEl.textContent : 'элемент не найден');
|
||
return {};
|
||
}
|
||
}
|
||
|
||
// Простая функция экранирования
|
||
function esc(s){
|
||
if (s === null || s === undefined) return '';
|
||
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/"/g,'"');
|
||
}
|
||
|
||
// Простые функции валидации
|
||
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){ // "31.12.2024" -> Date | null
|
||
console.log('parseDDMMYYYY вызвана с:', s);
|
||
// ИСПРАВЛЕНИЕ: используем [0-9] вместо \d
|
||
if (!/^[0-9]{2}\.[0-9]{2}\.[0-9]{4}$/.test(s)) {
|
||
console.log('parseDDMMYYYY: формат не подходит');
|
||
return null;
|
||
}
|
||
var [d,m,y] = s.split('.').map(Number);
|
||
console.log('parseDDMMYYYY: день=', d, 'месяц=', m, 'год=', y);
|
||
var dt = new Date(y, m-1, d);
|
||
var isValid = (dt.getFullYear()===y && dt.getMonth()===m-1 && dt.getDate()===d);
|
||
console.log('parseDDMMYYYY: результат=', dt, 'валидна=', isValid);
|
||
return isValid ? dt : null;
|
||
}
|
||
function parseYMD(s){ // "2024-12-31" -> Date | null
|
||
console.log('parseYMD вызвана с:', s);
|
||
console.log('parseYMD: длина строки:', s.length);
|
||
console.log('parseYMD: символы:', s.split('').map(c => c.charCodeAt(0)));
|
||
console.log('parseYMD: тест регексом:', /^\d{4}-\d{2}-\d{2}$/.test(s));
|
||
|
||
// Попробуем очистить строку от возможных невидимых символов
|
||
var cleanS = s.trim();
|
||
|
||
// ВРЕМЕННОЕ ИСПРАВЛЕНИЕ: заменяем любые дефисоподобные символы на обычные дефисы
|
||
cleanS = cleanS.replace(/[\u2012\u2013\u2014\u2015\u2212\uFF0D]/g, '-');
|
||
console.log('parseYMD: после замены дефисов:', cleanS);
|
||
console.log('parseYMD: очищенная строка:', cleanS, 'длина:', cleanS.length);
|
||
console.log('parseYMD: тест очищенной регексом:', /^\d{4}-\d{2}-\d{2}$/.test(cleanS));
|
||
|
||
// ДОПОЛНИТЕЛЬНАЯ ОТЛАДКА РЕГЕКСА
|
||
console.log('parseYMD: проверка по частям:');
|
||
console.log(' - первые 4 символа (год):', cleanS.substring(0,4), 'тест \\d{4}:', /^\d{4}$/.test(cleanS.substring(0,4)), 'тест [0-9]{4}:', /^[0-9]{4}$/.test(cleanS.substring(0,4)));
|
||
console.log(' - символ 4 (дефис):', cleanS.charAt(4), 'код:', cleanS.charCodeAt(4));
|
||
console.log(' - символы 5-6 (месяц):', cleanS.substring(5,7), 'тест \\d{2}:', /^\d{2}$/.test(cleanS.substring(5,7)), 'тест [0-9]{2}:', /^[0-9]{2}$/.test(cleanS.substring(5,7)));
|
||
console.log(' - символ 7 (дефис):', cleanS.charAt(7), 'код:', cleanS.charCodeAt(7));
|
||
console.log(' - символы 8-9 (день):', cleanS.substring(8,10), 'тест \\d{2}:', /^\d{2}$/.test(cleanS.substring(8,10)), 'тест [0-9]{2}:', /^[0-9]{2}$/.test(cleanS.substring(8,10)));
|
||
|
||
// ИСПРАВЛЕНИЕ: используем [0-9] вместо \d
|
||
if (!/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/.test(cleanS)) {
|
||
console.log('parseYMD: формат не подходит');
|
||
return null;
|
||
}
|
||
s = cleanS; // используем очищенную строку
|
||
var [y,m,d] = s.split('-').map(Number);
|
||
console.log('parseYMD: год=', y, 'месяц=', m, 'день=', d);
|
||
var dt = new Date(y, m-1, d);
|
||
var isValid = (dt.getFullYear()===y && dt.getMonth()===m-1 && dt.getDate()===d);
|
||
console.log('parseYMD: результат=', dt, 'валидна=', isValid);
|
||
return isValid ? dt : null;
|
||
}
|
||
function toYMD(dt){ // Date -> "YYYY-MM-DD"
|
||
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 clampDate(dt, minDt, maxDt){
|
||
if (minDt && dt < minDt) return false;
|
||
if (maxDt && dt > maxDt) return false;
|
||
return true;
|
||
}
|
||
|
||
// Глобальные референсы
|
||
var TODAY = new Date(); TODAY.setHours(0,0,0,0);
|
||
console.log('=== ОТЛАДКА ДАТ ===');
|
||
console.log('Текущая дата (TODAY):', TODAY.toISOString());
|
||
console.log('Текущая дата (локальная):', TODAY.toLocaleDateString('ru-RU'));
|
||
console.log('Год:', TODAY.getFullYear(), 'Месяц:', TODAY.getMonth()+1, 'День:', TODAY.getDate());
|
||
|
||
var MIN_1900 = new Date(1900,0,1);
|
||
var MIN_2000 = new Date(2000,0,1);
|
||
var PLUS_10Y = new Date(TODAY.getFullYear()+10, TODAY.getMonth(), TODAY.getDate());
|
||
|
||
// Единая проверка дат по ключу поля
|
||
function validateDateByKey(key, rawValue, field){
|
||
// rawValue может быть "YYYY-MM-DD" (input[type=date]) или "DD.MM.YYYY" (сохранённое)
|
||
console.log('validateDateByKey called:', {key, rawValue, type: typeof rawValue});
|
||
|
||
var dt = null;
|
||
if (typeof rawValue === 'string' && rawValue.includes('-')) dt = parseYMD(rawValue);
|
||
if (!dt && typeof rawValue === 'string' && rawValue.includes('.')) dt = parseDDMMYYYY(rawValue);
|
||
|
||
console.log('Parsed date:', {dt, today: TODAY});
|
||
|
||
if (!dt) return { ok:false, msg:'Некорректная дата' };
|
||
|
||
// Бизнес-правила по полям
|
||
if (key === 'birthday'){
|
||
// Дата рождения: 1900-01-01 … сегодня
|
||
if (dt > TODAY) return { ok:false, msg:'Дата рождения не может быть в будущем' };
|
||
if (dt < MIN_1900) return { ok:false, msg:'Дата рождения не может быть ранее 01.01.1900' };
|
||
return { ok:true };
|
||
}
|
||
|
||
if (key === 'agrdate'){
|
||
// Дата договора/события: 2000-01-01 … сегодня (без будущего)
|
||
if (!clampDate(dt, MIN_2000, TODAY)) return { ok:false, msg:'Дата договора не может быть в будущем' };
|
||
return { ok:true };
|
||
}
|
||
|
||
if (key === 'startdate' || key === 'finishdate'){
|
||
// Период: 2000-01-01 … сегодня+10 лет
|
||
if (!clampDate(dt, MIN_2000, PLUS_10Y)) return { ok:false, msg:'Дата вне допустимого диапазона' };
|
||
|
||
// Доп. правило: start <= finish
|
||
var root = field && field.getAttribute('data-root');
|
||
if (root === 'project'){
|
||
var s = (state.project && state.project.startdate) || '';
|
||
var f = (state.project && state.project.finishdate) || '';
|
||
var sd = s ? (s.includes('-') ? parseYMD(s) : parseDDMMYYYY(s)) : null;
|
||
var fd = f ? (f.includes('-') ? parseYMD(f) : parseDDMMYYYY(f)) : null;
|
||
|
||
if (key === 'startdate' && fd && dt > fd) return { ok:false, msg:'Дата начала не может быть позже даты окончания' };
|
||
if (key === 'finishdate' && sd && dt < sd) return { ok:false, msg:'Дата окончания не может быть раньше даты начала' };
|
||
}
|
||
return { ok:true };
|
||
}
|
||
|
||
// По умолчанию — только реальная дата
|
||
return { ok:true };
|
||
}
|
||
|
||
// Обновлённая простая проверка для внешних вызовов (сохраним совместимость)
|
||
function isValidDate(dateStr){
|
||
var dt = parseDDMMYYYY(dateStr);
|
||
if (!dt) return false;
|
||
// базовые разумные границы
|
||
var currentYear = TODAY.getFullYear();
|
||
var minYear = currentYear - 100;
|
||
var maxYear = currentYear + 50;
|
||
return dt.getFullYear() >= minYear && dt.getFullYear() <= maxYear;
|
||
}
|
||
|
||
// ✅ ПЕРЕНЕСЕНО из test/index.php
|
||
// Логика валидации ИНН на основе масок из тестового файла:
|
||
// - js-inn-mask: "999999999999" (строго 12 цифр для физлиц)
|
||
// - js-inn-mask2: "9{10,12}" (10-12 цифр для юрлиц)
|
||
function isValidINN(inn, isIndividual) {
|
||
if (!inn) return false;
|
||
var clean = String(inn).replace(/\D/g, '');
|
||
|
||
// Логика из test/index.php - проверка формата и длины
|
||
// Маска js-inn-mask: 12 цифр для физлиц
|
||
// Маска js-inn-mask2: 10-12 цифр для универсального использования
|
||
|
||
if (isIndividual) {
|
||
// Физлицо - строго 12 цифр (как в js-inn-mask)
|
||
return clean.length === 12;
|
||
} else {
|
||
// Юрлицо - 10 или 12 цифр (как в js-inn-mask2)
|
||
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); // латиница или цифры
|
||
var hasOnlyAllowed = true; // Временно отключаем сложную проверку
|
||
|
||
return hasCyrillic && !hasInvalidChars && hasOnlyAllowed;
|
||
}
|
||
|
||
function isValidAddress(address) {
|
||
if (!address) return false;
|
||
var trimmed = address.trim();
|
||
if (trimmed.length < 10) return false;
|
||
|
||
// Адрес должен содержать цифры (номер дома) и буквы
|
||
// Используем [0-9] для надёжного поиска цифр (исправлена проблема с /\d/)
|
||
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; // ИСПРАВИЛ: заменил \d на [0-9]
|
||
return parseFloat(s) > 0;
|
||
}
|
||
|
||
// Красивая валидация поля
|
||
function updateFieldStyle(field) {
|
||
// --- корректное чтение значения ---
|
||
var key = field.getAttribute('data-key') || '';
|
||
var value;
|
||
if (field.type === 'checkbox') {
|
||
value = field.checked; // ✅ вместо field.value
|
||
} else {
|
||
value = field.value || '';
|
||
}
|
||
|
||
// Сброс классов
|
||
field.classList.remove('required-empty', 'valid', 'verified');
|
||
|
||
var isValid = true;
|
||
var isEmpty = (field.type === 'checkbox') ? !value : !value || value.trim() === '';
|
||
|
||
// Централизованный список обязательных полей
|
||
var REQUIRED = new Set(['lastname','firstname','birthday','birthplace','mailingstreet','inn','agrdate','agrprice','accountname','address','reason','description']);
|
||
var isRequired = REQUIRED.has(key);
|
||
|
||
if (key === 'mobile') {
|
||
isValid = isEmpty || isValidPhone(value);
|
||
} else if (key === 'email') {
|
||
isValid = isEmpty || isValidEmail(value);
|
||
} else if (key === 'inn') {
|
||
// Определяем тип ИНН по контексту
|
||
var root = field.getAttribute('data-root');
|
||
var isIndividual = (root === 'user'); // user = физлицо, offender = юрлицо
|
||
|
||
if (isEmpty) {
|
||
isValid = !isRequired; // Если обязательное - невалидно, если нет - валидно
|
||
} else {
|
||
isValid = isValidINN(value, isIndividual);
|
||
}
|
||
} else if (key === 'birthday' || key === 'agrdate' || key === 'startdate' || key === 'finishdate') {
|
||
if (isEmpty) {
|
||
isValid = !isRequired;
|
||
} else {
|
||
console.log('Date validation debug:', {
|
||
key: key,
|
||
value: value,
|
||
fieldType: field.type,
|
||
fieldValue: field.value,
|
||
isEmpty: isEmpty
|
||
});
|
||
|
||
var dateValidation = validateDateByKey(key, value, field);
|
||
isValid = dateValidation.ok;
|
||
|
||
console.log('Date validation result:', dateValidation);
|
||
|
||
// Сохраняем сообщение об ошибке для вывода
|
||
if (!isValid) {
|
||
field.setAttribute('data-error-message', dateValidation.msg);
|
||
} else {
|
||
field.removeAttribute('data-error-message');
|
||
}
|
||
}
|
||
} else if (['lastname', 'firstname', 'secondname'].includes(key)) {
|
||
isValid = isEmpty || isValidName(value);
|
||
if (isRequired && isEmpty) isValid = false; // ФИО обязательно
|
||
} else if (key === 'birthplace') {
|
||
isValid = isEmpty || isValidName(value);
|
||
if (isRequired && isEmpty) isValid = false; // Место рождения обязательно
|
||
} else if (key === 'mailingstreet' || key === 'address') {
|
||
isValid = isEmpty || isValidAddress(value);
|
||
if (isRequired && isEmpty) isValid = false; // Адрес обязателен
|
||
} else if (key === 'agrprice') {
|
||
isValid = isEmpty ? !isRequired : isValidMoney(value);
|
||
if (isRequired && isEmpty) isValid = false;
|
||
} else if (['accountname', 'subject', 'reason', 'description'].includes(key)) {
|
||
isValid = isEmpty || isNotEmpty(value);
|
||
if (isRequired && isEmpty) isValid = false; // Эти поля обязательны
|
||
} else if (key === 'privacyConsent') {
|
||
// Для чекбокса согласия - должен быть отмечен
|
||
isValid = value === true;
|
||
}
|
||
|
||
// --- вывод сообщения только для ошибок ---
|
||
var msgEl = document.getElementById('msg_' + field.id);
|
||
if (msgEl) {
|
||
if (!isEmpty && !isValid) {
|
||
msgEl.style.display = 'block';
|
||
msgEl.className = 'validation-message error';
|
||
|
||
// Человечные сообщения об ошибках
|
||
var errorMessage = '';
|
||
if (key === 'inn') {
|
||
// Логика из test/index.php - разные требования для физлиц и юрлиц
|
||
var root = field.getAttribute('data-root');
|
||
var isIndividual = (root === 'user');
|
||
errorMessage = isIndividual
|
||
? 'ИНН физлица: ровно 12 цифр (как js-inn-mask)'
|
||
: 'ИНН организации: 10 или 12 цифр (как js-inn-mask2)';
|
||
} else if (key === 'agrprice') {
|
||
errorMessage = 'Введите сумму числом (например: 50000 или 50000.50)';
|
||
} else if (['lastname', 'firstname', 'secondname', 'birthplace'].includes(key)) {
|
||
errorMessage = 'Только русские буквы, без цифр и латиницы';
|
||
} else if (key === 'mailingstreet' || key === 'address') {
|
||
errorMessage = 'Укажите полный адрес с номером дома';
|
||
} else if (key === 'email') {
|
||
errorMessage = 'Неверный формат email';
|
||
} else if (key === 'mobile') {
|
||
errorMessage = 'Неверный формат телефона';
|
||
} else if (key.includes('date')) {
|
||
errorMessage = field.getAttribute('data-error-message') || 'Выберите корректную дату';
|
||
} else {
|
||
errorMessage = 'Поле заполнено неверно';
|
||
}
|
||
|
||
msgEl.textContent = errorMessage;
|
||
} else {
|
||
msgEl.style.display = 'none';
|
||
msgEl.textContent = '';
|
||
}
|
||
}
|
||
|
||
// Добавляем соответствующий класс с анимацией
|
||
if (!isEmpty && isValid) {
|
||
field.classList.add('valid');
|
||
} else if (!isEmpty && !isValid) {
|
||
field.classList.add('required-empty');
|
||
}
|
||
}
|
||
|
||
// Шаблоны текстов для разных типов услуг
|
||
var serviceTemplates = {
|
||
education: {
|
||
name: 'Образовательные услуги',
|
||
contractText: ' г. мною был заключен договор с {company} об оказании платных образовательных услуг{subject}.',
|
||
paymentText: 'По указанному договору мною внесена оплата в размере {amount} рублей.',
|
||
reasonText: 'В настоящий момент, в связи с неудовлетворительным качеством услуги, мною принято решение о расторжении договора и возврате уплаченных по договору денежных средств.',
|
||
placeholders: {
|
||
subject: ' по программе {subject}'
|
||
}
|
||
},
|
||
tourism: {
|
||
name: 'Туристические услуги',
|
||
contractText: ' г. мною был заключен договор с {company} об оказании туристических услуг{subject}.',
|
||
paymentText: 'По указанному договору мною внесена предоплата в размере {amount} рублей.',
|
||
reasonText: 'В настоящий момент, в связи с некачественным оказанием услуг/невозможностью оказания услуг, мною принято решение о расторжении договора и возврате уплаченных денежных средств.',
|
||
placeholders: {
|
||
subject: ' ({subject})'
|
||
}
|
||
},
|
||
medical: {
|
||
name: 'Медицинские услуги',
|
||
contractText: ' г. мною был заключен договор с {company} об оказании платных медицинских услуг{subject}.',
|
||
paymentText: 'По указанному договору мною внесена оплата в размере {amount} рублей.',
|
||
reasonText: 'В настоящий момент, в связи с неудовлетворительным качеством оказанных медицинских услуг, мною принято решение о расторжении договора и возврате уплаченных денежных средств.',
|
||
placeholders: {
|
||
subject: ' ({subject})'
|
||
}
|
||
},
|
||
fitness: {
|
||
name: 'Фитнес/спортивные услуги',
|
||
contractText: ' г. мною был заключен договор с {company} об оказании услуг фитнес-клуба/спортивных услуг{subject}.',
|
||
paymentText: 'По указанному договору мною внесена оплата в размере {amount} рублей.',
|
||
reasonText: 'В настоящий момент, в связи с неудовлетворительным качеством услуг/невозможностью посещения, мною принято решение о расторжении договора и возврате уплаченных денежных средств.',
|
||
placeholders: {
|
||
subject: ' ({subject})'
|
||
}
|
||
},
|
||
other: {
|
||
name: 'Другие услуги',
|
||
contractText: ' г. мною был заключен договор с {company} об оказании {serviceType}{subject}.',
|
||
paymentText: 'По указанному договору мною внесена оплата в размере {amount} рублей.',
|
||
reasonText: 'В настоящий момент, в связи с {reason}, мною принято решение о расторжении договора и возврате уплаченных по договору денежных средств.',
|
||
placeholders: {
|
||
subject: ' ({subject})',
|
||
serviceType: 'услуг',
|
||
reason: 'неудовлетворительным качеством услуги'
|
||
}
|
||
}
|
||
};
|
||
|
||
// Получаем данные
|
||
var injected = getData();
|
||
console.log('Data loaded:', injected);
|
||
|
||
var state = (injected && injected.case) ? injected.case : {
|
||
user: {},
|
||
project: {},
|
||
offenders: [{}],
|
||
meta: {}
|
||
};
|
||
|
||
// Добавляем поля для шаблонов, если их нет
|
||
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;
|
||
|
||
// Добавляем поля для SMS подтверждения
|
||
if (state.meta.smsCodeSent === undefined) state.meta.smsCodeSent = false;
|
||
if (state.meta.smsCode === undefined) state.meta.smsCode = '';
|
||
if (state.meta.smsVerified === undefined) state.meta.smsVerified = false;
|
||
|
||
console.log('State:', state);
|
||
console.log('SMS meta fields:', {
|
||
session_token: state.meta.session_token,
|
||
prefix: state.meta.prefix,
|
||
telegram_id: state.meta.telegram_id,
|
||
claim_id: state.meta.claim_id,
|
||
unified_id: state.meta.unified_id,
|
||
user_id: state.meta.user_id
|
||
});
|
||
|
||
// Красивая функция для создания поля
|
||
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 = '';
|
||
|
||
// 🔒 для ИНН — только цифры, логика из test/index.php
|
||
if (key === 'inn') {
|
||
// Определяем тип по контексту (физлицо vs юрлицо)
|
||
var isIndividual = (root === 'user'); // user = физлицо, offender = юрлицо
|
||
|
||
if (isIndividual) {
|
||
// Физлицо: строго 12 цифр (как js-inn-mask: "999999999999")
|
||
extra = ' inputmode="numeric" pattern="\\d{12}" maxlength="12" autocomplete="off" title="ИНН физического лица: ровно 12 цифр"';
|
||
if (!placeholder) placeholder = 'ИНН физлица (12 цифр)';
|
||
} else {
|
||
// Юрлицо: 10-12 цифр (как js-inn-mask2: "9{10,12}")
|
||
extra = ' inputmode="numeric" pattern="\\d{10,12}" maxlength="12" autocomplete="off" title="ИНН организации: 10 или 12 цифр"';
|
||
if (!placeholder) placeholder = 'ИНН организации (10-12 цифр)';
|
||
}
|
||
}
|
||
|
||
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 + ' />';
|
||
|
||
var msgId = 'msg_' + id;
|
||
var msgHtml = '<div id="' + msgId + '" class="validation-message" style="display:none"></div>';
|
||
return fieldHtml + msgHtml;
|
||
}
|
||
|
||
// Функция для создания readonly поля
|
||
function createReadonlyField(root, key, value, label) {
|
||
var id = 'field_' + root + '_' + key + '_readonly_' + Math.random().toString(36).slice(2);
|
||
var fieldHtml = '<input class="inline-field readonly-field" data-root="' + esc(root) + '" data-key="' + esc(key) + '" id="' + id + '" value="' + esc(value || '') + '" readonly />';
|
||
return fieldHtml;
|
||
}
|
||
|
||
// Функция для создания поля суммы с суффиксом "рублей" и автозаменой запятой
|
||
function createMoneyField(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 fieldHtml =
|
||
'<div style="display: flex; align-items: center; gap: 8px;">' +
|
||
'<input class="inline-field bind money-field" data-root="' + esc(root) + '" data-key="' + esc(key) + '"' + dataIndex +
|
||
' id="' + id + '" value="' + esc(value || '') + '" placeholder="' + esc(placeholder || '') + '"' +
|
||
' inputmode="decimal" pattern="[0-9]*[.,]?[0-9]*" autocomplete="off" title="Введите сумму (можно использовать запятую или точку)" />' +
|
||
'<span style="color: #6b7280; font-size: 14px;">рублей</span>' +
|
||
'</div>';
|
||
|
||
var msgId = 'msg_' + id;
|
||
var msgHtml = '<div id="' + msgId + '" class="validation-message" style="display:none"></div>';
|
||
|
||
return fieldHtml + msgHtml;
|
||
}
|
||
|
||
// Функция для создания чекбокса
|
||
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>';
|
||
|
||
var msgId = 'msg_' + id;
|
||
var msgHtml = '<div id="' + msgId + '" class="validation-message" style="display:none"></div>';
|
||
return checkboxHtml + msgHtml;
|
||
}
|
||
|
||
// Функция для создания textarea
|
||
function createTextarea(root, key, value, placeholder) {
|
||
var id = 'field_' + root + '_' + key + '_' + Math.random().toString(36).slice(2);
|
||
var fieldHtml = '<textarea class="inline-field bind full-width" data-root="' + esc(root) + '" data-key="' + esc(key) + '" id="' + id + '" placeholder="' + esc(placeholder || '') + '">' + esc(value || '') + '</textarea>';
|
||
var msgId = 'msg_' + id;
|
||
var msgHtml = '<div id="' + msgId + '" class="validation-message" style="display:none"></div>';
|
||
return fieldHtml + msgHtml;
|
||
}
|
||
|
||
// Функция для создания поля даты с календариком
|
||
function createDateField(root, key, value, placeholder) {
|
||
var id = 'field_' + root + '_' + key + '_' + Math.random().toString(36).slice(2);
|
||
|
||
console.log('createDateField called:', 'root=' + root, 'key=' + key, 'value=' + value, 'type=' + typeof value);
|
||
|
||
// Конвертируем дату из различных форматов в YYYY-MM-DD для input[type="date"]
|
||
var dateValue = '';
|
||
if (value) {
|
||
var cleanValue = String(value).trim();
|
||
if (cleanValue.match(/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/)) {
|
||
// Уже в формате YYYY-MM-DD
|
||
dateValue = cleanValue;
|
||
} else if (cleanValue.match(/^[0-9]{2}\.[0-9]{2}\.[0-9]{4}$/)) {
|
||
// Формат DD.MM.YYYY -> YYYY-MM-DD
|
||
var parts = cleanValue.split('.');
|
||
dateValue = parts[2] + '-' + parts[1] + '-' + parts[0];
|
||
}
|
||
}
|
||
|
||
console.log('createDateField result:', 'originalValue=' + value, 'convertedValue=' + dateValue, 'willCreateHTML=' + (dateValue ? 'YES' : 'NO'));
|
||
|
||
var fieldHtml = '<input type="date" class="inline-field bind date-field" data-root="' + esc(root) + '" data-key="' + esc(key) + '" id="' + id + '" value="' + esc(dateValue) + '" placeholder="' + esc(placeholder || '') + '" />';
|
||
var msgId = 'msg_' + id;
|
||
var msgHtml = '<div id="' + msgId + '" class="validation-message" style="display:none"></div>';
|
||
return fieldHtml + msgHtml;
|
||
}
|
||
|
||
// Функция для создания выпадающего списка
|
||
function createSelectField(root, key, value, options, placeholder) {
|
||
var id = 'field_' + root + '_' + key + '_' + Math.random().toString(36).slice(2);
|
||
var fieldHtml = '<select class="inline-field bind select-field" data-root="' + esc(root) + '" data-key="' + esc(key) + '" id="' + id + '">';
|
||
|
||
if (placeholder) {
|
||
fieldHtml += '<option value="">' + esc(placeholder) + '</option>';
|
||
}
|
||
|
||
for (var optKey in options) {
|
||
var selected = value === optKey ? ' selected' : '';
|
||
fieldHtml += '<option value="' + esc(optKey) + '"' + selected + '>' + esc(options[optKey]) + '</option>';
|
||
}
|
||
|
||
fieldHtml += '</select>';
|
||
var msgId = 'msg_' + id;
|
||
var msgHtml = '<div id="' + msgId + '" class="validation-message" style="display:none"></div>';
|
||
return fieldHtml + msgHtml;
|
||
}
|
||
|
||
// Функция для обработки шаблонов текста
|
||
function processTemplate(template, data) {
|
||
var result = template;
|
||
|
||
// Заменяем основные плейсхолдеры
|
||
result = result.replace(/\{company\}/g, data.company || '');
|
||
result = result.replace(/\{amount\}/g, data.amount || '');
|
||
|
||
// Обрабатываем условные блоки для subject
|
||
if (data.subject && data.subject.trim() !== '') {
|
||
var subjectTemplate = data.subjectTemplate || ' ({subject})';
|
||
var subjectText = subjectTemplate.replace(/\{subject\}/g, data.subject);
|
||
result = result.replace(/\{subject\}/g, subjectText);
|
||
} else {
|
||
result = result.replace(/\{subject\}/g, '');
|
||
}
|
||
|
||
// Обрабатываем дополнительные плейсхолдеры для типа "other"
|
||
result = result.replace(/\{serviceType\}/g, data.serviceType || 'услуг');
|
||
result = result.replace(/\{reason\}/g, data.reason || 'неудовлетворительным качеством услуги');
|
||
|
||
return result;
|
||
}
|
||
|
||
// Функция проверки всех обязательных полей
|
||
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 smsCodeSent = state.meta && state.meta.smsCodeSent === true;
|
||
var smsVerified = state.meta && state.meta.smsVerified === true;
|
||
|
||
if (!isConsentGiven) {
|
||
// Нет согласия - кнопка заблокирована
|
||
confirmBtn.disabled = true;
|
||
confirmBtn.style.opacity = '0.6';
|
||
confirmBtn.style.cursor = 'not-allowed';
|
||
confirmBtn.textContent = '✅ Подтвердить и отправить';
|
||
} else if (!smsCodeSent) {
|
||
// Согласие есть, SMS не отправлена - проверяем валидность основных полей
|
||
var validationErrors = validateAllFields();
|
||
|
||
if (validationErrors.length === 0) {
|
||
confirmBtn.disabled = false;
|
||
confirmBtn.style.opacity = '1';
|
||
confirmBtn.style.cursor = 'pointer';
|
||
confirmBtn.textContent = '📱 Получить SMS код';
|
||
} else {
|
||
confirmBtn.disabled = true;
|
||
confirmBtn.style.opacity = '0.6';
|
||
confirmBtn.style.cursor = 'not-allowed';
|
||
confirmBtn.textContent = '❌ Заполните все поля (' + validationErrors.length + ')';
|
||
confirmBtn.title = 'Не заполнены: ' + validationErrors.join(', ');
|
||
}
|
||
} else if (smsCodeSent && !smsVerified) {
|
||
// SMS отправлена, но не подтверждена - кнопка заблокирована
|
||
confirmBtn.disabled = true;
|
||
confirmBtn.style.opacity = '0.6';
|
||
confirmBtn.style.cursor = 'not-allowed';
|
||
confirmBtn.textContent = '⏳ Введите код из SMS';
|
||
} else if (smsVerified) {
|
||
// SMS подтверждена - проверяем валидность всех полей
|
||
var validationErrors = validateAllFields();
|
||
|
||
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 renderStatement() {
|
||
console.log('=== НАЧАЛО РЕНДЕРИНГА ===');
|
||
console.log('state:', state);
|
||
|
||
var u = state.user || {};
|
||
var o = (state.offenders && state.offenders[0]) || {};
|
||
var p = state.project || {};
|
||
|
||
console.log('user data:', u);
|
||
console.log('offender data:', o);
|
||
console.log('project data:', p);
|
||
console.log('=== ОТЛАДКА ДАТ ПРОЕКТА ===');
|
||
console.log('p.agrdate (дата договора):', p.agrdate, 'тип:', typeof p.agrdate);
|
||
console.log('p.startdate (начало периода):', p.startdate, 'тип:', typeof p.startdate);
|
||
console.log('p.finishdate (конец периода):', p.finishdate, 'тип:', typeof p.finishdate);
|
||
|
||
var html = '';
|
||
console.log('Starting HTML generation...');
|
||
|
||
// Заголовок
|
||
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.privacyConsent,
|
||
'Я ознакомлен(а) и согласен(а) с <a href="https://clientright.ru/person" target="_blank">Политикой обработки персональных данных</a> и даю согласие на обработку моих персональных данных в соответствии с Федеральным законом от 27.07.2006 №152-ФЗ «О персональных данных»',
|
||
true);
|
||
html += '</div>';
|
||
|
||
// SMS подтверждение (показывается только после отправки кода)
|
||
if (state.meta.smsCodeSent) {
|
||
html += '<div class="sms-verification-container fade-in" style="margin:24px 0;padding:20px;border:2px solid #667eea;border-radius:12px;background:rgba(102,126,234,0.05);">';
|
||
html += '<div style="display:flex;align-items:center;gap:12px;margin-bottom:16px;">';
|
||
html += '<div style="width:24px;height:24px;background:#667eea;border-radius:50%;display:flex;align-items:center;justify-content:center;">';
|
||
html += '<span style="color:white;font-size:14px;">📱</span>';
|
||
html += '</div>';
|
||
html += '<div>';
|
||
html += '<h4 style="margin:0;color:#1f2937;font-size:16px;">Подтверждение по SMS</h4>';
|
||
html += '<p style="margin:4px 0 0;color:#6b7280;font-size:14px;">Код отправлен на номер ' + esc(state.user.mobile || '') + '</p>';
|
||
html += '</div>';
|
||
html += '</div>';
|
||
|
||
html += '<div style="display:flex;gap:12px;align-items:center;">';
|
||
html += '<input type="text" ';
|
||
html += 'inputmode="numeric" pattern="[0-9]*" ';
|
||
html += 'oninput="this.value=this.value.replace(/[^0-9]/g,\\"\\").slice(0,6)" ';
|
||
html += 'class="sms-code-input bind" data-root="meta" data-key="smsCode" ';
|
||
html += 'placeholder="Введите 6-значный код" maxlength="6" ';
|
||
html += 'style="width:180px;padding:12px;font-size:18px;text-align:center;letter-spacing:6px;border:2px solid #e5e7eb;border-radius:8px;" ';
|
||
html += 'value="' + esc(state.meta.smsCode || '') + '" />';
|
||
|
||
if (!state.meta.smsVerified) {
|
||
html += '<button type="button" class="btn btn-secondary" onclick="resendSMS()" style="padding:12px 16px;">';
|
||
html += 'Отправить повторно';
|
||
html += '</button>';
|
||
} else {
|
||
html += '<span style="color:#10b981;font-weight:600;">✅ Подтверждено</span>';
|
||
}
|
||
html += '</div>';
|
||
|
||
if (!state.meta.smsVerified) {
|
||
html += '<p style="margin:12px 0 0;color:#6b7280;font-size:13px;">';
|
||
html += 'Не получили код? Проверьте папку "Спам" или нажмите "Отправить повторно"';
|
||
html += '</p>';
|
||
}
|
||
|
||
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>';
|
||
}
|
||
|
||
// Вставляем HTML
|
||
console.log('Generated HTML length:', html.length);
|
||
console.log('HTML preview (first 200 chars):', html.substring(0, 200));
|
||
|
||
var statementEl = document.getElementById('statement');
|
||
if (statementEl) {
|
||
console.log('Setting innerHTML...');
|
||
statementEl.innerHTML = html;
|
||
console.log('innerHTML set successfully');
|
||
|
||
console.log('Calling attachBindings...');
|
||
attachBindings();
|
||
|
||
// Обновляем состояние кнопки отправки
|
||
setTimeout(function() {
|
||
updateSubmitButton();
|
||
}, 100);
|
||
|
||
console.log('=== РЕНДЕРИНГ ЗАВЕРШЕН УСПЕШНО ===');
|
||
} else {
|
||
console.error('ОШИБКА: элемент #statement не найден в renderStatement!');
|
||
}
|
||
}
|
||
|
||
// Красивые обработчики событий
|
||
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, '.');
|
||
console.log('Заменена запятая на точку:', this.value);
|
||
}
|
||
|
||
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;
|
||
|
||
// Обновляем телефон в СБП (хотя поле теперь readonly, оставляем на всякий случай)
|
||
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;
|
||
|
||
// При смене типа услуги перерендериваем форму
|
||
if (key === 'serviceType') {
|
||
console.log('Service type changed to:', value);
|
||
setTimeout(function() {
|
||
renderStatement();
|
||
}, 50);
|
||
return; // Не выполняем валидацию, так как элемент будет пересоздан
|
||
}
|
||
} else if (root === 'offender') {
|
||
if (!Array.isArray(state.offenders)) state.offenders = [];
|
||
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();
|
||
}
|
||
|
||
// Для SMS кода автоматически проверяем при вводе 6 цифр
|
||
if (key === 'smsCode' && value.length === 6) {
|
||
console.log('SMS code entered, verifying...');
|
||
verifySMS(value);
|
||
}
|
||
}
|
||
|
||
// Валидация на лету
|
||
updateFieldStyle(field);
|
||
|
||
// Обновляем состояние кнопки при изменении любого поля
|
||
updateSubmitButton();
|
||
});
|
||
|
||
// ✅ ПЕРЕНЕСЕНО из test/index.php - блокировка нечисловых символов для ИНН
|
||
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();
|
||
console.log('Blocked non-numeric input for INN:', e.key);
|
||
}
|
||
}
|
||
});
|
||
|
||
// ✅ ПЕРЕНЕСЕНО из test/index.php - фильтрация при вставке для ИНН
|
||
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; // И для физлиц, и для юрлиц максимум 12
|
||
|
||
// Обрезаем до нужной длины
|
||
cleanPaste = cleanPaste.slice(0, maxLength);
|
||
|
||
// Вставляем очищенный текст
|
||
this.value = cleanPaste;
|
||
|
||
// Запускаем событие input для обновления состояния
|
||
var inputEvent = new Event('input', { bubbles: true });
|
||
this.dispatchEvent(inputEvent);
|
||
|
||
console.log('Filtered paste for INN:', paste, '→', cleanPaste);
|
||
}
|
||
});
|
||
|
||
// Временно отключаем валидацию телефона для отладки
|
||
/*
|
||
field.addEventListener('keypress', function(e) {
|
||
var key = this.getAttribute('data-key');
|
||
if (key === 'mobile') {
|
||
// Разрешаем только цифры, пробелы, +, -, (, )
|
||
var allowedChars = /^[0-9\+\-\(\)\s]$/;
|
||
if (!allowedChars.test(e.key) && e.key !== 'Backspace' && e.key !== 'Delete' && e.key !== 'Tab' && e.key !== 'ArrowLeft' && e.key !== 'ArrowRight') {
|
||
e.preventDefault();
|
||
}
|
||
}
|
||
});
|
||
*/
|
||
|
||
// Валидация при потере фокуса
|
||
field.addEventListener('blur', function() {
|
||
updateFieldStyle(this);
|
||
updateSubmitButton();
|
||
});
|
||
|
||
// Валидация при клике (для уже заполненных полей)
|
||
field.addEventListener('click', function() {
|
||
updateFieldStyle(this);
|
||
});
|
||
|
||
// Дополнительная обработка для чекбоксов
|
||
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');
|
||
}
|
||
}
|
||
}
|
||
});
|
||
}
|
||
});
|
||
}
|
||
|
||
// Функции для работы с SMS
|
||
function sendSMS() {
|
||
console.log('=== ОТПРАВКА SMS ===');
|
||
console.log('Phone:', state.user.mobile);
|
||
console.log('SMS meta data:', state.meta);
|
||
console.log('Injected data:', injected);
|
||
console.log('Available fields:', {
|
||
'state.meta.claim_id': state.meta.claim_id,
|
||
'injected.claim_id': injected.claim_id,
|
||
'state.meta.prefix': state.meta.prefix,
|
||
'injected.prefix': injected.prefix,
|
||
'state.meta.telegram_id': state.meta.telegram_id,
|
||
'injected.telegram_id': injected.telegram_id
|
||
});
|
||
|
||
// Проверяем наличие всех необходимых данных
|
||
if (!state.user.mobile) {
|
||
console.error('ERROR: No phone number');
|
||
alert('Ошибка: не указан номер телефона');
|
||
return;
|
||
}
|
||
|
||
if (!state.meta.unified_id) {
|
||
console.error('ERROR: No unified_id');
|
||
alert('Ошибка: отсутствует unified_id');
|
||
return;
|
||
}
|
||
|
||
var sessionToken = state.meta.session_token || injected.session_token || '';
|
||
if (!sessionToken) {
|
||
console.error('ERROR: No session_token');
|
||
console.log('Checked state.meta.session_token:', state.meta.session_token);
|
||
console.log('Checked injected.session_token:', injected.session_token);
|
||
alert('Ошибка: отсутствует session_token');
|
||
return;
|
||
}
|
||
|
||
var smsData = injected.sms_meta || {};
|
||
|
||
var payload = {
|
||
message: {
|
||
phone_number: state.user.mobile,
|
||
claim_id: smsData.claim_id || '',
|
||
prefix: smsData.prefix || '',
|
||
unified_id: smsData.unified_id || '',
|
||
telegram_id: parseInt(smsData.telegram_id) || null,
|
||
channel: 'web',
|
||
session_token: smsData.session_token || sessionToken
|
||
}
|
||
};
|
||
|
||
console.log('Sending SMS payload:', payload);
|
||
|
||
// Вызываем ваш n8n workflow для отправки SMS
|
||
fetch('https://n8n.clientright.pro/webhook/2cb814ef-b376-40ad-a1bc-85bb82adfa96', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(payload)
|
||
})
|
||
.then(function(response) {
|
||
console.log('SMS response status:', response.status);
|
||
console.log('SMS response headers:', response.headers.get('content-type'));
|
||
|
||
if (!response.ok) {
|
||
throw new Error('HTTP ' + response.status);
|
||
}
|
||
|
||
return response.text().then(function(text) {
|
||
console.log('SMS response text:', text);
|
||
if (!text || text.trim() === '') {
|
||
throw new Error('Empty response from server');
|
||
}
|
||
try {
|
||
return JSON.parse(text);
|
||
} catch (e) {
|
||
console.error('JSON parse error:', e);
|
||
console.error('Response text:', text);
|
||
throw new Error('Invalid JSON response: ' + text);
|
||
}
|
||
});
|
||
})
|
||
.then(function(data) {
|
||
console.log('SMS parsed data:', data);
|
||
if (data && data[0] && data[0].data && data[0].data.status === 'pending') {
|
||
state.meta.smsCodeSent = true;
|
||
state.meta.smsCodeGenerated = data[0].sms_code; // Сохраняем для отладки
|
||
console.log('SMS sent successfully via n8n, code:', data[0].sms_code);
|
||
renderStatement(); // Перерендериваем для показа поля ввода кода
|
||
} else {
|
||
console.error('SMS sending failed:', data);
|
||
alert('Ошибка отправки SMS: ' + (data && data[0] && data[0].data && data[0].data.error || 'Неизвестная ошибка'));
|
||
}
|
||
})
|
||
.catch(function(error) {
|
||
console.error('SMS request failed:', error);
|
||
alert('Ошибка отправки SMS. Попробуйте еще раз.');
|
||
});
|
||
}
|
||
|
||
function verifySMS(code) {
|
||
console.log('Verifying SMS code:', code);
|
||
|
||
var smsData = injected.sms_meta || {};
|
||
|
||
var payload = [{
|
||
answer_text: code,
|
||
prefix: smsData.prefix || injected.prefix || '',
|
||
unified_id: smsData.unified_id || '',
|
||
session_token: smsData.session_token || injected.session_token || '',
|
||
channel: 'web',
|
||
telegram_id: parseInt(smsData.telegram_id || injected.telegram_id) || null,
|
||
user_id: parseInt(smsData.user_id) || null
|
||
}];
|
||
|
||
console.log('Verifying SMS payload:', payload);
|
||
|
||
// Вызываем ваш n8n workflow для проверки SMS кода
|
||
return fetch('https://n8n.clientright.pro/webhook/2b0fb3ba-99a5-4e60-aa1e-36d942329895', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(payload)
|
||
})
|
||
.then(function(response) {
|
||
return response.json();
|
||
})
|
||
.then(function(data) {
|
||
console.log('SMS verification response:', data);
|
||
|
||
var result = data && data[0] ? data[0] : {};
|
||
|
||
if (result.success && result.verified) {
|
||
state.meta.smsVerified = true;
|
||
state.meta.smsCode = code;
|
||
console.log('SMS verified successfully via n8n');
|
||
|
||
// Убираем ошибки с поля ввода
|
||
var smsInput = document.querySelector('.sms-code-input');
|
||
if (smsInput) {
|
||
smsInput.style.borderColor = '#10b981';
|
||
smsInput.style.backgroundColor = '#f0fdf4';
|
||
}
|
||
|
||
renderStatement();
|
||
return true;
|
||
} else {
|
||
console.log('SMS verification failed:', result.error, result.message);
|
||
|
||
// Показываем ошибку пользователю
|
||
var smsInput = document.querySelector('.sms-code-input');
|
||
if (smsInput) {
|
||
smsInput.style.borderColor = '#ef4444';
|
||
smsInput.style.backgroundColor = '#fef2f2';
|
||
|
||
// Показываем сообщение об ошибке
|
||
var container = smsInput.closest('.sms-verification-container');
|
||
if (container) {
|
||
var errorMsg = container.querySelector('.sms-error-message');
|
||
if (!errorMsg) {
|
||
errorMsg = document.createElement('div');
|
||
errorMsg.className = 'sms-error-message';
|
||
errorMsg.style.cssText = 'color:#dc2626;font-size:14px;margin-top:8px;padding:8px 12px;background:#fef2f2;border:1px solid #fecaca;border-radius:8px;';
|
||
container.appendChild(errorMsg);
|
||
}
|
||
errorMsg.textContent = result.message || 'Ошибка проверки кода';
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
})
|
||
.catch(function(error) {
|
||
console.error('SMS verification request failed:', error);
|
||
alert('Ошибка проверки SMS кода. Попробуйте еще раз.');
|
||
return false;
|
||
});
|
||
}
|
||
|
||
function resendSMS() {
|
||
console.log('Resending SMS...');
|
||
state.meta.smsCodeSent = false;
|
||
state.meta.smsCode = '';
|
||
state.meta.smsVerified = false;
|
||
sendSMS();
|
||
}
|
||
|
||
// Отправка данных
|
||
function confirm() {
|
||
console.log('Confirm button clicked');
|
||
|
||
// Если согласие есть, но SMS не отправлена - отправляем SMS
|
||
if (state.meta.privacyConsent && !state.meta.smsCodeSent) {
|
||
sendSMS();
|
||
return;
|
||
}
|
||
|
||
// Если SMS не подтверждена - ничего не делаем
|
||
if (!state.meta.smsVerified) {
|
||
console.log('SMS not verified yet');
|
||
return;
|
||
}
|
||
|
||
// SMS подтверждена - отправляем заявление
|
||
console.log('Sending application...');
|
||
|
||
// Получаем токен для отправки формы
|
||
var formToken = injected.token || '';
|
||
console.log('=== ОТЛАДКА ТОКЕНА ===');
|
||
console.log('injected.token:', injected.token);
|
||
console.log('formToken:', formToken);
|
||
|
||
var payload = {
|
||
user: state.user,
|
||
project: state.project,
|
||
offenders: state.offenders,
|
||
statement_text: 'Заявление отправлено',
|
||
meta: state.meta
|
||
};
|
||
|
||
var body = {
|
||
payload: payload,
|
||
query: {
|
||
session_token: injected.session_token || '',
|
||
telegram_id: injected.telegram_id || ''
|
||
},
|
||
token: formToken,
|
||
_event: 'CONFIRM_STATEMENT'
|
||
};
|
||
|
||
var url = 'https://n8n.clientright.pro/webhook/bca73521-7cb1-4ffe-9e6d-a9ed1b7116e6';
|
||
|
||
fetch(url, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(body)
|
||
})
|
||
.then(function(res) {
|
||
if (res.ok) {
|
||
alert('Данные отправлены успешно!');
|
||
try {
|
||
var TG = window.Telegram && window.Telegram.WebApp;
|
||
if (TG && TG.close) TG.close();
|
||
} catch(e) {}
|
||
} else {
|
||
alert('Ошибка отправки: ' + res.status);
|
||
}
|
||
})
|
||
.catch(function(e) {
|
||
alert('Ошибка: ' + e.message);
|
||
});
|
||
}
|
||
|
||
// Функция инициализации
|
||
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;
|
||
}
|
||
|
||
// Пробуем рендерить
|
||
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){
|
||
updateFieldStyle(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');
|
||
confirm();
|
||
});
|
||
}
|
||
|
||
// Делегированная фильтрация ИНН
|
||
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;
|
||
}
|
||
|
||
// Моментальная валидация и обновление кнопки
|
||
updateFieldStyle(el);
|
||
updateSubmitButton();
|
||
}, { passive: true });
|
||
|
||
window.__innFilterAttached = true;
|
||
}
|
||
|
||
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>`;
|
||
|
||
// Отдаём HTML одной нодой
|
||
return [{ json: { html } }]; |