Commit 59e1896c authored by Administrator's avatar Administrator

Update 4 files via Son of Anton

parent 54a87b2e
<?php <?php
/**
* Sidebar component — reads menu items from MenuRegistry.
*/
use App\Core\App;
use App\Core\Registries\MenuRegistry;
$currentPath = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH); $currentPath = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH);
$employee = App::getInstance()->currentEmployee(); $currentPath = rtrim($currentPath, '/') ?: '/';
// Icon name → emoji mapping
$iconMap = [
'dashboard' => '📊', 'tachometer-alt' => '📊', 'home' => '🏠',
'users' => '👥', 'user' => '👤', 'user-plus' => '👤', 'user-clock' => '⏰',
'user-tie' => '👔', 'user-shield' => '🛡️', 'user-friends' => '👥',
'clipboard' => '📋', 'clipboard-list' => '📋', 'file-alt' => '📄',
'calendar' => '📅', 'calendar-alt' => '📅', 'calendar-check' => '✅',
'money-bill' => '💰', 'wallet' => '💳', 'cash-register' => '💰',
'credit-card' => '💳', 'receipt' => '🧾', 'coins' => '🪙',
'file-invoice-dollar' => '💲', 'hand-holding-usd' => '💰',
'exchange-alt' => '🔀', 'random' => '🔀', 'transfer' => '🔀',
'gavel' => '⚖️', 'balance-scale' => '⚖️', 'exclamation-triangle' => '⚠️',
'alert' => '⚠️', 'warning' => '⚠️', 'ban' => '🚫',
'trophy' => '🏆', 'medal' => '🏅', 'award' => '🎖️', 'star' => '⭐',
'globe' => '🌍', 'globe-americas' => '🌎', 'flag' => '🏳️',
'id-card' => '🪪', 'address-card' => '🪪', 'qrcode' => '📱',
'file' => '📁', 'folder' => '📁', 'folder-open' => '📂',
'sms' => '📱', 'envelope' => '✉️', 'bell' => '🔔', 'comment' => '💬',
'chart-bar' => '📈', 'chart-line' => '📈', 'chart-pie' => '📊',
'cog' => '⚙️', 'cogs' => '⚙️', 'wrench' => '🔧', 'tools' => '🛠️',
'sliders-h' => '⚙️', 'settings' => '⚙️',
'shield-alt' => '🔐', 'lock' => '🔒', 'key' => '🔑',
'building' => '🏢', 'city' => '🏙️', 'store' => '🏪',
'book' => '📖', 'history' => '📜', 'archive' => '🗄️',
'sitemap' => '🔄', 'project-diagram' => '🔄', 'workflow' => '🔄',
'heart' => '❤️', 'heartbeat' => '💓', 'cross' => '✝️',
'ring' => '💍', 'baby' => '👶', 'child' => '👦', 'children' => '👨‍👩‍👧‍👦',
'repeat' => '🔄', 'sync' => '🔄', 'redo' => '🔄',
'print' => '🖨️', 'search' => '🔍', 'plus' => '➕', 'edit' => '✏️',
'trash' => '🗑️', 'times' => '❌', 'check' => '✅',
'dollar-sign' => '💲', 'percentage' => '💹',
'swimming-pool' => '🏊', 'running' => '🏃', 'futbol' => '⚽',
'sun' => '☀️', 'umbrella-beach' => '🏖️',
];
$getIcon = function(?string $icon) use ($iconMap): string {
if ($icon === null || $icon === '') return '📌';
// Already an emoji
if (mb_strlen($icon) <= 2 && !ctype_alpha($icon)) return $icon;
// Check map
return $iconMap[$icon] ?? $iconMap[strtolower($icon)] ?? '📌';
};
// Get all permissions for current employee
$employeePermissions = [];
if ($employee && method_exists($employee, 'getAllPermissions')) {
$employeePermissions = $employee->getAllPermissions();
} elseif ($employee) {
try {
$db = App::getInstance()->db();
$empId = $employee->id ?? 0;
$perms = $db->select(
"SELECT DISTINCT rp.permission_key
FROM employee_roles er
JOIN role_permissions rp ON rp.role_id = er.role_id
WHERE er.employee_id = ? AND er.is_active = 1",
[(int) $empId]
);
$employeePermissions = array_column($perms, 'permission_key');
$isSuperAdmin = $db->selectOne( function sidebarActive(string $path, string $current): string {
"SELECT 1 FROM employee_roles er if ($path === '/' && $current === '/') return 'active';
JOIN roles r ON r.id = er.role_id if ($path !== '/' && str_starts_with($current, $path)) return 'active';
WHERE er.employee_id = ? AND r.role_code = 'super_admin' AND er.is_active = 1 return '';
LIMIT 1",
[(int) $empId]
);
if ($isSuperAdmin) {
$employeePermissions = ['*'];
}
} catch (\Throwable $e) {
$employeePermissions = [];
}
}
$hasPerm = function(string $perm) use ($employeePermissions): bool {
if (empty($perm)) return true;
if (in_array('*', $employeePermissions)) return true;
return in_array($perm, $employeePermissions);
};
$isActive = function(string $route) use ($currentPath): bool {
if ($route === '/') return $currentPath === '/';
return str_starts_with($currentPath, $route);
};
// Get menu items from registry
$menuItems = MenuRegistry::getAll();
usort($menuItems, fn($a, $b) => ($a['order'] ?? 999) <=> ($b['order'] ?? 999));
// Fallback if registry empty
if (empty($menuItems)) {
$menuItems = [
['key' => 'dashboard', 'label_ar' => 'لوحة التحكم', 'icon' => '📊', 'route' => '/dashboard', 'permission' => '', 'order' => 10, 'children' => []],
['key' => 'members', 'label_ar' => 'إدارة الأعضاء', 'icon' => '👥', 'route' => '/members', 'permission' => 'member.view', 'order' => 100, 'children' => [
['label_ar' => 'كل الأعضاء', 'route' => '/members', 'permission' => 'member.view'],
['label_ar' => 'عضو جديد', 'route' => '/members/create', 'permission' => 'member.create'],
]],
['key' => 'users', 'label_ar' => 'الموظفون', 'icon' => '👤', 'route' => '/users', 'permission' => 'user.view', 'order' => 910, 'children' => []],
['key' => 'roles', 'label_ar' => 'الأدوار', 'icon' => '🔐', 'route' => '/roles', 'permission' => 'role.view', 'order' => 920, 'children' => []],
['key' => 'branches', 'label_ar' => 'الفروع', 'icon' => '🏢', 'route' => '/branches', 'permission' => 'branch.view', 'order' => 930, 'children' => []],
['key' => 'settings', 'label_ar' => 'الإعدادات', 'icon' => '⚙️', 'route' => '/settings', 'permission' => 'settings.view', 'order' => 960, 'children' => []],
];
} }
?> ?>
<aside class="sidebar" id="mainSidebar">
<div class="sidebar-header"> <div class="sidebar-logo">
<div class="sidebar-brand">THE CLUB</div> <h1>🏛️ THE CLUB</h1>
</div> <div class="subtitle">نادي النادي شيراتون — Sports City</div>
</div>
<nav class="sidebar-nav">
<ul class="sidebar-menu"> <nav class="sidebar-nav">
<?php foreach ($menuItems as $item): ?> <div class="sidebar-section">الرئيسية</div>
<?php <a href="/dashboard" class="sidebar-link <?= sidebarActive('/dashboard', $currentPath) ?>"><span class="icon">📊</span> لوحة التحكم</a>
if (!empty($item['permission']) && !$hasPerm($item['permission'])) continue;
<div class="sidebar-section">الأعضاء</div>
if (!empty($item['is_separator'])) { <a href="/members" class="sidebar-link <?= sidebarActive('/members', $currentPath) ?>"><span class="icon">👥</span> إدارة الأعضاء</a>
echo '<li style="padding:15px 20px 5px;font-size:11px;color:#6B7280;text-transform:uppercase;letter-spacing:1px;">' . e($item['label_ar']) . '</li>'; <a href="/members/search" class="sidebar-link <?= sidebarActive('/members/search', $currentPath) ?>"><span class="icon">🔍</span> بحث الأعضاء</a>
continue; <a href="/interviews" class="sidebar-link <?= sidebarActive('/interviews', $currentPath) ?>"><span class="icon">🗓️</span> المقابلات</a>
} <a href="/carnets" class="sidebar-link <?= sidebarActive('/carnets', $currentPath) ?>"><span class="icon">🪪</span> الكارنيهات</a>
$children = $item['children'] ?? []; <div class="sidebar-section">مالية</div>
$visibleChildren = []; <a href="/payments" class="sidebar-link <?= sidebarActive('/payments', $currentPath) ?>"><span class="icon">💰</span> المدفوعات</a>
foreach ($children as $child) { <a href="/receipts" class="sidebar-link <?= sidebarActive('/receipts', $currentPath) ?>"><span class="icon">🧾</span> الإيصالات</a>
if (empty($child['permission']) || $hasPerm($child['permission'])) { <a href="/installments" class="sidebar-link <?= sidebarActive('/installments', $currentPath) ?>"><span class="icon">📅</span> الأقساط</a>
$visibleChildren[] = $child; <a href="/subscriptions" class="sidebar-link <?= sidebarActive('/subscriptions', $currentPath) ?>"><span class="icon">📋</span> الاشتراكات</a>
}
} <div class="sidebar-section">المخالفات</div>
<a href="/violations" class="sidebar-link <?= sidebarActive('/violations', $currentPath) ?>"><span class="icon">⚠️</span> المخالفات</a>
$hasChildren = !empty($visibleChildren); <a href="/fines" class="sidebar-link <?= sidebarActive('/fines', $currentPath) ?>"><span class="icon">💸</span> الغرامات</a>
$itemRoute = $item['route'] ?? '#';
$itemActive = $isActive($itemRoute); <div class="sidebar-section">عمليات</div>
<a href="/transfers" class="sidebar-link <?= sidebarActive('/transfers', $currentPath) ?>"><span class="icon">🔄</span> التحويلات</a>
$childActive = false; <a href="/divorce" class="sidebar-link <?= sidebarActive('/divorce', $currentPath) ?>"><span class="icon">💔</span> الطلاق</a>
foreach ($visibleChildren as $child) { <a href="/death" class="sidebar-link <?= sidebarActive('/death', $currentPath) ?>"><span class="icon">🕊️</span> الوفاة</a>
if ($isActive($child['route'] ?? '')) { $childActive = true; break; } <a href="/waiver" class="sidebar-link <?= sidebarActive('/waiver', $currentPath) ?>"><span class="icon">📝</span> التنازل</a>
}
<div class="sidebar-section">أنواع العضوية</div>
$isOpen = $itemActive || $childActive; <a href="/temporary" class="sidebar-link <?= sidebarActive('/temporary', $currentPath) ?>"><span class="icon">⏱️</span> المؤقتون</a>
$icon = $getIcon($item['icon'] ?? ''); <a href="/seasonal" class="sidebar-link <?= sidebarActive('/seasonal', $currentPath) ?>"><span class="icon">🌊</span> الموسمية</a>
?> <a href="/sports" class="sidebar-link <?= sidebarActive('/sports', $currentPath) ?>"><span class="icon">🏅</span> الرياضية</a>
<li class="sidebar-item<?= $isOpen && $hasChildren ? ' open' : '' ?>"> <a href="/honorary" class="sidebar-link <?= sidebarActive('/honorary', $currentPath) ?>"><span class="icon">🎖️</span> الشرفية</a>
<?php if ($hasChildren): ?> <a href="/foreign" class="sidebar-link <?= sidebarActive('/foreign', $currentPath) ?>"><span class="icon">🌍</span> الأجانب</a>
<a href="javascript:void(0)" class="sidebar-link<?= $itemActive ? ' active' : '' ?>" onclick="toggleSubmenu(this)">
<span class="sidebar-icon"><?= $icon ?></span> <div class="sidebar-section">نماذج و مستندات</div>
<span class="sidebar-text"><?= e($item['label_ar']) ?></span> <a href="/forms/submissions" class="sidebar-link <?= sidebarActive('/forms', $currentPath) ?>"><span class="icon">📄</span> النماذج</a>
<span class="sidebar-arrow"></span> <a href="/documents" class="sidebar-link <?= sidebarActive('/documents', $currentPath) ?>"><span class="icon">📁</span> المستندات</a>
</a>
<ul class="sidebar-submenu" style="display:<?= $isOpen ? 'block' : 'none' ?>;"> <div class="sidebar-section">تقارير و إشعارات</div>
<?php foreach ($visibleChildren as $child): ?> <a href="/reports" class="sidebar-link <?= sidebarActive('/reports', $currentPath) ?>"><span class="icon">📈</span> التقارير</a>
<?php $childRoute = $child['route'] ?? '#'; ?> <a href="/notifications/templates" class="sidebar-link <?= sidebarActive('/notifications', $currentPath) ?>"><span class="icon">📱</span> الإشعارات</a>
<li>
<a href="<?= e($childRoute) ?>" class="sidebar-sublink<?= $isActive($childRoute) ? ' active' : '' ?>"> <div class="sidebar-section">النظام</div>
<?= e($child['label_ar']) ?> <a href="/users" class="sidebar-link <?= sidebarActive('/users', $currentPath) ?>"><span class="icon">👤</span> المستخدمون</a>
</a> <a href="/roles" class="sidebar-link <?= sidebarActive('/roles', $currentPath) ?>"><span class="icon">🔐</span> الصلاحيات</a>
</li> <a href="/branches" class="sidebar-link <?= sidebarActive('/branches', $currentPath) ?>"><span class="icon">🏢</span> الفروع</a>
<?php endforeach; ?> <a href="/rules" class="sidebar-link <?= sidebarActive('/rules', $currentPath) ?>"><span class="icon">⚙️</span> القواعد</a>
</ul> <a href="/pricing" class="sidebar-link <?= sidebarActive('/pricing', $currentPath) ?>"><span class="icon">💲</span> التسعير</a>
<?php else: ?> <a href="/settings" class="sidebar-link <?= sidebarActive('/settings', $currentPath) ?>"><span class="icon">🛠️</span> الإعدادات</a>
<a href="<?= e($itemRoute) ?>" class="sidebar-link<?= $itemActive ? ' active' : '' ?>"> <a href="/workflow" class="sidebar-link <?= sidebarActive('/workflow', $currentPath) ?>"><span class="icon">🔀</span> سير العمل</a>
<span class="sidebar-icon"><?= $icon ?></span> <a href="/audit" class="sidebar-link <?= sidebarActive('/audit', $currentPath) ?>"><span class="icon">📜</span> سجل المراجعة</a>
<span class="sidebar-text"><?= e($item['label_ar']) ?></span> <a href="/archive" class="sidebar-link <?= sidebarActive('/archive', $currentPath) ?>"><span class="icon">🗄️</span> الأرشيف</a>
</a> </nav>
<?php endif; ?> </aside>
</li> \ No newline at end of file
<?php endforeach; ?>
</ul>
</nav>
\ No newline at end of file
<?php
use App\Core\CSRF;
?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="ar" dir="rtl"> <html lang="ar" dir="rtl">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="csrf-token" content="<?= e(CSRF::token()) ?>"> <title><?php $__template->yield('title', 'تسجيل الدخول'); ?> — نادي النادي</title>
<title><?= $__template->yield('title', 'تسجيل الدخول') ?> — نادي النادي شيراتون</title> <link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Cairo:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="<?= url('assets/css/main.css') ?>"> <link rel="stylesheet" href="<?= url('assets/css/main.css') ?>">
<style> <style>
body { body { display: flex; align-items: center; justify-content: center; min-height: 100vh; background: linear-gradient(135deg, #0D7377 0%, #1A1A2E 100%); padding: 20px; }
display: flex; .auth-card { background: #fff; border-radius: 12px; padding: 40px; width: 100%; max-width: 420px; box-shadow: 0 20px 60px rgba(0,0,0,0.3); }
justify-content: center; .auth-logo { text-align: center; margin-bottom: 30px; }
align-items: center; .auth-logo h1 { font-size: 24px; color: #0D7377; margin: 0; }
min-height: 100vh; .auth-logo p { color: #6B7280; font-size: 13px; margin-top: 5px; }
margin: 0; @media (max-width: 480px) {
background: linear-gradient(135deg, #1A1A2E 0%, #0D7377 100%); .auth-card { padding: 25px 20px; }
font-family: 'Cairo', 'Segoe UI', Tahoma, Arial, sans-serif; .auth-logo h1 { font-size: 20px; }
direction: rtl;
}
.auth-card {
background: #fff;
border-radius: 12px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
padding: 40px;
width: 100%;
max-width: 420px;
}
.auth-header {
text-align: center;
margin-bottom: 30px;
}
.auth-header h1 {
color: #0D7377;
font-size: 24px;
margin: 0 0 5px;
}
.auth-header p {
color: #6B7280;
font-size: 14px;
margin: 0;
} }
</style> </style>
</head> </head>
<body> <body>
<div class="auth-card"> <div class="auth-card">
<div class="auth-header"> <div class="auth-logo">
<h1>نادي النادي شيراتون</h1> <h1>🏛️ THE CLUB</h1>
<p>THE CLUB Sheraton</p> <p>نادي النادي شيراتون — Sports City</p>
</div> </div>
<?php <?php
$session = \App\Core\App::getInstance()->session(); $__session = \App\Core\App::getInstance()->session();
$alerts = $session->getAlerts(); $__alerts = $__session->flash('_alerts') ?? [];
if (!empty($alerts)):
foreach ($alerts as $alert):
?>
<div style="padding:10px 15px;border-radius:6px;margin-bottom:15px;font-size:13px;
background:<?= ($alert['type'] ?? '') === 'error' ? '#FEF2F2' : '#F0FDF4' ?>;
color:<?= ($alert['type'] ?? '') === 'error' ? '#DC2626' : '#059669' ?>;
border:1px solid <?= ($alert['type'] ?? '') === 'error' ? '#FECACA' : '#BBF7D0' ?>;">
<?= e($alert['message'] ?? '') ?>
</div>
<?php
endforeach;
endif;
?> ?>
<?php if (!empty($__alerts)): ?>
<?php foreach ($__alerts as $__alert): ?>
<div class="alert alert-<?= e($__alert['type'] ?? 'info') ?>" style="margin-bottom:15px;"><?= e($__alert['message'] ?? '') ?></div>
<?php endforeach; ?>
<?php endif; ?>
<?= $__template->yield('content', '') ?> <?php $__template->yield('content'); ?>
</div> </div>
</body> </body>
</html> </html>
\ No newline at end of file
<?php
/**
* Main application layout — RTL Arabic-first.
* All authenticated pages use this layout.
*/
use App\Core\App;
use App\Core\CSRF;
$app = App::getInstance();
$employee = $app->currentEmployee();
$employeeName = $employee ? ($employee->full_name_ar ?? 'مستخدم') : 'زائر';
$currentPath = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH);
?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="ar" dir="rtl"> <html lang="ar" dir="rtl">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="csrf-token" content="<?= e(CSRF::token()) ?>"> <title><?php $__template->yield('title', 'النظام'); ?> — نادي النادي</title>
<title><?= $__template->yield('title', 'لوحة التحكم') ?> — نادي النادي شيراتون</title> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="stylesheet" href="<?= url('assets/css/main.css') ?>"> <link href="https://fonts.googleapis.com/css2?family=Cairo:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<?= $__template->yield('styles', '') ?> <link rel="stylesheet" href="<?= url('assets/css/main.css') ?>?v=<?= filemtime(App\Core\App::getInstance()->basePath() . '/public/assets/css/main.css') ?>">
</head> </head>
<body> <body>
<!-- Sidebar --> <div class="app-layout">
<aside class="sidebar" id="sidebar"> <!-- Sidebar Overlay (mobile) -->
<?php $__template->include('Shared.Components.sidebar'); ?> <div class="sidebar-overlay" id="sidebarOverlay"></div>
</aside>
<!-- Main Wrapper --> <!-- Sidebar -->
<div class="main-wrapper" id="main-wrapper"> <?php $__template->include('Shared.Components.sidebar'); ?>
<!-- Top Header --> <!-- Main Wrapper -->
<header class="top-header"> <div class="main-wrapper">
<div class="header-right"> <!-- Header -->
<button class="sidebar-toggle-btn" onclick="toggleSidebar()"></button> <header class="main-header">
<div class="header-title"> <div style="display:flex;align-items:center;gap:12px;">
<h1><?= $__template->yield('title', 'لوحة التحكم') ?></h1> <button class="hamburger-btn" id="hamburgerBtn" aria-label="القائمة"></button>
</div> <h1 class="page-title"><?php $__template->yield('title', 'لوحة التحكم'); ?></h1>
</div> </div>
<div class="header-left"> <div class="page-actions">
<?= $__template->yield('page_actions', '') ?> <?php $__template->yield('page_actions', ''); ?>
<div class="header-user"> <div class="user-info">
<span class="header-user-name"><?= e($employeeName) ?></span> <?php
<a href="/logout" class="header-logout" title="تسجيل الخروج">🚪</a> $__employee = \App\Core\App::getInstance()->currentEmployee();
if ($__employee):
?>
<span class="user-name-text"><?= e($__employee->full_name_ar ?? '') ?></span>
<a href="/logout" class="btn btn-sm btn-outline" style="color:var(--red);border-color:var(--red);">خروج</a>
<?php endif; ?>
</div> </div>
</div> </div>
</header> </header>
<!-- Alerts --> <!-- Alerts -->
<?php $__template->include('Shared.Components.alerts'); ?> <?php
$__session = \App\Core\App::getInstance()->session();
$__alerts = $__session->flash('_alerts') ?? [];
?>
<?php if (!empty($__alerts)): ?>
<div style="padding:15px 25px 0;">
<?php foreach ($__alerts as $__alert): ?>
<div class="alert alert-<?= e($__alert['type'] ?? 'info') ?>"><?= e($__alert['message'] ?? '') ?></div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<!-- Page Content --> <!-- Main Content -->
<main class="page-content"> <main class="main-content">
<?= $__template->yield('content', '') ?> <?php $__template->yield('content'); ?>
</main> </main>
</div>
<!-- Footer -->
<footer class="page-footer">
<span>نادي النادي شيراتون &copy; <?= date('Y') ?></span>
<span>الإصدار 1.0.0</span>
<span><?= arabic_date(date('Y-m-d')) ?></span>
</footer>
</div> </div>
<script src="<?= url('assets/js/app.js') ?>"></script> <script src="<?= url('assets/js/app.js') ?>"></script>
<script> <script>
function toggleSidebar() { (function() {
var sb = document.getElementById('sidebar'); var sidebar = document.querySelector('.sidebar');
var mw = document.getElementById('main-wrapper'); var overlay = document.getElementById('sidebarOverlay');
sb.classList.toggle('collapsed'); var hamburger = document.getElementById('hamburgerBtn');
mw.classList.toggle('sidebar-collapsed');
} function openSidebar() {
function toggleSubmenu(el) { sidebar.classList.add('open');
var parent = el.parentElement; overlay.classList.add('active');
var submenu = parent.querySelector('.sidebar-submenu'); document.body.style.overflow = 'hidden';
if (submenu) { }
var isOpen = submenu.style.display === 'block'; function closeSidebar() {
submenu.style.display = isOpen ? 'none' : 'block'; sidebar.classList.remove('open');
parent.classList.toggle('open', !isOpen); overlay.classList.remove('active');
document.body.style.overflow = '';
}
if (hamburger) {
hamburger.addEventListener('click', function(e) {
e.stopPropagation();
if (sidebar.classList.contains('open')) {
closeSidebar();
} else {
openSidebar();
}
});
}
if (overlay) {
overlay.addEventListener('click', closeSidebar);
}
// Close sidebar on link click (mobile)
var sidebarLinks = sidebar ? sidebar.querySelectorAll('.sidebar-link') : [];
sidebarLinks.forEach(function(link) {
link.addEventListener('click', function() {
if (window.innerWidth <= 768) {
closeSidebar();
}
});
});
// Close on Escape
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') closeSidebar();
});
// Close sidebar if window resizes above mobile
window.addEventListener('resize', function() {
if (window.innerWidth > 768) {
closeSidebar();
} }
} });
})();
</script> </script>
<?= $__template->yield('scripts', '') ?> <?php $__template->yield('scripts', ''); ?>
</body> </body>
</html> </html>
\ No newline at end of file
/* ═══════════════════════════════════════════ /* ═══════════════════════════════════════════════════
ADD THESE TO THE END OF YOUR main.css THE CLUB ERP — MAIN STYLESHEET
IF SIDEBAR STYLES ARE MISSING RTL-first · Mobile-responsive · Production-ready
═══════════════════════════════════════════ */ ═══════════════════════════════════════════════════ */
/* ── Reset & Base ── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--primary: #0D7377;
--primary-dark: #0A5C5F;
--primary-light: #E6F4F4;
--dark: #1A1A2E;
--gray-50: #F9FAFB;
--gray-100: #F3F4F6;
--gray-200: #E5E7EB;
--gray-300: #D1D5DB;
--gray-400: #9CA3AF;
--gray-500: #6B7280;
--gray-600: #4B5563;
--gray-700: #374151;
--red: #DC2626;
--green: #059669;
--amber: #D97706;
--blue: #0284C7;
--purple: #7C3AED;
--sidebar-width: 260px;
--header-height: 60px;
}
html { font-size: 14px; }
body {
font-family: 'Cairo', 'Segoe UI', Tahoma, Arial, sans-serif;
background: var(--gray-50);
color: var(--dark);
direction: rtl;
line-height: 1.6;
min-height: 100vh;
-webkit-font-smoothing: antialiased;
}
a { color: var(--primary); text-decoration: none; }
a:hover { text-decoration: underline; }
/* ── Layout Shell ── */
.app-layout {
display: flex;
min-height: 100vh;
}
/* ── Sidebar ── */
.sidebar { .sidebar {
width: var(--sidebar-width);
background: var(--dark);
color: #fff;
position: fixed; position: fixed;
top: 0; top: 0;
right: 0; right: 0;
width: 260px; bottom: 0;
height: 100vh; z-index: 1000;
background: #1A1A2E;
color: #E5E7EB;
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
z-index: 1000; transition: transform 0.3s ease;
transition: width 0.3s ease, transform 0.3s ease;
display: flex;
flex-direction: column;
} }
.sidebar.collapsed { .sidebar-logo {
width: 0; padding: 20px;
transform: translateX(260px); text-align: center;
}
.sidebar-header {
padding: 20px 15px;
border-bottom: 1px solid rgba(255,255,255,0.1); border-bottom: 1px solid rgba(255,255,255,0.1);
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
} }
.sidebar-logo h1 {
.sidebar-brand {
font-size: 18px; font-size: 18px;
font-weight: 700; font-weight: 700;
color: #14b8a6;
letter-spacing: 1px;
}
.sidebar-toggle {
background: none;
border: none;
color: #9CA3AF;
font-size: 18px;
cursor: pointer;
padding: 5px;
}
.sidebar-toggle:hover {
color: #fff; color: #fff;
margin: 0;
} }
.sidebar-logo .subtitle {
.sidebar-nav { font-size: 11px;
flex: 1; color: var(--gray-400);
overflow-y: auto; margin-top: 2px;
padding: 10px 0;
} }
.sidebar-menu { .sidebar-nav { padding: 10px 0; }
list-style: none;
padding: 0;
margin: 0;
}
.sidebar-item { .sidebar-section {
margin: 2px 0; padding: 8px 20px 4px;
font-size: 10px;
text-transform: uppercase;
letter-spacing: 1px;
color: var(--gray-400);
font-weight: 700;
} }
.sidebar-link { .sidebar-link {
...@@ -75,212 +96,90 @@ ...@@ -75,212 +96,90 @@
align-items: center; align-items: center;
gap: 10px; gap: 10px;
padding: 10px 20px; padding: 10px 20px;
color: #D1D5DB; color: var(--gray-300);
text-decoration: none; font-size: 13px;
font-size: 14px;
font-weight: 500; font-weight: 500;
transition: all 0.2s ease; transition: all 0.15s;
border-radius: 0; border-right: 3px solid transparent;
position: relative; text-decoration: none;
} }
.sidebar-link:hover { .sidebar-link:hover {
background: rgba(255,255,255,0.08); background: rgba(255,255,255,0.08);
color: #fff; color: #fff;
}
.sidebar-link.active {
background: rgba(13, 115, 119, 0.3);
color: #14b8a6;
border-left: 3px solid #14b8a6;
}
.sidebar-icon {
font-size: 16px;
width: 24px;
text-align: center;
flex-shrink: 0;
}
.sidebar-text {
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.sidebar-arrow {
font-size: 10px;
transition: transform 0.2s ease;
color: #6B7280;
}
.sidebar-item.open > .sidebar-link .sidebar-arrow {
transform: rotate(-90deg);
}
.sidebar-submenu {
list-style: none;
padding: 0;
margin: 0;
background: rgba(0,0,0,0.15);
}
.sidebar-sublink {
display: block;
padding: 8px 20px 8px 45px;
color: #9CA3AF;
text-decoration: none; text-decoration: none;
font-size: 13px;
transition: all 0.2s ease;
} }
.sidebar-link.active {
.sidebar-sublink:hover { background: rgba(13,115,119,0.3);
color: #fff; color: #fff;
background: rgba(255,255,255,0.05); border-right-color: var(--primary);
} font-weight: 700;
.sidebar-sublink.active {
color: #14b8a6;
font-weight: 600;
} }
.sidebar-link .icon { font-size: 16px; width: 22px; text-align: center; flex-shrink: 0; }
/* ── Main Wrapper ── */
.main-wrapper { .main-wrapper {
margin-right: 260px; flex: 1;
margin-right: var(--sidebar-width);
min-height: 100vh; min-height: 100vh;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
transition: margin-right 0.3s ease;
background: #F3F4F6;
}
.main-wrapper.sidebar-collapsed {
margin-right: 0;
} }
/* ── Top Header ── */ /* ── Header ── */
.top-header { .main-header {
height: var(--header-height);
background: #fff; background: #fff;
border-bottom: 1px solid #E5E7EB; border-bottom: 1px solid var(--gray-200);
padding: 12px 25px;
display: flex; display: flex;
justify-content: space-between;
align-items: center; align-items: center;
justify-content: space-between;
padding: 0 25px;
position: sticky; position: sticky;
top: 0; top: 0;
z-index: 100; z-index: 900;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
} }
.main-header .page-title {
.header-right {
display: flex;
align-items: center;
gap: 15px;
}
.header-left {
display: flex;
align-items: center;
gap: 15px;
}
.header-title h1 {
margin: 0;
font-size: 18px; font-size: 18px;
font-weight: 700; font-weight: 700;
color: #1A1A2E; color: var(--dark);
} }
.main-header .page-actions {
.header-user {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 10px; gap: 10px;
font-size: 14px;
color: #4B5563;
} }
.main-header .user-info {
.header-user-name { display: flex;
font-weight: 600; align-items: center;
} gap: 8px;
font-size: 13px;
.header-logout { color: var(--gray-500);
text-decoration: none;
font-size: 18px;
padding: 4px;
} }
.hamburger-btn {
.sidebar-toggle-btn { display: none;
background: none; background: none;
border: 1px solid #E5E7EB; border: none;
border-radius: 6px; font-size: 24px;
padding: 6px 10px;
cursor: pointer; cursor: pointer;
font-size: 16px; padding: 5px;
color: #4B5563; color: var(--dark);
display: none; line-height: 1;
} }
/* ── Page Content ── */ /* ── Main Content ── */
.page-content { .main-content {
flex: 1; flex: 1;
padding: 25px; padding: 25px;
} }
/* ── Footer ── */
.page-footer {
padding: 15px 25px;
background: #fff;
border-top: 1px solid #E5E7EB;
display: flex;
justify-content: space-between;
font-size: 12px;
color: #9CA3AF;
}
/* ── Cards ── */ /* ── Cards ── */
.card { .card {
background: #fff; background: #fff;
border-radius: 8px; border-radius: 8px;
border: 1px solid #E5E7EB; box-shadow: 0 1px 3px rgba(0,0,0,0.06);
box-shadow: 0 1px 3px rgba(0,0,0,0.04); border: 1px solid var(--gray-200);
margin-bottom: 0;
overflow: hidden; overflow: hidden;
} }
/* ── Tables ── */
.table-responsive {
overflow-x: auto;
}
.data-table {
width: 100%;
border-collapse: collapse;
font-size: 14px;
}
.data-table thead {
background: #0D7377;
color: #fff;
}
.data-table th {
padding: 12px 15px;
text-align: right;
font-weight: 600;
white-space: nowrap;
}
.data-table td {
padding: 10px 15px;
border-bottom: 1px solid #F3F4F6;
text-align: right;
}
.data-table tbody tr:hover {
background: #F9FAFB;
}
/* ── Buttons ── */ /* ── Buttons ── */
.btn { .btn {
display: inline-flex; display: inline-flex;
...@@ -288,193 +187,337 @@ ...@@ -288,193 +187,337 @@
justify-content: center; justify-content: center;
gap: 6px; gap: 6px;
padding: 8px 16px; padding: 8px 16px;
border-radius: 6px; font-size: 13px;
font-size: 14px;
font-weight: 600; font-weight: 600;
text-decoration: none; border-radius: 6px;
cursor: pointer;
border: 1px solid transparent; border: 1px solid transparent;
transition: all 0.2s ease; cursor: pointer;
transition: all 0.15s;
text-decoration: none;
white-space: nowrap; white-space: nowrap;
font-family: inherit; font-family: inherit;
line-height: 1.4;
} }
.btn:hover { text-decoration: none; opacity: 0.9; }
.btn:active { transform: scale(0.98); }
.btn-primary { .btn-primary { background: var(--primary); color: #fff; border-color: var(--primary); }
background: #0D7377; .btn-primary:hover { background: var(--primary-dark); }
color: #fff;
border-color: #0D7377;
}
.btn-primary:hover { .btn-outline { background: #fff; color: var(--dark); border-color: var(--gray-300); }
background: #0a5c5f; .btn-outline:hover { background: var(--gray-50); border-color: var(--gray-400); }
}
.btn-outline {
background: transparent;
color: #0D7377;
border-color: #D1D5DB;
}
.btn-outline:hover { .btn-sm { padding: 4px 10px; font-size: 12px; }
background: #F3F4F6; .btn-xs { padding: 2px 6px; font-size: 11px; }
border-color: #0D7377;
}
.btn-sm {
padding: 4px 10px;
font-size: 12px;
}
/* ── Forms ── */ /* ── Forms ── */
.form-group { .form-group { margin-bottom: 0; }
margin-bottom: 0;
}
.form-label { .form-label {
display: block; display: block;
margin-bottom: 5px;
font-size: 13px; font-size: 13px;
font-weight: 600; font-weight: 600;
color: #374151; color: var(--gray-700);
margin-bottom: 4px;
} }
.form-input, .form-input, .form-select, .form-textarea {
.form-select,
.form-textarea {
width: 100%; width: 100%;
padding: 8px 12px; padding: 8px 12px;
border: 1px solid #D1D5DB;
border-radius: 6px;
font-size: 14px; font-size: 14px;
font-family: inherit; font-family: inherit;
border: 1px solid var(--gray-300);
border-radius: 6px;
background: #fff; background: #fff;
color: #1A1A2E; color: var(--dark);
transition: border-color 0.2s; transition: border-color 0.15s;
box-sizing: border-box; direction: rtl;
} }
.form-input:focus, .form-select:focus, .form-textarea:focus {
.form-input:focus,
.form-select:focus,
.form-textarea:focus {
outline: none; outline: none;
border-color: #0D7377; border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(13, 115, 119, 0.1); box-shadow: 0 0 0 3px rgba(13,115,119,0.1);
} }
.form-textarea { resize: vertical; min-height: 60px; }
.form-select { appearance: auto; }
.form-textarea { /* ── Tables ── */
resize: vertical; .table-responsive {
min-height: 80px; overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.data-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
.data-table thead { background: var(--gray-50); }
.data-table th {
padding: 10px 14px;
text-align: right;
font-weight: 700;
color: var(--gray-600);
border-bottom: 2px solid var(--gray-200);
white-space: nowrap;
font-size: 12px;
}
.data-table td {
padding: 10px 14px;
border-bottom: 1px solid var(--gray-100);
vertical-align: middle;
} }
.data-table tbody tr:hover { background: var(--gray-50); }
/* ── Alerts ── */ /* ── Alerts ── */
.alert { .alert {
padding: 12px 20px; padding: 12px 16px;
border-radius: 8px; border-radius: 6px;
margin: 0 25px 15px; margin-bottom: 15px;
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
display: flex; display: flex;
justify-content: space-between;
align-items: center; align-items: center;
gap: 8px;
} }
.alert-success { background: #F0FDF4; border: 1px solid #BBF7D0; color: var(--green); }
.alert-error { background: #FEF2F2; border: 1px solid #FECACA; color: var(--red); }
.alert-warning { background: #FFF7ED; border: 1px solid #FED7AA; color: var(--amber); }
.alert-info { background: #EFF6FF; border: 1px solid #BFDBFE; color: var(--blue); }
.alert-success { /* ── Pagination ── */
background: #F0FDF4; .pagination-wrapper { display: flex; justify-content: center; }
color: #059669; .pagination {
border: 1px solid #BBF7D0; display: flex;
} gap: 5px;
list-style: none;
.alert-error { padding: 0;
background: #FEF2F2; margin: 0;
color: #DC2626; flex-wrap: wrap;
border: 1px solid #FECACA; justify-content: center;
}
.alert-warning {
background: #FFF7ED;
color: #D97706;
border: 1px solid #FED7AA;
} }
.alert-info { /* ── Sidebar Overlay (mobile) ── */
background: #EFF6FF; .sidebar-overlay {
color: #0284C7; display: none;
border: 1px solid #BFDBFE; position: fixed;
} inset: 0;
background: rgba(0,0,0,0.5);
z-index: 999;
}
.sidebar-overlay.active { display: block; }
/* ── Scrollbar ── */
.sidebar::-webkit-scrollbar { width: 4px; }
.sidebar::-webkit-scrollbar-track { background: transparent; }
.sidebar::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.2); border-radius: 4px; }
/* ── Utility ── */
.text-center { text-align: center; }
.text-left { text-align: left; }
.text-right { text-align: right; }
.font-bold { font-weight: 700; }
.hidden { display: none !important; }
/* ══════════════════════════════════════════
RESPONSIVE — TABLET (≤1024px)
══════════════════════════════════════════ */
@media (max-width: 1024px) {
:root { --sidebar-width: 220px; }
.alert-close { .sidebar-link { padding: 9px 15px; font-size: 12px; }
background: none; .sidebar-section { padding: 8px 15px 4px; }
border: none; .main-content { padding: 20px; }
cursor: pointer; .main-header { padding: 0 20px; }
font-size: 18px;
color: inherit;
opacity: 0.6;
padding: 0 5px;
} }
.alert-close:hover { /* ══════════════════════════════════════════
opacity: 1; RESPONSIVE — MOBILE (≤768px)
} ══════════════════════════════════════════ */
@media (max-width: 768px) {
:root {
--sidebar-width: 270px;
--header-height: 54px;
}
/* ── Responsive ── */ /* Sidebar: off-canvas */
@media (max-width: 1024px) {
.sidebar { .sidebar {
transform: translateX(260px); transform: translateX(100%);
width: 260px;
} }
.sidebar.show { .sidebar.open {
transform: translateX(0); transform: translateX(0);
} }
/* Main wrapper: full width */
.main-wrapper { .main-wrapper {
margin-right: 0; margin-right: 0;
} }
.sidebar-toggle-btn {
/* Show hamburger */
.hamburger-btn {
display: block; display: block;
} }
}
/* ── Print ── */ /* Header */
@media print { .main-header {
.sidebar, .top-header, .page-footer, .btn, .sidebar-toggle-btn { padding: 0 15px;
display: none !important; gap: 10px;
} }
.main-wrapper { .main-header .page-title {
margin-right: 0 !important; font-size: 15px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 50vw;
}
.main-header .page-actions {
gap: 6px;
flex-wrap: wrap;
} }
.page-content { .main-header .page-actions .btn {
padding: 0 !important; padding: 5px 10px;
font-size: 11px;
}
.main-header .user-info .user-name-text {
display: none;
} }
}
/* ── Body Reset ── */ /* Content */
* { .main-content {
box-sizing: border-box; padding: 15px;
} }
body { /* ── Grid overrides: stack everything ── */
margin: 0; [style*="grid-template-columns: 1fr 1fr"],
padding: 0; [style*="grid-template-columns:1fr 1fr"],
font-family: 'Cairo', 'Segoe UI', Tahoma, Arial, sans-serif; [style*="grid-template-columns: 2fr 1fr"],
font-size: 14px; [style*="grid-template-columns:2fr 1fr"],
color: #1A1A2E; [style*="grid-template-columns: 1fr 300px"],
background: #F3F4F6; [style*="grid-template-columns:1fr 300px"],
direction: rtl; [style*="grid-template-columns: 250px 1fr"],
line-height: 1.6; [style*="grid-template-columns:250px 1fr"] {
} grid-template-columns: 1fr !important;
}
a { /* Stats cards row */
color: #0D7377; [style*="grid-template-columns: repeat(auto-fit"],
text-decoration: none; [style*="grid-template-columns:repeat(auto-fit"] {
grid-template-columns: 1fr 1fr !important;
}
/* ── Filter forms: stack vertically ── */
.card form[style*="display:flex"],
.card form[style*="display: flex"] {
flex-direction: column !important;
align-items: stretch !important;
}
.card form[style*="display:flex"] > div,
.card form[style*="display: flex"] > div {
min-width: 0 !important;
width: 100% !important;
}
.card form[style*="display:flex"] .form-input,
.card form[style*="display:flex"] .form-select,
.card form[style*="display: flex"] .form-input,
.card form[style*="display: flex"] .form-select {
min-width: 0 !important;
width: 100% !important;
}
.card form[style*="display:flex"] .btn,
.card form[style*="display: flex"] .btn {
width: 100%;
}
/* ── Tables: tighter ── */
.data-table th, .data-table td {
padding: 8px 8px;
font-size: 12px;
}
.data-table th { font-size: 11px; }
/* ── Cards: less padding ── */
.card > div[style*="padding:20px"],
.card > div[style*="padding: 20px"] {
padding: 15px !important;
}
.card > div[style*="padding:15px 20px"],
.card > div[style*="padding: 15px 20px"] {
padding: 12px 15px !important;
}
/* ── Buttons in table cells: stack ── */
td [style*="display:flex"],
td [style*="display: flex"] {
flex-wrap: wrap !important;
}
/* ── Member show page: action buttons wrap ── */
.member-actions {
flex-wrap: wrap !important;
}
/* ── Inline forms (pay buttons etc) ── */
td form[style*="display:flex"],
td form[style*="display: flex"] {
flex-wrap: wrap !important;
}
/* ── Full-width grid children on mobile ── */
[style*="grid-column:1/-1"],
[style*="grid-column: 1/-1"] {
grid-column: 1 / -1 !important;
}
/* ── Print button row ── */
[style*="display:flex;gap:10px;"],
[style*="display: flex; gap: 10px;"] {
flex-wrap: wrap !important;
}
} }
a:hover { /* ══════════════════════════════════════════
text-decoration: underline; RESPONSIVE — SMALL MOBILE (≤480px)
══════════════════════════════════════════ */
@media (max-width: 480px) {
html { font-size: 13px; }
.main-content { padding: 10px; }
.main-header .page-title { font-size: 14px; max-width: 40vw; }
/* Stats cards: single column */
[style*="grid-template-columns: repeat(auto-fit"],
[style*="grid-template-columns:repeat(auto-fit"] {
grid-template-columns: 1fr !important;
}
/* Even tighter table cells */
.data-table th, .data-table td {
padding: 6px 6px;
font-size: 11px;
}
.btn { padding: 6px 12px; font-size: 12px; }
.btn-sm { padding: 3px 8px; font-size: 11px; }
/* Card section headers */
.card h3 { font-size: 15px !important; }
.card h4 { font-size: 14px !important; }
/* Large money displays */
[style*="font-size:28px"] { font-size: 22px !important; }
[style*="font-size: 28px"] { font-size: 22px !important; }
[style*="font-size:20px"] { font-size: 16px !important; }
[style*="font-size: 20px"] { font-size: 16px !important; }
} }
code { /* ══════════════════════════════════════════
background: #F3F4F6; PRINT
padding: 2px 6px; ══════════════════════════════════════════ */
border-radius: 4px; @media print {
font-size: 12px; .sidebar, .main-header, .hamburger-btn, .sidebar-overlay { display: none !important; }
font-family: 'Courier New', monospace; .main-wrapper { margin-right: 0 !important; }
.main-content { padding: 0 !important; }
.card { box-shadow: none; border: 1px solid #ddd; }
.btn { display: none !important; }
} }
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment