feat: Обновления после последнего коммита

Изменения в backend:
- Обновления в n8n_proxy.py
- Изменения в SMS API
- Обновления конфигурации
- Улучшения SMS сервиса

Изменения в frontend:
- Обновления Step1Phone компонента
- Изменения в Step3Payment
- Улучшения generateConfirmationFormHTML
- Обновления ClaimForm страницы
- Изменения в vite.config.ts

Статистика: +242 строки, -81 строка
This commit is contained in:
Fedor
2026-01-02 17:37:37 +03:00
parent f7d27388a0
commit 73524465fd
10 changed files with 341 additions and 81 deletions

99
GIT_STATUS.md Normal file
View 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

View File

@@ -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)}")

View File

@@ -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(

View File

@@ -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

View File

@@ -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:
# Получаем актуальный токен # Получаем актуальный токен

View File

@@ -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('Код скопирован!');
} }
}} }}

View File

@@ -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 });

View File

@@ -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 = 'Ошибка загрузки списка банков';
}
}); });
}); });
} }

View File

@@ -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 (

View File

@@ -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
} }
} }