feat: добавлена визуальная подсветка обязательных полей

- Добавлены звёздочки (*) рядом с обязательными полями
- Незаполненные обязательные поля подсвечиваются жёлтой рамкой
- Добавлен блок с предупреждением о незаполненных полях перед кнопкой отправки
- Улучшена валидация с визуальной обратной связью
- Пользователю теперь понятно, какие поля нужно заполнить
This commit is contained in:
Fedor
2025-12-03 18:59:58 +03:00
parent 38457394c1
commit 346d9a77d2

View File

@@ -373,6 +373,58 @@ export function generateConfirmationFormHTML(data: any, contact_data_confirmed:
box-shadow:0 0 0 2px rgba(239,68,68,0.1) !important;
background-color:#fef2f2 !important;
}
/* ⚠️ Желтая рамка для незаполненных обязательных полей */
.inline-field.required-empty{
border-color:#f59e0b !important;
background-color:#fffbeb !important;
border-width:2px !important;
}
.inline-field.required-empty:focus{
border-color:#f59e0b !important;
box-shadow:0 0 0 3px rgba(245,158,11,0.2) !important;
background-color:#fffbeb !important;
}
/* Звёздочка для обязательных полей */
.required-marker{
color:#ef4444;
font-weight:bold;
margin-left:2px;
}
/* Блок с предупреждением о незаполненных полях */
.validation-warning{
margin:16px 0;
padding:12px 16px;
background:#fffbeb;
border:2px solid #f59e0b;
border-radius:8px;
font-size:14px;
color:#92400e;
}
.validation-warning-title{
font-weight:600;
margin-bottom:8px;
display:flex;
align-items:center;
gap:8px;
}
.validation-warning-list{
margin:0;
padding-left:20px;
list-style:none;
}
.validation-warning-list li{
margin:4px 0;
padding-left:20px;
position:relative;
}
.validation-warning-list li:before{
content:'•';
position:absolute;
left:0;
color:#f59e0b;
font-weight:bold;
font-size:18px;
}
.inline-field.large{
min-width:200px;max-width:500px;
}
@@ -925,28 +977,28 @@ export function generateConfirmationFormHTML(data: any, contact_data_confirmed:
html += '</div>';
html += '<p><strong>Заявитель:</strong> ';
html += createField('user', 'lastname', u.lastname, 'Фамилия (обязательно)');
html += createField('user', 'lastname', u.lastname, 'Фамилия') + '<span class="required-marker">*</span>';
html += ' ';
html += createField('user', 'firstname', u.firstname, 'Имя (обязательно)');
html += createField('user', 'firstname', u.firstname, 'Имя') + '<span class="required-marker">*</span>';
html += ' ';
html += createField('user', 'secondname', u.secondname, 'Отчество');
html += '</p>';
html += '<p><strong>Дата рождения:</strong> ';
html += createDateField('user', 'birthday', u.birthday);
html += '</p>';
html += '<span class="required-marker">*</span></p>';
html += '<p><strong>Место рождения:</strong> ';
html += createField('user', 'birthplace', u.birthplace, 'Место рождения (обязательно)');
html += '</p>';
html += createField('user', 'birthplace', u.birthplace, 'Место рождения');
html += '<span class="required-marker">*</span></p>';
html += '<p><strong>ИНН:</strong> ';
html += createField('user', 'inn', u.inn, '12-значный ИНН (обязательно)');
html += '</p>';
html += createField('user', 'inn', u.inn, '12-значный ИНН');
html += '<span class="required-marker">*</span></p>';
html += '<p><strong>Адрес:</strong> ';
html += createField('user', 'mailingstreet', u.mailingstreet, 'Адрес регистрации как в паспорте (обязательно)');
html += '</p>';
html += createField('user', 'mailingstreet', u.mailingstreet, 'Адрес регистрации как в паспорте');
html += '<span class="required-marker">*</span></p>';
html += '<p><strong>Телефон:</strong> ';
html += createReadonlyField('user', 'mobile', u.mobile);
@@ -961,9 +1013,9 @@ export function generateConfirmationFormHTML(data: any, contact_data_confirmed:
// Возмещение
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 += '<p><strong>Банк для получения выплаты (обязательно):</strong> ';
html += '<p><strong>Банк для получения выплаты:</strong> ';
html += createBankSelect('user', 'bank_id', u.bank_id || '');
html += '</p>';
html += '<span class="required-marker">*</span></p>';
html += '<div class="section-break"></div>';
@@ -983,12 +1035,12 @@ export function generateConfirmationFormHTML(data: any, contact_data_confirmed:
// Дата события / заключения договора
html += '<p><strong>Дата события / заключения договора:</strong> ';
html += createDateField('project', 'agrdate', p.agrdate);
html += '</p>';
html += '<span class="required-marker">*</span></p>';
// Сумма оплаты / стоимость
html += '<p><strong>Сумма оплаты / стоимость:</strong> ';
html += createMoneyField('project', 'agrprice', p.agrprice);
html += '</p>';
html += '<span class="required-marker">*</span></p>';
// Период
html += '<p><strong>Период:</strong> ';
@@ -1013,19 +1065,19 @@ export function generateConfirmationFormHTML(data: any, contact_data_confirmed:
html += '<p><strong>Наименование:</strong> ';
html += createField('offender', 'accountname', offender.accountname, 'Название организации', i);
html += '</p>';
html += '<span class="required-marker">*</span></p>';
html += '<p><strong>ИНН:</strong> ';
html += createField('offender', 'inn', offender.inn, 'ИНН организации (10 или 12 цифр) (обязательно)', i);
html += '</p>';
html += createField('offender', 'inn', offender.inn, 'ИНН организации (10 или 12 цифр)', i);
html += '<span class="required-marker">*</span></p>';
html += '<p><strong>Адрес:</strong> ';
html += createField('offender', 'address', offender.address, 'Адрес (обязательно)', i);
html += '</p>';
html += createField('offender', 'address', offender.address, 'Адрес', i);
html += '<span class="required-marker">*</span></p>';
html += '<p><strong>E-mail:</strong> ';
html += createField('offender', 'email', offender.email, 'email@example.com (обязательно)', i);
html += '</p>';
html += createField('offender', 'email', offender.email, 'email@example.com', i);
html += '<span class="required-marker">*</span></p>';
html += '<p><strong>Телефон:</strong> ';
html += createField('offender', 'phone', offender.phone, '+7 (___) ___-__-__', i);
@@ -1043,15 +1095,18 @@ export function generateConfirmationFormHTML(data: any, contact_data_confirmed:
// Причина обращения (редактируемая)
html += '<p><strong>Причина обращения:</strong> ';
html += createField('project', 'reason', p.reason, 'Можете уточнить или изменить причину обращения');
html += '</p>';
html += '<span class="required-marker">*</span></p>';
html += '<p><strong>Описание проблемы:</strong></p>';
html += '<p><strong>Описание проблемы:</strong> <span class="required-marker">*</span></p>';
html += createTextarea('project', 'description', p.description);
html += '<p>На основании вышеизложенного и руководствуясь ст. 45 Закона «О защите прав потребителей», ст. 3, ч. 1 ст. 46 ГПК РФ, прошу вас защитить мои потребительские права, обратиться в суд с заявлением о защите моих потребительских прав и/или с коллективным иском о защите группы потребителей, и представлять мои интересы во всех судебных органах РФ, а также обращаться с заявлениями во все госорганы, подавать претензии, письма и жалобы.</p>';
html += '<div class="section-break"></div>';
// Блок с предупреждением о незаполненных полях (будет обновляться динамически)
html += '<div id="validation-warning-block" style="display:none;"></div>';
// Согласие на обработку персональных данных
html += '<div style="margin:24px 0;">';
html += createCheckbox('meta', 'privacyConsent', state.meta && state.meta.privacyConsent,
@@ -1183,39 +1238,62 @@ export function generateConfirmationFormHTML(data: any, contact_data_confirmed:
return parseFloat(s) > 0;
}
// Список обязательных полей
var requiredFieldsList = [
{ 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: 'inn', name: 'ИНН организации' },
{ root: 'offender', key: 'address', name: 'Адрес организации' },
{ root: 'offender', key: 'email', name: 'E-mail организации' },
{ root: 'user', key: 'bank_id', name: 'Банк для получения выплаты' }
];
// Функция проверки, является ли поле обязательным
function isRequiredField(root, key) {
return requiredFieldsList.some(function(f) {
return f.root === root && f.key === key;
});
}
// Функция получения значения поля
function getFieldValue(root, key, index) {
if (root === 'user') {
return state.user[key] || '';
} else if (root === 'project') {
return state.project[key] || '';
} else if (root === 'offender') {
var offender = state.offenders[index || 0];
return (offender && offender[key]) || '';
}
return '';
}
// Функция проверки, заполнено ли поле
function isFieldFilled(root, key, index) {
var value = getFieldValue(root, key, index);
if (value === null || value === undefined) return false;
if (typeof value === 'string') {
return value.trim().length > 0;
}
return !!value;
}
// Функция проверки всех обязательных полей
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: 'inn', name: 'ИНН организации' },
{ root: 'offender', key: 'address', name: 'Адрес организации' },
{ root: 'offender', key: 'email', name: 'E-mail организации' },
{ root: 'user', key: 'bank_id', 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() === '')) {
for (var i = 0; i < requiredFieldsList.length; i++) {
var field = requiredFieldsList[i];
if (!isFieldFilled(field.root, field.key, field.root === 'offender' ? 0 : undefined)) {
errors.push(field.name);
}
}
@@ -1223,6 +1301,34 @@ export function generateConfirmationFormHTML(data: any, contact_data_confirmed:
return errors;
}
// Функция обновления блока с предупреждением о незаполненных полях
function updateValidationWarning() {
var warningBlock = document.getElementById('validation-warning-block');
if (!warningBlock) return;
var validationErrors = validateAllFields();
if (validationErrors.length === 0) {
warningBlock.style.display = 'none';
return;
}
// Формируем HTML для предупреждения
var warningHtml = '<div class="validation-warning">';
warningHtml += '<div class="validation-warning-title">';
warningHtml += '⚠️ Пожалуйста, заполните все обязательные поля (' + validationErrors.length + '):';
warningHtml += '</div>';
warningHtml += '<ul class="validation-warning-list">';
for (var i = 0; i < validationErrors.length; i++) {
warningHtml += '<li>' + esc(validationErrors[i]) + '</li>';
}
warningHtml += '</ul>';
warningHtml += '</div>';
warningBlock.innerHTML = warningHtml;
warningBlock.style.display = 'block';
}
// Функция обновления состояния кнопки отправки
function updateSubmitButton() {
var confirmBtn = document.getElementById('confirmBtn');
@@ -1231,6 +1337,15 @@ export function generateConfirmationFormHTML(data: any, contact_data_confirmed:
var isConsentGiven = state.meta && state.meta.privacyConsent === true;
var validationErrors = validateAllFields();
// Обновляем блок с предупреждением
updateValidationWarning();
// Обновляем стили всех полей
var fields = document.querySelectorAll('.bind');
Array.prototype.forEach.call(fields, function(field) {
updateFieldStyle(field);
});
if (!isConsentGiven) {
confirmBtn.disabled = true;
confirmBtn.style.opacity = '0.6';
@@ -1245,7 +1360,7 @@ export function generateConfirmationFormHTML(data: any, contact_data_confirmed:
confirmBtn.disabled = true;
confirmBtn.style.opacity = '0.6';
confirmBtn.style.cursor = 'not-allowed';
confirmBtn.textContent = '❌ Заполните все поля (' + validationErrors.length + ')';
confirmBtn.textContent = '❌ Заполните все обязательные поля (' + validationErrors.length + ')';
confirmBtn.title = 'Не заполнены: ' + validationErrors.join(', ');
}
}
@@ -1256,10 +1371,16 @@ export function generateConfirmationFormHTML(data: any, contact_data_confirmed:
var hasValue = field.type === 'checkbox' ? value : value.length > 0;
var key = field.getAttribute('data-key');
var root = field.getAttribute('data-root');
var index = field.getAttribute('data-index');
var fieldIndex = index !== null ? parseInt(index, 10) : undefined;
// Убираем оба класса сначала
// Убираем все классы сначала
field.classList.remove('filled');
field.classList.remove('invalid');
field.classList.remove('required-empty');
// Проверяем, является ли поле обязательным
var isRequired = isRequiredField(root, key);
if (hasValue) {
// Проверяем валидность для телефона и email
@@ -1283,10 +1404,16 @@ export function generateConfirmationFormHTML(data: any, contact_data_confirmed:
}
if (isValid) {
field.classList.add('filled');
} else {
field.classList.add('filled');
} else {
field.classList.add('invalid');
}
} else {
// Поле не заполнено
if (isRequired) {
// Обязательное поле не заполнено - подсвечиваем жёлтым
field.classList.add('required-empty');
}
}
}