feat: Telegram Mini App integration and UX improvements

- Добавлена полная интеграция с Telegram Mini App (динамическая загрузка SDK)
- Отдельный компактный дизайн для Telegram Mini App
- Добавлен loader при инициализации (предотвращает мелькание SMS-авторизации)
- Улучшена навигация: кнопки "Назад" и "К списку заявок" теперь сохраняют авторизацию
- Telegram Mini App: кнопка "Выход" просто закрывает приложение
- Telegram Mini App: заявки "В работе" скрыты из списка
- Веб-версия: для заявок "В работе" добавлена кнопка "Просмотреть в Telegram" (ссылка на @klientprav_bot)
- Telegram Mini App: кнопки действий в черновиках расположены вертикально
- Веб-версия: убрано отображение номера телефона в приветствии
- Исправлена проблема с возвратом к списку черновиков (не требует повторной SMS-авторизации)
- Заблокировано удаление и редактирование заявок со статусом "В работе"
- Добавлена документация по Telegram Mini App интеграции
This commit is contained in:
AI Assistant
2026-01-29 16:12:48 +03:00
parent 73524465fd
commit 2e45786e46
57 changed files with 6776 additions and 234 deletions

View File

@@ -0,0 +1,81 @@
// ============================================================================
// n8n Code Node: Полная обработка - HTML → Base64 PDF
// ============================================================================
// Этот код обрабатывает ответ от HTTP Request и возвращает base64 PDF
// Используйте ПОСЛЕ HTTP Request ноды, которая конвертирует HTML в PDF
// ============================================================================
// Получаем данные из HTTP Request ноды
const httpResponse = $input.first();
if (!httpResponse) {
throw new Error('Ответ от HTTP Request не получен');
}
// ==== ВАРИАНТ 1: Сервис вернул base64 напрямую в JSON ====
if (httpResponse.json && httpResponse.json.pdf) {
const base64 = httpResponse.json.pdf;
return [{
json: {
pdf_base64: base64,
pdf_size_bytes: Math.floor(base64.length * 0.75), // Примерный размер
pdf_size_mb: (Math.floor(base64.length * 0.75) / (1024 * 1024)).toFixed(2),
success: true,
source: 'json_response'
}
}];
}
// ==== ВАРИАНТ 2: Сервис вернул binary данные ====
if (httpResponse.binary && httpResponse.binary.data) {
const pdfBinary = httpResponse.binary.data;
// Конвертируем binary в base64
// В n8n binary.data может быть Buffer или строка
let base64;
if (Buffer.isBuffer(pdfBinary)) {
base64 = pdfBinary.toString('base64');
} else if (typeof pdfBinary === 'string') {
// Если уже base64 строка
base64 = pdfBinary;
} else {
// Пытаемся преобразовать
base64 = Buffer.from(pdfBinary).toString('base64');
}
const sizeBytes = Buffer.from(base64, 'base64').length;
return [{
json: {
pdf_base64: base64,
pdf_size_bytes: sizeBytes,
pdf_size_mb: (sizeBytes / (1024 * 1024)).toFixed(2),
success: true,
source: 'binary_response'
}
}];
}
// ==== ВАРИАНТ 3: Сервис вернул base64 в поле body или data ====
if (httpResponse.json) {
const body = httpResponse.json.body || httpResponse.json.data || httpResponse.json;
if (body.pdf || body.base64 || body.content) {
const base64 = body.pdf || body.base64 || body.content;
const sizeBytes = Buffer.from(base64, 'base64').length;
return [{
json: {
pdf_base64: base64,
pdf_size_bytes: sizeBytes,
pdf_size_mb: (sizeBytes / (1024 * 1024)).toFixed(2),
success: true,
source: 'body_field'
}
}];
}
}
// ==== ОШИБКА: Не удалось извлечь PDF ====
throw new Error('Не удалось извлечь PDF из ответа. Структура ответа: ' + JSON.stringify(Object.keys(httpResponse), null, 2));