336 lines
17 KiB
PHP
336 lines
17 KiB
PHP
<!DOCTYPE html>
|
|
<html lang="es">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>@yield('title', 'Admin - OnAPB')</title>
|
|
<link rel="icon" type="image/png" sizes="32x32" href="{{ asset('favicon-32.png') }}">
|
|
<link rel="icon" type="image/png" sizes="16x16" href="{{ asset('favicon-16.png') }}">
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css" rel="stylesheet">
|
|
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=Plus+Jakarta+Sans:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/sweetalert2@11/dist/sweetalert2.min.css">
|
|
<link rel="stylesheet" href="{{ asset('static/admin-kinetic.css') }}?v={{ time() }}">
|
|
<link rel="stylesheet" href="{{ asset('static/kinetic-arena-v3.min.css') }}?v={{ @filemtime(public_path('static/kinetic-arena-v3.min.css')) ?: '3' }}">
|
|
@yield('styles')
|
|
</head>
|
|
<body>
|
|
<div class="admin-wrapper">
|
|
<!-- Sidebar -->
|
|
<aside class="admin-sidebar" id="adminSidebar">
|
|
<div class="sidebar-header d-flex align-items-center">
|
|
<a href="{{ route('admin.dashboard') }}" class="text-decoration-none d-flex align-items-center">
|
|
<img src="{{ asset('logo.png') }}" alt="OnAPB" class="sidebar-logo">
|
|
<span class="sidebar-title">ADMIN<span class="text-primary">.</span></span>
|
|
</a>
|
|
</div>
|
|
<nav class="sidebar-nav">
|
|
<div class="px-4 mb-3"><span class="small fw-bold text-muted text-uppercase tracking-widest">General</span></div>
|
|
<a href="{{ route('admin.dashboard') }}" class="sidebar-link {{ request()->routeIs('admin.dashboard') ? 'active' : '' }}">
|
|
<i class="bi bi-grid-fill"></i> Dashboard
|
|
</a>
|
|
<a href="{{ route('documentacion.index') }}" class="sidebar-link" target="_blank">
|
|
<i class="bi bi-book-fill"></i> Manual / Ayuda
|
|
</a>
|
|
|
|
@if(session('admin_role') == 1 || session('admin_role') == 2)
|
|
<div class="px-4 mt-4 mb-3"><span class="small fw-bold text-muted text-uppercase tracking-widest">Estructura</span></div>
|
|
@endif
|
|
|
|
@if(session('admin_role') == 1)
|
|
<a href="{{ route('admin.clubes.index') }}" class="sidebar-link {{ request()->routeIs('admin.clubes.*') ? 'active' : '' }}">
|
|
<i class="bi bi-shield-shaded"></i> Clubes
|
|
</a>
|
|
@endif
|
|
|
|
{{-- Equipos visible para SuperAdmin y Admin de Club --}}
|
|
@if(session('admin_role') == 1 || session('admin_role') == 2)
|
|
<a href="{{ route('admin.equipos.index') }}" class="sidebar-link {{ request()->routeIs('admin.equipos.*') ? 'active' : '' }}">
|
|
<i class="bi bi-people-fill"></i> Equipos
|
|
</a>
|
|
@endif
|
|
|
|
@if(session('admin_role') == 1)
|
|
<a href="{{ route('admin.categorias.index') }}" class="sidebar-link {{ request()->routeIs('admin.categorias.*') ? 'active' : '' }}">
|
|
<i class="bi bi-tags-fill"></i> Categorías
|
|
</a>
|
|
<a href="{{ route('admin.torneos.index') }}" class="sidebar-link {{ request()->routeIs('admin.torneos.*') ? 'active' : '' }}">
|
|
<i class="bi bi-trophy-fill"></i> Torneos
|
|
</a>
|
|
@endif
|
|
|
|
{{-- Gestionar Club (Solo para Admin de Club) --}}
|
|
@if(session('admin_role') == 2)
|
|
<a href="{{ route('admin.clubes.edit', session('admin_id_club')) }}" class="sidebar-link {{ request()->routeIs('admin.clubes.edit') ? 'active' : '' }}">
|
|
<i class="bi bi-shield-shaded"></i> Gestionar Club
|
|
</a>
|
|
@endif
|
|
|
|
<div class="px-4 mt-4 mb-3"><span class="small fw-bold text-muted text-uppercase tracking-widest">Competición</span></div>
|
|
<a href="{{ route('admin.jugadores.index') }}" class="sidebar-link {{ request()->routeIs('admin.jugadores.*') ? 'active' : '' }}">
|
|
<i class="bi bi-person-badge-fill"></i> Jugadores
|
|
</a>
|
|
<a href="{{ route('admin.pases.index') ?? '#' }}" class="sidebar-link {{ request()->routeIs('admin.pases.*') ? 'active' : '' }}">
|
|
<i class="bi bi-arrow-left-right"></i> Pases
|
|
</a>
|
|
<a href="{{ route('admin.eventos.index') }}" class="sidebar-link {{ request()->routeIs('admin.eventos.*') ? 'active' : '' }}">
|
|
<i class="bi bi-calendar-check-fill"></i> Partidos
|
|
</a>
|
|
<a href="{{ route('admin.escanear') }}" class="sidebar-link {{ request()->routeIs('admin.escanear') ? 'active' : '' }}">
|
|
<i class="bi bi-qr-code-scan"></i> Escanear QR
|
|
</a>
|
|
|
|
@if(session('admin_role') == 1)
|
|
<div class="px-4 mt-4 mb-3"><span class="small fw-bold text-muted text-uppercase tracking-widest">Editorial</span></div>
|
|
<a href="{{ route('admin.promociones.index') }}" class="sidebar-link {{ request()->routeIs('admin.promociones.*') ? 'active' : '' }}">
|
|
<i class="bi bi-geo-alt-fill"></i> Lugares
|
|
</a>
|
|
<a href="{{ route('admin.noticias.index') }}" class="sidebar-link {{ request()->routeIs('admin.noticias.*') ? 'active' : '' }}">
|
|
<i class="bi bi-newspaper"></i> Noticias
|
|
</a>
|
|
<a href="{{ route('admin.carousel.index') }}" class="sidebar-link {{ request()->routeIs('admin.carousel.*') ? 'active' : '' }}">
|
|
<i class="bi bi-images"></i> Carrusel
|
|
</a>
|
|
<a href="{{ route('admin.sponsors.index') }}" class="sidebar-link {{ request()->routeIs('admin.sponsors.*') ? 'active' : '' }}">
|
|
<i class="bi bi-star-fill text-warning"></i> Sponsors
|
|
</a>
|
|
@endif
|
|
|
|
@if(session('admin_role') == 1)
|
|
<div class="px-4 mt-4 mb-3"><span class="small fw-bold text-muted text-uppercase tracking-widest">Sistema</span></div>
|
|
<a href="{{ route('admin.usuarios.index') }}" class="sidebar-link {{ request()->routeIs('admin.usuarios.*') ? 'active' : '' }}">
|
|
<i class="bi bi-people-fill"></i> Administradores
|
|
</a>
|
|
<a href="{{ route('admin.settings.index') }}" class="sidebar-link {{ request()->routeIs('admin.settings.*') ? 'active' : '' }}">
|
|
<i class="bi bi-gear-fill"></i> Configuración
|
|
</a>
|
|
@endif
|
|
|
|
<div class="mt-5 pt-4 border-top border-secondary border-opacity-25 mx-4">
|
|
<a href="{{ route('home') }}" class="sidebar-link p-0 mb-3 small d-flex align-items-center opacity-75">
|
|
<i class="bi bi-house-door me-2"></i> Ver Sitio
|
|
</a>
|
|
<form method="POST" action="{{ route('logout') }}">
|
|
@csrf
|
|
<button type="submit" class="sidebar-link p-0 small d-flex align-items-center text-danger" style="background:none;border:none;cursor:pointer;">
|
|
<i class="bi bi-box-arrow-right me-2"></i> Salir
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</nav>
|
|
</aside>
|
|
|
|
<!-- Main content -->
|
|
<div class="admin-main">
|
|
<!-- Top bar -->
|
|
<header class="admin-topbar no-line">
|
|
<button class="btn btn-link sidebar-toggle d-lg-none no-ripple" id="sidebarToggle">
|
|
<i class="bi bi-list fs-3 text-dark"></i>
|
|
</button>
|
|
<div class="d-none d-md-flex align-items-center gap-2 ms-2">
|
|
<span class="kfx-live-pill">
|
|
<span>Live · Panel activo</span>
|
|
</span>
|
|
</div>
|
|
<div class="ms-auto d-flex align-items-center gap-3">
|
|
<div class="text-end d-none d-sm-block">
|
|
<span class="d-block small fw-bold text-uppercase text-muted">Autenticado como</span>
|
|
<span class="fw-bold">{{ session('admin_username', 'Admin') }}</span>
|
|
</div>
|
|
<div class="bg-primary text-white d-flex align-items-center justify-content-center" style="width: 45px; height: 45px;" title="{{ session('admin_username', 'Admin') }}">
|
|
<i class="bi bi-person-fill fs-4"></i>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- Page content -->
|
|
<main class="admin-content">
|
|
@yield('content')
|
|
</main>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Overlay for mobile sidebar -->
|
|
<div class="sidebar-overlay" id="sidebarOverlay"></div>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
|
<script>
|
|
// Sidebar toggle
|
|
document.getElementById('sidebarToggle')?.addEventListener('click', () => {
|
|
document.getElementById('adminSidebar').classList.toggle('show');
|
|
document.getElementById('sidebarOverlay').classList.toggle('show');
|
|
});
|
|
document.getElementById('sidebarOverlay')?.addEventListener('click', () => {
|
|
document.getElementById('adminSidebar').classList.remove('show');
|
|
document.getElementById('sidebarOverlay').classList.remove('show');
|
|
});
|
|
|
|
// Notifications
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const brandColors = {
|
|
primary: '#B00000',
|
|
success: '#2e7d32',
|
|
info: '#0277bd',
|
|
danger: '#B00000',
|
|
cancel: '#6c757d'
|
|
};
|
|
|
|
@if(session('admin_msg'))
|
|
Swal.fire({
|
|
icon: 'success',
|
|
title: 'LISTO',
|
|
text: '{{ session("admin_msg") }}',
|
|
timer: 4000,
|
|
timerProgressBar: true,
|
|
showConfirmButton: false,
|
|
position: 'top-end',
|
|
toast: true,
|
|
iconColor: brandColors.success
|
|
});
|
|
@endif
|
|
|
|
@if(session('admin_error'))
|
|
Swal.fire({
|
|
icon: 'error',
|
|
title: 'ERROR',
|
|
text: '{{ session("admin_error") }}',
|
|
timer: 5000,
|
|
timerProgressBar: true,
|
|
showConfirmButton: false,
|
|
position: 'top-end',
|
|
toast: true,
|
|
iconColor: brandColors.danger
|
|
});
|
|
@endif
|
|
|
|
@if(session('admin_error_modal'))
|
|
Swal.fire({
|
|
icon: 'error',
|
|
title: 'ALGO SALIÓ MAL',
|
|
text: @json(session('admin_error_modal')),
|
|
confirmButtonText: 'ENTENDIDO',
|
|
confirmButtonColor: brandColors.danger,
|
|
iconColor: brandColors.danger,
|
|
allowOutsideClick: false,
|
|
allowEscapeKey: false
|
|
});
|
|
@endif
|
|
|
|
@if($errors->any())
|
|
let errorHtml = '<ul class="text-start mb-0" style="list-style-type: none; padding-left: 0;">';
|
|
@foreach($errors->all() as $error)
|
|
errorHtml += '<li>{{ $error }}</li>';
|
|
@endforeach
|
|
errorHtml += '</ul>';
|
|
|
|
Swal.fire({
|
|
icon: 'error',
|
|
title: 'ERRORES DE VALIDACIÓN',
|
|
html: errorHtml,
|
|
confirmButtonColor: brandColors.danger
|
|
});
|
|
@endif
|
|
|
|
// ── Sistema Proactivo de Validación y Protección de Formularios ──
|
|
const handleFormValidation = () => {
|
|
const forms = document.querySelectorAll('form:not(.no-auto-validate)');
|
|
|
|
forms.forEach(form => {
|
|
const submitBtn = form.querySelector('button[type="submit"]:not(.no-lock)');
|
|
if (!submitBtn) return;
|
|
|
|
const requiredFields = form.querySelectorAll('[required]');
|
|
if (requiredFields.length === 0) return;
|
|
|
|
const originalBtnHtml = submitBtn.innerHTML;
|
|
|
|
const validate = () => {
|
|
let allValid = true;
|
|
requiredFields.forEach(field => {
|
|
const isFieldValid = field.value.trim() !== '';
|
|
if (!isFieldValid) {
|
|
allValid = false;
|
|
// Feedback visual suave: borde sutil si interactuó
|
|
if (field.dataset.touched) field.style.borderColor = '#ffcdd2';
|
|
} else {
|
|
field.style.borderColor = '';
|
|
}
|
|
});
|
|
submitBtn.disabled = !allValid;
|
|
submitBtn.style.opacity = allValid ? '1' : '0.5';
|
|
submitBtn.style.cursor = allValid ? 'pointer' : 'not-allowed';
|
|
};
|
|
|
|
// Monitorear cambios
|
|
form.addEventListener('input', (e) => {
|
|
if (e.target.hasAttribute('required')) {
|
|
e.target.dataset.touched = 'true';
|
|
}
|
|
validate();
|
|
});
|
|
|
|
// Estado inicial
|
|
validate();
|
|
|
|
// Protección contra Doble Click y Feedback de Carga
|
|
form.addEventListener('submit', (e) => {
|
|
if (form.classList.contains('confirm-submit')) return; // Confirma vía SweetAlert primero
|
|
|
|
if (!submitBtn.disabled) {
|
|
submitBtn.disabled = true;
|
|
submitBtn.innerHTML = `<span class="spinner-border spinner-border-sm me-2"></span> PROCESANDO...`;
|
|
}
|
|
});
|
|
});
|
|
};
|
|
|
|
handleFormValidation();
|
|
|
|
// Interceptor Global para Confirmaciones
|
|
document.addEventListener('submit', function(e) {
|
|
const form = e.target;
|
|
if (form.classList.contains('confirm-submit')) {
|
|
e.preventDefault();
|
|
const text = form.getAttribute('data-confirm-text') || '¿Estás seguro de realizar esta acción?';
|
|
const confirmButtonText = form.getAttribute('data-confirm-button') || 'SÍ, CONFIRMAR';
|
|
const icon = form.getAttribute('data-confirm-icon') || 'warning';
|
|
const submitBtn = form.querySelector('button[type="submit"]');
|
|
|
|
let confirmColor = brandColors.primary;
|
|
if (icon === 'success') confirmColor = brandColors.success;
|
|
if (icon === 'question') confirmColor = brandColors.info;
|
|
if (form.classList.contains('delete-form')) confirmColor = brandColors.danger;
|
|
|
|
Swal.fire({
|
|
title: 'CONFIRMAR ACCIÓN',
|
|
text: text,
|
|
icon: icon,
|
|
iconColor: confirmColor,
|
|
showCancelButton: true,
|
|
confirmButtonColor: confirmColor,
|
|
cancelButtonColor: brandColors.cancel,
|
|
confirmButtonText: confirmButtonText,
|
|
cancelButtonText: 'CANCELAR',
|
|
reverseButtons: true,
|
|
allowOutsideClick: () => !Swal.isLoading()
|
|
}).then((result) => {
|
|
if (result.isConfirmed) {
|
|
if (submitBtn) {
|
|
submitBtn.disabled = true;
|
|
submitBtn.innerHTML = `<span class="spinner-border spinner-border-sm me-2"></span> PROCESANDO...`;
|
|
}
|
|
form.classList.remove('confirm-submit');
|
|
form.submit();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
{{-- Kinetic FX: capa de innovación visual también en admin --}}
|
|
<script src="{{ asset('static/kinetic-fx.js') }}?v={{ @filemtime(public_path('static/kinetic-fx.js')) ?: '3' }}" defer></script>
|
|
@yield('scripts')
|
|
</body>
|
|
</html>
|