182 lines
11 KiB
PHP
182 lines
11 KiB
PHP
<!DOCTYPE html>
|
|
<html lang="vi">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
|
<title>GMO-Z.com RUNSYSTEM - @yield('title', 'Dashboard')</title>
|
|
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
|
@stack('styles')
|
|
</head>
|
|
<body class="bg-bg-main text-text-dark font-sans antialiased" data-sidebar="expanded">
|
|
<div class="app-layout">
|
|
<!-- Mobile overlay (click outside to close) -->
|
|
<div id="sidebar-overlay" class="fixed inset-0 bg-black/30 hidden" aria-hidden="true"></div>
|
|
<aside class="sidebar" aria-label="Main navigation">
|
|
<div class="flex flex-col h-full justify-between">
|
|
<div class="flex-1 flex flex-col min-h-0 overflow-hidden">
|
|
<div class="h-16 flex items-center justify-between px-6 border-b border-border-light">
|
|
<div class="flex items-center gap-2">
|
|
<img src="{{ asset('images/logo.png') }}" alt="GMO-Z.com RUNSYSTEM" class="h-8 w-auto object-contain">
|
|
</div>
|
|
<!-- Collapse / Expand button (desktop) -->
|
|
<button id="sidebar-toggle" class="hidden lg:inline-flex p-2 focus:outline-none focus:ring-2 focus:ring-white" aria-label="Collapse sidebar" aria-controls="sidebar" aria-expanded="true">
|
|
<x-icon name="icn-chevron-left" class="w-5 h-5" />
|
|
</button>
|
|
<!-- Mobile hamburger button -->
|
|
<button id="sidebar-hamburger" class="lg:hidden p-2 focus:outline-none focus:ring-2 focus:ring-white" aria-label="Open navigation" aria-controls="sidebar" aria-expanded="false">
|
|
<x-icon name="icn-menu" class="w-5 h-5" />
|
|
</button>
|
|
</div>
|
|
|
|
<nav class="p-4 space-y-1 overflow-y-auto flex-1">
|
|
@if(Auth::check() && Auth::user()->role == \App\Models\User::ROLE_ADMIN)
|
|
<!-- ADMIN SIDEBAR -->
|
|
<a href="{{ route('admin.users.index') }}" class="sidebar-link {{ request()->routeIs('admin.users.index') ? 'sidebar-link-active' : '' }}">
|
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-5 h-5">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z" />
|
|
</svg>
|
|
<span>{{ __('layout.manage_users') }}</span>
|
|
</a>
|
|
<a href="{{ route('admin.users.create') }}" class="sidebar-link {{ request()->routeIs('admin.users.create') ? 'sidebar-link-active' : '' }}">
|
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-5 h-5">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
|
|
</svg>
|
|
<span>{{ __('layout.add_user') }}</span>
|
|
</a>
|
|
@else
|
|
<!-- USER SIDEBAR -->
|
|
<a href="{{ route('user.dashboard') }}" class="sidebar-link {{ request()->routeIs('user.dashboard') ? 'sidebar-link-active' : '' }}">
|
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-5 h-5">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" />
|
|
</svg>
|
|
<span>{{ __('layout.my_page') }}</span>
|
|
</a>
|
|
<a href="{{ route('user.send') }}" class="sidebar-link {{ request()->routeIs('user.send') ? 'sidebar-link-active' : '' }}">
|
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-5 h-5">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 12L3.269 3.126A59.768 59.768 0 0121.485 12 59.77 59.77 0 013.27 20.876L5.999 12zm0 0h7.5" />
|
|
</svg>
|
|
<span>{{ __('layout.send_card') }}</span>
|
|
</a>
|
|
@endif
|
|
|
|
<a href="{{ route('user.change_password') }}" class="sidebar-link {{ request()->routeIs('user.change_password') ? 'sidebar-link-active' : '' }}">
|
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-5 h-5">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M16.5 10.5V6.75a4.5 4.5 0 10-9 0V10.5m-2.812 10.5h14.625c.621 0 1.125-.504 1.125-1.125V11.25c0-.621-.504-1.125-1.125-1.125H3.75c-.621 0-1.125.504-1.125 1.125v7.875c0 .621.504 1.125 1.125 1.125z" />
|
|
</svg>
|
|
<span>{{ __('layout.change_password') }}</span>
|
|
</a>
|
|
</nav>
|
|
</div>
|
|
|
|
<div class="shrink-0">
|
|
<div class="px-6 py-4 border-t border-border-light text-center">
|
|
<span class="text-text-muted text-[10px]">© 2026 GMO-Z.com RUNSYSTEM</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
|
|
<main class="main-content">
|
|
<header class="top-header">
|
|
<div class="flex items-center gap-3 overflow-x-auto py-2">
|
|
<span class="text-text-light text-xs font-bold whitespace-nowrap">Thank Card System</span>
|
|
</div>
|
|
|
|
<div class="flex items-center gap-4 shrink-0">
|
|
<div class="flex items-center gap-2 border-l border-border-light pl-4">
|
|
<div class="w-8 h-8 rounded-full bg-primary-light text-primary flex items-center justify-center font-bold text-sm">
|
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-5 h-5 text-primary">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0A17.933 17.933 0 0112 21.75c-2.676 0-5.216-.584-7.499-1.632z" />
|
|
</svg>
|
|
</div>
|
|
<span class="text-xs font-bold text-text-dark">{{ Auth::check() ? Auth::user()->mail : 'GUEST' }}</span>
|
|
|
|
<form method="POST" action="{{ route('logout') }}" class="ml-2">
|
|
@csrf
|
|
<button type="submit" class="text-xs text-red-500 font-bold hover:underline">{{ __('layout.logout') }}</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="flex-1 overflow-y-auto p-6 space-y-6">
|
|
@if(session('success'))
|
|
<div class="bg-green-50 border border-green-200 text-green-700 px-4 py-3 rounded relative">
|
|
<span class="block sm:inline">{{ session('success') }}</span>
|
|
</div>
|
|
@endif
|
|
|
|
@if(session('error'))
|
|
<div class="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded relative">
|
|
<span class="block sm:inline">{{ session('error') }}</span>
|
|
</div>
|
|
@endif
|
|
|
|
@yield('content')
|
|
</div>
|
|
</main>
|
|
</div>
|
|
@yield('modals')
|
|
@stack('modals')
|
|
<script>
|
|
window.openModal = function(id) {
|
|
const modal = document.getElementById(id);
|
|
if (!modal) return;
|
|
|
|
// 1. Prevent body scroll
|
|
document.body.style.overflow = 'hidden';
|
|
|
|
// 2. Setup initial display state
|
|
modal.classList.remove('hidden');
|
|
modal.classList.add('flex');
|
|
|
|
// 3. Trigger animations
|
|
requestAnimationFrame(() => {
|
|
modal.classList.remove('opacity-0');
|
|
modal.classList.add('opacity-100');
|
|
const panel = modal.querySelector('.modal-panel');
|
|
if (panel) {
|
|
panel.classList.remove('scale-95', 'translate-y-4');
|
|
panel.classList.add('scale-100', 'translate-y-0');
|
|
}
|
|
});
|
|
|
|
// 4. ESC to close and Trap focus
|
|
modal.setAttribute('tabindex', '-1');
|
|
modal.focus();
|
|
|
|
const escHandler = function(e) {
|
|
if (e.key === 'Escape') {
|
|
closeModal(id);
|
|
modal.removeEventListener('keydown', escHandler);
|
|
}
|
|
};
|
|
modal.addEventListener('keydown', escHandler);
|
|
};
|
|
|
|
window.closeModal = function(id) {
|
|
const modal = document.getElementById(id);
|
|
if (!modal) return;
|
|
|
|
// 1. Trigger exit animations
|
|
modal.classList.remove('opacity-100');
|
|
modal.classList.add('opacity-0');
|
|
const panel = modal.querySelector('.modal-panel');
|
|
if (panel) {
|
|
panel.classList.remove('scale-100', 'translate-y-0');
|
|
panel.classList.add('scale-95', 'translate-y-4');
|
|
}
|
|
|
|
// 2. Wait for transition then hide
|
|
setTimeout(() => {
|
|
modal.classList.add('hidden');
|
|
modal.classList.remove('flex');
|
|
document.body.style.overflow = '';
|
|
}, 300); // 300ms matches Tailwind duration-300
|
|
};
|
|
</script>
|
|
@stack('scripts')
|
|
</body>
|
|
</html>
|