🎨 feat: Add sidebar navigation for Smart Broadcasts and Digests sections

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
Admin 2026-04-06 23:21:31 +04:00
parent 222c2ba521
commit f7aaf96cfa
4 changed files with 183 additions and 46 deletions

View file

@ -71,7 +71,7 @@
<div class="col-md-2 sidebar p-0">
<div class="p-3 text-white text-center border-bottom border-secondary">
<h4 class="mb-1"><i class="bi bi-house-door"></i> Домовой</h4>
<small class="text-white-50">Админ Панель v1.3</small>
<small class="text-white-50">Админ Панель v3.1</small>
</div>
<nav class="nav flex-column mt-3">
<a class="nav-link {% block nav_dashboard %}{% endblock %}" href="/">
@ -86,15 +86,41 @@
<span class="badge bg-danger">{{ pending_count }}</span>
{% endif %}
</a>
<a class="nav-link {% block nav_phones %}{% endblock %}" href="/phones">
<i class="bi bi-telephone"></i> Телефоны
</a>
<!-- РАССЫЛКИ - ПОДМЕНЮ -->
<div class="nav-item mt-3 px-3">
<small class="text-white-50 text-uppercase" style="font-size: 0.7em; letter-spacing: 1px;">РАССЫЛКИ</small>
</div>
<a class="nav-link {% block nav_broadcast %}{% endblock %}" href="/broadcast">
<i class="bi bi-broadcast"></i> Рассылки
<i class="bi bi-broadcast"></i> Обычная рассылка
</a>
<a class="nav-link {% block nav_smart_broadcast %}{% endblock %}" href="/broadcasts">
<i class="bi bi-envelope-check"></i> Умная рассылка
<span class="badge bg-success" style="font-size: 0.6em;">NEW</span>
</a>
<a class="nav-link {% block nav_scheduled_posts %}{% endblock %}" href="/scheduled_posts">
<i class="bi bi-calendar2-plus"></i> Запланированные посты
</a>
<!-- ДАЙДЖЕСТЫ -->
<div class="nav-item mt-3 px-3">
<small class="text-white-50 text-uppercase" style="font-size: 0.7em; letter-spacing: 1px;">ДАЙДЖЕСТЫ</small>
</div>
<a class="nav-link {% block nav_digests %}{% endblock %}" href="/digests">
<i class="bi bi-newspaper"></i> Еженедельные дайджесты
<span class="badge bg-success" style="font-size: 0.6em;">NEW</span>
</a>
<a class="nav-link {% block nav_digest_settings %}{% endblock %}" href="/digest_settings">
<i class="bi bi-gear"></i> Настройки дайджеста
</a>
<!-- ОСТАЛЬНОЕ МЕНЮ -->
<div class="nav-item mt-3 px-3">
<small class="text-white-50 text-uppercase" style="font-size: 0.7em; letter-spacing: 1px;">УПРАВЛЕНИЕ</small>
</div>
<a class="nav-link {% block nav_phones %}{% endblock %}" href="/phones">
<i class="bi bi-telephone"></i> Телефоны
</a>
<a class="nav-link {% block nav_polls %}{% endblock %}" href="/polls">
<i class="bi bi-bar-chart"></i> Опросы
</a>

View file

@ -1,12 +1,15 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>📢 Умные рассылки - Домовой Бот</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f7fa; color: #333; }
{% extends "base.html" %}
{% block title %}Умные рассылки - Домовой Бот{% endblock %}
{% block nav_smart_broadcast %}active{% endblock %}
{% block extra_style %}
* { margin: 0; padding: 0; box-sizing: border-box; }
{% endblock %}
{% block content %}
<style>
.container { max-width: 1400px; margin: 0 auto; padding: 20px; }
.header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px; border-radius: 10px; margin-bottom: 30px; }
.header h1 { font-size: 2em; margin-bottom: 10px; }
@ -16,17 +19,28 @@
.stat-card { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.stat-card h3 { font-size: 0.9em; color: #666; margin-bottom: 5px; }
.stat-card .value { font-size: 2em; font-weight: bold; color: #667eea; }
table { width: 100%; background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
th { background: #667eea; color: white; padding: 15px; text-align: left; }
td { padding: 15px; border-bottom: 1px solid #eee; }
tr:hover { background: #f8f9fa; }
.btn { padding: 8px 16px; border: none; border-radius: 5px; cursor: pointer; font-size: 0.9em; transition: all 0.2s; }
.create-form { background: white; padding: 25px; border-radius: 10px; margin-bottom: 30px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
.create-form h2 { color: #667eea; margin-bottom: 15px; }
.form-group { margin-bottom: 20px; }
.form-group label { display: block; font-weight: 500; margin-bottom: 5px; }
.form-group textarea { width: 100%; min-height: 120px; padding: 12px; border: 2px solid #e2e8f0; border-radius: 5px; font-family: inherit; font-size: 1em; resize: vertical; }
.form-group textarea:focus { outline: none; border-color: #667eea; }
.form-group select, .form-group input[type="file"] { width: 100%; padding: 10px; border: 2px solid #e2e8f0; border-radius: 5px; font-size: 1em; }
.checkbox-group { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.checkbox-group input[type="checkbox"] { width: 20px; height: 20px; }
.btn { padding: 12px 24px; border: none; border-radius: 5px; cursor: pointer; font-size: 1em; transition: all 0.2s; }
.btn-primary { background: #667eea; color: white; }
.btn-primary:hover { background: #5568d3; }
.btn-success { background: #48bb78; color: white; }
.btn-success:hover { background: #38a169; }
.btn-warning { background: #ed8936; color: white; }
.btn-warning:hover { background: #dd6b20; }
.btn-info { background: #4299e1; color: white; }
.btn-info:hover { background: #3182ce; }
table { width: 100%; background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
th { background: #667eea; color: white; padding: 15px; text-align: left; }
td { padding: 15px; border-bottom: 1px solid #eee; }
tr:hover { background: #f8f9fa; }
.badge { display: inline-block; padding: 4px 8px; border-radius: 4px; font-size: 0.85em; font-weight: 500; }
.badge-success { background: #c6f6d5; color: #22543d; }
.badge-warning { background: #feebc8; color: #7c2d12; }
@ -38,6 +52,9 @@
.modal-content { background: white; padding: 30px; border-radius: 10px; max-width: 800px; width: 90%; max-height: 80vh; overflow-y: auto; }
.modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
.close-btn { background: none; border: none; font-size: 1.5em; cursor: pointer; }
.status-msg { padding: 15px; border-radius: 5px; margin: 15px 0; display: none; }
.status-msg.success { background: #c6f6d5; color: #22543d; border: 1px solid #9ae6b4; }
.status-msg.error { background: #fed7d7; color: #742a2a; border: 1px solid #feb2b2; }
</style>
</head>
<body>
@ -45,9 +62,42 @@
<div class="header">
<a href="/" class="back-link">На главную</a>
<h1>📢 Умные рассылки</h1>
<p>Отслеживание прочтений и статистика</p>
<p>Создание рассылок с отслеживанием прочтений</p>
</div>
<!-- Форма создания рассылки -->
<div class="create-form">
<h2>✨ Создать новую рассылку</h2>
<div id="statusMsg" class="status-msg"></div>
<div class="form-group">
<label for="broadcastText">Текст рассылки *</label>
<textarea id="broadcastText" placeholder="Введите текст рассылки. Можно использовать HTML: <b>жирный</b>, <i>курсив</i>, <code>код</code>"></textarea>
</div>
<div class="form-group">
<label for="broadcastPhoto">Фото (необязательно)</label>
<input type="file" id="broadcastPhoto" accept="image/*">
</div>
<div class="form-group">
<label for="broadcastRecipients">Получатели *</label>
<select id="broadcastRecipients">
<option value="all_and_chat">💬 В чат + всем верифицированным в личку</option>
<option value="all_verified">✅ Только верифицированным в личку</option>
<option value="ig_only">👥 Только инициативной группе</option>
</select>
</div>
<div class="checkbox-group">
<input type="checkbox" id="hasReadButton" checked>
<label for="hasReadButton">Добавить кнопку "✅ Прочитал"</label>
</div>
<button class="btn btn-primary" onclick="sendBroadcast()">📢 Отправить рассылку</button>
</div>
<!-- Статистика -->
<div class="stats">
<div class="stat-card">
<h3>Всего рассылок</h3>
@ -67,6 +117,7 @@
</div>
</div>
<!-- Таблица рассылок -->
<table>
<thead>
<tr>
@ -96,7 +147,7 @@
<small>{{ b.read_percent }}%</small>
</td>
<td>
<button class="btn btn-primary" onclick="showReaders({{ b.id }})">👥 Кто прочитал</button>
<button class="btn btn-info" onclick="showReaders({{ b.id }})">👥 Кто прочитал</button>
<button class="btn btn-warning" onclick="showNonReaders({{ b.id }})">⚠️ Кто НЕ прочитал</button>
{% if not b.is_reminder_sent %}
<button class="btn btn-success" onclick="sendReminder({{ b.id }})">📬 Напоминание</button>
@ -112,7 +163,7 @@
{% if broadcasts|length == 0 %}
<div style="text-align: center; padding: 50px; background: white; border-radius: 8px; margin-top: 20px;">
<h2>📭 Рассылок пока нет</h2>
<p style="margin-top: 10px;">Используйте команду /broadcast в Telegram для создания рассылки</p>
<p style="margin-top: 10px;">Создайте первую рассылку формой выше!</p>
</div>
{% endif %}
</div>
@ -140,6 +191,62 @@
</div>
<script>
async function sendBroadcast() {
const text = document.getElementById('broadcastText').value.trim();
const photo = document.getElementById('broadcastPhoto').files[0];
const recipients = document.getElementById('broadcastRecipients').value;
const hasReadButton = document.getElementById('hasReadButton').checked;
const statusMsg = document.getElementById('statusMsg');
if (!text) {
statusMsg.textContent = '❌ Введите текст рассылки!';
statusMsg.className = 'status-msg error';
statusMsg.style.display = 'block';
return;
}
const formData = new FormData();
formData.append('text', text);
formData.append('recipients', recipients);
formData.append('has_read_button', hasReadButton);
if (photo) {
formData.append('photo', photo);
}
statusMsg.textContent = '⏳ Отправка рассылки...';
statusMsg.className = 'status-msg';
statusMsg.style.display = 'block';
try {
const response = await fetch('/api/broadcast/create', {
method: 'POST',
body: formData
});
const result = await response.json();
if (result.success) {
statusMsg.textContent = '✅ ' + result.message;
statusMsg.className = 'status-msg success';
statusMsg.style.display = 'block';
// Очистка формы
document.getElementById('broadcastText').value = '';
document.getElementById('broadcastPhoto').value = '';
// Перезагрузка через 2 секунды
setTimeout(() => location.reload(), 2000);
} else {
statusMsg.textContent = '❌ ' + (result.error || 'Ошибка отправки');
statusMsg.className = 'status-msg error';
statusMsg.style.display = 'block';
}
} catch (error) {
statusMsg.textContent = '❌ Ошибка: ' + error.message;
statusMsg.className = 'status-msg error';
statusMsg.style.display = 'block';
}
}
function showReaders(broadcastId) {
fetch(`/api/broadcast/readers/${broadcastId}`)
.then(r => r.json())
@ -191,5 +298,4 @@
}
}
</script>
</body>
</html>
{% endblock %}

View file

@ -1,12 +1,16 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>⚙️ Настройки дайджеста - Домовой Бот</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f7fa; color: #333; }
{% extends "base.html" %}
{% block title %}Настройки дайджеста - Домовой Бот{% endblock %}
{% block nav_digest_settings %}active{% endblock %}
{% block extra_style %}
{% endblock %}
{% block content %}
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
</style>
.container { max-width: 900px; margin: 0 auto; padding: 20px; }
.header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px; border-radius: 10px; margin-bottom: 30px; }
.header h1 { font-size: 2em; margin-bottom: 10px; }
@ -101,5 +105,4 @@
alert('✅ Настройки сохранены! (Функционал в разработке)');
}
</script>
</body>
</html>
{% endblock %}

View file

@ -1,12 +1,15 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>📰 Дайджесты - Домовой Бот</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f7fa; color: #333; }
{% extends "base.html" %}
{% block title %}Еженедельные дайджесты - Домовой Бот{% endblock %}
{% block nav_digests %}active{% endblock %}
{% block extra_style %}
* { margin: 0; padding: 0; box-sizing: border-box; }
{% endblock %}
{% block content %}
<style>
.container { max-width: 1400px; margin: 0 auto; padding: 20px; }
.header { background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); color: white; padding: 30px; border-radius: 10px; margin-bottom: 30px; }
.header h1 { font-size: 2em; margin-bottom: 10px; }
@ -246,5 +249,4 @@
alert('Генерация дайджеста запущена! Проверьте Telegram - бот отправит превью.');
}
</script>
</body>
</html>
{% endblock %}