diff --git a/frontend/src/components/form/generateConfirmationFormHTML.ts b/frontend/src/components/form/generateConfirmationFormHTML.ts index 067e000..fe99be4 100644 --- a/frontend/src/components/form/generateConfirmationFormHTML.ts +++ b/frontend/src/components/form/generateConfirmationFormHTML.ts @@ -218,6 +218,81 @@ export function generateConfirmationFormHTML(data: any): string { 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; @@ -358,6 +433,20 @@ export function generateConfirmationFormHTML(data: any): string { return ''; } + 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 = ''; + + return checkboxHtml; + } + var injected = getData(); var state = (injected && injected.case) ? injected.case : { user: {}, project: {}, offenders: [{}], meta: {} }; @@ -407,48 +496,90 @@ export function generateConfirmationFormHTML(data: any): string { html += '
'; + // Возмещение + html += '

Возмещение:

'; + html += '

Выплата возмещения возможна по системе быстрых платежей (СБП) по номеру телефона заявителя: ' + esc(u.mobile || '') + '

'; + + html += '
'; + + // Заявление html += '

ЗАЯВЛЕНИЕ

'; + // Тема обращения (только для чтения) html += '

Тема обращения: '; html += createReadonlyField('project', 'category', p.category); html += '

'; + // Название договора / предмет html += '

Предмет договора: '; html += createField('project', 'subject', p.subject, 'Название договора или предмет услуг'); html += '

'; + // Дата события / заключения договора html += '

Дата события / заключения договора: '; html += createDateField('project', 'agrdate', p.agrdate); html += '

'; + // Сумма оплаты / стоимость html += '

Сумма оплаты / стоимость: '; html += createMoneyField('project', 'agrprice', p.agrprice); html += '

'; + // Период + html += '

Период: '; + 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 += '

'; + html += '
'; + // Контрагенты / участники html += '

Контрагенты / участники:

'; for (var i = 0; i < state.offenders.length; i++) { var offender = state.offenders[i]; html += '
'; + html += '

Наименование: '; html += createField('offender', 'accountname', offender.accountname, 'Название организации', i); html += '

'; + html += '

ИНН: '; html += createField('offender', 'inn', offender.inn, 'ИНН организации (10 или 12 цифр)', i); html += '

'; + html += '

ОГРН: '; html += createField('offender', 'ogrn', offender.ogrn, 'ОГРН', i); html += '

'; + html += '

Адрес: '; html += createField('offender', 'address', offender.address, 'Адрес', i); html += '

'; + + html += '

E-mail: '; + html += createField('offender', 'email', offender.email, 'email@example.com', i); + html += '

'; + + html += '

Телефон: '; + html += createField('offender', 'phone', offender.phone, '+7 (___) ___-__-__', i); + html += '

'; + + html += '

Сайт: '; + html += createField('offender', 'website', offender.website, 'https://example.com', i); + html += '

'; + html += '
'; } html += '
'; + // Причина обращения (редактируемая) html += '

Причина обращения: '; html += createField('project', 'reason', p.reason, 'Можете уточнить или изменить причину обращения'); html += '

'; @@ -456,24 +587,237 @@ export function generateConfirmationFormHTML(data: any): string { html += '

Описание проблемы:

'; html += createTextarea('project', 'description', p.description); + html += '

На основании вышеизложенного и руководствуясь ст. 45 Закона «О защите прав потребителей», ст. 3, ч. 1 ст. 46 ГПК РФ, прошу вас защитить мои потребительские права, обратиться в суд с заявлением о защите моих потребительских прав и/или с коллективным иском о защите группы потребителей, и представлять мои интересы во всех судебных органах РФ, а также обращаться с заявлениями во все госорганы, подавать претензии, письма и жалобы.

'; + + html += '
'; + + // Согласие на обработку персональных данных + html += '
'; + html += createCheckbox('meta', 'privacyConsent', state.meta && state.meta.privacyConsent, + 'Я ознакомлен(а) и согласен(а) с Политикой обработки персональных данных и даю согласие на обработку моих персональных данных в соответствии с Федеральным законом от 27.07.2006 №152-ФЗ «О персональных данных»', + true); + html += '
'; + + // Список приложенных документов + if (state.attachments && state.attachments.length > 0) { + html += '
'; + html += '

Приложенные документы:

'; + html += '
'; + + for (var i = 0; i < state.attachments.length; i++) { + var fileName = state.attachments[i]; + html += '
'; + html += '📎'; + html += '' + esc(fileName) + ''; + html += '
'; + } + + html += '

'; + html += 'Всего документов: ' + state.attachments.length; + html += '

'; + html += '
'; + } + 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; @@ -481,37 +825,221 @@ export function generateConfirmationFormHTML(data: any): string { 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() { - renderStatement(); - - var confirmBtn = document.getElementById('confirmBtn'); - if (confirmBtn) { - confirmBtn.addEventListener('click', function(e) { - e.preventDefault(); - window.parent.postMessage({ - type: 'claim_confirmed', - data: { - user: state.user, - project: state.project, - offenders: state.offenders, - meta: state.meta - }, - originalData: injected - }, '*'); - }); + 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; + } + + // Отправляем сообщение родителю при загрузке + window.parent.postMessage({type: 'claim_form_loaded'}, '*'); + + 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 = '

Ошибка загрузки: ' + e.message + '

Проверьте консоль браузера (F12) для деталей.

'; + } } - - window.parent.postMessage({type: 'claim_form_loaded'}, '*'); } + // Запускаем инициализацию когда DOM готов if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', initialize); + console.log('DOM еще загружается, ждем события DOMContentLoaded...'); + document.addEventListener('DOMContentLoaded', function() { + console.log('DOMContentLoaded fired, starting initialization...'); + initialize(); + }); } else { + console.log('DOM уже готов, запускаем инициализацию сразу...'); initialize(); } })();