This commit is contained in:
Laucha1312
2026-06-04 15:20:26 -03:00
parent fdd0fef3f0
commit cc049c6cb6
64 changed files with 8914 additions and 0 deletions
+335
View File
@@ -0,0 +1,335 @@
<!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>