feat(forms): автоподстановка банков и улучшенная обработка телефона
- Step1Phone: добавлена обработка вставки телефона с автоматической очисткой от +7 и обрезкой до 10 цифр - Step3Payment: заменён Select на AutoComplete для выбора банка с автоподстановкой - generateConfirmationFormHTML: заменён select на input с datalist для автоподстановки банков в форме подтверждения - Добавлены скрытые поля bank_id для сохранения ID банка отдельно от названия - Добавлены файлы для проверки заявки 226564ce Улучшения UX: - Пользователь может вводить название банка вместо прокрутки длинного списка - Автоматическая фильтрация списка банков при вводе - Предупреждение при обрезке номера телефона при вставке
This commit is contained in:
@@ -278,6 +278,25 @@ export default function Step1Phone({
|
||||
maxLength={10}
|
||||
size="large"
|
||||
style={{ flex: 1 }}
|
||||
onPaste={(e) => {
|
||||
// Обработка вставки: очищаем от +7, пробелов и других символов
|
||||
e.preventDefault();
|
||||
const pastedText = (e.clipboardData || (window as any).clipboardData).getData('text');
|
||||
// Убираем все нецифровые символы
|
||||
let cleanText = pastedText.replace(/\D/g, '');
|
||||
// Если начинается с 7 или 8, убираем первую цифру (код страны)
|
||||
if (cleanText.length === 11 && (cleanText.startsWith('7') || cleanText.startsWith('8'))) {
|
||||
cleanText = cleanText.substring(1);
|
||||
}
|
||||
// Оставляем только первые 10 цифр
|
||||
cleanText = cleanText.substring(0, 10);
|
||||
// Устанавливаем очищенное значение
|
||||
form.setFieldValue('phone', cleanText);
|
||||
// Показываем предупреждение, если номер был обрезан
|
||||
if (pastedText.replace(/\D/g, '').length > 10) {
|
||||
message.warning('Номер автоматически обрезан до 10 цифр');
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Space.Compact>
|
||||
</Form.Item>
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Form, Input, Button, Select, message, Space, Divider } from 'antd';
|
||||
import { Form, Input, Button, AutoComplete, message, Space, Divider } from 'antd';
|
||||
import { PhoneOutlined, SafetyOutlined, QrcodeOutlined, MailOutlined, CopyOutlined } from '@ant-design/icons';
|
||||
|
||||
const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8200';
|
||||
const NSPK_BANKS_API = 'http://212.193.27.93/api/payouts/dictionaries/nspk-banks';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
interface Bank {
|
||||
bankid: string;
|
||||
bankname: string;
|
||||
@@ -61,15 +59,34 @@ export default function Step3Payment({
|
||||
setBanks(banksData);
|
||||
addDebugEvent?.('banks', 'success', `✅ Загружено ${banksData.length} банков`, { count: banksData.length });
|
||||
|
||||
// Если есть сохранённый bankName, но нет bankId - пытаемся найти по названию
|
||||
if (formData.bankName && !formData.bankId) {
|
||||
// Если есть сохранённый bankName или bankId - восстанавливаем значения
|
||||
if (formData.bankName) {
|
||||
const foundBank = banksData.find(b =>
|
||||
b.bankname.toLowerCase() === formData.bankName.toLowerCase() ||
|
||||
b.bankname.toLowerCase().includes(formData.bankName.toLowerCase())
|
||||
);
|
||||
if (foundBank) {
|
||||
updateFormData({ bankId: foundBank.bankid });
|
||||
form.setFieldsValue({ bankId: foundBank.bankid });
|
||||
updateFormData({
|
||||
bankId: foundBank.bankid,
|
||||
bankName: foundBank.bankname
|
||||
});
|
||||
form.setFieldsValue({
|
||||
bankId: foundBank.bankid,
|
||||
bankName: foundBank.bankname
|
||||
});
|
||||
}
|
||||
} else if (formData.bankId) {
|
||||
// Если есть только bankId, находим по ID
|
||||
const foundBank = banksData.find(b => b.bankid === formData.bankId);
|
||||
if (foundBank) {
|
||||
updateFormData({
|
||||
bankId: foundBank.bankid,
|
||||
bankName: foundBank.bankname
|
||||
});
|
||||
form.setFieldsValue({
|
||||
bankId: foundBank.bankid,
|
||||
bankName: foundBank.bankname
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
@@ -189,12 +206,15 @@ export default function Step3Payment({
|
||||
}
|
||||
};
|
||||
|
||||
// Инициализация формы с bankId если есть
|
||||
// Инициализация формы с bankId и bankName если есть
|
||||
useEffect(() => {
|
||||
if (formData.bankId) {
|
||||
form.setFieldsValue({ bankId: formData.bankId });
|
||||
if (formData.bankId || formData.bankName) {
|
||||
form.setFieldsValue({
|
||||
bankId: formData.bankId,
|
||||
bankName: formData.bankName
|
||||
});
|
||||
}
|
||||
}, [formData.bankId, form]);
|
||||
}, [formData.bankId, formData.bankName, form]);
|
||||
|
||||
return (
|
||||
<Form
|
||||
@@ -202,7 +222,8 @@ export default function Step3Payment({
|
||||
layout="vertical"
|
||||
initialValues={{
|
||||
...formData,
|
||||
bankId: formData.bankId || formData.bankName, // Fallback на bankName для совместимости
|
||||
bankId: formData.bankId,
|
||||
bankName: formData.bankName,
|
||||
}}
|
||||
style={{ marginTop: 24 }}
|
||||
>
|
||||
@@ -377,40 +398,78 @@ export default function Step3Payment({
|
||||
</div>
|
||||
</Form.Item>
|
||||
|
||||
{/* Скрытое поле для bankId */}
|
||||
<Form.Item name="bankId" hidden>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="Выберите ваш банк"
|
||||
name="bankId"
|
||||
rules={[{ required: true, message: 'Выберите банк для получения выплаты' }]}
|
||||
>
|
||||
<Select
|
||||
placeholder={banksLoading ? "Загрузка списка банков..." : "Выберите банк"}
|
||||
size="large"
|
||||
showSearch
|
||||
loading={banksLoading}
|
||||
notFoundContent={banksLoading ? "Загрузка..." : "Банк не найден"}
|
||||
filterOption={(input: string, option: any) => {
|
||||
const label = option?.label || option?.children;
|
||||
if (typeof label === 'string') {
|
||||
return label.toLowerCase().includes(input.toLowerCase());
|
||||
label="Банк для получения выплаты"
|
||||
name="bankName"
|
||||
rules={[
|
||||
{ required: true, message: 'Выберите банк для получения выплаты' },
|
||||
{
|
||||
validator: (_, value) => {
|
||||
if (!value) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
const foundBank = banks.find(b =>
|
||||
b.bankname.toLowerCase() === value.toLowerCase()
|
||||
);
|
||||
if (!foundBank) {
|
||||
return Promise.reject(new Error('Выберите банк из списка'));
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
]}
|
||||
>
|
||||
<AutoComplete
|
||||
placeholder={banksLoading ? "Загрузка списка банков..." : "Начните вводить название банка"}
|
||||
size="large"
|
||||
loading={banksLoading}
|
||||
notFoundContent={banksLoading ? "Загрузка..." : "Банк не найден. Попробуйте ввести другое название"}
|
||||
options={banks.map((bank) => ({
|
||||
value: bank.bankname,
|
||||
label: bank.bankname,
|
||||
}))}
|
||||
filterOption={(inputValue, option) => {
|
||||
if (!option?.label) return false;
|
||||
return option.label.toLowerCase().includes(inputValue.toLowerCase());
|
||||
}}
|
||||
onChange={(value) => {
|
||||
const selectedBank = banks.find(b => b.bankid === value);
|
||||
onSelect={(value) => {
|
||||
// При выборе из списка находим банк и сохраняем оба поля
|
||||
const selectedBank = banks.find(b => b.bankname === value);
|
||||
if (selectedBank) {
|
||||
updateFormData({
|
||||
bankId: selectedBank.bankid,
|
||||
bankName: selectedBank.bankname
|
||||
});
|
||||
// Устанавливаем bankId в скрытое поле
|
||||
form.setFieldsValue({ bankId: selectedBank.bankid });
|
||||
}
|
||||
}}
|
||||
>
|
||||
{banks.map((bank) => (
|
||||
<Option key={bank.bankid} value={bank.bankid} label={bank.bankname}>
|
||||
{bank.bankname}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
onChange={(value) => {
|
||||
// При вводе текста ищем точное совпадение по названию
|
||||
if (typeof value === 'string') {
|
||||
const foundBank = banks.find(b =>
|
||||
b.bankname.toLowerCase() === value.toLowerCase()
|
||||
);
|
||||
if (foundBank) {
|
||||
updateFormData({
|
||||
bankId: foundBank.bankid,
|
||||
bankName: foundBank.bankname
|
||||
});
|
||||
form.setFieldsValue({ bankId: foundBank.bankid });
|
||||
} else if (value === '') {
|
||||
// Если поле очищено, очищаем и bankId
|
||||
updateFormData({ bankId: undefined, bankName: undefined });
|
||||
form.setFieldsValue({ bankId: undefined });
|
||||
}
|
||||
}
|
||||
}}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
|
||||
@@ -775,10 +775,16 @@ export function generateConfirmationFormHTML(data: any): string {
|
||||
|
||||
function createBankSelect(root, key, value) {
|
||||
var id = 'field_' + root + '_' + key + '_' + Math.random().toString(36).slice(2);
|
||||
var selectHtml = '<select class="inline-field bind bank-select" data-root="' + esc(root) + '" data-key="' + esc(key) + '" id="' + id + '">';
|
||||
selectHtml += '<option value="">Загрузка списка банков...</option>';
|
||||
selectHtml += '</select>';
|
||||
return selectHtml;
|
||||
var datalistId = 'bank-datalist-' + id;
|
||||
// Создаём input с datalist для автоподстановки
|
||||
var inputHtml = '<input type="text" class="inline-field bind bank-select" data-root="' + esc(root) + '" data-key="' + esc(key) + '" id="' + id + '" list="' + datalistId + '" placeholder="Начните вводить название банка" autocomplete="off" />';
|
||||
inputHtml += '<datalist id="' + datalistId + '" class="bank-datalist">';
|
||||
inputHtml += '<option value="">Загрузка списка банков...</option>';
|
||||
inputHtml += '</datalist>';
|
||||
// Скрытое поле для bank_id
|
||||
var hiddenId = id + '_id';
|
||||
inputHtml += '<input type="hidden" class="bank-id-field" data-root="' + esc(root) + '" data-key="bank_id" id="' + hiddenId + '" />';
|
||||
return inputHtml;
|
||||
}
|
||||
|
||||
function createCheckbox(root, key, checked, labelText, required) {
|
||||
@@ -1240,6 +1246,19 @@ export function generateConfirmationFormHTML(data: any): string {
|
||||
var fields = document.querySelectorAll('.bind');
|
||||
console.log('Found fields:', fields.length);
|
||||
|
||||
// Обработка скрытых полей bank_id
|
||||
var bankIdFields = document.querySelectorAll('.bank-id-field');
|
||||
Array.prototype.forEach.call(bankIdFields, function(field) {
|
||||
field.addEventListener('change', function() {
|
||||
var root = this.getAttribute('data-root');
|
||||
var value = this.value;
|
||||
if (root === 'user') {
|
||||
state.user = state.user || {};
|
||||
state.user.bank_id = value;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ✅ Устанавливаем начальный стиль для всех полей и форматируем телефоны
|
||||
Array.prototype.forEach.call(fields, function(field) {
|
||||
var key = field.getAttribute('data-key');
|
||||
@@ -1335,6 +1354,12 @@ export function generateConfirmationFormHTML(data: any): string {
|
||||
// Обновляем состояние
|
||||
if (root === 'user') {
|
||||
state.user = state.user || {};
|
||||
// Для bank_id не сохраняем название банка, только ID из скрытого поля
|
||||
if (key === 'bank_id' && this.classList.contains('bank-select')) {
|
||||
// Это текстовое поле для названия банка - не сохраняем в state
|
||||
// bank_id будет сохранён из скрытого поля
|
||||
return;
|
||||
}
|
||||
state.user[key] = value;
|
||||
|
||||
// Обновляем телефон в СБП
|
||||
@@ -1437,8 +1462,8 @@ export function generateConfirmationFormHTML(data: any): string {
|
||||
|
||||
// Загрузка списка банков СБП
|
||||
function loadBanks() {
|
||||
var bankSelects = document.querySelectorAll('.bank-select');
|
||||
if (bankSelects.length === 0) {
|
||||
var bankInputs = document.querySelectorAll('.bank-select');
|
||||
if (bankInputs.length === 0) {
|
||||
console.log('Bank select fields not found');
|
||||
return;
|
||||
}
|
||||
@@ -1458,32 +1483,109 @@ export function generateConfirmationFormHTML(data: any): string {
|
||||
return a.bankname.localeCompare(b.bankname, 'ru');
|
||||
});
|
||||
|
||||
// Заполняем все bank-select элементы
|
||||
Array.prototype.forEach.call(bankSelects, function(select) {
|
||||
var currentValue = select.getAttribute('data-selected') || state.user?.bank_id || '';
|
||||
select.innerHTML = '<option value="">Выберите банк</option>';
|
||||
// Сохраняем список банков глобально для поиска
|
||||
window.__banksList = banks;
|
||||
|
||||
// Заполняем все datalist элементы
|
||||
Array.prototype.forEach.call(bankInputs, function(input) {
|
||||
var datalistId = input.getAttribute('list');
|
||||
var datalist = document.getElementById(datalistId);
|
||||
var hiddenId = input.id + '_id';
|
||||
var hiddenField = document.getElementById(hiddenId);
|
||||
var currentBankId = state.user?.bank_id || '';
|
||||
var currentBankName = '';
|
||||
|
||||
if (!datalist) {
|
||||
console.error('Datalist not found for input:', input.id);
|
||||
return;
|
||||
}
|
||||
|
||||
// Очищаем datalist
|
||||
datalist.innerHTML = '';
|
||||
|
||||
// Заполняем datalist опциями
|
||||
banks.forEach(function(bank) {
|
||||
var option = document.createElement('option');
|
||||
option.value = bank.bankid;
|
||||
option.textContent = bank.bankname;
|
||||
if (bank.bankid === currentValue) {
|
||||
option.selected = true;
|
||||
option.value = bank.bankname;
|
||||
option.setAttribute('data-bank-id', bank.bankid);
|
||||
datalist.appendChild(option);
|
||||
|
||||
// Если это текущий банк, устанавливаем значение
|
||||
if (bank.bankid === currentBankId) {
|
||||
currentBankName = bank.bankname;
|
||||
}
|
||||
select.appendChild(option);
|
||||
});
|
||||
|
||||
// Если выбран банк, обновляем стиль
|
||||
if (currentValue && select.value) {
|
||||
select.classList.add('filled');
|
||||
updateFieldStyle(select);
|
||||
// Устанавливаем текущее значение если есть
|
||||
if (currentBankName) {
|
||||
input.value = currentBankName;
|
||||
if (hiddenField) {
|
||||
hiddenField.value = currentBankId;
|
||||
}
|
||||
input.classList.add('filled');
|
||||
updateFieldStyle(input);
|
||||
}
|
||||
|
||||
// Обработчик изменения для поиска банка по названию
|
||||
input.addEventListener('input', function() {
|
||||
var inputValue = this.value.trim();
|
||||
var foundBank = null;
|
||||
|
||||
// Ищем точное совпадение
|
||||
if (inputValue) {
|
||||
foundBank = banks.find(function(b) {
|
||||
return b.bankname.toLowerCase() === inputValue.toLowerCase();
|
||||
});
|
||||
}
|
||||
|
||||
if (foundBank) {
|
||||
// Найден банк - сохраняем ID
|
||||
if (hiddenField) {
|
||||
hiddenField.value = foundBank.bankid;
|
||||
}
|
||||
state.user = state.user || {};
|
||||
state.user.bank_id = foundBank.bankid;
|
||||
this.classList.add('filled');
|
||||
} else {
|
||||
// Банк не найден - очищаем ID
|
||||
if (hiddenField) {
|
||||
hiddenField.value = '';
|
||||
}
|
||||
state.user = state.user || {};
|
||||
state.user.bank_id = '';
|
||||
this.classList.remove('filled');
|
||||
}
|
||||
updateFieldStyle(this);
|
||||
updateSubmitButton();
|
||||
});
|
||||
|
||||
// Обработчик выбора из списка
|
||||
input.addEventListener('change', function() {
|
||||
var inputValue = this.value.trim();
|
||||
var foundBank = banks.find(function(b) {
|
||||
return b.bankname.toLowerCase() === inputValue.toLowerCase();
|
||||
});
|
||||
|
||||
if (foundBank) {
|
||||
if (hiddenField) {
|
||||
hiddenField.value = foundBank.bankid;
|
||||
}
|
||||
state.user = state.user || {};
|
||||
state.user.bank_id = foundBank.bankid;
|
||||
this.classList.add('filled');
|
||||
updateFieldStyle(this);
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.error('Error loading banks:', error);
|
||||
Array.prototype.forEach.call(bankSelects, function(select) {
|
||||
select.innerHTML = '<option value="">Ошибка загрузки банков. Обновите страницу.</option>';
|
||||
Array.prototype.forEach.call(bankInputs, function(input) {
|
||||
var datalistId = input.getAttribute('list');
|
||||
var datalist = document.getElementById(datalistId);
|
||||
if (datalist) {
|
||||
datalist.innerHTML = '<option value="">Ошибка загрузки банков. Обновите страницу.</option>';
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1552,6 +1654,17 @@ export function generateConfirmationFormHTML(data: any): string {
|
||||
return;
|
||||
}
|
||||
|
||||
// Собираем bank_id из скрытых полей перед отправкой
|
||||
var bankIdFields = document.querySelectorAll('.bank-id-field');
|
||||
Array.prototype.forEach.call(bankIdFields, function(field) {
|
||||
var root = field.getAttribute('data-root');
|
||||
var bankId = field.value;
|
||||
if (root === 'user' && bankId) {
|
||||
state.user = state.user || {};
|
||||
state.user.bank_id = bankId;
|
||||
}
|
||||
});
|
||||
|
||||
window.parent.postMessage({
|
||||
type: 'claim_confirmed',
|
||||
data: {
|
||||
|
||||
Reference in New Issue
Block a user