domovoy_bot/web/templates/scheduled_posts.html
Admin 97c3e5864d 🎨 refactor: Единая навигация — ВСЕ 14 страниц используют base.html
БЫЛО: Каждая из 14 страниц имела своё собственное вшитое меню
      с разным набором кнопок (от 4 до 10 пунктов)

СТАЛО: Все 14 страниц наследуют base.html с единым меню из 14 пунктов
       в 4 разделах: Пользователи / Коммуникации / Контент / Система

Убрано 1949 строк дублирующегося кода, добавлено 1323 строки чистого контента.
Экономия: -626 строк (32% сокращение).

Переписанные страницы:
- dashboard.html, users.html, verification.html
- phones.html, polls.html, events.html
- schedules.html, ads.html, scheduled_posts.html, export.html

Результат: На ЛЮБОЙ странице пользователь видит ВСЕ 14 пунктов меню.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-12 18:52:58 +04:00

269 lines
12 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends "base.html" %}
{% block title %}Запланированные посты - Домовой Бот{% endblock %}
{% block nav_scheduled_posts %}active{% endblock %}
{% block extra_style %}
#preview { max-width: 400px; margin-top: 1rem; display: none; }
#preview img { max-width: 100%; border-radius: 8px; }
.post-card { transition: transform 0.2s; }
.post-card:hover { transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0,0,0,0.1); }
{% endblock %}
{% block content %}
<h2 class="mb-4"><i class="bi bi-calendar2-plus"></i> Запланированные посты</h2>
<!-- Форма создания поста -->
<div class="card mb-4">
<div class="card-header bg-primary text-white">
<h5 class="mb-0"><i class="bi bi-plus-circle"></i> Создать новый пост</h5>
</div>
<div class="card-body">
<form id="scheduledPostForm" enctype="multipart/form-data">
<div class="mb-3">
<label class="form-label">Текст поста</label>
<textarea class="form-control" id="postText" rows="5"
placeholder="Введите текст поста..." required></textarea>
<small class="text-muted">Поддерживается HTML: &lt;b&gt;жирный&lt;/b&gt;, &lt;i&gt;курсив&lt;/i&gt;, &lt;emoji&gt;</small>
</div>
<div class="mb-3">
<label class="form-label">Картинка (необязательно)</label>
<input type="file" class="form-control" id="postPhoto" accept="image/*"
onchange="previewImage()">
<small class="text-muted">JPG, PNG до 5MB</small>
<div id="preview">
<img id="previewImg" alt="Предпросмотр">
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">Тема форума</label>
<select class="form-select" id="postTopic" required>
<option value="general">💬 Общий чат</option>
<option value="memes">😂 Мемы</option>
<option value="ads">📢 Важные объявления</option>
<option value="meetings">🏛️ Собрания собственников</option>
<option value="kgm">🗑️ Вывоз КГМ</option>
<option value="lostfound">🔍 Потеряшки</option>
<option value="auto">🚗 Для автовладельцев</option>
<option value="phones">📞 Важные телефоны</option>
<option value="links">🔗 Ссылки</option>
<option value="uo">📝 Переписка с УО</option>
</select>
<small class="text-muted">В какую тему отправить пост</small>
</div>
<div class="col-md-6 mb-3">
<label class="form-label">Получатели</label>
<select class="form-select" id="postRecipients">
<option value="chat_only">📢 Только в чат</option>
<option value="all_verified">✅ Всем верифицированным в личку</option>
<option value="all_and_chat">✅ Всем + в чат</option>
</select>
<small class="text-muted">Кому отправить пост</small>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">Дата публикации</label>
<input type="date" class="form-control" id="postDate" required>
<small class="text-muted">🕐 По твоему времени (UTC+4)</small>
</div>
<div class="col-md-6 mb-3">
<label class="form-label">Время публикации</label>
<input type="time" class="form-control" id="postTime" required>
<small class="text-muted">🕐 По твоему времени (UTC+4)</small>
</div>
</div>
<div class="alert alert-info">
<i class="bi bi-info-circle"></i>
<b>Часовой пояс:</b> Все времена указаны в твоём локальном времени (UTC+4, Екатеринбург/Ульяновск).
<br>Бот автоматически конвертирует время в UTC для публикации по расписанию.
</div>
<button type="submit" class="btn btn-success btn-lg">
<i class="bi bi-calendar-check"></i> Запланировать пост
</button>
</form>
<div id="result" class="mt-4"></div>
</div>
</div>
<!-- Список запланированных постов -->
<div class="card">
<div class="card-header bg-info text-white">
<h5 class="mb-0"><i class="bi bi-list-check"></i> Запланированные посты</h5>
</div>
<div class="card-body">
<div id="postsList"></div>
<div id="noPosts" class="text-center text-muted py-5">
<i class="bi bi-calendar-x" style="font-size: 3rem;"></i>
<p class="mt-3">Пока нет запланированных постов</p>
</div>
</div>
</div>
{% endblock %}
{% block extra_script %}
<script>
// Установка минимальной даты (сегодня)
document.addEventListener('DOMContentLoaded', function() {
const today = new Date();
const todayStr = today.toISOString().split('T')[0];
document.getElementById('postDate').setAttribute('min', todayStr);
// Показываем текущее время в браузере пользователя
const tzOffset = -today.getTimezoneOffset() / 60;
const tzLabel = tzOffset >= 0 ? `UTC+${tzOffset}` : `UTC${tzOffset}`;
console.log(`🕐 Твой часовой пояс: ${tzLabel}`);
loadPosts();
});
function previewImage() {
const file = document.getElementById('postPhoto').files[0];
const preview = document.getElementById('preview');
const previewImg = document.getElementById('previewImg');
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
previewImg.src = e.target.result;
preview.style.display = 'block';
}
reader.readAsDataURL(file);
} else {
preview.style.display = 'none';
}
}
// Отправка формы
document.getElementById('scheduledPostForm').addEventListener('submit', async (e) => {
e.preventDefault();
const text = document.getElementById('postText').value;
const photo = document.getElementById('postPhoto').files[0];
const topic = document.getElementById('postTopic').value;
const recipients = document.getElementById('postRecipients').value;
const date = document.getElementById('postDate').value;
const time = document.getElementById('postTime').value;
const resultDiv = document.getElementById('result');
if (!text.trim()) {
alert('Введите текст поста!');
return;
}
if (!date || !time) {
alert('Выберите дату и время!');
return;
}
const scheduledTime = `${date}T${time}:00`;
const formData = new FormData();
formData.append('text', text);
formData.append('topic_name', topic);
formData.append('recipients', recipients);
formData.append('scheduled_time', scheduledTime);
if (photo) {
formData.append('photo', photo);
}
try {
const r = await fetch('/api/scheduled_posts/create', {
method: 'POST',
body: formData
});
const result = await r.json();
if (result.success) {
resultDiv.innerHTML = `<div class="alert alert-success">✅ ${result.message}</div>`;
document.getElementById('scheduledPostForm').reset();
document.getElementById('preview').style.display = 'none';
loadPosts();
} else {
resultDiv.innerHTML = `<div class="alert alert-danger">❌ Ошибка: ${result.error || 'Неизвестная'}</div>`;
}
} catch (err) {
resultDiv.innerHTML = `<div class="alert alert-danger">❌ Ошибка: ${err.message}</div>`;
}
});
// Загрузка постов
async function loadPosts() {
try {
const r = await fetch('/api/scheduled_posts/list');
const result = await r.json();
const postsList = document.getElementById('postsList');
const noPosts = document.getElementById('noPosts');
if (result.success && result.posts.length > 0) {
noPosts.style.display = 'none';
postsList.innerHTML = '';
result.posts.forEach(post => {
const card = document.createElement('div');
card.className = 'card mb-3 post-card';
card.innerHTML = `
<div class="card-body">
<div class="d-flex justify-content-between align-items-start mb-2">
<h6 class="mb-1">${post.topic_emoji} ${post.topic_name || 'Без темы'}</h6>
<span class="badge bg-${post.status === 'pending' ? 'warning' : post.status === 'sent' ? 'success' : post.status === 'failed' ? 'danger' : 'secondary'}">
${post.status_emoji} ${post.status}
</span>
</div>
<p class="mb-2">${post.text.substring(0, 100)}${post.text.length > 100 ? '...' : ''}</p>
<div class="d-flex justify-content-between align-items-center">
<small class="text-muted">
📅 ${post.scheduled_time}
</small>
${post.status === 'pending' ? `
<button class="btn btn-sm btn-danger" onclick="cancelPost(${post.id})">
<i class="bi bi-x-circle"></i> Отменить
</button>
` : ''}
</div>
</div>
`;
postsList.appendChild(card);
});
} else {
noPosts.style.display = 'block';
postsList.innerHTML = '';
}
} catch (err) {
console.error('Ошибка загрузки постов:', err);
}
}
// Отмена поста
async function cancelPost(postId) {
if (!confirm('Отменить этот пост?')) return;
try {
const r = await fetch(`/api/scheduled_posts/${postId}/cancel`, {
method: 'POST'
});
const result = await r.json();
if (result.success) {
alert('✅ Пост отменён');
loadPosts();
} else {
alert('❌ Ошибка: ' + (result.error || 'Неизвестная'));
}
} catch (err) {
alert('❌ Ошибка: ' + err.message);
}
}
</script>
{% endblock %}