Files
minicrm/public/admin-profiles.html
2026-01-26 22:26:03 +05:00

1242 lines
44 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.
<!-- public/admin-profiles.html -->
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Профили пользователей - School CRM</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
color: #333;
}
.admin-container {
max-width: 1400px;
margin: 0 auto;
}
.admin-header {
display: flex;
justify-content: space-between;
align-items: center;
background: white;
padding: 20px;
border-radius: 15px;
margin-bottom: 20px;
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
}
.admin-header h1 {
color: #333;
font-size: 24px;
}
.user-info {
display: flex;
align-items: center;
gap: 15px;
}
.user-name {
font-weight: 500;
color: #555;
}
.logout-btn {
background: #f44336;
color: white;
border: none;
padding: 8px 16px;
border-radius: 5px;
cursor: pointer;
font-weight: 500;
transition: all 0.3s ease;
}
.logout-btn:hover {
background: #d32f2f;
transform: translateY(-2px);
}
.admin-nav {
background: white;
border-radius: 15px;
margin-bottom: 20px;
padding: 15px;
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
}
.nav-list {
display: flex;
gap: 10px;
list-style: none;
}
.nav-item a {
text-decoration: none;
color: #555;
padding: 10px 20px;
border-radius: 8px;
display: flex;
align-items: center;
gap: 8px;
transition: all 0.3s ease;
}
.nav-item a:hover {
background: #f0f2f5;
color: #667eea;
}
.nav-item a.active {
background: #667eea;
color: white;
}
.admin-content {
display: grid;
grid-template-columns: 1fr 350px;
gap: 20px;
}
.profiles-section {
background: white;
border-radius: 15px;
padding: 20px;
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 1px solid #eee;
}
.section-header h2 {
color: #333;
font-size: 20px;
}
.search-box {
position: relative;
}
.search-box input {
padding: 10px 15px 10px 40px;
border: 1px solid #ddd;
border-radius: 8px;
width: 300px;
font-size: 14px;
}
.search-box i {
position: absolute;
left: 15px;
top: 50%;
transform: translateY(-50%);
color: #999;
}
.controls {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 8px;
cursor: pointer;
font-weight: 500;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 8px;
}
.btn-primary {
background: #667eea;
color: white;
}
.btn-primary:hover {
background: #5a6fd8;
transform: translateY(-2px);
}
.btn-secondary {
background: #f0f2f5;
color: #555;
}
.btn-secondary:hover {
background: #e4e6e9;
transform: translateY(-2px);
}
.btn-success {
background: #4CAF50;
color: white;
}
.btn-success:hover {
background: #45a049;
transform: translateY(-2px);
}
.profiles-table {
overflow-x: auto;
}
table {
width: 100%;
border-collapse: collapse;
}
th {
background: #f8f9fa;
padding: 15px;
text-align: left;
font-weight: 600;
color: #555;
border-bottom: 2px solid #eee;
}
td {
padding: 15px;
border-bottom: 1px solid #eee;
}
tr:hover {
background: #f8f9fa;
}
.user-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: 16px;
}
.user-cell {
display: flex;
align-items: center;
gap: 12px;
}
.user-name {
font-weight: 500;
}
.user-login {
color: #888;
font-size: 13px;
}
.badge {
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
}
.badge-admin {
background: #ffebee;
color: #f44336;
}
.badge-teacher {
background: #e8f5e9;
color: #4CAF50;
}
.badge-ldap {
background: #e3f2fd;
color: #2196F3;
}
.badge-local {
background: #f3e5f5;
color: #9C27B0;
}
.email-cell {
position: relative;
}
.email-address {
color: #2196F3;
font-weight: 500;
}
.email-notification {
font-size: 12px;
color: #888;
margin-top: 4px;
}
.email-disabled {
color: #f44336;
}
.email-enabled {
color: #4CAF50;
}
.actions {
display: flex;
gap: 8px;
}
.action-btn {
padding: 6px;
border: none;
border-radius: 5px;
cursor: pointer;
color: white;
transition: all 0.3s ease;
}
.action-btn:hover {
transform: translateY(-2px);
box-shadow: 0 3px 10px rgba(0,0,0,0.2);
}
.action-edit {
background: #2196F3;
}
.action-test {
background: #FF9800;
}
.action-email {
background: #4CAF50;
}
.sidebar {
background: white;
border-radius: 15px;
padding: 20px;
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
}
.sidebar h3 {
margin-bottom: 20px;
color: #333;
}
.profile-form {
display: flex;
flex-direction: column;
gap: 15px;
}
.form-group {
display: flex;
flex-direction: column;
gap: 8px;
}
.form-group label {
font-weight: 500;
color: #555;
}
.form-group input[type="text"],
.form-group input[type="email"],
.form-group select {
padding: 10px;
border: 1px solid #ddd;
border-radius: 8px;
font-size: 14px;
}
.form-group input[type="checkbox"] {
width: 18px;
height: 18px;
}
.checkbox-group {
flex-direction: row;
align-items: center;
gap: 10px;
}
.form-actions {
display: flex;
gap: 10px;
margin-top: 20px;
}
.stats {
margin-top: 30px;
padding: 20px;
background: #f8f9fa;
border-radius: 10px;
}
.stat-item {
display: flex;
justify-content: space-between;
padding: 10px 0;
border-bottom: 1px solid #eee;
}
.stat-item:last-child {
border-bottom: none;
}
.stat-label {
color: #666;
}
.stat-value {
font-weight: 600;
color: #333;
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
z-index: 1000;
align-items: center;
justify-content: center;
}
.modal.show {
display: flex;
}
.modal-content {
background: white;
border-radius: 15px;
padding: 30px;
max-width: 500px;
width: 90%;
max-height: 80vh;
overflow-y: auto;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.modal-header h3 {
color: #333;
}
.modal-close {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #999;
}
.bulk-form .form-group {
margin-bottom: 15px;
}
.users-list {
max-height: 200px;
overflow-y: auto;
border: 1px solid #ddd;
border-radius: 8px;
padding: 10px;
margin-bottom: 20px;
}
.user-checkbox {
display: flex;
align-items: center;
gap: 10px;
padding: 8px;
border-radius: 5px;
}
.user-checkbox:hover {
background: #f0f2f5;
}
.loading {
text-align: center;
padding: 40px;
color: #666;
}
.toast {
position: fixed;
bottom: 20px;
right: 20px;
padding: 15px 20px;
border-radius: 8px;
color: white;
font-weight: 500;
z-index: 1001;
opacity: 0;
transform: translateY(20px);
transition: all 0.3s ease;
}
.toast.show {
opacity: 1;
transform: translateY(0);
}
.toast-success {
background: #4CAF50;
}
.toast-error {
background: #f44336;
}
.toast-warning {
background: #FF9800;
}
@media (max-width: 1200px) {
.admin-content {
grid-template-columns: 1fr;
}
.sidebar {
order: -1;
}
}
@media (max-width: 768px) {
.admin-header {
flex-direction: column;
gap: 15px;
text-align: center;
}
.search-box input {
width: 100%;
}
.controls {
flex-wrap: wrap;
}
.section-header {
flex-direction: column;
gap: 15px;
}
.nav-list {
flex-wrap: wrap;
}
}
</style>
</head>
<body>
<div class="admin-container">
<header class="admin-header">
<h1><i class="fas fa-users-cog"></i> Профили пользователей</h1>
<div class="user-info">
<span class="user-name" id="currentUser">Загрузка...</span>
<button class="logout-btn" onclick="logout()">
<i class="fas fa-sign-out-alt"></i> Выйти
</button>
</div>
</header>
<nav class="admin-nav">
<ul class="nav-list">
<li class="nav-item">
<a href="/admin">
<i class="fas fa-chart-bar"></i> Статистика
</a>
</li>
<li class="nav-item">
<a href="/admin/users">
<i class="fas fa-users"></i> Пользователи
</a>
</li>
<li class="nav-item">
<a href="/admin/tasks">
<i class="fas fa-tasks"></i> Задачи
</a>
</li>
<li class="nav-item">
<a href="/admin/activity-logs">
<i class="fas fa-history"></i> Логи
</a>
</li>
<li class="nav-item">
<a href="/admin/notification-logs">
<i class="fas fa-bell"></i> Уведомления
</a>
</li>
<li class="nav-item">
<a href="/admin/profiles" class="active">
<i class="fas fa-id-card"></i> Профили
</a>
</li>
</ul>
</nav>
<main class="admin-content">
<section class="profiles-section">
<div class="section-header">
<h2>Список профилей пользователей</h2>
<div class="search-box">
<i class="fas fa-search"></i>
<input type="text" id="searchInput" placeholder="Поиск по имени или email..." onkeyup="filterProfiles()">
</div>
</div>
<div class="controls">
<button class="btn btn-primary" onclick="loadProfiles()">
<i class="fas fa-sync-alt"></i> Обновить
</button>
<button class="btn btn-secondary" onclick="showBulkModal()">
<i class="fas fa-users"></i> Массовое редактирование
</button>
<button class="btn btn-success" onclick="exportProfiles()">
<i class="fas fa-download"></i> Экспорт
</button>
</div>
<div class="profiles-table">
<table id="profilesTable">
<thead>
<tr>
<th><input type="checkbox" id="selectAll" onchange="toggleSelectAll(this)"></th>
<th>Пользователь</th>
<th>Роль</th>
<th>Email уведомлений</th>
<th>Статус</th>
<th>Последний вход</th>
<th>Действия</th>
</tr>
</thead>
<tbody id="profilesBody">
<tr>
<td colspan="7" class="loading">
<i class="fas fa-spinner fa-spin"></i> Загрузка профилей...
</td>
</tr>
</tbody>
</table>
</div>
</section>
<aside class="sidebar">
<h3>Редактирование профиля</h3>
<div class="profile-form" id="profileForm">
<div class="loading">
<i class="fas fa-spinner fa-spin"></i> Загрузка формы...
</div>
</div>
<div class="stats" id="statsSection">
<h3>Статистика</h3>
<div class="stat-item">
<span class="stat-label">Всего пользователей:</span>
<span class="stat-value" id="totalUsers">0</span>
</div>
<div class="stat-item">
<span class="stat-label">Email уведомления:</span>
<span class="stat-value" id="withEmail">0</span>
</div>
<div class="stat-item">
<span class="stat-label">Без email:</span>
<span class="stat-value" id="withoutEmail">0</span>
</div>
<div class="stat-item">
<span class="stat-label">Отключены:</span>
<span class="stat-value" id="disabled">0</span>
</div>
</div>
</aside>
</main>
</div>
<!-- Модальное окно для массового редактирования -->
<div class="modal" id="bulkModal">
<div class="modal-content">
<div class="modal-header">
<h3>Массовое редактирование</h3>
<button class="modal-close" onclick="closeBulkModal()">&times;</button>
</div>
<form class="bulk-form" id="bulkForm" onsubmit="bulkUpdate(event)">
<div class="form-group">
<label>Выбранные пользователи:</label>
<div class="users-list" id="selectedUsersList">
Пользователи не выбраны
</div>
</div>
<div class="form-group">
<label>Включить email уведомления:</label>
<select id="bulkEmailNotifications" onchange="toggleBulkEmail()">
<option value="">Не изменять</option>
<option value="1">Включить</option>
<option value="0">Отключить</option>
</select>
</div>
<div class="form-group" id="bulkEmailGroup" style="display: none;">
<label>Email для уведомлений:</label>
<input type="email" id="bulkNotificationEmail" placeholder="example@domain.com">
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">Применить</button>
<button type="button" class="btn btn-secondary" onclick="closeBulkModal()">Отмена</button>
</div>
</form>
</div>
</div>
<!-- Уведомления -->
<div class="toast" id="toast"></div>
<script>
let currentUser = null;
let profiles = [];
let selectedProfiles = new Set();
let editingProfileId = null;
// Проверка авторизации и загрузка данных
document.addEventListener('DOMContentLoaded', async () => {
await checkAuth();
await loadCurrentUser();
await loadProfiles();
});
// Проверка авторизации
async function checkAuth() {
try {
const response = await fetch('/api/user');
if (!response.ok) {
window.location.href = '/';
return false;
}
const data = await response.json();
if (data.user.role !== 'admin') {
alert('Доступ запрещен! Требуются права администратора.');
window.location.href = '/';
return false;
}
return true;
} catch (error) {
console.error('Ошибка проверки авторизации:', error);
window.location.href = '/';
return false;
}
}
// Загрузка текущего пользователя
async function loadCurrentUser() {
try {
const response = await fetch('/api/user');
const data = await response.json();
currentUser = data.user;
document.getElementById('currentUser').textContent = currentUser.name;
} catch (error) {
console.error('Ошибка загрузки пользователя:', error);
}
}
// Выход из системы
async function logout() {
try {
await fetch('/api/logout', { method: 'POST' });
window.location.href = '/';
} catch (error) {
console.error('Ошибка выхода:', error);
}
}
// Загрузка профилей
async function loadProfiles() {
try {
const response = await fetch('/admin/user-profiles');
if (!response.ok) throw new Error('Ошибка загрузки профилей');
profiles = await response.json();
renderProfiles();
updateStats();
loadProfileForm(null);
} catch (error) {
console.error('Ошибка загрузки профилей:', error);
showToast('Ошибка загрузки профилей', 'error');
}
}
// Отображение профилей в таблице
function renderProfiles() {
const tbody = document.getElementById('profilesBody');
if (profiles.length === 0) {
tbody.innerHTML = `
<tr>
<td colspan="7" class="loading">
<i class="fas fa-user-slash"></i> Нет данных о профилях
</td>
</tr>
`;
return;
}
tbody.innerHTML = profiles.map(profile => `
<tr data-id="${profile.id}">
<td>
<input type="checkbox" class="profile-checkbox"
onchange="toggleProfileSelection(${profile.id}, this.checked)"
${selectedProfiles.has(profile.id) ? 'checked' : ''}>
</td>
<td>
<div class="user-cell">
<div class="user-avatar">${getInitials(profile.name)}</div>
<div>
<div class="user-name">${escapeHtml(profile.name)}</div>
<div class="user-login">${escapeHtml(profile.login)}</div>
</div>
</div>
</td>
<td>
<span class="badge badge-${profile.role}">${profile.role === 'admin' ? 'Админ' : 'Учитель'}</span>
<span class="badge badge-${profile.auth_type}">${profile.auth_type}</span>
</td>
<td class="email-cell">
<div class="email-address">
${profile.notification_email || profile.user_email || 'Не указан'}
</div>
<div class="email-notification ${profile.email_notifications ? 'email-enabled' : 'email-disabled'}">
<i class="fas fa-${profile.email_notifications ? 'check-circle' : 'times-circle'}"></i>
${profile.email_notifications ? 'Включены' : 'Отключены'}
</div>
</td>
<td>${profile.last_login ? formatDate(profile.last_login) : 'Никогда'}</td>
<td>
<div class="actions">
<button class="action-btn action-edit" onclick="editProfile(${profile.id})" title="Редактировать">
<i class="fas fa-edit"></i>
</button>
<button class="action-btn action-test" onclick="testNotification(${profile.id})"
${!profile.email_notifications ? 'disabled style="opacity:0.5;cursor:not-allowed"' : ''}
title="Тестовое уведомление">
<i class="fas fa-paper-plane"></i>
</button>
<button class="action-btn action-email" onclick="toggleEmailNotifications(${profile.id}, ${!profile.email_notifications})"
title="${profile.email_notifications ? 'Отключить' : 'Включить'} email уведомления">
<i class="fas fa-${profile.email_notifications ? 'bell-slash' : 'bell'}"></i>
</button>
</div>
</td>
</tr>
`).join('');
}
// Получение инициалов для аватара
function getInitials(name) {
return name.split(' ').map(word => word[0]).join('').toUpperCase().slice(0, 2);
}
// Экранирование HTML
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// Форматирование даты
function formatDate(dateString) {
const date = new Date(dateString);
return date.toLocaleDateString('ru-RU') + ' ' + date.toLocaleTimeString('ru-RU');
}
// Фильтрация профилей
function filterProfiles() {
const searchTerm = document.getElementById('searchInput').value.toLowerCase();
const rows = document.querySelectorAll('#profilesTable tbody tr');
rows.forEach(row => {
if (row.classList.contains('loading')) return;
const text = row.textContent.toLowerCase();
row.style.display = text.includes(searchTerm) ? '' : 'none';
});
}
// Переключение выбора всех профилей
function toggleSelectAll(checkbox) {
const checkboxes = document.querySelectorAll('.profile-checkbox');
checkboxes.forEach(cb => {
cb.checked = checkbox.checked;
const profileId = parseInt(cb.closest('tr').dataset.id);
if (checkbox.checked) {
selectedProfiles.add(profileId);
} else {
selectedProfiles.delete(profileId);
}
});
updateSelectedUsersList();
}
// Переключение выбора отдельного профиля
function toggleProfileSelection(profileId, isSelected) {
if (isSelected) {
selectedProfiles.add(profileId);
} else {
selectedProfiles.delete(profileId);
}
updateSelectAllCheckbox();
updateSelectedUsersList();
}
// Обновление чекбокса "Выбрать все"
function updateSelectAllCheckbox() {
const selectAllCheckbox = document.getElementById('selectAll');
const allCheckboxes = document.querySelectorAll('.profile-checkbox');
const checkedCount = document.querySelectorAll('.profile-checkbox:checked').length;
selectAllCheckbox.checked = checkedCount === allCheckboxes.length;
selectAllCheckbox.indeterminate = checkedCount > 0 && checkedCount < allCheckboxes.length;
}
// Редактирование профиля
async function editProfile(profileId) {
editingProfileId = profileId;
await loadProfileForm(profileId);
}
// Загрузка формы редактирования
async function loadProfileForm(profileId) {
const formContainer = document.getElementById('profileForm');
if (!profileId) {
formContainer.innerHTML = `
<div class="loading">
<i class="fas fa-user"></i> Выберите профиль для редактирования
</div>
`;
return;
}
try {
const response = await fetch(`/admin/user-profiles/${profileId}`);
if (!response.ok) throw new Error('Ошибка загрузки профиля');
const profile = await response.json();
formContainer.innerHTML = `
<div class="form-group">
<label>Имя пользователя:</label>
<input type="text" value="${escapeHtml(profile.name)}" disabled>
</div>
<div class="form-group">
<label>Логин:</label>
<input type="text" value="${escapeHtml(profile.login)}" disabled>
</div>
<div class="form-group">
<label>Email профиля:</label>
<input type="email" value="${escapeHtml(profile.user_email || '')}" disabled>
</div>
<div class="form-group checkbox-group">
<input type="checkbox" id="emailNotifications"
${profile.email_notifications ? 'checked' : ''}>
<label for="emailNotifications">Включить email уведомления</label>
</div>
<div class="form-group">
<label>Email для уведомлений:</label>
<input type="email" id="notificationEmail"
value="${escapeHtml(profile.notification_email || '')}"
placeholder="Оставьте пустым для использования email профиля">
</div>
<div class="form-group checkbox-group">
<input type="checkbox" id="telegramNotifications" disabled>
<label for="telegramNotifications">Telegram уведомления (в разработке)</label>
</div>
<div class="form-group checkbox-group">
<input type="checkbox" id="vkNotifications" disabled>
<label for="vkNotifications">VK уведомления (в разработке)</label>
</div>
<div class="form-actions">
<button type="button" class="btn btn-primary" onclick="saveProfileSettings()">
<i class="fas fa-save"></i> Сохранить
</button>
<button type="button" class="btn btn-secondary" onclick="testNotification(${profileId})"
${!profile.email_notifications ? 'disabled' : ''}>
<i class="fas fa-paper-plane"></i> Тест
</button>
</div>
`;
// Отключаем email поле если уведомления выключены
const emailCheckbox = document.getElementById('emailNotifications');
const emailInput = document.getElementById('notificationEmail');
emailCheckbox.addEventListener('change', function() {
emailInput.disabled = !this.checked;
});
emailInput.disabled = !emailCheckbox.checked;
} catch (error) {
console.error('Ошибка загрузки формы:', error);
formContainer.innerHTML = `
<div class="loading">
<i class="fas fa-exclamation-triangle"></i> Ошибка загрузки профиля
</div>
`;
}
}
// Сохранение настроек профиля
async function saveProfileSettings() {
if (!editingProfileId) return;
const emailNotifications = document.getElementById('emailNotifications').checked;
const notificationEmail = document.getElementById('notificationEmail').value;
// Валидация
if (emailNotifications && notificationEmail) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(notificationEmail)) {
showToast('Неверный формат email', 'error');
return;
}
}
try {
const response = await fetch(`/admin/user-profiles/${editingProfileId}/notification-settings`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email_notifications: emailNotifications,
notification_email: notificationEmail,
telegram_notifications: false,
telegram_chat_id: '',
vk_notifications: false,
vk_user_id: ''
})
});
if (!response.ok) throw new Error('Ошибка сохранения');
const data = await response.json();
showToast(data.message || 'Настройки сохранены', 'success');
loadProfiles(); // Обновляем список
} catch (error) {
console.error('Ошибка сохранения:', error);
showToast('Ошибка сохранения настроек', 'error');
}
}
// Тестовое уведомление
async function testNotification(profileId) {
if (!confirm('Отправить тестовое уведомление пользователю?')) return;
try {
const response = await fetch(`/admin/user-profiles/${profileId}/test-notification`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ notification_type: 'test' })
});
if (!response.ok) throw new Error('Ошибка отправки');
const data = await response.json();
showToast(data.message || 'Уведомление отправлено', 'success');
} catch (error) {
console.error('Ошибка отправки:', error);
showToast('Ошибка отправки уведомления', 'error');
}
}
// Переключение email уведомлений
async function toggleEmailNotifications(profileId, enable) {
const profile = profiles.find(p => p.id === profileId);
if (!profile) return;
const action = enable ? 'включить' : 'отключить';
if (!confirm(`Вы уверены, что хотите ${action} email уведомления для пользователя ${profile.name}?`)) return;
try {
const response = await fetch(`/admin/user-profiles/${profileId}/notification-settings`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email_notifications: enable,
notification_email: profile.notification_email || profile.user_email || '',
telegram_notifications: profile.telegram_notifications || false,
telegram_chat_id: profile.telegram_chat_id || '',
vk_notifications: profile.vk_notifications || false,
vk_user_id: profile.vk_user_id || ''
})
});
if (!response.ok) throw new Error('Ошибка обновления');
const data = await response.json();
showToast(data.message || 'Настройки обновлены', 'success');
loadProfiles(); // Обновляем список
} catch (error) {
console.error('Ошибка обновления:', error);
showToast('Ошибка обновления настроек', 'error');
}
}
// Обновление статистики
function updateStats() {
const total = profiles.length;
const withEmail = profiles.filter(p => p.email_notifications && (p.notification_email || p.user_email)).length;
const withoutEmail = profiles.filter(p => !p.email_notifications || (!p.notification_email && !p.user_email)).length;
const disabled = profiles.filter(p => !p.email_notifications).length;
document.getElementById('totalUsers').textContent = total;
document.getElementById('withEmail').textContent = withEmail;
document.getElementById('withoutEmail').textContent = withoutEmail;
document.getElementById('disabled').textContent = disabled;
}
// Массовое редактирование
function showBulkModal() {
if (selectedProfiles.size === 0) {
showToast('Выберите пользователей для массового редактирования', 'warning');
return;
}
updateSelectedUsersList();
document.getElementById('bulkModal').classList.add('show');
}
function closeBulkModal() {
document.getElementById('bulkModal').classList.remove('show');
document.getElementById('bulkForm').reset();
document.getElementById('bulkEmailGroup').style.display = 'none';
}
function toggleBulkEmail() {
const select = document.getElementById('bulkEmailNotifications');
const emailGroup = document.getElementById('bulkEmailGroup');
emailGroup.style.display = select.value === '1' ? 'block' : 'none';
}
function updateSelectedUsersList() {
const listContainer = document.getElementById('selectedUsersList');
const selectedProfilesArray = profiles.filter(p => selectedProfiles.has(p.id));
if (selectedProfilesArray.length === 0) {
listContainer.innerHTML = '<div class="loading">Пользователи не выбраны</div>';
return;
}
listContainer.innerHTML = selectedProfilesArray.map(profile => `
<div class="user-checkbox">
<i class="fas fa-user"></i>
<span>${escapeHtml(profile.name)} (${escapeHtml(profile.login)})</span>
</div>
`).join('');
}
async function bulkUpdate(event) {
event.preventDefault();
const emailNotifications = document.getElementById('bulkEmailNotifications').value;
const notificationEmail = document.getElementById('bulkNotificationEmail').value;
if (emailNotifications === '1' && !notificationEmail) {
showToast('Укажите email для уведомлений', 'error');
return;
}
if (emailNotifications === '1' && notificationEmail) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(notificationEmail)) {
showToast('Неверный формат email', 'error');
return;
}
}
try {
const response = await fetch('/admin/bulk-email-settings', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
users: Array.from(selectedProfiles),
email_notifications: emailNotifications === '1',
notification_email: notificationEmail
})
});
if (!response.ok) throw new Error('Ошибка массового обновления');
const data = await response.json();
closeBulkModal();
showToast(`Успешно обновлено ${data.results.success} профилей`, 'success');
selectedProfiles.clear();
loadProfiles();
} catch (error) {
console.error('Ошибка массового обновления:', error);
showToast('Ошибка массового обновления', 'error');
}
}
// Экспорт профилей
async function exportProfiles() {
try {
const response = await fetch('/admin/export?dataType=users&format=csv');
if (!response.ok) throw new Error('Ошибка экспорта');
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `profiles_export_${new Date().toISOString().split('T')[0]}.csv`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
} catch (error) {
console.error('Ошибка экспорта:', error);
showToast('Ошибка экспорта', 'error');
}
}
// Показать уведомление
function showToast(message, type = 'info') {
const toast = document.getElementById('toast');
toast.textContent = message;
toast.className = `toast toast-${type}`;
toast.classList.add('show');
setTimeout(() => {
toast.classList.remove('show');
}, 3000);
}
</script>
</body>
</html>