Files

154 lines
8.1 KiB
HTML

{% extends "base.html" %}
{% block title %}Roles y Grupos de Acceso - Edu-Space Admin{% endblock %}
{% block content %}
<div class="container-fluid pt-5 mt-4">
<!-- Header -->
<div class="d-flex justify-content-between align-items-center mb-4 flex-wrap gap-2">
<div>
<h1 class="h3 mb-1">
<i class="bi bi-person-badge text-primary me-2"></i>Roles y Grupos de Acceso
</h1>
<p class="text-muted small mb-0">Administración de perfiles de usuario y matriz granular de permisos por módulo del sistema.</p>
</div>
<div>
<a href="/admin/role_add" class="btn btn-primary">
<i class="bi bi-plus-circle me-1"></i>Nuevo Rol
</a>
</div>
</div>
<!-- Roles Grid / Table -->
<div class="card border-0 shadow-sm">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="bg-body-tertiary">
<tr>
<th class="ps-4">Nombre del Rol</th>
<th>Descripción</th>
<th>Integrantes</th>
<th>Resumen de Permisos</th>
<th class="text-end pe-4">Acciones</th>
</tr>
</thead>
<tbody>
{% for role in roles %}
<tr>
<td class="ps-4">
<div class="d-flex align-items-center">
<div class="badge-brand rounded-circle p-2 me-3 d-inline-flex align-items-center justify-content-center" style="width: 38px; height: 38px;">
<i class="bi bi-shield-check fs-6 text-white"></i>
</div>
<div>
<div class="fw-bold text-primary">{{ role.name }}</div>
{% if role.is_system %}
<span class="badge bg-secondary-subtle text-secondary border small">Protegido del Sistema</span>
{% endif %}
</div>
</div>
</td>
<td>
<span class="text-muted small">{{ role.description or _('Sin descripción provista.') }}</span>
</td>
<td>
<span class="badge bg-primary-subtle text-primary border px-2 py-1">
<i class="bi bi-people me-1"></i>{{ role.user_count or (role.users and role.users|length) or 0 }} usuarios
</span>
</td>
<td>
<div class="d-flex flex-wrap gap-1">
{% for mod in modules %}
{% set p_level = role.permissions[mod.id] if role.permissions else 'none' %}
{% if p_level == 'read_write' %}
<span class="badge bg-success-subtle text-success border small" title="{{ mod.name }}: Lectura y Escritura">
<i class="bi {{ mod.icon }} me-1"></i>{{ mod.name }}: RW
</span>
{% elif p_level == 'read' %}
<span class="badge bg-info-subtle text-info border small" title="{{ mod.name }}: Solo Lectura">
<i class="bi {{ mod.icon }} me-1"></i>{{ mod.name }}: R
</span>
{% else %}
<span class="badge bg-light text-muted border small opacity-75" title="{{ mod.name }}: Sin Acceso">
<i class="bi {{ mod.icon }} me-1"></i>{{ mod.name }}: -
</span>
{% endif %}
{% endfor %}
</div>
</td>
<td class="text-end pe-4">
<div class="btn-group btn-group-sm">
<a href="/admin/roles/edit/{{ role.id }}" class="btn btn-outline-primary" title="Configurar Permisos">
<i class="bi bi-pencil-square me-1"></i>Permisos
</a>
{% if not role.is_system %}
<button type="button" class="btn btn-outline-danger btn-delete-role"
data-role-id="{{ role.id }}"
data-role-name="{{ role.name }}"
data-action-url="/admin/role_delete"
title="Eliminar Rol"
{% if (role.user_count or 0) > 0 or (role.users and role.users|length > 0) %}disabled{% endif %}>
<i class="bi bi-trash"></i>
</button>
{% endif %}
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<!-- Modal Global de Eliminación fuera de la tabla (evita desplazamientos visuales) -->
<div class="modal fade" id="deleteRoleModal" tabindex="-1" aria-hidden="true" data-bs-backdrop="static">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0 shadow-lg" style="border-radius: 14px;">
<div class="modal-header border-0 pb-0">
<div class="d-flex align-items-center">
<div class="badge bg-danger-subtle text-danger p-2 rounded-circle me-2">
<i class="bi bi-exclamation-triangle-fill fs-5"></i>
</div>
<h5 class="modal-title fw-bold text-danger mb-0">Confirmar Eliminación de Rol</h5>
</div>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body text-start py-3">
<p class="mb-2">¿Está seguro de que desea eliminar el rol <strong id="deleteRoleName" class="text-primary"></strong>?</p>
<p class="text-muted small mb-0"><i class="bi bi-info-circle me-1"></i>Esta acción no se puede deshacer.</p>
</div>
<div class="modal-footer border-0 pt-0">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancelar</button>
<form id="deleteRoleForm" method="POST" class="d-inline">
<button type="submit" class="btn btn-danger px-3">
<i class="bi bi-trash me-1"></i>Eliminar Rol
</button>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
document.addEventListener('DOMContentLoaded', function() {
var deleteModalEl = document.getElementById('deleteRoleModal');
var deleteModal = deleteModalEl ? new bootstrap.Modal(deleteModalEl) : null;
var deleteRoleName = document.getElementById('deleteRoleName');
var deleteRoleForm = document.getElementById('deleteRoleForm');
document.querySelectorAll('.btn-delete-role').forEach(function(btn) {
btn.addEventListener('click', function() {
var roleName = this.getAttribute('data-role-name');
var actionUrl = this.getAttribute('data-action-url');
if (deleteRoleName) deleteRoleName.textContent = roleName;
if (deleteRoleForm) deleteRoleForm.action = actionUrl;
if (deleteModal) deleteModal.show();
});
});
});
</script>
{% endblock %}