329 lines
12 KiB
Twig
329 lines
12 KiB
Twig
{#
|
||
organizations/users.twig - Страница управления пользователями организации
|
||
#}
|
||
{% extends 'layouts/base.twig' %}
|
||
|
||
{% block title %}Участники организации - {{ parent() }}{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="container-fluid py-4">
|
||
{# Заголовок #}
|
||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||
<div>
|
||
<h1 class="h3 mb-0">Участники организации</h1>
|
||
<h3 class="text-muted mb-0">{{ organization.name }}</h3>
|
||
</div>
|
||
{% if can_manage_users %}
|
||
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#inviteUserModal">
|
||
<i class="fa-solid fa-user-plus me-2"></i>Пригласить пользователя
|
||
</button>
|
||
{% endif %}
|
||
</div>
|
||
|
||
{# Статистика #}
|
||
<div class="row g-3 mb-4">
|
||
<div class="col-md-3">
|
||
<div class="card bg-primary text-white">
|
||
<div class="card-body">
|
||
<div class="d-flex justify-content-between align-items-center">
|
||
<div>
|
||
<div class="small opacity-75">Всего участников</div>
|
||
<div class="fs-4 fw-bold">{{ users|length }}</div>
|
||
</div>
|
||
<i class="fa-solid fa-users fs-1 opacity-25"></i>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-3">
|
||
<div class="card bg-success text-white">
|
||
<div class="card-body">
|
||
<div class="d-flex justify-content-between align-items-center">
|
||
<div>
|
||
<div class="small opacity-75">Активных</div>
|
||
<div class="fs-4 fw-bold">{{ users|filter(u => u.status == 'active')|length }}</div>
|
||
</div>
|
||
<i class="fa-solid fa-check-circle fs-1 opacity-25"></i>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-3">
|
||
<div class="card bg-warning text-dark">
|
||
<div class="card-body">
|
||
<div class="d-flex justify-content-between align-items-center">
|
||
<div>
|
||
<div class="small opacity-75">Ожидают ответа</div>
|
||
<div class="fs-4 fw-bold">{{ users|filter(u => u.status == 'pending')|length }}</div>
|
||
</div>
|
||
<i class="fa-solid fa-clock fs-1 opacity-25"></i>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-3">
|
||
<div class="card bg-danger text-white">
|
||
<div class="card-body">
|
||
<div class="d-flex justify-content-between align-items-center">
|
||
<div>
|
||
<div class="small opacity-75">Заблокировано</div>
|
||
<div class="fs-4 fw-bold">{{ users|filter(u => u.status == 'blocked')|length }}</div>
|
||
</div>
|
||
<i class="fa-solid fa-ban fs-1 opacity-25"></i>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{# Таблица пользователей #}
|
||
{{ tableHtml|raw }}
|
||
|
||
{# CSRF токен для AJAX запросов #}
|
||
{{ csrf_field()|raw }}
|
||
|
||
{# Кнопка выхода из организации #}
|
||
{% if current_role != 'owner' %}
|
||
<div class="mt-4">
|
||
<button type="button" class="btn btn-outline-danger" onclick="leaveOrganization()">
|
||
<i class="fa-solid fa-sign-out-alt me-2"></i>Покинуть организацию
|
||
</button>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
{# Модалка приглашения #}
|
||
{% include 'organizations/invite_modal.twig' %}
|
||
|
||
{# Модалка подтверждения действий #}
|
||
{% include 'organizations/confirm_modal.twig' %}
|
||
|
||
{% endblock %}
|
||
|
||
{% block stylesheets %}
|
||
{{ parent() }}
|
||
<link rel="stylesheet" href="/assets/css/modules/data-table.css">
|
||
{% endblock %}
|
||
|
||
{% block scripts %}
|
||
{{ parent() }}
|
||
<script src="/assets/js/modules/DataTable.js"></script>
|
||
<script>
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
document.querySelectorAll('.data-table').forEach(function(container) {
|
||
const id = container.id;
|
||
const url = container.dataset.url;
|
||
const perPage = parseInt(container.dataset.perPage) || 10;
|
||
|
||
if (window.dataTables && window.dataTables[id]) {
|
||
return;
|
||
}
|
||
|
||
const table = new DataTable(id, {
|
||
url: url,
|
||
perPage: perPage
|
||
});
|
||
|
||
window.dataTables = window.dataTables || {};
|
||
window.dataTables[id] = table;
|
||
});
|
||
});
|
||
</script>
|
||
|
||
<script>
|
||
// Функции для действий с пользователями
|
||
function openEditRoleModal(userId, email, currentRole) {
|
||
document.getElementById('editRoleUserId').value = userId;
|
||
document.getElementById('editRoleUserEmail').textContent = email;
|
||
document.getElementById('editRoleSelect').value = currentRole;
|
||
|
||
const modal = new bootstrap.Modal(document.getElementById('editRoleModal'));
|
||
modal.show();
|
||
}
|
||
|
||
function saveRole() {
|
||
const userId = document.getElementById('editRoleUserId').value;
|
||
const role = document.getElementById('editRoleSelect').value;
|
||
|
||
const form = document.createElement('form');
|
||
form.method = 'POST';
|
||
form.action = '/organizations/users/' + {{ organization_id }} + '/role';
|
||
|
||
const tokenInput = document.createElement('input');
|
||
tokenInput.type = 'hidden';
|
||
tokenInput.name = '<?= csrf_token() ?>';
|
||
tokenInput.value = '<?= csrf_hash() ?>';
|
||
form.appendChild(tokenInput);
|
||
|
||
const userIdInput = document.createElement('input');
|
||
userIdInput.type = 'hidden';
|
||
userIdInput.name = 'user_id';
|
||
userIdInput.value = userId;
|
||
form.appendChild(userIdInput);
|
||
|
||
const roleInput = document.createElement('input');
|
||
roleInput.type = 'hidden';
|
||
roleInput.name = 'role';
|
||
roleInput.value = role;
|
||
form.appendChild(roleInput);
|
||
|
||
document.body.appendChild(form);
|
||
form.submit();
|
||
}
|
||
|
||
function blockUser(userId) {
|
||
showConfirmModal(
|
||
'Блокировка пользователя',
|
||
'Вы уверены, что хотите заблокировать этого пользователя? Он потеряет доступ к организации.',
|
||
'/organizations/users/' + {{ organization_id }} + '/block/' + userId
|
||
);
|
||
}
|
||
|
||
function unblockUser(userId) {
|
||
const form = document.createElement('form');
|
||
form.method = 'POST';
|
||
form.action = '/organizations/users/' + {{ organization_id }} + '/unblock/' + userId;
|
||
|
||
const tokenInput = document.createElement('input');
|
||
tokenInput.type = 'hidden';
|
||
tokenInput.name = '<?= csrf_token() ?>';
|
||
tokenInput.value = '<?= csrf_hash() ?>';
|
||
form.appendChild(tokenInput);
|
||
|
||
document.body.appendChild(form);
|
||
form.submit();
|
||
}
|
||
|
||
function removeUser(userId) {
|
||
showConfirmModal(
|
||
'Удаление пользователя',
|
||
'Вы уверены, что хотите удалить этого пользователя из организации? Это действие нельзя отменить.',
|
||
'/organizations/users/' + {{ organization_id }} + '/remove/' + userId
|
||
);
|
||
}
|
||
|
||
function leaveOrganization() {
|
||
showConfirmModal(
|
||
'Покинуть организацию',
|
||
'Вы уверены, что хотите покинуть эту организацию?',
|
||
'/organizations/users/' + {{ organization_id }} + '/leave',
|
||
true
|
||
);
|
||
}
|
||
|
||
// Функция показа модалки подтверждения
|
||
function showConfirmModal(title, message, actionUrl, isDanger) {
|
||
document.getElementById('confirmModalTitle').textContent = title;
|
||
document.getElementById('confirmModalMessage').textContent = message;
|
||
|
||
const btn = document.getElementById('confirmModalBtn');
|
||
btn.onclick = function() {
|
||
const form = document.createElement('form');
|
||
form.method = 'POST';
|
||
form.action = actionUrl;
|
||
|
||
const tokenInput = document.createElement('input');
|
||
tokenInput.type = 'hidden';
|
||
tokenInput.name = '<?= csrf_token() ?>';
|
||
tokenInput.value = '<?= csrf_hash() ?>';
|
||
form.appendChild(tokenInput);
|
||
|
||
document.body.appendChild(form);
|
||
form.submit();
|
||
};
|
||
|
||
if (isDanger) {
|
||
btn.className = 'btn btn-danger';
|
||
} else {
|
||
btn.className = 'btn btn-primary';
|
||
}
|
||
|
||
const modal = new bootstrap.Modal(document.getElementById('confirmModal'));
|
||
modal.show();
|
||
}
|
||
|
||
// Обработка отправки приглашения
|
||
document.getElementById('inviteUserForm').addEventListener('submit', function(e) {
|
||
e.preventDefault();
|
||
|
||
const form = this;
|
||
const submitBtn = form.querySelector('button[type="submit"]');
|
||
const originalText = submitBtn.innerHTML;
|
||
submitBtn.innerHTML = '<i class="fa-solid fa-spinner fa-spin me-2"></i>Отправка...';
|
||
submitBtn.disabled = true;
|
||
|
||
const formData = new FormData(form);
|
||
|
||
fetch(form.action, {
|
||
method: 'POST',
|
||
body: formData
|
||
})
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
if (data.success) {
|
||
// Показываем ссылку приглашения
|
||
showInviteLinkModal(data.invite_link, data.email);
|
||
form.reset();
|
||
// Перезагружаем страницу чтобы увидеть нового пользователя
|
||
setTimeout(() => {
|
||
window.location.reload();
|
||
}, 2000);
|
||
} else {
|
||
alert(data.message || 'Ошибка при отправке приглашения');
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('Error:', error);
|
||
alert('Произошла ошибка');
|
||
})
|
||
.finally(() => {
|
||
submitBtn.innerHTML = originalText;
|
||
submitBtn.disabled = false;
|
||
});
|
||
});
|
||
|
||
function showInviteLinkModal(link, email) {
|
||
document.getElementById('inviteLinkInput').value = link;
|
||
document.getElementById('inviteEmailDisplay').textContent = email;
|
||
|
||
// Скрываем модалку приглашения
|
||
bootstrap.Modal.getInstance(document.getElementById('inviteUserModal')).hide();
|
||
|
||
// Показываем модалку со ссылкой
|
||
const modal = new bootstrap.Modal(document.getElementById('inviteLinkModal'));
|
||
modal.show();
|
||
}
|
||
|
||
function copyInviteLink() {
|
||
const input = document.getElementById('inviteLinkInput');
|
||
input.select();
|
||
input.setSelectionRange(0, 99999);
|
||
navigator.clipboard.writeText(input.value).then(() => {
|
||
const btn = document.getElementById('copyLinkBtn');
|
||
const originalIcon = btn.innerHTML;
|
||
btn.innerHTML = '<i class="fa-solid fa-check"></i>';
|
||
setTimeout(() => {
|
||
btn.innerHTML = originalIcon;
|
||
}, 2000);
|
||
});
|
||
}
|
||
|
||
function shareToTelegram() {
|
||
const link = document.getElementById('inviteLinkInput').value;
|
||
const text = encodeURIComponent('Присоединяйся к нашей организации!');
|
||
window.open('https://t.me/share/url?url=' + encodeURIComponent(link) + '&text=' + text, '_blank');
|
||
}
|
||
|
||
function shareViaWebShare() {
|
||
const link = document.getElementById('inviteLinkInput').value;
|
||
if (navigator.share) {
|
||
navigator.share({
|
||
title: 'Приглашение в Бизнес.Точка',
|
||
text: 'Присоединяйся к нашей организации!',
|
||
url: link
|
||
});
|
||
}
|
||
}
|
||
</script>
|
||
{% endblock %}
|