feat: Обновления после последнего коммита
Изменения в backend: - Обновления в n8n_proxy.py - Изменения в SMS API - Обновления конфигурации - Улучшения SMS сервиса Изменения в frontend: - Обновления Step1Phone компонента - Изменения в Step3Payment - Улучшения generateConfirmationFormHTML - Обновления ClaimForm страницы - Изменения в vite.config.ts Статистика: +242 строки, -81 строка
This commit is contained in:
99
GIT_STATUS.md
Normal file
99
GIT_STATUS.md
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
# 📊 Статус Git репозитория DEV
|
||||||
|
|
||||||
|
**Дата проверки:** 2 января 2025
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📅 Последний коммит
|
||||||
|
|
||||||
|
**Дата:** 29 декабря 2025, 10:59:21
|
||||||
|
**Автор:** Fedor (fedor@clientright.ru)
|
||||||
|
**Сообщение:** `feat: Add SMS debug code modal for dev environment`
|
||||||
|
**Хеш:** `f7d27388a0b62380e4f1bdeba3c997f50ff10587`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ Незакоммиченные изменения
|
||||||
|
|
||||||
|
**Всего изменено файлов:** 9
|
||||||
|
|
||||||
|
### Backend (4 файла):
|
||||||
|
- `backend/app/api/n8n_proxy.py` - изменён
|
||||||
|
- `backend/app/api/sms.py` - изменён
|
||||||
|
- `backend/app/config.py` - изменён
|
||||||
|
- `backend/app/services/sms_service.py` - изменён
|
||||||
|
|
||||||
|
### Frontend (5 файлов):
|
||||||
|
- `frontend/src/components/form/Step1Phone.tsx` - изменён
|
||||||
|
- `frontend/src/components/form/Step3Payment.tsx` - изменён
|
||||||
|
- `frontend/src/components/form/generateConfirmationFormHTML.ts` - изменён
|
||||||
|
- `frontend/src/pages/ClaimForm.tsx` - изменён
|
||||||
|
- `frontend/vite.config.ts` - изменён
|
||||||
|
|
||||||
|
**Статистика изменений:**
|
||||||
|
- Добавлено: ~242 строки
|
||||||
|
- Удалено: ~81 строка
|
||||||
|
- Чистое изменение: +161 строка
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📤 Статус с remote
|
||||||
|
|
||||||
|
**Ветка:** `main`
|
||||||
|
**Remote:** `origin/main`
|
||||||
|
**Статус:** Есть локальные изменения, которые не запушены в remote
|
||||||
|
|
||||||
|
**Не запушенные изменения:**
|
||||||
|
- `backend/app/api/n8n_proxy.py`
|
||||||
|
- `backend/app/api/sms.py`
|
||||||
|
- `backend/app/config.py`
|
||||||
|
- `backend/app/services/sms_service.py`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔄 История коммитов (последние 5)
|
||||||
|
|
||||||
|
1. **2025-12-29** - `feat: Add SMS debug code modal for dev environment`
|
||||||
|
2. **2025-12-29** - `Add docker-compose.dev.yml for dev environment (ports 5177, 8201)`
|
||||||
|
3. **2025-12-29** - `docs: Move session log to root`
|
||||||
|
4. **2025-12-29** - `Add session log 2025-12-29`
|
||||||
|
5. **2025-12-29** - `Production fixes: n8n workflow auto-restart, user-friendly messages, fixed navigation buttons`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💡 Рекомендации
|
||||||
|
|
||||||
|
### 1. Закоммитить изменения
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /var/www/fastuser/data/www/crm.clientright.ru/aiform_dev
|
||||||
|
|
||||||
|
# Посмотреть что изменилось
|
||||||
|
git diff
|
||||||
|
|
||||||
|
# Добавить все изменения
|
||||||
|
git add .
|
||||||
|
|
||||||
|
# Закоммитить
|
||||||
|
git commit -m "feat: Описание изменений"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Запушить в remote
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Отправить в dev репозиторий
|
||||||
|
git push origin main
|
||||||
|
|
||||||
|
# Или если remote называется aiform_dev
|
||||||
|
git push aiform_dev main
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Перенести в PROD (если нужно)
|
||||||
|
|
||||||
|
После коммита и пуша, можно перенести изменения в PROD папку.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Автор:** AI Assistant + Фёдор
|
||||||
|
**Дата:** 2 января 2025
|
||||||
|
|
||||||
@@ -15,11 +15,11 @@ logger = logging.getLogger(__name__)
|
|||||||
router = APIRouter(prefix="/api/n8n", tags=["n8n-proxy"])
|
router = APIRouter(prefix="/api/n8n", tags=["n8n-proxy"])
|
||||||
|
|
||||||
|
|
||||||
# URL webhooks из .env (будут добавлены)
|
# URL webhooks - берём из settings (defaults в config.py)
|
||||||
N8N_POLICY_CHECK_WEBHOOK = getattr(settings, 'n8n_policy_check_webhook', None)
|
N8N_POLICY_CHECK_WEBHOOK = settings.n8n_policy_check_webhook or None
|
||||||
N8N_FILE_UPLOAD_WEBHOOK = getattr(settings, 'n8n_file_upload_webhook', None)
|
N8N_FILE_UPLOAD_WEBHOOK = settings.n8n_file_upload_webhook or None
|
||||||
N8N_CREATE_CONTACT_WEBHOOK = getattr(settings, 'n8n_create_contact_webhook', 'https://n8n.clientright.pro/webhook/511fde97-88bb-4fb4-bea5-cafdc364be27')
|
N8N_CREATE_CONTACT_WEBHOOK = settings.n8n_create_contact_webhook
|
||||||
N8N_CREATE_CLAIM_WEBHOOK = getattr(settings, 'n8n_create_claim_webhook', 'https://n8n.clientright.pro/webhook/d5bf4ca6-9e44-44b9-9714-3186ea703e7d')
|
N8N_CREATE_CLAIM_WEBHOOK = settings.n8n_create_claim_webhook
|
||||||
|
|
||||||
|
|
||||||
@router.post("/policy/check")
|
@router.post("/policy/check")
|
||||||
@@ -124,7 +124,9 @@ async def proxy_create_contact(request: Request):
|
|||||||
logger.error("⏱️ N8N webhook timeout")
|
logger.error("⏱️ N8N webhook timeout")
|
||||||
raise HTTPException(status_code=504, detail="Таймаут подключения к n8n")
|
raise HTTPException(status_code=504, detail="Таймаут подключения к n8n")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
import traceback
|
||||||
logger.error(f"❌ Error proxying to n8n: {e}")
|
logger.error(f"❌ Error proxying to n8n: {e}")
|
||||||
|
logger.error(f"❌ Traceback: {traceback.format_exc()}")
|
||||||
raise HTTPException(status_code=500, detail=f"Ошибка создания контакта: {str(e)}")
|
raise HTTPException(status_code=500, detail=f"Ошибка создания контакта: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ async def send_sms_code(request: SMSSendRequest):
|
|||||||
return {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
"message": "Код отправлен на указанный номер",
|
"message": "Код отправлен на указанный номер",
|
||||||
"debug_code": code if sms_service.enabled else None # Показываем код только в dev
|
"debug_code": code # Всегда возвращаем код для dev модалки
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
|
|||||||
@@ -177,8 +177,8 @@ class Settings(BaseSettings):
|
|||||||
n8n_api_key: str = "" # Нужно задать в .env
|
n8n_api_key: str = "" # Нужно задать в .env
|
||||||
n8n_policy_check_webhook: str = ""
|
n8n_policy_check_webhook: str = ""
|
||||||
n8n_file_upload_webhook: str = ""
|
n8n_file_upload_webhook: str = ""
|
||||||
n8n_create_contact_webhook: str = ""
|
n8n_create_contact_webhook: str = "https://n8n.clientright.pro/webhook/511fde97-88bb-4fb4-bea5-cafdc364be27"
|
||||||
n8n_create_claim_webhook: str = ""
|
n8n_create_claim_webhook: str = "https://n8n.clientright.pro/webhook/d5bf4ca6-9e44-44b9-9714-3186ea703e7d"
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
# LOGGING
|
# LOGGING
|
||||||
|
|||||||
@@ -65,11 +65,17 @@ class SMSService:
|
|||||||
logger.warning("SMS отправка отключена в конфигурации")
|
logger.warning("SMS отправка отключена в конфигурации")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# 🔧 DEV: ПРИНУДИТЕЛЬНО ОТКЛЮЧЕНА ОТПРАВКА SMS
|
||||||
|
# Раскомментировать для продакшена!
|
||||||
|
logger.info(f"🔧 DEV MODE: SMS to {phone} ЗАБЛОКИРОВАНА (экономим бюджет!)")
|
||||||
|
logger.info(f"📱 Message: {message}")
|
||||||
|
return True
|
||||||
|
|
||||||
# DEBUG MODE: Не отправляем реальные SMS, экономим бюджет
|
# DEBUG MODE: Не отправляем реальные SMS, экономим бюджет
|
||||||
if settings.debug or settings.app_env == "development":
|
# if settings.debug or settings.app_env == "development":
|
||||||
logger.info(f"🔧 DEBUG MODE: SMS to {phone} not sent (saving money!)")
|
# logger.info(f"🔧 DEBUG MODE: SMS to {phone} not sent (saving money!)")
|
||||||
logger.info(f"📱 Message would be: {message}")
|
# logger.info(f"📱 Message would be: {message}")
|
||||||
return True
|
# return True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Получаем актуальный токен
|
# Получаем актуальный токен
|
||||||
|
|||||||
@@ -352,7 +352,18 @@ export default function Step1Phone({
|
|||||||
icon={<CopyOutlined />}
|
icon={<CopyOutlined />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (debugCode) {
|
if (debugCode) {
|
||||||
navigator.clipboard.writeText(debugCode);
|
// Fallback для HTTP (clipboard API требует HTTPS)
|
||||||
|
if (navigator.clipboard && window.isSecureContext) {
|
||||||
|
navigator.clipboard.writeText(debugCode);
|
||||||
|
} else {
|
||||||
|
// Fallback: копируем через textarea
|
||||||
|
const textArea = document.createElement('textarea');
|
||||||
|
textArea.value = debugCode;
|
||||||
|
document.body.appendChild(textArea);
|
||||||
|
textArea.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
document.body.removeChild(textArea);
|
||||||
|
}
|
||||||
message.success('Код скопирован!');
|
message.success('Код скопирован!');
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -51,10 +51,17 @@ export default function Step3Payment({
|
|||||||
throw new Error(`HTTP ${response.status}`);
|
throw new Error(`HTTP ${response.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const banksData: Bank[] = await response.json();
|
let banksData: Bank[] = await response.json();
|
||||||
|
|
||||||
|
// ✅ Фильтруем банки без названия
|
||||||
|
banksData = banksData.filter(bank => bank && bank.bankname && typeof bank.bankname === 'string');
|
||||||
|
|
||||||
// Сортируем по названию для удобства
|
// Сортируем по названию для удобства
|
||||||
banksData.sort((a, b) => a.bankname.localeCompare(b.bankname, 'ru'));
|
banksData.sort((a, b) => {
|
||||||
|
const nameA = (a.bankname || '').toString();
|
||||||
|
const nameB = (b.bankname || '').toString();
|
||||||
|
return nameA.localeCompare(nameB, 'ru');
|
||||||
|
});
|
||||||
|
|
||||||
setBanks(banksData);
|
setBanks(banksData);
|
||||||
addDebugEvent?.('banks', 'success', `✅ Загружено ${banksData.length} банков`, { count: banksData.length });
|
addDebugEvent?.('banks', 'success', `✅ Загружено ${banksData.length} банков`, { count: banksData.length });
|
||||||
@@ -62,29 +69,31 @@ export default function Step3Payment({
|
|||||||
// Если есть сохранённый bankName или bankId - восстанавливаем значения
|
// Если есть сохранённый bankName или bankId - восстанавливаем значения
|
||||||
if (formData.bankName) {
|
if (formData.bankName) {
|
||||||
const foundBank = banksData.find(b =>
|
const foundBank = banksData.find(b =>
|
||||||
b.bankname.toLowerCase() === formData.bankName.toLowerCase() ||
|
b && b.bankname && (
|
||||||
b.bankname.toLowerCase().includes(formData.bankName.toLowerCase())
|
b.bankname.toLowerCase() === formData.bankName.toLowerCase() ||
|
||||||
|
b.bankname.toLowerCase().includes(formData.bankName.toLowerCase())
|
||||||
|
)
|
||||||
);
|
);
|
||||||
if (foundBank) {
|
if (foundBank && foundBank.bankname) {
|
||||||
updateFormData({
|
updateFormData({
|
||||||
bankId: foundBank.bankid,
|
bankId: foundBank.bankid || '',
|
||||||
bankName: foundBank.bankname
|
bankName: foundBank.bankname
|
||||||
});
|
});
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
bankId: foundBank.bankid,
|
bankId: foundBank.bankid || '',
|
||||||
bankName: foundBank.bankname
|
bankName: foundBank.bankname
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (formData.bankId) {
|
} else if (formData.bankId) {
|
||||||
// Если есть только bankId, находим по ID
|
// Если есть только bankId, находим по ID
|
||||||
const foundBank = banksData.find(b => b.bankid === formData.bankId);
|
const foundBank = banksData.find(b => b && b.bankid === formData.bankId);
|
||||||
if (foundBank) {
|
if (foundBank && foundBank.bankname) {
|
||||||
updateFormData({
|
updateFormData({
|
||||||
bankId: foundBank.bankid,
|
bankId: foundBank.bankid || '',
|
||||||
bankName: foundBank.bankname
|
bankName: foundBank.bankname
|
||||||
});
|
});
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
bankId: foundBank.bankid,
|
bankId: foundBank.bankid || '',
|
||||||
bankName: foundBank.bankname
|
bankName: foundBank.bankname
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -414,7 +423,7 @@ export default function Step3Payment({
|
|||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
const foundBank = banks.find(b =>
|
const foundBank = banks.find(b =>
|
||||||
b.bankname.toLowerCase() === value.toLowerCase()
|
b && b.bankname && b.bankname.toLowerCase() === value.toLowerCase()
|
||||||
);
|
);
|
||||||
if (!foundBank) {
|
if (!foundBank) {
|
||||||
return Promise.reject(new Error('Выберите банк из списка'));
|
return Promise.reject(new Error('Выберите банк из списка'));
|
||||||
@@ -429,38 +438,40 @@ export default function Step3Payment({
|
|||||||
size="large"
|
size="large"
|
||||||
loading={banksLoading}
|
loading={banksLoading}
|
||||||
notFoundContent={banksLoading ? "Загрузка..." : "Банк не найден. Попробуйте ввести другое название"}
|
notFoundContent={banksLoading ? "Загрузка..." : "Банк не найден. Попробуйте ввести другое название"}
|
||||||
options={banks.map((bank) => ({
|
options={banks
|
||||||
value: bank.bankname,
|
.filter(bank => bank && bank.bankname)
|
||||||
label: bank.bankname,
|
.map((bank) => ({
|
||||||
}))}
|
value: bank.bankname,
|
||||||
|
label: bank.bankname,
|
||||||
|
}))}
|
||||||
filterOption={(inputValue, option) => {
|
filterOption={(inputValue, option) => {
|
||||||
if (!option?.label) return false;
|
if (!option?.label) return false;
|
||||||
return option.label.toLowerCase().includes(inputValue.toLowerCase());
|
return option.label.toLowerCase().includes(inputValue.toLowerCase());
|
||||||
}}
|
}}
|
||||||
onSelect={(value) => {
|
onSelect={(value) => {
|
||||||
// При выборе из списка находим банк и сохраняем оба поля
|
// При выборе из списка находим банк и сохраняем оба поля
|
||||||
const selectedBank = banks.find(b => b.bankname === value);
|
const selectedBank = banks.find(b => b && b.bankname && b.bankname === value);
|
||||||
if (selectedBank) {
|
if (selectedBank && selectedBank.bankname) {
|
||||||
updateFormData({
|
updateFormData({
|
||||||
bankId: selectedBank.bankid,
|
bankId: selectedBank.bankid || '',
|
||||||
bankName: selectedBank.bankname
|
bankName: selectedBank.bankname
|
||||||
});
|
});
|
||||||
// Устанавливаем bankId в скрытое поле
|
// Устанавливаем bankId в скрытое поле
|
||||||
form.setFieldsValue({ bankId: selectedBank.bankid });
|
form.setFieldsValue({ bankId: selectedBank.bankid || '' });
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
// При вводе текста ищем точное совпадение по названию
|
// При вводе текста ищем точное совпадение по названию
|
||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
const foundBank = banks.find(b =>
|
const foundBank = banks.find(b =>
|
||||||
b.bankname.toLowerCase() === value.toLowerCase()
|
b && b.bankname && b.bankname.toLowerCase() === value.toLowerCase()
|
||||||
);
|
);
|
||||||
if (foundBank) {
|
if (foundBank && foundBank.bankname) {
|
||||||
updateFormData({
|
updateFormData({
|
||||||
bankId: foundBank.bankid,
|
bankId: foundBank.bankid || '',
|
||||||
bankName: foundBank.bankname
|
bankName: foundBank.bankname
|
||||||
});
|
});
|
||||||
form.setFieldsValue({ bankId: foundBank.bankid });
|
form.setFieldsValue({ bankId: foundBank.bankid || '' });
|
||||||
} else if (value === '') {
|
} else if (value === '') {
|
||||||
// Если поле очищено, очищаем и bankId
|
// Если поле очищено, очищаем и bankId
|
||||||
updateFormData({ bankId: undefined, bankName: undefined });
|
updateFormData({ bankId: undefined, bankName: undefined });
|
||||||
|
|||||||
@@ -1064,17 +1064,7 @@ export function generateConfirmationFormHTML(data: any, contact_data_confirmed:
|
|||||||
html += createMoneyField('project', 'agrprice', p.agrprice);
|
html += createMoneyField('project', 'agrprice', p.agrprice);
|
||||||
html += '<span class="required-marker">*</span></p>';
|
html += '<span class="required-marker">*</span></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 += '<div class="section-break"></div>';
|
||||||
|
|
||||||
@@ -1685,11 +1675,49 @@ export function generateConfirmationFormHTML(data: any, contact_data_confirmed:
|
|||||||
.then(function(banks) {
|
.then(function(banks) {
|
||||||
console.log('Loaded ' + banks.length + ' banks');
|
console.log('Loaded ' + banks.length + ' banks');
|
||||||
|
|
||||||
|
// ✅ Нормализуем данные: API возвращает bankId/bankName, приводим к bankid/bankname
|
||||||
|
if (banks.length > 0) {
|
||||||
|
console.log('🔍 Первый банк до нормализации:', JSON.stringify(banks[0]));
|
||||||
|
}
|
||||||
|
banks = banks.map(function(bank) {
|
||||||
|
if (!bank) return null;
|
||||||
|
return {
|
||||||
|
bankid: bank.bankId || bank.bankid || '',
|
||||||
|
bankname: bank.bankName || bank.bankname || ''
|
||||||
|
};
|
||||||
|
});
|
||||||
|
if (banks.length > 0 && banks[0]) {
|
||||||
|
console.log('🔍 Первый банк после нормализации:', JSON.stringify(banks[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Фильтруем банки без названия и сортируем по названию
|
||||||
|
var initialCount = banks.length;
|
||||||
|
banks = banks.filter(function(bank) {
|
||||||
|
return bank && bank.bankname && typeof bank.bankname === 'string' && bank.bankname.trim() !== '';
|
||||||
|
});
|
||||||
|
console.log('✅ Фильтрация банков: было ' + initialCount + ', стало ' + banks.length);
|
||||||
|
|
||||||
|
if (banks.length === 0) {
|
||||||
|
console.error('❌ Нет валидных банков после фильтрации!');
|
||||||
|
Array.prototype.forEach.call(bankInputs, function(input) {
|
||||||
|
var datalistId = input.getAttribute('list');
|
||||||
|
var datalist = document.getElementById(datalistId);
|
||||||
|
if (datalist) {
|
||||||
|
datalist.innerHTML = '<option value="">Ошибка: нет валидных банков</option>';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Сортируем по названию
|
// Сортируем по названию
|
||||||
banks.sort(function(a, b) {
|
banks.sort(function(a, b) {
|
||||||
return a.bankname.localeCompare(b.bankname, 'ru');
|
const nameA = (a.bankname || '').toString();
|
||||||
|
const nameB = (b.bankname || '').toString();
|
||||||
|
return nameA.localeCompare(nameB, 'ru');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log('✅ Банки отфильтрованы и отсортированы: ' + banks.length + ' шт.');
|
||||||
|
|
||||||
// Сохраняем список банков глобально для поиска
|
// Сохраняем список банков глобально для поиска
|
||||||
window.__banksList = banks;
|
window.__banksList = banks;
|
||||||
|
|
||||||
@@ -1703,19 +1731,29 @@ export function generateConfirmationFormHTML(data: any, contact_data_confirmed:
|
|||||||
var currentBankName = '';
|
var currentBankName = '';
|
||||||
|
|
||||||
if (!datalist) {
|
if (!datalist) {
|
||||||
console.error('Datalist not found for input:', input.id);
|
console.error('❌ Datalist not found for input:', input.id, 'datalistId:', datalistId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('📋 Заполняю datalist для input:', input.id, 'datalistId:', datalistId);
|
||||||
|
|
||||||
// Очищаем datalist
|
// Очищаем datalist
|
||||||
datalist.innerHTML = '';
|
datalist.innerHTML = '';
|
||||||
|
|
||||||
// Заполняем datalist опциями
|
// Заполняем datalist опциями
|
||||||
|
var optionsAdded = 0;
|
||||||
banks.forEach(function(bank) {
|
banks.forEach(function(bank) {
|
||||||
|
// ✅ Проверяем наличие bankname перед использованием
|
||||||
|
if (!bank || !bank.bankname) {
|
||||||
|
console.warn('⚠️ Пропущен банк без названия:', bank);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var option = document.createElement('option');
|
var option = document.createElement('option');
|
||||||
option.value = bank.bankname;
|
option.value = bank.bankname;
|
||||||
option.setAttribute('data-bank-id', bank.bankid);
|
option.setAttribute('data-bank-id', bank.bankid || '');
|
||||||
datalist.appendChild(option);
|
datalist.appendChild(option);
|
||||||
|
optionsAdded++;
|
||||||
|
|
||||||
// Если это текущий банк, устанавливаем значение
|
// Если это текущий банк, устанавливаем значение
|
||||||
if (bank.bankid === currentBankId) {
|
if (bank.bankid === currentBankId) {
|
||||||
@@ -1723,6 +1761,22 @@ export function generateConfirmationFormHTML(data: any, contact_data_confirmed:
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log('✅ Добавлено опций в datalist:', optionsAdded, 'для input:', input.id);
|
||||||
|
|
||||||
|
// Проверяем, что опции действительно добавлены
|
||||||
|
var actualOptionsCount = datalist.querySelectorAll('option').length;
|
||||||
|
console.log('🔍 Проверка datalist:', {
|
||||||
|
datalistId: datalistId,
|
||||||
|
optionsAdded: optionsAdded,
|
||||||
|
actualOptionsInDOM: actualOptionsCount,
|
||||||
|
inputId: input.id,
|
||||||
|
inputListAttr: input.getAttribute('list')
|
||||||
|
});
|
||||||
|
|
||||||
|
if (actualOptionsCount === 0 && optionsAdded > 0) {
|
||||||
|
console.error('❌ КРИТИЧЕСКАЯ ОШИБКА: опции не добавлены в DOM!');
|
||||||
|
}
|
||||||
|
|
||||||
// Устанавливаем текущее значение если есть
|
// Устанавливаем текущее значение если есть
|
||||||
if (currentBankName) {
|
if (currentBankName) {
|
||||||
input.value = currentBankName;
|
input.value = currentBankName;
|
||||||
@@ -1744,17 +1798,17 @@ export function generateConfirmationFormHTML(data: any, contact_data_confirmed:
|
|||||||
// Ищем точное совпадение
|
// Ищем точное совпадение
|
||||||
if (inputValue) {
|
if (inputValue) {
|
||||||
foundBank = banks.find(function(b) {
|
foundBank = banks.find(function(b) {
|
||||||
return b.bankname.toLowerCase() === inputValue.toLowerCase();
|
return b && b.bankname && b.bankname.toLowerCase() === inputValue.toLowerCase();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (foundBank) {
|
if (foundBank && foundBank.bankname) {
|
||||||
// Найден банк - сохраняем ID и название
|
// Найден банк - сохраняем ID и название
|
||||||
if (hiddenField) {
|
if (hiddenField) {
|
||||||
hiddenField.value = foundBank.bankid;
|
hiddenField.value = foundBank.bankid || '';
|
||||||
}
|
}
|
||||||
state.user = state.user || {};
|
state.user = state.user || {};
|
||||||
state.user.bank_id = foundBank.bankid;
|
state.user.bank_id = foundBank.bankid || '';
|
||||||
state.user.bank_name = foundBank.bankname; // ✅ Сохраняем название банка
|
state.user.bank_name = foundBank.bankname; // ✅ Сохраняем название банка
|
||||||
this.classList.add('filled');
|
this.classList.add('filled');
|
||||||
} else {
|
} else {
|
||||||
@@ -1775,15 +1829,15 @@ export function generateConfirmationFormHTML(data: any, contact_data_confirmed:
|
|||||||
input.addEventListener('change', function() {
|
input.addEventListener('change', function() {
|
||||||
var inputValue = this.value.trim();
|
var inputValue = this.value.trim();
|
||||||
var foundBank = banks.find(function(b) {
|
var foundBank = banks.find(function(b) {
|
||||||
return b.bankname.toLowerCase() === inputValue.toLowerCase();
|
return b && b.bankname && b.bankname.toLowerCase() === inputValue.toLowerCase();
|
||||||
});
|
});
|
||||||
|
|
||||||
if (foundBank) {
|
if (foundBank && foundBank.bankname) {
|
||||||
if (hiddenField) {
|
if (hiddenField) {
|
||||||
hiddenField.value = foundBank.bankid;
|
hiddenField.value = foundBank.bankid || '';
|
||||||
}
|
}
|
||||||
state.user = state.user || {};
|
state.user = state.user || {};
|
||||||
state.user.bank_id = foundBank.bankid;
|
state.user.bank_id = foundBank.bankid || '';
|
||||||
state.user.bank_name = foundBank.bankname; // ✅ Сохраняем название банка
|
state.user.bank_name = foundBank.bankname; // ✅ Сохраняем название банка
|
||||||
this.classList.add('filled');
|
this.classList.add('filled');
|
||||||
updateFieldStyle(this);
|
updateFieldStyle(this);
|
||||||
@@ -1793,12 +1847,23 @@ export function generateConfirmationFormHTML(data: any, contact_data_confirmed:
|
|||||||
})
|
})
|
||||||
.catch(function(error) {
|
.catch(function(error) {
|
||||||
console.error('Error loading banks:', error);
|
console.error('Error loading banks:', error);
|
||||||
|
console.error('Error details:', {
|
||||||
|
message: error.message,
|
||||||
|
stack: error.stack,
|
||||||
|
name: error.name
|
||||||
|
});
|
||||||
|
|
||||||
|
// Показываем сообщение об ошибке пользователю
|
||||||
Array.prototype.forEach.call(bankInputs, function(input) {
|
Array.prototype.forEach.call(bankInputs, function(input) {
|
||||||
var datalistId = input.getAttribute('list');
|
var datalistId = input.getAttribute('list');
|
||||||
var datalist = document.getElementById(datalistId);
|
var datalist = document.getElementById(datalistId);
|
||||||
if (datalist) {
|
if (datalist) {
|
||||||
datalist.innerHTML = '<option value="">Ошибка загрузки банков. Обновите страницу.</option>';
|
datalist.innerHTML = '<option value="">Ошибка загрузки банков. Обновите страницу.</option>';
|
||||||
}
|
}
|
||||||
|
// Показываем placeholder с ошибкой
|
||||||
|
if (input.placeholder) {
|
||||||
|
input.placeholder = 'Ошибка загрузки списка банков';
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ export default function ClaimForm() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 🔥 VERSION CHECK: Если видишь это в консоли - фронт обновился!
|
// 🔥 VERSION CHECK: Если видишь это в консоли - фронт обновился!
|
||||||
console.log('🔥 ClaimForm v3.8 - 2025-11-20 15:10 - Fix session_id priority in loadDraft');
|
console.log('🔥 ClaimForm v3.9 - 2025-12-29 - Auto redirect to drafts after success');
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// ✅ Восстановление сессии при загрузке страницы
|
// ✅ Восстановление сессии при загрузке страницы
|
||||||
@@ -998,6 +998,42 @@ export default function ClaimForm() {
|
|||||||
setCurrentStep(1); // ✅ Переходим к описанию (индекс 1)
|
setCurrentStep(1); // ✅ Переходим к описанию (индекс 1)
|
||||||
}, [updateFormData, currentStep, isPhoneVerified, formData.unified_id, formData.phone]);
|
}, [updateFormData, currentStep, isPhoneVerified, formData.unified_id, formData.phone]);
|
||||||
|
|
||||||
|
// ✅ Автоматический редирект на экран черновиков после успешной отправки
|
||||||
|
useEffect(() => {
|
||||||
|
if (isSubmitted) {
|
||||||
|
console.log('✅ Обращение успешно отправлено, ждём 2.5 секунды перед редиректом на черновики...');
|
||||||
|
|
||||||
|
const redirectTimer = setTimeout(async () => {
|
||||||
|
console.log('🔄 Выполняем редирект на экран черновиков');
|
||||||
|
|
||||||
|
// Проверяем наличие черновиков
|
||||||
|
const hasDraftsResult = await checkDrafts(
|
||||||
|
formData.unified_id,
|
||||||
|
formData.phone,
|
||||||
|
sessionIdRef.current
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('🔍 Результат проверки черновиков:', hasDraftsResult);
|
||||||
|
|
||||||
|
// Переходим на экран черновиков
|
||||||
|
setShowDraftSelection(true);
|
||||||
|
setHasDrafts(hasDraftsResult);
|
||||||
|
setIsSubmitted(false); // Сбрасываем флаг отправки
|
||||||
|
setSelectedDraftId(null); // Сбрасываем выбранный черновик
|
||||||
|
|
||||||
|
// Переходим на шаг 0 (черновики)
|
||||||
|
setTimeout(() => {
|
||||||
|
setCurrentStep(0);
|
||||||
|
console.log('✅ Переход на экран черновиков выполнен');
|
||||||
|
}, 100);
|
||||||
|
}, 2500); // Задержка 2.5 секунды
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(redirectTimer);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [isSubmitted, formData.unified_id, formData.phone, checkDrafts]);
|
||||||
|
|
||||||
const handleSubmit = useCallback(async () => {
|
const handleSubmit = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
addDebugEvent('form', 'info', '📤 Отправка заявки в n8n через backend');
|
addDebugEvent('form', 'info', '📤 Отправка заявки в n8n через backend');
|
||||||
@@ -1211,6 +1247,7 @@ export default function ClaimForm() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Шаг подтверждения заявления (показывается после получения данных из claim:plan)
|
// Шаг подтверждения заявления (показывается после получения данных из claim:plan)
|
||||||
|
// ✅ НОВЫЙ ФЛОУ: StepClaimConfirmation с SMS подтверждением
|
||||||
if (formData.showClaimConfirmation && formData.claimPlanData) {
|
if (formData.showClaimConfirmation && formData.claimPlanData) {
|
||||||
stepsArray.push({
|
stepsArray.push({
|
||||||
title: 'Подтверждение',
|
title: 'Подтверждение',
|
||||||
@@ -1225,25 +1262,26 @@ export default function ClaimForm() {
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
// ✅ СТАРЫЙ ФЛОУ: Step3Payment (только если нет StepClaimConfirmation)
|
||||||
|
// Используется как fallback, если данные claim:plan не получены
|
||||||
|
stepsArray.push({
|
||||||
|
title: 'Заявление',
|
||||||
|
description: 'Подтверждение',
|
||||||
|
content: (
|
||||||
|
<Step3Payment
|
||||||
|
formData={formData}
|
||||||
|
updateFormData={updateFormData}
|
||||||
|
onPrev={prevStep}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
isPhoneVerified={isPhoneVerified}
|
||||||
|
setIsPhoneVerified={setIsPhoneVerified}
|
||||||
|
addDebugEvent={addDebugEvent}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Последний шаг: Payment (всегда)
|
|
||||||
stepsArray.push({
|
|
||||||
title: 'Заявление',
|
|
||||||
description: 'Подтверждение',
|
|
||||||
content: (
|
|
||||||
<Step3Payment
|
|
||||||
formData={formData} // ✅ claim_id уже в formData
|
|
||||||
updateFormData={updateFormData}
|
|
||||||
onPrev={prevStep}
|
|
||||||
onSubmit={handleSubmit}
|
|
||||||
isPhoneVerified={isPhoneVerified}
|
|
||||||
setIsPhoneVerified={setIsPhoneVerified}
|
|
||||||
addDebugEvent={addDebugEvent}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
return stepsArray;
|
return stepsArray;
|
||||||
}, [formData, isPhoneVerified, nextStep, prevStep, updateFormData, handleSubmit, setIsPhoneVerified, addDebugEvent, showDraftSelection, selectedDraftId, hasDrafts, handleSelectDraft, handleNewClaim, checkDrafts]);
|
}, [formData, isPhoneVerified, nextStep, prevStep, updateFormData, handleSubmit, setIsPhoneVerified, addDebugEvent, showDraftSelection, selectedDraftId, hasDrafts, handleSelectDraft, handleNewClaim, checkDrafts]);
|
||||||
|
|
||||||
@@ -1290,10 +1328,38 @@ export default function ClaimForm() {
|
|||||||
// Удаляем session_token из localStorage
|
// Удаляем session_token из localStorage
|
||||||
localStorage.removeItem('session_token');
|
localStorage.removeItem('session_token');
|
||||||
|
|
||||||
// Сбрасываем форму
|
// ✅ Полный сброс: очищаем все данные авторизации и черновиков
|
||||||
handleReset();
|
setIsSubmitted(false);
|
||||||
|
setShowDraftSelection(false);
|
||||||
|
setHasDrafts(false);
|
||||||
|
setSelectedDraftId(null);
|
||||||
|
|
||||||
|
// ✅ Генерируем новую сессию для нового пользователя
|
||||||
|
const newSessionId = `sess-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||||
|
sessionIdRef.current = newSessionId;
|
||||||
|
|
||||||
|
// ✅ Полностью очищаем formData, включая unified_id и phone
|
||||||
|
setFormData({
|
||||||
|
voucher: '',
|
||||||
|
claim_id: undefined,
|
||||||
|
session_id: newSessionId,
|
||||||
|
paymentMethod: 'sbp',
|
||||||
|
unified_id: undefined, // ✅ Очищаем unified_id
|
||||||
|
phone: undefined, // ✅ Очищаем phone
|
||||||
|
contact_id: undefined, // ✅ Очищаем contact_id
|
||||||
|
is_new_contact: undefined,
|
||||||
|
isPhoneVerified: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ✅ Сбрасываем флаг верификации телефона
|
||||||
|
setIsPhoneVerified(false);
|
||||||
|
|
||||||
|
// ✅ Переходим на экран входа (Step1Phone)
|
||||||
|
// Если showDraftSelection = false и нет unified_id, то шаг 0 будет Step1Phone
|
||||||
|
setCurrentStep(0);
|
||||||
|
|
||||||
message.info('Сессия завершена. До свидания!');
|
message.info('Сессия завершена. До свидания!');
|
||||||
|
addDebugEvent('system', 'info', '🔄 Форма сброшена');
|
||||||
}, [formData.session_id, addDebugEvent]);
|
}, [formData.session_id, addDebugEvent]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export default defineConfig({
|
|||||||
port: 3000,
|
port: 3000,
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'http://host.docker.internal:8200',
|
target: 'http://host.docker.internal:8201',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
// SSE support
|
// SSE support
|
||||||
configure: (proxy) => {
|
configure: (proxy) => {
|
||||||
@@ -24,7 +24,7 @@ export default defineConfig({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
'/events': {
|
'/events': {
|
||||||
target: 'http://host.docker.internal:8200',
|
target: 'http://host.docker.internal:8201',
|
||||||
changeOrigin: true
|
changeOrigin: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user