bp/app/Views/organizations/users.twig

329 lines
12 KiB
Twig
Raw Permalink 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.

{#
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 %}