This commit is contained in:
2025-12-23 01:12:10 +03:00
parent 69706d0cb7
commit 6ec4bd00e2
22 changed files with 1923 additions and 175 deletions
+40 -4
View File
@@ -34,29 +34,62 @@
<li class="nav-item">
<a class="nav-link {% if request.path == '/' %}active{% endif %}" href="/">
<i class="bi bi-speedometer2 me-1"></i> Главная
<i class="bi bi-speedometer2 me-2"></i>Главная
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.path.startswith('/medods') %}active{% endif %}" href="/medods">
<i class="bi bi-diagram-3 me-1"></i> Medods
<i class="bi bi-plug me-2"></i>Medods
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.path.startswith('/vk') %}active{% endif %}" href="/vk">
<i class="bi bi-chat-dots me-1"></i> VK
<i class="bi bi-chat-dots me-2"></i>VK
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.path.startswith('/posts') %}active{% endif %}" href="/posts">
<i class="bi bi-file-earmark-text me-1"></i> Посты
<i class="bi bi-file-earmark-text me-2"></i>Посты
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.path.startswith('/birthdate') %}active{% endif %}"
href="/birthdate">
<i class="bi bi-balloon-heart-fill text-pink me-2"></i>Дни рождения
</a>
</li>
</ul>
<div class="dropstart">
<button type="button" class="btn btn-danger dropdown-toggle" id="changePasswordBtn"
data-bs-toggle="dropdown" aria-expanded="false" data-bs-auto-close="outside">
<i class="bi bi-shield-lock-fill me-1"></i>
</button>
<div class="dropdown-menu p-4" style="width: 200px;">
<button type="button" class="btn btn-danger" onclick="deleteAuthToken()">
<i class="bi bi-box-arrow-right me-1"></i>Выйти
</button>
<div class="dropdown-divider"></div>
<form id="changePasswordForm">
<div class="mb-3">
<label for="newPassword" class="form-label">Новый пароль</label>
<input type="password" class="form-control" id="newPassword" placeholder="Новый пароль"
required>
</div>
<button type="submit" class="btn btn-outline-danger"><i
class="bi bi-key-fill me-1"></i>Изменить</button>
</form>
</div>
</div>
</div>
</div>
</nav>
@@ -64,8 +97,11 @@
<main class="container-fluid pt-2">
{% block content %}{% endblock %}
</main>
<!-- Контейнер для уведомлений -->
<div id="alertContainer" class="alert-fixed"></div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="/static/js/base.js"></script>
{% block scripts %}{% endblock %}
</body>
+267
View File
@@ -0,0 +1,267 @@
{% extends "base.html" %}
{% block title %}Дни рождения{% endblock %}
{% block styles %}
<link href="/static/css/birthdate.css" rel="stylesheet">
{% endblock %}
{% block content %}
<!-- Заголовок -->
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h2 class="mb-1"><i class="bi bi-balloon-heart-fill text-pink me-2"></i>Дни рождения</h2>
<p class="text-muted mb-0">Управление поздравлениями сотрудников</p>
</div>
<div class="badge bg-pink fs-6 px-3 py-2">
<i class="bi bi-calendar-heart me-1"></i>Поздравления
</div>
</div>
<!-- Основной контент -->
<div class="row g-4 mb-3">
<!-- Левая колонка: Список сотрудников -->
<div class="col-lg-7">
<div class="card h-100">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0"><i class="bi bi-people-fill me-2"></i>Сотрудники</h5>
<button type="button" class="btn btn-sm btn-outline-primary" onclick="refreshUsersList()">
<i class="bi bi-arrow-clockwise me-1"></i>Обновить список
</button>
</div>
<div class="card-body p-0">
<!-- Таблица сотрудников -->
<div class="table-responsive">
<table class="table table-hover mb-0" id="usersTable">
<thead class="table-light">
<tr>
<th width="120">Дата рождения</th>
<th>Имя</th>
<th>Короткое имя</th>
<th>Полная дата<br><small>(возраст)</small></th>
<th>Пол</th>
<th>Специальности</th>
<th width="100" class="text-center">Статус</th>
<th width="80" class="text-center">Данные</th>
</tr>
</thead>
<tbody id="usersTableBody">
<!-- Данные будут загружены через JS -->
<tr>
<td colspan="8" class="text-center py-5">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Загрузка...</span>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="card-footer">
<div class="row g-2">
<div class="col-md-6">
<button type="button" class="btn btn-sm btn-outline-success w-100" onclick="enableAllUsers()">
<i class="bi bi-check-circle me-1"></i>Включить всех
</button>
</div>
<div class="col-md-6">
<button type="button" class="btn btn-sm btn-outline-secondary w-100"
onclick="disableAllUsers()">
<i class="bi bi-x-circle me-1"></i>Отключить всех
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Правая колонка: Данные сотрудника и планировщик -->
<div class="col-lg-5">
<!-- Данные выбранного сотрудника -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">
<i class="bi bi-person-badge me-2"></i>
<span id="selectedUserName">Выберите сотрудника</span>
</h5>
</div>
<div class="card-body">
<form id="userForm">
<input type="hidden" id="userId">
<!-- Переключатель enabled -->
<div class="mb-4">
<div class="form-check form-switch d-flex align-items-center gap-2 p-0">
<input class="form-check-input m-0" type="checkbox" role="switch" id="user_enabled"
onchange="markUserChanged()">
<label class="form-check-label fw-semibold mb-0" for="user_enabled">
Включить поздравление для этого сотрудника
</label>
</div>
</div>
<!-- Ссылка на фото -->
<div class="mb-4">
<label class="form-label fw-semibold">
<i class="bi bi-image me-2"></i>Ссылка на фото для поздравления
</label>
<input type="url" class="form-control" id="photo_link"
placeholder="https://vk.com/groupname?z=photo-27937673_457248154"
oninput="markUserChanged()">
<div class="form-text">
Ссылка на изображение для поздравления. Должна быть доступна для всех. Лишние символы в
ссылке будут удалены автоматически.
</div>
</div>
<!-- Текст поздравления -->
<div class="mb-4">
<label class="form-label fw-semibold">
<i class="bi bi-chat-heart me-2"></i>Текст поздравления
</label>
<textarea class="form-control" id="congratulations" rows="5"
placeholder="Дорогой(ая) [Имя], поздравляем с днем рождения! 🎉"
oninput="markUserChanged()"></textarea>
<div class="form-text">
Не поддерживается никакая разметка. Только текст и эмоджи. Максимум 2000 символов.
</div>
</div>
<!-- Информация о посте -->
<div id="postInfo" style="display: none;">
<div class="alert alert-info">
<h6 class="alert-heading"><i class="bi bi-info-circle me-2"></i>Информация о посте</h6>
<div class="mb-2" id="postLinkContainer">
<!-- Ссылка на пост будет здесь -->
</div>
<div class="small" id="publishTime">
<!-- Время публикации будет здесь -->
</div>
</div>
</div>
<!-- Кнопка сохранения -->
<div class="d-flex gap-2 pt-3 border-top">
<button type="button" class="btn btn-outline-secondary" onclick="resetUserForm()">
<i class="bi bi-x-lg me-1"></i>Отмена
</button>
<button type="button" class="btn btn-primary" id="saveUserButton" onclick="saveUserData()"
disabled>
<i class="bi bi-save me-1"></i>Сохранить данные сотрудника
</button>
</div>
</form>
</div>
</div>
<!-- Настройки планировщика -->
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0"><i class="bi bi-clock me-2"></i>Настройки планировщика</h5>
<span id="schedulerStatus" class="badge">
{% if data.schedulerStatus.scheduler %}
<span class="badge bg-success"><i class="bi bi-play-circle me-1"></i>Активен</span>
{% else %}
<span class="badge bg-secondary"><i class="bi bi-stop-circle me-1"></i>Неактивен</span>
{% endif %}
</span>
</div>
<div class="card-body">
<form id="schedulerForm">
<!-- Время запуска -->
<div class="row g-3 mb-3">
<div class="col-md-6">
<label class="form-label fw-semibold">Час запуска</label>
<input type="number" class="form-control" id="scheduler_hour" min="0" max="23"
placeholder="9"
value="{{ data.schedulerSettings.hour if data.schedulerSettings else 9 }}"
oninput="markSchedulerChanged()">
</div>
<div class="col-md-6">
<label class="form-label fw-semibold">Минута запуска</label>
<input type="number" class="form-control" id="scheduler_minute" min="0" max="59"
placeholder="0"
value="{{ data.schedulerSettings.minute if data.schedulerSettings else 0 }}"
oninput="markSchedulerChanged()">
</div>
</div>
<!-- Включение планировщика -->
<div class="mb-4">
<div class="form-check form-switch d-flex align-items-center gap-2 p-0">
<input class="form-check-input m-0" type="checkbox" role="switch" id="scheduler_enabled" {%
if data.schedulerSettings and data.schedulerSettings.enabled %}checked{% endif %}
onchange="markSchedulerChanged()">
<label class="form-check-label fw-semibold mb-0" for="scheduler_enabled">
Включить автоматическую публикацию
</label>
</div>
<div class="form-text">
Планировщик будет проверять дни рождения и публиковать поздравления ежедневно в
указанное время.
</div>
</div>
<!-- Информация о следующем запуске -->
{% if data.schedulerStatus.next_run_time %}
<div class="alert alert-success">
<div class="d-flex align-items-center">
<i class="bi bi-calendar-event fs-5 me-3"></i>
<div>
<h6 class="alert-heading mb-1">Следующий запуск</h6>
<p class="mb-0">{{ data.schedulerStatus.next_run_time }}</p>
</div>
</div>
</div>
{% endif %}
<!-- Кнопка сохранения -->
<div class="d-flex gap-2 pt-3 border-top">
<button type="button" class="btn btn-outline-secondary" onclick="resetSchedulerForm()">
<i class="bi bi-x-lg me-1"></i>Сбросить
</button>
<button type="button" class="btn btn-warning" id="saveSchedulerButton"
onclick="saveSchedulerSettings()" disabled>
<i class="bi bi-save me-1"></i>Сохранить настройки планировщика
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<!-- Модальное окно для подтверждения сохранения -->
<div class="modal fade" id="confirmModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="bi bi-exclamation-triangle text-warning me-2"></i>Несохраненные
изменения</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p>У вас есть несохраненные изменения для сотрудника <span id="modalUserName"
class="fw-semibold"></span>.</p>
<p>Вы хотите сохранить изменения перед переходом к другому сотруднику?</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" onclick="cancelSwitchUser()">
<i class="bi bi-x-circle me-1"></i>Отмена
</button>
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal"
onclick="discardUserChanges()">
<i class="bi bi-trash me-1"></i>Не сохранять
</button>
<button type="button" class="btn btn-primary" data-bs-dismiss="modal" onclick="saveAndSwitchUser()">
<i class="bi bi-save me-1"></i>Сохранить и перейти
</button>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script src="/static/js/birthdate.js"></script>
{% endblock %}
-2
View File
@@ -421,8 +421,6 @@
</div>
</div>
<!-- Контейнер для уведомлений -->
<div id="alertContainer" class="alert-fixed"></div>
{% endblock %}
{% block scripts %}
+99
View File
@@ -0,0 +1,99 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Авторизация</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body {
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: #f8f9fa;
}
.login-card {
width: 100%;
max-width: 360px;
}
</style>
</head>
<body>
<div class="card login-card shadow">
<div class="card-body">
<h4 class="text-center mb-4">Вход</h4>
<!-- ВАЖНО: form -->
<form id="loginForm">
<div class="mb-3">
<input type="password" class="form-control" id="password" placeholder="Введите пароль" autofocus>
</div>
<!-- Кнопка может остаться, но теперь не обязательна -->
<button type="submit" class="btn btn-success w-100">
Войти
</button>
</form>
<div id="error" class="alert alert-danger mt-3 d-none"></div>
</div>
</div>
<script>
document.getElementById("loginForm").addEventListener("submit", function (e) {
e.preventDefault(); // НЕ перезагружать страницу
login();
});
async function login() {
const password = document.getElementById("password").value;
const errorBox = document.getElementById("error");
errorBox.classList.add("d-none");
if (!password) {
errorBox.textContent = "Введите пароль";
errorBox.classList.remove("d-none");
return;
}
try {
const response = await fetch("/login", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ password })
});
const data = await response.json();
if (data.status === "ok" && data.token) {
setCookie("auth_token", data.token, 365);
window.location.href = "/";
} else {
errorBox.textContent = data.errorMessage || "Неверный пароль";
errorBox.classList.remove("d-none");
}
} catch (e) {
errorBox.textContent = "Ошибка соединения";
errorBox.classList.remove("d-none");
}
}
function setCookie(name, value, days) {
const date = new Date();
date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
document.cookie = `${name}=${value}; expires=${date.toUTCString()}; path=/`;
}
</script>
</body>
</html>
-3
View File
@@ -255,9 +255,6 @@
</div>
</div>
</div>
<!-- Всплывающие уведомления -->
<div id="alertContainer" style="position: fixed; top: 80px; right: 20px; z-index: 1050;"></div>
{% endblock %}
{% block scripts %}
+1 -3
View File
@@ -9,7 +9,7 @@
<!-- Заголовок -->
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h2 class="mb-1"><i class="bi bi-megaphone-fill text-primary me-2"></i>Управление постами</h2>
<h2 class="mb-1"><i class="bi bi-file-earmark-text text-primary me-2"></i>Управление постами</h2>
<p class="text-muted mb-0">Создание и планирование публикаций в VK</p>
</div>
<button type="button" class="btn btn-success btn-lg px-5" onclick="saveSettings()">
@@ -215,8 +215,6 @@
</div>
</div>
<!-- Контейнер для уведомлений -->
<div id="alertContainer" class="alert-fixed"></div>
{% endblock %}
{% block scripts %}
+1 -3
View File
@@ -9,7 +9,7 @@
<!-- Заголовок -->
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h2 class="mb-1"><i class="bi bi-megaphone vk-icon me-2"></i>VK Настройки</h2>
<h2 class="mb-1"><i class="bi bi-chat-dots vk-icon me-2"></i>VK Настройки</h2>
<p class="text-muted mb-0">Настройки для работы с VK API и сообществом</p>
</div>
<div class="badge bg-primary fs-6 px-3 py-2">
@@ -155,8 +155,6 @@
</div>
</div>
<!-- Контейнер для уведомлений -->
<div id="alertContainer" class="alert-fixed"></div>
{% endblock %}
{% block scripts %}