Commit 59e1896c authored by Administrator's avatar Administrator

Update 4 files via Son of Anton

parent 54a87b2e
<?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);
$employee = App::getInstance()->currentEmployee();
$currentPath = rtrim($currentPath, '/') ?: '/';
function sidebarActive(string $path, string $current): string {
if ($path === '/' && $current === '/') return 'active';
if ($path !== '/' && str_starts_with($current, $path)) return 'active';
return '';
}
?>
<aside class="sidebar" id="mainSidebar">
<div class="sidebar-logo">
<h1>🏛️ THE CLUB</h1>
<div class="subtitle">نادي النادي شيراتون — Sports City</div>
</div>
// 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' => '🏖️',
];
<nav class="sidebar-nav">
<div class="sidebar-section">الرئيسية</div>
<a href="/dashboard" class="sidebar-link <?= sidebarActive('/dashboard', $currentPath) ?>"><span class="icon">📊</span> لوحة التحكم</a>
$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)] ?? '📌';
};
<div class="sidebar-section">الأعضاء</div>
<a href="/members" class="sidebar-link <?= sidebarActive('/members', $currentPath) ?>"><span class="icon">👥</span> إدارة الأعضاء</a>
<a href="/members/search" class="sidebar-link <?= sidebarActive('/members/search', $currentPath) ?>"><span class="icon">🔍</span> بحث الأعضاء</a>
<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>
// 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(
"SELECT 1 FROM employee_roles er
JOIN roles r ON r.id = er.role_id
WHERE er.employee_id = ? AND r.role_code = 'super_admin' AND er.is_active = 1
LIMIT 1",
[(int) $empId]
);
if ($isSuperAdmin) {
$employeePermissions = ['*'];
}
} catch (\Throwable $e) {
$employeePermissions = [];
}
}
<div class="sidebar-section">مالية</div>
<a href="/payments" class="sidebar-link <?= sidebarActive('/payments', $currentPath) ?>"><span class="icon">💰</span> المدفوعات</a>
<a href="/receipts" class="sidebar-link <?= sidebarActive('/receipts', $currentPath) ?>"><span class="icon">🧾</span> الإيصالات</a>
<a href="/installments" class="sidebar-link <?= sidebarActive('/installments', $currentPath) ?>"><span class="icon">📅</span> الأقساط</a>
<a href="/subscriptions" class="sidebar-link <?= sidebarActive('/subscriptions', $currentPath) ?>"><span class="icon">📋</span> الاشتراكات</a>
$hasPerm = function(string $perm) use ($employeePermissions): bool {
if (empty($perm)) return true;
if (in_array('*', $employeePermissions)) return true;
return in_array($perm, $employeePermissions);
};
<div class="sidebar-section">المخالفات</div>
<a href="/violations" class="sidebar-link <?= sidebarActive('/violations', $currentPath) ?>"><span class="icon">⚠️</span> المخالفات</a>
<a href="/fines" class="sidebar-link <?= sidebarActive('/fines', $currentPath) ?>"><span class="icon">💸</span> الغرامات</a>
$isActive = function(string $route) use ($currentPath): bool {
if ($route === '/') return $currentPath === '/';
return str_starts_with($currentPath, $route);
};
<div class="sidebar-section">عمليات</div>
<a href="/transfers" class="sidebar-link <?= sidebarActive('/transfers', $currentPath) ?>"><span class="icon">🔄</span> التحويلات</a>
<a href="/divorce" class="sidebar-link <?= sidebarActive('/divorce', $currentPath) ?>"><span class="icon">💔</span> الطلاق</a>
<a href="/death" class="sidebar-link <?= sidebarActive('/death', $currentPath) ?>"><span class="icon">🕊️</span> الوفاة</a>
<a href="/waiver" class="sidebar-link <?= sidebarActive('/waiver', $currentPath) ?>"><span class="icon">📝</span> التنازل</a>
// Get menu items from registry
$menuItems = MenuRegistry::getAll();
usort($menuItems, fn($a, $b) => ($a['order'] ?? 999) <=> ($b['order'] ?? 999));
<div class="sidebar-section">أنواع العضوية</div>
<a href="/temporary" class="sidebar-link <?= sidebarActive('/temporary', $currentPath) ?>"><span class="icon">⏱️</span> المؤقتون</a>
<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>
<a href="/honorary" class="sidebar-link <?= sidebarActive('/honorary', $currentPath) ?>"><span class="icon">🎖️</span> الشرفية</a>
<a href="/foreign" class="sidebar-link <?= sidebarActive('/foreign', $currentPath) ?>"><span class="icon">🌍</span> الأجانب</a>
// 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' => []],
];
}
?>
<div class="sidebar-section">نماذج و مستندات</div>
<a href="/forms/submissions" class="sidebar-link <?= sidebarActive('/forms', $currentPath) ?>"><span class="icon">📄</span> النماذج</a>
<a href="/documents" class="sidebar-link <?= sidebarActive('/documents', $currentPath) ?>"><span class="icon">📁</span> المستندات</a>
<div class="sidebar-header">
<div class="sidebar-brand">THE CLUB</div>
</div>
<div class="sidebar-section">تقارير و إشعارات</div>
<a href="/reports" class="sidebar-link <?= sidebarActive('/reports', $currentPath) ?>"><span class="icon">📈</span> التقارير</a>
<a href="/notifications/templates" class="sidebar-link <?= sidebarActive('/notifications', $currentPath) ?>"><span class="icon">📱</span> الإشعارات</a>
<nav class="sidebar-nav">
<ul class="sidebar-menu">
<?php foreach ($menuItems as $item): ?>
<?php
if (!empty($item['permission']) && !$hasPerm($item['permission'])) continue;
if (!empty($item['is_separator'])) {
echo '<li style="padding:15px 20px 5px;font-size:11px;color:#6B7280;text-transform:uppercase;letter-spacing:1px;">' . e($item['label_ar']) . '</li>';
continue;
}
$children = $item['children'] ?? [];
$visibleChildren = [];
foreach ($children as $child) {
if (empty($child['permission']) || $hasPerm($child['permission'])) {
$visibleChildren[] = $child;
}
}
$hasChildren = !empty($visibleChildren);
$itemRoute = $item['route'] ?? '#';
$itemActive = $isActive($itemRoute);
$childActive = false;
foreach ($visibleChildren as $child) {
if ($isActive($child['route'] ?? '')) { $childActive = true; break; }
}
$isOpen = $itemActive || $childActive;
$icon = $getIcon($item['icon'] ?? '');
?>
<li class="sidebar-item<?= $isOpen && $hasChildren ? ' open' : '' ?>">
<?php if ($hasChildren): ?>
<a href="javascript:void(0)" class="sidebar-link<?= $itemActive ? ' active' : '' ?>" onclick="toggleSubmenu(this)">
<span class="sidebar-icon"><?= $icon ?></span>
<span class="sidebar-text"><?= e($item['label_ar']) ?></span>
<span class="sidebar-arrow"></span>
</a>
<ul class="sidebar-submenu" style="display:<?= $isOpen ? 'block' : 'none' ?>;">
<?php foreach ($visibleChildren as $child): ?>
<?php $childRoute = $child['route'] ?? '#'; ?>
<li>
<a href="<?= e($childRoute) ?>" class="sidebar-sublink<?= $isActive($childRoute) ? ' active' : '' ?>">
<?= e($child['label_ar']) ?>
</a>
</li>
<?php endforeach; ?>
</ul>
<?php else: ?>
<a href="<?= e($itemRoute) ?>" class="sidebar-link<?= $itemActive ? ' active' : '' ?>">
<span class="sidebar-icon"><?= $icon ?></span>
<span class="sidebar-text"><?= e($item['label_ar']) ?></span>
</a>
<?php endif; ?>
</li>
<?php endforeach; ?>
</ul>
</nav>
\ No newline at end of file
<div class="sidebar-section">النظام</div>
<a href="/users" class="sidebar-link <?= sidebarActive('/users', $currentPath) ?>"><span class="icon">👤</span> المستخدمون</a>
<a href="/roles" class="sidebar-link <?= sidebarActive('/roles', $currentPath) ?>"><span class="icon">🔐</span> الصلاحيات</a>
<a href="/branches" class="sidebar-link <?= sidebarActive('/branches', $currentPath) ?>"><span class="icon">🏢</span> الفروع</a>
<a href="/rules" class="sidebar-link <?= sidebarActive('/rules', $currentPath) ?>"><span class="icon">⚙️</span> القواعد</a>
<a href="/pricing" class="sidebar-link <?= sidebarActive('/pricing', $currentPath) ?>"><span class="icon">💲</span> التسعير</a>
<a href="/settings" class="sidebar-link <?= sidebarActive('/settings', $currentPath) ?>"><span class="icon">🛠️</span> الإعدادات</a>
<a href="/workflow" class="sidebar-link <?= sidebarActive('/workflow', $currentPath) ?>"><span class="icon">🔀</span> سير العمل</a>
<a href="/audit" class="sidebar-link <?= sidebarActive('/audit', $currentPath) ?>"><span class="icon">📜</span> سجل المراجعة</a>
<a href="/archive" class="sidebar-link <?= sidebarActive('/archive', $currentPath) ?>"><span class="icon">🗄️</span> الأرشيف</a>
</nav>
</aside>
\ No newline at end of file
<?php
use App\Core\CSRF;
?>
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="csrf-token" content="<?= e(CSRF::token()) ?>">
<title><?= $__template->yield('title', 'تسجيل الدخول') ?> — نادي النادي شيراتون</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title><?php $__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') ?>">
<style>
body {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
background: linear-gradient(135deg, #1A1A2E 0%, #0D7377 100%);
font-family: 'Cairo', 'Segoe UI', Tahoma, Arial, sans-serif;
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;
body { display: flex; align-items: center; justify-content: center; min-height: 100vh; background: linear-gradient(135deg, #0D7377 0%, #1A1A2E 100%); padding: 20px; }
.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); }
.auth-logo { text-align: center; margin-bottom: 30px; }
.auth-logo h1 { font-size: 24px; color: #0D7377; margin: 0; }
.auth-logo p { color: #6B7280; font-size: 13px; margin-top: 5px; }
@media (max-width: 480px) {
.auth-card { padding: 25px 20px; }
.auth-logo h1 { font-size: 20px; }
}
</style>
</head>
<body>
<div class="auth-card">
<div class="auth-header">
<h1>نادي النادي شيراتون</h1>
<p>THE CLUB Sheraton</p>
</div>
<div class="auth-card">
<div class="auth-logo">
<h1>🏛️ THE CLUB</h1>
<p>نادي النادي شيراتون — Sports City</p>
</div>
<?php
$session = \App\Core\App::getInstance()->session();
$alerts = $session->getAlerts();
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
$__session = \App\Core\App::getInstance()->session();
$__alerts = $__session->flash('_alerts') ?? [];
?>
<?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', '') ?>
</div>
<?php $__template->yield('content'); ?>
</div>
</body>
</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>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="csrf-token" content="<?= e(CSRF::token()) ?>">
<title><?= $__template->yield('title', 'لوحة التحكم') ?> — نادي النادي شيراتون</title>
<link rel="stylesheet" href="<?= url('assets/css/main.css') ?>">
<?= $__template->yield('styles', '') ?>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title><?php $__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') ?>?v=<?= filemtime(App\Core\App::getInstance()->basePath() . '/public/assets/css/main.css') ?>">
</head>
<body>
<!-- Sidebar -->
<aside class="sidebar" id="sidebar">
<?php $__template->include('Shared.Components.sidebar'); ?>
</aside>
<div class="app-layout">
<!-- Sidebar Overlay (mobile) -->
<div class="sidebar-overlay" id="sidebarOverlay"></div>
<!-- Main Wrapper -->
<div class="main-wrapper" id="main-wrapper">
<!-- Sidebar -->
<?php $__template->include('Shared.Components.sidebar'); ?>
<!-- Top Header -->
<header class="top-header">
<div class="header-right">
<button class="sidebar-toggle-btn" onclick="toggleSidebar()"></button>
<div class="header-title">
<h1><?= $__template->yield('title', 'لوحة التحكم') ?></h1>
<!-- Main Wrapper -->
<div class="main-wrapper">
<!-- Header -->
<header class="main-header">
<div style="display:flex;align-items:center;gap:12px;">
<button class="hamburger-btn" id="hamburgerBtn" aria-label="القائمة"></button>
<h1 class="page-title"><?php $__template->yield('title', 'لوحة التحكم'); ?></h1>
</div>
</div>
<div class="header-left">
<?= $__template->yield('page_actions', '') ?>
<div class="header-user">
<span class="header-user-name"><?= e($employeeName) ?></span>
<a href="/logout" class="header-logout" title="تسجيل الخروج">🚪</a>
<div class="page-actions">
<?php $__template->yield('page_actions', ''); ?>
<div class="user-info">
<?php
$__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>
</header>
</header>
<!-- Alerts -->
<?php $__template->include('Shared.Components.alerts'); ?>
<!-- Page Content -->
<main class="page-content">
<?= $__template->yield('content', '') ?>
</main>
<!-- 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; ?>
<!-- Footer -->
<footer class="page-footer">
<span>نادي النادي شيراتون &copy; <?= date('Y') ?></span>
<span>الإصدار 1.0.0</span>
<span><?= arabic_date(date('Y-m-d')) ?></span>
</footer>
<!-- Main Content -->
<main class="main-content">
<?php $__template->yield('content'); ?>
</main>
</div>
</div>
<script src="<?= url('assets/js/app.js') ?>"></script>
<script>
function toggleSidebar() {
var sb = document.getElementById('sidebar');
var mw = document.getElementById('main-wrapper');
sb.classList.toggle('collapsed');
mw.classList.toggle('sidebar-collapsed');
}
function toggleSubmenu(el) {
var parent = el.parentElement;
var submenu = parent.querySelector('.sidebar-submenu');
if (submenu) {
var isOpen = submenu.style.display === 'block';
submenu.style.display = isOpen ? 'none' : 'block';
parent.classList.toggle('open', !isOpen);
(function() {
var sidebar = document.querySelector('.sidebar');
var overlay = document.getElementById('sidebarOverlay');
var hamburger = document.getElementById('hamburgerBtn');
function openSidebar() {
sidebar.classList.add('open');
overlay.classList.add('active');
document.body.style.overflow = 'hidden';
}
function closeSidebar() {
sidebar.classList.remove('open');
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>
<?= $__template->yield('scripts', '') ?>
<?php $__template->yield('scripts', ''); ?>
</body>
</html>
\ No newline at end of file
/* ═══════════════════════════════════════════
ADD THESE TO THE END OF YOUR main.css
IF SIDEBAR STYLES ARE MISSING
═══════════════════════════════════════════ */
/* ═══════════════════════════════════════════════════
THE CLUB ERP — MAIN STYLESHEET
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 {
width: var(--sidebar-width);
background: var(--dark);
color: #fff;
position: fixed;
top: 0;
right: 0;
width: 260px;
height: 100vh;
background: #1A1A2E;
color: #E5E7EB;
bottom: 0;
z-index: 1000;
overflow-y: auto;
overflow-x: hidden;
z-index: 1000;
transition: width 0.3s ease, transform 0.3s ease;
display: flex;
flex-direction: column;
transition: transform 0.3s ease;
}
.sidebar.collapsed {
width: 0;
transform: translateX(260px);
}
.sidebar-header {
padding: 20px 15px;
.sidebar-logo {
padding: 20px;
text-align: center;
border-bottom: 1px solid rgba(255,255,255,0.1);
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
}
.sidebar-brand {
.sidebar-logo h1 {
font-size: 18px;
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;
margin: 0;
}
.sidebar-nav {
flex: 1;
overflow-y: auto;
padding: 10px 0;
.sidebar-logo .subtitle {
font-size: 11px;
color: var(--gray-400);
margin-top: 2px;
}
.sidebar-menu {
list-style: none;
padding: 0;
margin: 0;
}
.sidebar-nav { padding: 10px 0; }
.sidebar-item {
margin: 2px 0;
.sidebar-section {
padding: 8px 20px 4px;
font-size: 10px;
text-transform: uppercase;
letter-spacing: 1px;
color: var(--gray-400);
font-weight: 700;
}
.sidebar-link {
......@@ -75,212 +96,90 @@
align-items: center;
gap: 10px;
padding: 10px 20px;
color: #D1D5DB;
text-decoration: none;
font-size: 14px;
color: var(--gray-300);
font-size: 13px;
font-weight: 500;
transition: all 0.2s ease;
border-radius: 0;
position: relative;
transition: all 0.15s;
border-right: 3px solid transparent;
text-decoration: none;
}
.sidebar-link:hover {
background: rgba(255,255,255,0.08);
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;
font-size: 13px;
transition: all 0.2s ease;
}
.sidebar-sublink:hover {
.sidebar-link.active {
background: rgba(13,115,119,0.3);
color: #fff;
background: rgba(255,255,255,0.05);
}
.sidebar-sublink.active {
color: #14b8a6;
font-weight: 600;
border-right-color: var(--primary);
font-weight: 700;
}
.sidebar-link .icon { font-size: 16px; width: 22px; text-align: center; flex-shrink: 0; }
/* ── Main Wrapper ── */
.main-wrapper {
margin-right: 260px;
flex: 1;
margin-right: var(--sidebar-width);
min-height: 100vh;
display: flex;
flex-direction: column;
transition: margin-right 0.3s ease;
background: #F3F4F6;
}
.main-wrapper.sidebar-collapsed {
margin-right: 0;
}
/* ── Top Header ── */
.top-header {
/* ── Header ── */
.main-header {
height: var(--header-height);
background: #fff;
border-bottom: 1px solid #E5E7EB;
padding: 12px 25px;
border-bottom: 1px solid var(--gray-200);
display: flex;
justify-content: space-between;
align-items: center;
justify-content: space-between;
padding: 0 25px;
position: sticky;
top: 0;
z-index: 100;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
}
.header-right {
display: flex;
align-items: center;
gap: 15px;
z-index: 900;
}
.header-left {
display: flex;
align-items: center;
gap: 15px;
}
.header-title h1 {
margin: 0;
.main-header .page-title {
font-size: 18px;
font-weight: 700;
color: #1A1A2E;
color: var(--dark);
}
.header-user {
.main-header .page-actions {
display: flex;
align-items: center;
gap: 10px;
font-size: 14px;
color: #4B5563;
}
.header-user-name {
font-weight: 600;
}
.header-logout {
text-decoration: none;
font-size: 18px;
padding: 4px;
.main-header .user-info {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
color: var(--gray-500);
}
.sidebar-toggle-btn {
.hamburger-btn {
display: none;
background: none;
border: 1px solid #E5E7EB;
border-radius: 6px;
padding: 6px 10px;
border: none;
font-size: 24px;
cursor: pointer;
font-size: 16px;
color: #4B5563;
display: none;
padding: 5px;
color: var(--dark);
line-height: 1;
}
/* ── Page Content ── */
.page-content {
/* ── Main Content ── */
.main-content {
flex: 1;
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 ── */
.card {
background: #fff;
border-radius: 8px;
border: 1px solid #E5E7EB;
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
margin-bottom: 0;
box-shadow: 0 1px 3px rgba(0,0,0,0.06);
border: 1px solid var(--gray-200);
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 ── */
.btn {
display: inline-flex;
......@@ -288,193 +187,337 @@
justify-content: center;
gap: 6px;
padding: 8px 16px;
border-radius: 6px;
font-size: 14px;
font-size: 13px;
font-weight: 600;
text-decoration: none;
cursor: pointer;
border-radius: 6px;
border: 1px solid transparent;
transition: all 0.2s ease;
cursor: pointer;
transition: all 0.15s;
text-decoration: none;
white-space: nowrap;
font-family: inherit;
line-height: 1.4;
}
.btn:hover { text-decoration: none; opacity: 0.9; }
.btn:active { transform: scale(0.98); }
.btn-primary {
background: #0D7377;
color: #fff;
border-color: #0D7377;
}
.btn-primary:hover {
background: #0a5c5f;
}
.btn-outline {
background: transparent;
color: #0D7377;
border-color: #D1D5DB;
}
.btn-primary { background: var(--primary); color: #fff; border-color: var(--primary); }
.btn-primary:hover { background: var(--primary-dark); }
.btn-outline:hover {
background: #F3F4F6;
border-color: #0D7377;
}
.btn-outline { background: #fff; color: var(--dark); border-color: var(--gray-300); }
.btn-outline:hover { background: var(--gray-50); border-color: var(--gray-400); }
.btn-sm {
padding: 4px 10px;
font-size: 12px;
}
.btn-sm { padding: 4px 10px; font-size: 12px; }
.btn-xs { padding: 2px 6px; font-size: 11px; }
/* ── Forms ── */
.form-group {
margin-bottom: 0;
}
.form-group { margin-bottom: 0; }
.form-label {
display: block;
margin-bottom: 5px;
font-size: 13px;
font-weight: 600;
color: #374151;
color: var(--gray-700);
margin-bottom: 4px;
}
.form-input,
.form-select,
.form-textarea {
.form-input, .form-select, .form-textarea {
width: 100%;
padding: 8px 12px;
border: 1px solid #D1D5DB;
border-radius: 6px;
font-size: 14px;
font-family: inherit;
border: 1px solid var(--gray-300);
border-radius: 6px;
background: #fff;
color: #1A1A2E;
transition: border-color 0.2s;
box-sizing: border-box;
color: var(--dark);
transition: border-color 0.15s;
direction: rtl;
}
.form-input:focus,
.form-select:focus,
.form-textarea:focus {
.form-input:focus, .form-select:focus, .form-textarea:focus {
outline: none;
border-color: #0D7377;
box-shadow: 0 0 0 3px rgba(13, 115, 119, 0.1);
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(13,115,119,0.1);
}
.form-textarea { resize: vertical; min-height: 60px; }
.form-select { appearance: auto; }
/* ── Tables ── */
.table-responsive {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.form-textarea {
resize: vertical;
min-height: 80px;
.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 ── */
.alert {
padding: 12px 20px;
border-radius: 8px;
margin: 0 25px 15px;
padding: 12px 16px;
border-radius: 6px;
margin-bottom: 15px;
font-size: 14px;
font-weight: 500;
display: flex;
justify-content: space-between;
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 {
background: #F0FDF4;
color: #059669;
border: 1px solid #BBF7D0;
}
.alert-error {
background: #FEF2F2;
color: #DC2626;
border: 1px solid #FECACA;
}
.alert-warning {
background: #FFF7ED;
color: #D97706;
border: 1px solid #FED7AA;
/* ── Pagination ── */
.pagination-wrapper { display: flex; justify-content: center; }
.pagination {
display: flex;
gap: 5px;
list-style: none;
padding: 0;
margin: 0;
flex-wrap: wrap;
justify-content: center;
}
.alert-info {
background: #EFF6FF;
color: #0284C7;
border: 1px solid #BFDBFE;
}
/* ── Sidebar Overlay (mobile) ── */
.sidebar-overlay {
display: none;
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 {
background: none;
border: none;
cursor: pointer;
font-size: 18px;
color: inherit;
opacity: 0.6;
padding: 0 5px;
.sidebar-link { padding: 9px 15px; font-size: 12px; }
.sidebar-section { padding: 8px 15px 4px; }
.main-content { padding: 20px; }
.main-header { padding: 0 20px; }
}
.alert-close:hover {
opacity: 1;
}
/* ══════════════════════════════════════════
RESPONSIVE — MOBILE (≤768px)
══════════════════════════════════════════ */
@media (max-width: 768px) {
:root {
--sidebar-width: 270px;
--header-height: 54px;
}
/* ── Responsive ── */
@media (max-width: 1024px) {
/* Sidebar: off-canvas */
.sidebar {
transform: translateX(260px);
width: 260px;
transform: translateX(100%);
}
.sidebar.show {
.sidebar.open {
transform: translateX(0);
}
/* Main wrapper: full width */
.main-wrapper {
margin-right: 0;
}
.sidebar-toggle-btn {
/* Show hamburger */
.hamburger-btn {
display: block;
}
}
/* ── Print ── */
@media print {
.sidebar, .top-header, .page-footer, .btn, .sidebar-toggle-btn {
display: none !important;
/* Header */
.main-header {
padding: 0 15px;
gap: 10px;
}
.main-wrapper {
margin-right: 0 !important;
.main-header .page-title {
font-size: 15px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 50vw;
}
.page-content {
padding: 0 !important;
.main-header .page-actions {
gap: 6px;
flex-wrap: wrap;
}
.main-header .page-actions .btn {
padding: 5px 10px;
font-size: 11px;
}
.main-header .user-info .user-name-text {
display: none;
}
}
/* ── Body Reset ── */
* {
box-sizing: border-box;
}
/* Content */
.main-content {
padding: 15px;
}
body {
margin: 0;
padding: 0;
font-family: 'Cairo', 'Segoe UI', Tahoma, Arial, sans-serif;
font-size: 14px;
color: #1A1A2E;
background: #F3F4F6;
direction: rtl;
line-height: 1.6;
}
/* ── Grid overrides: stack everything ── */
[style*="grid-template-columns: 1fr 1fr"],
[style*="grid-template-columns:1fr 1fr"],
[style*="grid-template-columns: 2fr 1fr"],
[style*="grid-template-columns:2fr 1fr"],
[style*="grid-template-columns: 1fr 300px"],
[style*="grid-template-columns:1fr 300px"],
[style*="grid-template-columns: 250px 1fr"],
[style*="grid-template-columns:250px 1fr"] {
grid-template-columns: 1fr !important;
}
a {
color: #0D7377;
text-decoration: none;
/* Stats cards row */
[style*="grid-template-columns: repeat(auto-fit"],
[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;
padding: 2px 6px;
border-radius: 4px;
font-size: 12px;
font-family: 'Courier New', monospace;
/* ══════════════════════════════════════════
PRINT
══════════════════════════════════════════ */
@media print {
.sidebar, .main-header, .hamburger-btn, .sidebar-overlay { display: none !important; }
.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