Commit 223727b5 authored by Administrator's avatar Administrator

Update 5 files via Son of Anton

parent 7b9f7368
Pipeline #24 canceled with stage
...@@ -3,113 +3,93 @@ declare(strict_types=1); ...@@ -3,113 +3,93 @@ declare(strict_types=1);
namespace Engine\Template; namespace Engine\Template;
/**
* Simple PHP template engine with layout support.
*
* Usage:
* $engine->render('dashboard/super_admin', ['user' => $user]);
*
* Templates can specify layout at the top:
* (no explicit call needed — all templates use app layout by default for logged-in users)
*/
final class TemplateEngine final class TemplateEngine
{ {
private string $templateDir; private string $basePath;
private string $cacheDir; private string $cachePath;
private array $globals = []; private string $defaultLayout = 'layouts/app';
private array $sections = [];
private array $sectionStack = [];
private ?string $layout = null;
private string $childContent = '';
public function __construct(string $templateDir, string $cacheDir) public function __construct(string $basePath = '', string $cachePath = '')
{ {
$this->templateDir = rtrim($templateDir, '/'); $this->basePath = $basePath ?: (defined('ROOT_PATH') ? ROOT_PATH . '/templates' : __DIR__ . '/../../templates');
$this->cacheDir = rtrim($cacheDir, '/'); $this->cachePath = $cachePath ?: (defined('ROOT_PATH') ? ROOT_PATH . '/storage/cache/templates' : '/tmp');
if (!is_dir($this->cacheDir)) {
mkdir($this->cacheDir, 0755, true);
}
} }
public function addGlobal(string $key, mixed $value): void /**
* Render a template with data and wrap in layout.
*/
public function render(string $template, array $data = [], ?string $layout = null): string
{ {
$this->globals[$key] = $value; $templateFile = $this->basePath . '/' . $template . '.php';
}
public function render(string $template, array $data = []): string
{
$this->sections = [];
$this->layout = null;
$content = $this->renderFile($template, $data);
if ($this->layout) {
$layoutTemplate = $this->layout;
$this->childContent = $content;
$this->layout = null;
$content = $this->renderFile($layoutTemplate, $data);
}
return $content;
}
private function renderFile(string $template, array $data): string if (!file_exists($templateFile)) {
{ throw new \RuntimeException("Template not found: {$template} (looked in {$templateFile})");
$file = $this->templateDir . '/' . str_replace('.', '/', $template) . '.php';
if (!file_exists($file)) {
throw new \RuntimeException("Template not found: {$file}");
} }
extract(array_merge($this->globals, $data)); // Render the child template first
$__engine = $this; $content = $this->renderFile($templateFile, $data);
ob_start();
try {
include $file;
} catch (\Throwable $e) {
ob_end_clean();
throw $e;
}
return ob_get_clean();
}
public function extend(string $layout): void // Determine layout
{ $layoutName = $layout;
$this->layout = $layout; if ($layoutName === null) {
// Auto-detect: use 'auth' layout for auth pages, 'app' for everything else
if (str_starts_with($template, 'auth/') || str_starts_with($template, 'errors/')) {
$layoutName = 'layouts/auth';
} else {
$layoutName = $this->defaultLayout;
} }
public function section(string $name): void
{
$this->sectionStack[] = $name;
ob_start();
} }
public function endSection(): void if ($layoutName === 'none' || $layoutName === false || $layoutName === '') {
{ return $content;
$name = array_pop($this->sectionStack);
$this->sections[$name] = ob_get_clean();
} }
public function yield(string $name, string $default = ''): string $layoutFile = $this->basePath . '/' . $layoutName . '.php';
{ if (!file_exists($layoutFile)) {
return $this->sections[$name] ?? $default; // No layout file? Just return the content directly.
return $content;
} }
public function content(): string // Render layout with content injected
{ $layoutData = array_merge($data, ['content' => $content]);
return $this->childContent; return $this->renderFile($layoutFile, $layoutData);
} }
/**
* Render a partial (no layout wrapping).
*/
public function partial(string $template, array $data = []): string public function partial(string $template, array $data = []): string
{ {
return $this->renderFile($template, $data); $file = $this->basePath . '/' . $template . '.php';
if (!file_exists($file)) {
return "<!-- partial not found: {$template} -->";
} }
return $this->renderFile($file, $data);
public function e(mixed $value): string
{
return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
} }
public function csrf(): string /**
* Render a PHP file with extracted data and return output as string.
*/
private function renderFile(string $file, array $data): string
{ {
$token = $_SESSION['csrf_token'] ?? ($_COOKIE['csrf_token'] ?? ''); extract($data, EXTR_SKIP);
return '<input type="hidden" name="_csrf_token" value="' . $this->e($token) . '">';
}
public function csrfMeta(): string ob_start();
{ try {
$token = $_SESSION['csrf_token'] ?? ($_COOKIE['csrf_token'] ?? ''); require $file;
return '<meta name="csrf-token" content="' . $this->e($token) . '">'; } catch (\Throwable $e) {
ob_end_clean();
throw new \RuntimeException("Template render error in {$file}: " . $e->getMessage(), 0, $e);
}
return ob_get_clean();
} }
} }
\ No newline at end of file
/* ====================================================== /* ============================================================================
AL-ARCADE HR PLATFORM v3.0 — "THE GRIND" * AL-ARCADE HR PLATFORM v3.0 — "THE GRIND"
Core Stylesheet — Phase 1 * Complete Application Stylesheet
====================================================== */ * ============================================================================ */
/* ─── RESET & BASE ─── */
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
:root { :root {
--primary: #6366F1; /* Colors */
--primary-dark: #4F46E5; --bg-primary: #0f1117;
--success: #22C55E; --bg-secondary: #1a1d27;
--warning: #EAB308; --bg-tertiary: #242836;
--danger: #EF4444; --bg-card: #1e2230;
--gold: #F59E0B; --bg-hover: #2a2f3f;
--bg: #F8FAFC; --bg-input: #1a1d27;
--bg-card: #FFFFFF;
--text: #1E293B; --text-primary: #e8eaed;
--text-muted: #64748B; --text-secondary: #9aa0a6;
--border: #E2E8F0; --text-muted: #5f6368;
--shadow: 0 1px 3px rgba(0,0,0,0.1); --text-inverse: #0f1117;
--border-color: #2d3348;
--border-light: #3d4460;
--accent-primary: #6366f1;
--accent-primary-hover: #818cf8;
--accent-primary-bg: rgba(99, 102, 241, 0.12);
--success: #22c55e;
--success-bg: rgba(34, 197, 94, 0.12);
--warning: #f59e0b;
--warning-bg: rgba(245, 158, 11, 0.12);
--danger: #ef4444;
--danger-bg: rgba(239, 68, 68, 0.12);
--info: #3b82f6;
--info-bg: rgba(59, 130, 246, 0.12);
--bounty: #f59e0b;
--bounty-bg: rgba(245, 158, 11, 0.12);
/* Spacing */
--sidebar-width: 240px;
--nav-height: 60px;
--radius: 8px; --radius: 8px;
--font: 'Segoe UI', system-ui, -apple-system, sans-serif; --radius-lg: 12px;
} --radius-sm: 6px;
[data-theme="dark"] { /* Shadows */
--bg: #0F172A; --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
--bg-card: #1E293B; --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.3);
--text: #E2E8F0; --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.4);
--text-muted: #94A3B8;
--border: #334155; /* Transitions */
--shadow: 0 1px 3px rgba(0,0,0,0.3); --transition: all 0.2s ease;
}
/* Typography */
* { margin: 0; padding: 0; box-sizing: border-box; } --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
body { font-family: var(--font); background: var(--bg); color: var(--text); line-height: 1.6; } --font-mono: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace;
}
/* Navigation */
.top-nav { display: flex; align-items: center; gap: 16px; padding: 8px 24px; background: var(--bg-card); border-bottom: 1px solid var(--border); box-shadow: var(--shadow); position: sticky; top: 0; z-index: 100; } /* Light theme overrides */
.nav-brand a { text-decoration: none; font-size: 1.2em; font-weight: 700; color: var(--primary); } [data-theme="light"] {
.nav-search { flex: 1; max-width: 400px; } --bg-primary: #f8f9fa;
.nav-search input { width: 100%; padding: 8px 12px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg); color: var(--text); font-size: 0.9em; } --bg-secondary: #ffffff;
.nav-actions { display: flex; align-items: center; gap: 12px; margin-left: auto; } --bg-tertiary: #f1f3f5;
.nav-btn { background: none; border: none; cursor: pointer; font-size: 1.1em; padding: 6px; border-radius: var(--radius); color: var(--text); position: relative; } --bg-card: #ffffff;
.nav-btn:hover { background: var(--bg); } --bg-hover: #e9ecef;
.nav-user { display: flex; flex-direction: column; align-items: end; } --bg-input: #f1f3f5;
.nav-user span:first-child { font-weight: 600; font-size: 0.9em; }
.role-badge { font-size: 0.7em; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px; } --text-primary: #1a1a2e;
.badge { position: absolute; top: -2px; right: -2px; background: var(--danger); color: white; border-radius: 50%; width: 18px; height: 18px; font-size: 0.65em; display: flex; align-items: center; justify-content: center; } --text-secondary: #495057;
--text-muted: #868e96;
/* HUD */ --text-inverse: #ffffff;
.hud { padding: 12px 24px; background: var(--bg-card); border-bottom: 2px solid var(--border); }
.hud-primary { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; } --border-color: #dee2e6;
.hud-month { font-weight: 700; } --border-light: #e9ecef;
.hud-bar-container { flex: 1; min-width: 200px; height: 24px; background: var(--border); border-radius: 12px; overflow: hidden; }
.hud-bar { height: 100%; border-radius: 12px; transition: width 0.5s ease, background 0.5s ease; } --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.06);
.hud-exceptional .hud-bar, [data-color="hud-exceptional"] .hud-bar { background: linear-gradient(90deg, var(--gold), #FBBF24); } --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08);
.hud-healthy .hud-bar, [data-color="hud-healthy"] .hud-bar { background: var(--success); } --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.12);
.hud-warning .hud-bar, [data-color="hud-warning"] .hud-bar { background: var(--warning); } }
.hud-critical .hud-bar, [data-color="hud-critical"] .hud-bar { background: var(--danger); }
.hud-amount { font-weight: 700; font-size: 1.1em; white-space: nowrap; } html {
.hud-secondary { display: flex; gap: 16px; margin-top: 4px; font-size: 0.85em; color: var(--text-muted); } font-size: 14px;
.hud-deductions { color: var(--danger); } -webkit-font-smoothing: antialiased;
.hud-bounties { color: var(--success); } -moz-osx-font-smoothing: grayscale;
}
/* Main Content */
.main-content { padding: 24px; max-width: 1400px; margin: 0 auto; } body {
.container { max-width: 1200px; margin: 0 auto; } font-family: var(--font-sans);
background: var(--bg-primary);
/* Cards */ color: var(--text-primary);
.card { background: var(--bg-card); border-radius: var(--radius); border: 1px solid var(--border); padding: 20px; box-shadow: var(--shadow); } line-height: 1.6;
.card h3 { margin-bottom: 12px; font-size: 1em; color: var(--text-muted); } min-height: 100vh;
.card-wide { grid-column: span 2; } overflow-x: hidden;
}
/* Dashboard Grid */
.dashboard-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap: 20px; margin-top: 20px; } a {
.stat-card { text-align: center; } color: var(--accent-primary);
.stat-number { font-size: 2.5em; font-weight: 800; color: var(--primary); } text-decoration: none;
.stat-sub { font-size: 0.85em; color: var(--text-muted); } transition: var(--transition);
}
/* Forms */ a:hover {
.form-group { margin-bottom: 16px; } color: var(--accent-primary-hover);
.form-group label { display: block; font-weight: 600; margin-bottom: 4px; font-size: 0.9em; } }
.form-group input, .form-group select, .form-group textarea { width: 100%; padding: 10px 12px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg); color: var(--text); font-size: 0.95em; }
.form-group textarea { min-height: 100px; resize: vertical; } img { max-width: 100%; height: auto; }
/* Buttons */ /* ─── TYPOGRAPHY ─── */
.btn { display: inline-block; padding: 10px 20px; border-radius: var(--radius); font-weight: 600; text-decoration: none; cursor: pointer; border: none; font-size: 0.9em; transition: all 0.2s; } h1, h2, h3, h4, h5, h6 {
.btn-primary { background: var(--primary); color: white; } font-weight: 600;
.btn-primary:hover { background: var(--primary-dark); } line-height: 1.3;
.btn-secondary { background: var(--border); color: var(--text); } color: var(--text-primary);
.btn-danger { background: var(--danger); color: white; } }
.btn-success { background: var(--success); color: white; } h1 { font-size: 1.75rem; }
.btn-sm { padding: 6px 12px; font-size: 0.8em; } h2 { font-size: 1.4rem; }
.btn-lg { padding: 14px 28px; font-size: 1.1em; } h3 { font-size: 1.15rem; }
.btn-block { display: block; width: 100%; text-align: center; } h4 { font-size: 1rem; }
.btn-link { background: none; color: var(--primary); padding: 0; }
/* ─── TOP NAVIGATION ─── */
/* Alerts */ .top-nav {
.alert { padding: 12px 16px; border-radius: var(--radius); margin-bottom: 16px; font-size: 0.9em; } position: fixed;
.alert-error { background: #FEF2F2; color: #991B1B; border: 1px solid #FECACA; } top: 0;
.alert-success { background: #F0FDF4; color: #166534; border: 1px solid #BBF7D0; } left: 0;
.alert-warning { background: #FFFBEB; color: #92400E; border: 1px solid #FDE68A; } right: 0;
height: var(--nav-height);
/* Auth Page */ background: var(--bg-secondary);
.auth-page { display: flex; align-items: center; justify-content: center; min-height: 100vh; } border-bottom: 1px solid var(--border-color);
.auth-container { width: 100%; max-width: 420px; padding: 20px; } display: flex;
.auth-logo { text-align: center; font-size: 2em; font-weight: 800; margin-bottom: 30px; color: var(--primary); } align-items: center;
.auth-form { background: var(--bg-card); padding: 30px; border-radius: var(--radius); box-shadow: var(--shadow); border: 1px solid var(--border); } justify-content: space-between;
.auth-form h2 { text-align: center; margin-bottom: 24px; } padding: 0 20px;
z-index: 1000;
/* Blocking Notification */ box-shadow: var(--shadow-sm);
.blocking-page { display: flex; align-items: center; justify-content: center; min-height: 100vh; background: rgba(0,0,0,0.85); } }
.blocking-overlay { width: 100%; max-width: 600px; padding: 20px; }
.blocking-card { background: var(--bg-card); padding: 40px; border-radius: var(--radius); text-align: center; } .nav-left {
.blocking-icon { font-size: 3em; margin-bottom: 16px; } display: flex;
.blocking-content { text-align: left; margin: 20px 0; padding: 16px; background: var(--bg); border-radius: var(--radius); } align-items: center;
.blocking-note { font-size: 0.85em; color: var(--text-muted); margin: 16px 0; font-style: italic; } gap: 20px;
}
/* Notifications */
.notification-list { display: flex; flex-direction: column; gap: 8px; } .nav-brand {
.notification-item { padding: 16px; background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); } font-size: 1.2rem;
.notification-item.unread { border-left: 3px solid var(--primary); background: #F0F4FF; } font-weight: 700;
.notification-item.tier-blocking { border-left-color: var(--danger); } color: var(--text-primary) !important;
.notif-header { display: flex; justify-content: space-between; margin-bottom: 4px; } display: flex;
.notif-time { font-size: 0.8em; color: var(--text-muted); } align-items: center;
gap: 8px;
/* Tasks */ white-space: nowrap;
.task-list { display: flex; flex-direction: column; gap: 8px; } }
.task-item { display: flex; align-items: center; gap: 8px; padding: 8px; border: 1px solid var(--border); border-radius: var(--radius); font-size: 0.9em; } .nav-brand:hover { color: var(--accent-primary) !important; }
.task-key { font-weight: 700; color: var(--primary); font-size: 0.8em; white-space: nowrap; }
.task-title { flex: 1; } .search-bar input {
.task-status { padding: 2px 8px; border-radius: 12px; font-size: 0.75em; font-weight: 600; } background: var(--bg-input);
.overdue { color: var(--danger); font-weight: 600; } border: 1px solid var(--border-color);
border-radius: var(--radius);
/* Badges */ padding: 8px 14px;
.badge-doing { background: #DBEAFE; color: #1E40AF; } color: var(--text-primary);
.badge-todo { background: #FEF9C3; color: #854D0E; } font-size: 0.9rem;
.badge-in_review { background: #E0E7FF; color: #4338CA; } width: 280px;
.badge-frozen { background: #E0F2FE; color: #075985; } transition: var(--transition);
.badge-done { background: #DCFCE7; color: #166534; } }
.badge-backlog { background: var(--border); color: var(--text-muted); } .search-bar input:focus {
.badge-success { background: #DCFCE7; color: #166534; } outline: none;
.badge-warning { background: #FEF9C3; color: #854D0E; } border-color: var(--accent-primary);
.badge-danger { background: #FEF2F2; color: #991B1B; } box-shadow: 0 0 0 3px var(--accent-primary-bg);
width: 360px;
/* Page Header */ }
.page-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; } .search-bar input::placeholder {
color: var(--text-muted);
/* Team Status */ }
.team-member-row { display: flex; justify-content: space-between; align-items: center; padding: 8px 0; border-bottom: 1px solid var(--border); }
.nav-right {
/* Toast */ display: flex;
#toast-container { position: fixed; bottom: 20px; right: 20px; z-index: 9999; display: flex; flex-direction: column; gap: 8px; } align-items: center;
.toast { padding: 12px 20px; border-radius: var(--radius); color: white; font-weight: 600; animation: slideIn 0.3s ease; box-shadow: 0 4px 12px rgba(0,0,0,0.2); } gap: 16px;
.toast-success { background: var(--success); } }
.toast-error { background: var(--danger); }
.toast-warning { background: var(--warning); color: #000; } .nav-icon {
.toast-info { background: var(--primary); } position: relative;
.toast-gold { background: linear-gradient(135deg, #F59E0B, #D97706); } font-size: 1.3rem;
color: var(--text-secondary) !important;
@keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } padding: 6px;
border-radius: var(--radius-sm);
/* Error Pages */ transition: var(--transition);
.error-page { display: flex; align-items: center; justify-content: center; min-height: 100vh; } }
.error-container { text-align: center; } .nav-icon:hover {
.error-container h1 { font-size: 6em; font-weight: 800; color: var(--primary); } background: var(--bg-hover);
color: var(--text-primary) !important;
/* Responsive */ }
.notification-badge {
position: absolute;
top: -2px;
right: -4px;
background: var(--danger);
color: white;
font-size: 10px;
font-weight: 700;
padding: 1px 5px;
border-radius: 10px;
min-width: 18px;
text-align: center;
line-height: 1.4;
}
.nav-user {
display: flex;
flex-direction: column;
align-items: flex-end;
line-height: 1.2;
}
.nav-user-name {
font-weight: 600;
font-size: 0.85rem;
color: var(--text-primary);
}
.nav-user-role {
font-size: 0.7rem;
color: var(--text-muted);
letter-spacing: 0.5px;
}
.nav-avatar {
width: 36px;
height: 36px;
border-radius: 50%;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
background: var(--bg-tertiary);
font-size: 1.2rem;
border: 2px solid var(--border-color);
}
.nav-avatar img {
width: 100%;
height: 100%;
object-fit: cover;
}
/* ─── SIDEBAR ─── */
.sidebar {
position: fixed;
top: var(--nav-height);
left: 0;
bottom: 0;
width: var(--sidebar-width);
background: var(--bg-secondary);
border-right: 1px solid var(--border-color);
overflow-y: auto;
padding: 12px 0;
z-index: 999;
}
.sidebar ul {
list-style: none;
}
.sidebar li a,
.sidebar-link-btn {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 20px;
color: var(--text-secondary);
font-size: 0.9rem;
font-weight: 500;
transition: var(--transition);
border: none;
background: none;
cursor: pointer;
width: 100%;
text-align: left;
font-family: inherit;
}
.sidebar li a:hover,
.sidebar-link-btn:hover {
background: var(--bg-hover);
color: var(--text-primary);
}
.sidebar li a.active {
background: var(--accent-primary-bg);
color: var(--accent-primary);
font-weight: 600;
border-right: 3px solid var(--accent-primary);
}
.sidebar-divider {
margin: 8px 16px;
border-top: 1px solid var(--border-color);
}
/* Scrollbar for sidebar */
.sidebar::-webkit-scrollbar { width: 4px; }
.sidebar::-webkit-scrollbar-track { background: transparent; }
.sidebar::-webkit-scrollbar-thumb { background: var(--border-color); border-radius: 4px; }
/* ─── MAIN CONTENT ─── */
.main-content {
margin-left: var(--sidebar-width);
margin-top: var(--nav-height);
padding: 24px;
min-height: calc(100vh - var(--nav-height));
}
/* ─── CARDS / PANELS ─── */
.card, .panel, .stat-card, .health-card {
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: var(--radius-lg);
padding: 20px;
box-shadow: var(--shadow-sm);
transition: var(--transition);
}
.card:hover, .panel:hover {
box-shadow: var(--shadow-md);
}
.card-header, .panel-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid var(--border-color);
}
.card-title {
font-size: 1.05rem;
font-weight: 600;
color: var(--text-primary);
}
/* ─── GRID LAYOUTS ─── */
.grid {
display: grid;
gap: 20px;
}
.grid-2 { grid-template-columns: repeat(2, 1fr); }
.grid-3 { grid-template-columns: repeat(3, 1fr); }
.grid-4 { grid-template-columns: repeat(4, 1fr); }
@media (max-width: 1200px) {
.grid-4 { grid-template-columns: repeat(2, 1fr); }
.grid-3 { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 768px) { @media (max-width: 768px) {
.top-nav { flex-wrap: wrap; gap: 8px; padding: 8px 12px; } .grid-4, .grid-3, .grid-2 { grid-template-columns: 1fr; }
.nav-search { order: 3; max-width: 100%; flex-basis: 100%; } .sidebar { display: none; }
.dashboard-grid { grid-template-columns: 1fr; } .main-content { margin-left: 0; }
.card-wide { grid-column: span 1; } .search-bar input { width: 160px; }
.hud { padding: 8px 12px; } .search-bar input:focus { width: 200px; }
.main-content { padding: 12px; } }
/* ─── STAT CARDS (Dashboard) ─── */
.stat-card {
display: flex;
flex-direction: column;
gap: 8px;
position: relative;
overflow: hidden;
}
.stat-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
}
.stat-card.stat-primary::before { background: var(--accent-primary); }
.stat-card.stat-success::before { background: var(--success); }
.stat-card.stat-warning::before { background: var(--warning); }
.stat-card.stat-danger::before { background: var(--danger); }
.stat-card.stat-info::before { background: var(--info); }
.stat-card.stat-bounty::before { background: var(--bounty); }
.stat-label {
font-size: 0.8rem;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.5px;
font-weight: 600;
}
.stat-value {
font-size: 1.8rem;
font-weight: 700;
color: var(--text-primary);
line-height: 1;
}
.stat-sub {
font-size: 0.8rem;
color: var(--text-secondary);
}
/* ─── BUTTONS ─── */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 9px 18px;
font-size: 0.875rem;
font-weight: 600;
font-family: inherit;
border-radius: var(--radius);
border: 1px solid transparent;
cursor: pointer;
transition: var(--transition);
white-space: nowrap;
text-decoration: none;
line-height: 1.4;
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn-primary {
background: var(--accent-primary);
color: white;
border-color: var(--accent-primary);
}
.btn-primary:hover:not(:disabled) {
background: var(--accent-primary-hover);
transform: translateY(-1px);
box-shadow: var(--shadow-md);
}
.btn-success {
background: var(--success);
color: white;
}
.btn-success:hover:not(:disabled) { filter: brightness(1.1); }
.btn-danger {
background: var(--danger);
color: white;
}
.btn-danger:hover:not(:disabled) { filter: brightness(1.1); }
.btn-warning {
background: var(--warning);
color: var(--text-inverse);
}
.btn-ghost {
background: transparent;
color: var(--text-secondary);
border-color: var(--border-color);
}
.btn-ghost:hover:not(:disabled) {
background: var(--bg-hover);
color: var(--text-primary);
}
.btn-sm { padding: 5px 12px; font-size: 0.8rem; }
.btn-lg { padding: 12px 24px; font-size: 1rem; }
.btn-block { width: 100%; }
/* ─── FORMS ─── */
.form-group {
margin-bottom: 16px;
}
.form-label {
display: block;
font-size: 0.85rem;
font-weight: 600;
color: var(--text-secondary);
margin-bottom: 6px;
}
.form-control {
width: 100%;
padding: 10px 14px;
font-size: 0.9rem;
font-family: inherit;
background: var(--bg-input);
border: 1px solid var(--border-color);
border-radius: var(--radius);
color: var(--text-primary);
transition: var(--transition);
}
.form-control:focus {
outline: none;
border-color: var(--accent-primary);
box-shadow: 0 0 0 3px var(--accent-primary-bg);
}
.form-control::placeholder { color: var(--text-muted); }
select.form-control {
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%239aa0a6' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 12px center;
padding-right: 36px;
}
textarea.form-control {
min-height: 100px;
resize: vertical;
}
.form-hint {
font-size: 0.75rem;
color: var(--text-muted);
margin-top: 4px;
}
.form-error {
font-size: 0.75rem;
color: var(--danger);
margin-top: 4px;
}
/* ─── TABLES ─── */
.data-table {
width: 100%;
border-collapse: collapse;
font-size: 0.875rem;
}
.data-table th {
background: var(--bg-tertiary);
font-weight: 600;
color: var(--text-secondary);
text-transform: uppercase;
font-size: 0.75rem;
letter-spacing: 0.5px;
padding: 10px 14px;
text-align: left;
border-bottom: 2px solid var(--border-color);
white-space: nowrap;
}
.data-table td {
padding: 10px 14px;
border-bottom: 1px solid var(--border-color);
color: var(--text-primary);
vertical-align: middle;
}
.data-table tbody tr:hover {
background: var(--bg-hover);
}
.data-table .right { text-align: right; }
.data-table .center { text-align: center; }
.data-table .mono { font-family: var(--font-mono); font-size: 0.8rem; }
/* ─── BADGES / TAGS ─── */
.badge {
display: inline-flex;
align-items: center;
padding: 3px 10px;
font-size: 0.72rem;
font-weight: 600;
border-radius: 20px;
white-space: nowrap;
letter-spacing: 0.3px;
}
.badge-success { background: var(--success-bg); color: var(--success); }
.badge-warning { background: var(--warning-bg); color: var(--warning); }
.badge-danger { background: var(--danger-bg); color: var(--danger); }
.badge-info { background: var(--info-bg); color: var(--info); }
.badge-primary { background: var(--accent-primary-bg); color: var(--accent-primary); }
.badge-muted { background: var(--bg-tertiary); color: var(--text-muted); }
/* ─── HUD (Heads-Up Display for Contractors) ─── */
.hud-container {
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: var(--radius-lg);
padding: 20px;
margin-bottom: 24px;
position: relative;
overflow: hidden;
}
.hud-top {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.hud-salary-label { font-size: 0.8rem; color: var(--text-muted); }
.hud-salary-value {
font-size: 2rem;
font-weight: 800;
font-family: var(--font-mono);
}
.hud-bar-container {
height: 8px;
background: var(--bg-tertiary);
border-radius: 4px;
overflow: hidden;
margin: 12px 0;
}
.hud-bar {
height: 100%;
border-radius: 4px;
transition: width 0.6s ease, background 0.3s ease;
}
.hud-bar.hud-exceptional { background: linear-gradient(90deg, var(--success), gold); }
.hud-bar.hud-healthy { background: var(--success); }
.hud-bar.hud-warning { background: var(--warning); }
.hud-bar.hud-critical { background: var(--danger); }
.hud-details {
display: flex;
gap: 20px;
flex-wrap: wrap;
margin-top: 12px;
}
.hud-detail {
display: flex;
flex-direction: column;
gap: 2px;
}
.hud-detail-label { font-size: 0.72rem; color: var(--text-muted); text-transform: uppercase; }
.hud-detail-value { font-size: 0.95rem; font-weight: 600; font-family: var(--font-mono); }
.hud-detail-value.positive { color: var(--success); }
.hud-detail-value.negative { color: var(--danger); }
/* ─── DASHBOARD SPECIFIC ─── */
.dashboard-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24px;
}
.dashboard-header h1 {
display: flex;
align-items: center;
gap: 10px;
}
.dashboard-date {
font-size: 0.9rem;
color: var(--text-secondary);
}
.welcome-banner {
background: linear-gradient(135deg, var(--accent-primary), #4f46e5);
border-radius: var(--radius-lg);
padding: 24px 28px;
color: white;
margin-bottom: 24px;
position: relative;
overflow: hidden;
}
.welcome-banner::after {
content: '🎮';
position: absolute;
right: 30px;
top: 50%;
transform: translateY(-50%);
font-size: 3rem;
opacity: 0.3;
}
.welcome-banner h2 {
color: white;
font-size: 1.4rem;
margin-bottom: 4px;
}
.welcome-banner p {
opacity: 0.85;
font-size: 0.9rem;
}
.quick-actions {
display: flex;
gap: 10px;
margin-top: 12px;
}
/* ─── TASK LIST ─── */
.task-list {
list-style: none;
}
.task-item {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 0;
border-bottom: 1px solid var(--border-color);
} }
.task-item:last-child { border-bottom: none; }
.task-key {
font-family: var(--font-mono);
font-size: 0.78rem;
font-weight: 600;
color: var(--accent-primary);
background: var(--accent-primary-bg);
padding: 2px 8px;
border-radius: var(--radius-sm);
white-space: nowrap;
}
.task-title {
flex: 1;
font-size: 0.9rem;
color: var(--text-primary);
}
.task-meta {
font-size: 0.78rem;
color: var(--text-muted);
white-space: nowrap;
}
/* ─── PRIORITY DOTS ─── */
.priority-dot {
width: 8px;
height: 8px;
border-radius: 50%;
display: inline-block;
flex-shrink: 0;
}
.priority-critical { background: var(--danger); }
.priority-high { background: #f97316; }
.priority-medium { background: var(--warning); }
.priority-low { background: var(--info); }
.priority-none { background: var(--text-muted); }
/* ─── REPORT STATUS ─── */
.report-status {
padding: 2px 10px;
border-radius: 12px;
font-size: 0.75rem;
font-weight: 600;
display: inline-flex;
align-items: center;
gap: 4px;
}
.report-submitted { background: var(--info-bg); color: var(--info); }
.report-approved { background: var(--success-bg); color: var(--success); }
.report-late { background: var(--warning-bg); color: var(--warning); }
.report-unreported { background: var(--danger-bg); color: var(--danger); }
.report-flagged { background: var(--danger-bg); color: var(--danger); }
/* ─── HEALTH GRID (System Health) ─── */
.health-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
gap: 20px;
}
.health-card.full-width {
grid-column: 1 / -1;
}
.health-card h3 {
margin-bottom: 12px;
font-size: 1rem;
}
.stat-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 6px 0;
border-bottom: 1px solid var(--border-color);
font-size: 0.85rem;
}
.stat-row:last-child { border-bottom: none; }
.stat-row span { color: var(--text-secondary); }
.stat-row strong { color: var(--text-primary); }
.big-number {
font-size: 2.5rem;
font-weight: 800;
color: var(--accent-primary);
margin-bottom: 12px;
}
/* ─── SESSION / MEMBER LIST ─── */
.session-list {
list-style: none;
font-size: 0.8rem;
color: var(--text-secondary);
}
.session-list li {
padding: 4px 0;
border-bottom: 1px solid var(--border-color);
}
/* ─── EMPTY STATE ─── */
.empty-state {
text-align: center;
padding: 40px 20px;
color: var(--text-muted);
}
.empty-state-icon {
font-size: 3rem;
margin-bottom: 12px;
}
.empty-state-text {
font-size: 1rem;
margin-bottom: 8px;
}
/* ─── PAGINATION ─── */
.pagination {
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
margin-top: 20px;
}
.pagination a, .pagination span {
padding: 6px 12px;
border-radius: var(--radius-sm);
font-size: 0.85rem;
font-weight: 500;
}
.pagination a {
color: var(--text-secondary);
border: 1px solid var(--border-color);
}
.pagination a:hover {
background: var(--bg-hover);
}
.pagination .active {
background: var(--accent-primary);
color: white;
border: 1px solid var(--accent-primary);
}
/* ─── LOADING ─── */
.loading-spinner {
display: inline-block;
width: 20px;
height: 20px;
border: 2px solid var(--border-color);
border-top-color: var(--accent-primary);
border-radius: 50%;
animation: spin 0.6s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
/* ─── ERROR CELLS ─── */
.error-cell {
max-width: 300px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 0.78rem;
color: var(--danger);
}
/* ─── MODAL ─── */
.modal-overlay {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 2000;
backdrop-filter: blur(4px);
}
.modal {
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: var(--radius-lg);
padding: 24px;
min-width: 400px;
max-width: 600px;
max-height: 80vh;
overflow-y: auto;
box-shadow: var(--shadow-lg);
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.modal-close {
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
color: var(--text-muted);
padding: 4px 8px;
border-radius: var(--radius-sm);
}
.modal-close:hover { background: var(--bg-hover); }
/* ─── AUTH PAGES ─── */
.auth-container {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: var(--bg-primary);
}
.auth-box {
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: var(--radius-lg);
padding: 40px;
width: 100%;
max-width: 420px;
box-shadow: var(--shadow-lg);
}
.auth-title {
text-align: center;
margin-bottom: 24px;
font-size: 1.5rem;
}
.auth-error {
background: var(--danger-bg);
color: var(--danger);
padding: 10px 14px;
border-radius: var(--radius);
margin-bottom: 16px;
font-size: 0.85rem;
}
/* ─── ERROR PAGES ─── */
.error-page {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.error-container {
text-align: center;
}
.error-container h1 {
font-size: 6rem;
color: var(--accent-primary);
line-height: 1;
margin-bottom: 12px;
}
/* ─── TOAST CONTAINER ─── */
#toast-container {
position: fixed;
top: 80px;
right: 20px;
z-index: 99999;
display: flex;
flex-direction: column;
gap: 8px;
max-width: 400px;
}
/* ─── SCROLLBARS ─── */
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border-color); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: var(--border-light); }
/* ─── UTILITY CLASSES ─── */
.text-success { color: var(--success) !important; }
.text-danger { color: var(--danger) !important; }
.text-warning { color: var(--warning) !important; }
.text-muted { color: var(--text-muted) !important; }
.text-primary { color: var(--accent-primary) !important; }
.text-right { text-align: right; }
.text-center { text-align: center; }
.font-mono { font-family: var(--font-mono); }
.font-bold { font-weight: 700; }
.mt-0 { margin-top: 0; }
.mt-1 { margin-top: 8px; }
.mt-2 { margin-top: 16px; }
.mt-3 { margin-top: 24px; }
.mb-0 { margin-bottom: 0; }
.mb-1 { margin-bottom: 8px; }
.mb-2 { margin-bottom: 16px; }
.mb-3 { margin-bottom: 24px; }
.gap-1 { gap: 8px; }
.gap-2 { gap: 16px; }
.flex { display: flex; }
.flex-wrap { flex-wrap: wrap; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }
.hidden { display: none !important; }
\ No newline at end of file
<?php $__engine->extend('layouts/app'); ?> <?php
<?php $__engine->section('title'); ?>Admin Dashboard<?php $__engine->endSection(); ?> /** @var array $user */
/** @var array $notifications */
/** @var int $unread_count */
/** @var int $active_contractors */
/** @var int $onboarding_count */
/** @var array $month_deductions */
/** @var array $month_bounties */
/** @var int $active_pips */
<div class="dashboard"> $month = date('F Y');
<h1>Admin Dashboard</h1> ?>
<div class="dashboard-grid">
<div class="card stat-card"> <div class="dashboard-header">
<h3>👥 Active Contractors</h3> <h1>📊 Dashboard</h1>
<div class="stat-number"><?= $active_contractors ?? 0 ?></div> <span class="dashboard-date"><?= date('l, F j, Y') ?></span>
</div> </div>
<div class="card stat-card">
<h3>📋 Onboarding</h3> <div class="welcome-banner">
<div class="stat-number"><?= $onboarding_count ?? 0 ?></div> <h2>Welcome back, <?= htmlspecialchars($user['full_name_en'] ?? 'Admin') ?></h2>
</div> <p>Administration overview for <?= $month ?>.</p>
<div class="card stat-card"> </div>
<h3>⚠️ Deductions This Month</h3>
<div class="stat-number"><?= $month_deductions['cnt'] ?? 0 ?></div> <div class="grid grid-4 mb-3">
<div class="stat-sub">EGP <?= number_format((float)($month_deductions['total'] ?? 0), 0) ?></div> <div class="stat-card stat-primary">
</div> <span class="stat-label">Active Contractors</span>
<div class="card stat-card"> <span class="stat-value"><?= $active_contractors ?? 0 ?></span>
<h3>💰 Bounties This Month</h3> <span class="stat-sub"><?= $onboarding_count ?? 0 ?> onboarding</span>
<div class="stat-number"><?= $month_bounties['cnt'] ?? 0 ?></div> </div>
<div class="stat-sub">EGP <?= number_format((float)($month_bounties['total'] ?? 0), 0) ?></div> <div class="stat-card stat-danger">
</div> <span class="stat-label">Deductions (<?= date('M') ?>)</span>
<div class="card stat-card"> <span class="stat-value font-mono"><?= number_format($month_deductions['total'] ?? 0, 0) ?></span>
<h3>📊 Active PIPs</h3> <span class="stat-sub"><?= $month_deductions['cnt'] ?? 0 ?> issued</span>
<div class="stat-number"><?= $active_pips ?? 0 ?></div> </div>
<div class="stat-card stat-success">
<span class="stat-label">Bounties (<?= date('M') ?>)</span>
<span class="stat-value font-mono"><?= number_format($month_bounties['total'] ?? 0, 0) ?></span>
<span class="stat-sub"><?= $month_bounties['cnt'] ?? 0 ?> paid</span>
</div>
<div class="stat-card stat-warning">
<span class="stat-label">Active PIPs</span>
<span class="stat-value"><?= $active_pips ?? 0 ?></span>
</div>
</div>
<div class="grid grid-2">
<div class="card">
<div class="card-header">
<span class="card-title">🔔 Notifications</span>
<a href="/notifications" class="btn btn-sm btn-ghost">View All</a>
</div>
<?php if (!empty($notifications)): ?>
<ul class="task-list">
<?php foreach ($notifications as $n): ?>
<li class="task-item">
<span class="badge <?= $n['tier'] === 'blocking' ? 'badge-danger' : 'badge-info' ?>"><?= strtoupper($n['tier']) ?></span>
<span class="task-title"><?= htmlspecialchars($n['title'] ?? '') ?></span>
<span class="task-meta"><?= date('M j', strtotime($n['created_at'] ?? 'now')) ?></span>
</li>
<?php endforeach; ?>
</ul>
<?php else: ?>
<div class="empty-state"><div class="empty-state-icon">🔕</div><div>No recent notifications</div></div>
<?php endif; ?>
</div>
<div class="card">
<div class="card-header"><span class="card-title">⚡ Quick Access</span></div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;">
<a href="/boards" class="btn btn-ghost btn-block">📋 Boards</a>
<a href="/users" class="btn btn-ghost btn-block">👥 Directory</a>
<a href="/reports/review" class="btn btn-ghost btn-block">📝 Review Reports</a>
<a href="/deductions" class="btn btn-ghost btn-block">⚠️ Deductions</a>
<a href="/payroll" class="btn btn-ghost btn-block">💰 Payroll</a>
<a href="/invites" class="btn btn-ghost btn-block">📨 Invites</a>
</div> </div>
</div> </div>
</div> </div>
\ No newline at end of file
<?php $__engine->extend('layouts/app'); ?> <?php
<?php $__engine->section('title'); ?>Super Admin Dashboard<?php $__engine->endSection(); ?> /** @var array $user */
/** @var array $notifications */
<div class="dashboard"> /** @var int $unread_count */
<h1>Super Admin Dashboard</h1> /** @var int $active_contractors */
<div class="dashboard-grid"> /** @var int $onboarding_count */
<div class="card stat-card"> /** @var array $month_deductions */
<h3>👥 Active Contractors</h3> /** @var array $month_bounties */
<div class="stat-number"><?= $active_contractors ?? 0 ?></div> /** @var array $payroll_status */
</div> /** @var int $active_pips */
<div class="card stat-card"> /** @var float $total_payroll */
<h3>💵 Total Payroll</h3> /** @var int $expiring_contracts */
<div class="stat-number">EGP <?= number_format($total_payroll ?? 0, 0) ?></div>
</div> $month = date('F Y');
<div class="card stat-card"> $greeting = match(true) {
<h3>⚠️ Deductions</h3> date('H') < 12 => 'Good morning',
<div class="stat-number"><?= $month_deductions['cnt'] ?? 0 ?></div> date('H') < 18 => 'Good afternoon',
<div class="stat-sub">EGP <?= number_format((float)($month_deductions['total'] ?? 0), 0) ?></div> default => 'Good evening',
</div> };
<div class="card stat-card"> ?>
<h3>💰 Bounties</h3>
<div class="stat-number"><?= $month_bounties['cnt'] ?? 0 ?></div> <div class="dashboard-header">
<div class="stat-sub">EGP <?= number_format((float)($month_bounties['total'] ?? 0), 0) ?></div> <h1>📊 Dashboard</h1>
</div> <span class="dashboard-date"><?= date('l, F j, Y') ?></span>
<div class="card stat-card"> </div>
<h3>📋 Onboarding</h3>
<div class="stat-number"><?= $onboarding_count ?? 0 ?></div> <div class="welcome-banner">
</div> <h2><?= $greeting ?>, <?= htmlspecialchars($user['full_name_en'] ?? 'Admin') ?>!</h2>
<div class="card stat-card"> <p>Here's your operational overview for <?= $month ?>.</p>
<h3>📊 Active PIPs</h3> <div class="quick-actions">
<div class="stat-number"><?= $active_pips ?? 0 ?></div> <a href="/invites" class="btn btn-sm" style="background:rgba(255,255,255,0.2);color:white;border:1px solid rgba(255,255,255,0.3);">📨 Create Invite</a>
</div> <a href="/payroll?month=<?= date('Y-m') ?>" class="btn btn-sm" style="background:rgba(255,255,255,0.2);color:white;border:1px solid rgba(255,255,255,0.3);">💰 View Payroll</a>
<div class="card stat-card"> <a href="/reports/review?date=<?= date('Y-m-d') ?>" class="btn btn-sm" style="background:rgba(255,255,255,0.2);color:white;border:1px solid rgba(255,255,255,0.3);">📝 Review Reports</a>
<h3>📄 Expiring Contracts</h3> </div>
<div class="stat-number"><?= $expiring_contracts ?? 0 ?></div> </div>
<div class="stat-sub">within 90 days</div>
<!-- Key Metrics -->
<div class="grid grid-4 mb-3">
<div class="stat-card stat-primary">
<span class="stat-label">Active Contractors</span>
<span class="stat-value"><?= $active_contractors ?? 0 ?></span>
<span class="stat-sub"><?= $onboarding_count ?? 0 ?> onboarding</span>
</div>
<div class="stat-card stat-bounty">
<span class="stat-label">Total Payroll (<?= date('M') ?>)</span>
<span class="stat-value font-mono"><?= number_format($total_payroll ?? 0, 0) ?></span>
<span class="stat-sub">EGP this month</span>
</div>
<div class="stat-card stat-danger">
<span class="stat-label">Deductions (<?= date('M') ?>)</span>
<span class="stat-value font-mono"><?= number_format($month_deductions['total'] ?? 0, 0) ?></span>
<span class="stat-sub"><?= $month_deductions['cnt'] ?? 0 ?> deductions issued</span>
</div>
<div class="stat-card stat-success">
<span class="stat-label">Bounties (<?= date('M') ?>)</span>
<span class="stat-value font-mono"><?= number_format($month_bounties['total'] ?? 0, 0) ?></span>
<span class="stat-sub"><?= $month_bounties['cnt'] ?? 0 ?> bounties paid</span>
</div>
</div>
<div class="grid grid-3 mb-3">
<div class="stat-card stat-warning">
<span class="stat-label">Active PIPs</span>
<span class="stat-value"><?= $active_pips ?? 0 ?></span>
<span class="stat-sub">Under performance improvement</span>
</div>
<div class="stat-card stat-info">
<span class="stat-label">Expiring Contracts</span>
<span class="stat-value"><?= $expiring_contracts ?? 0 ?></span>
<span class="stat-sub">Within 90 days</span>
</div>
<div class="stat-card stat-primary">
<span class="stat-label">Payroll Status</span>
<span class="stat-value" style="font-size:1.2rem"><?= ucfirst(str_replace('_', ' ', $payroll_status['status'] ?? 'N/A')) ?></span>
<span class="stat-sub"><?= $payroll_status['cnt'] ?? 0 ?> records</span>
</div>
</div>
<!-- Recent Notifications -->
<div class="grid grid-2">
<div class="card">
<div class="card-header">
<span class="card-title">🔔 Recent Notifications</span>
<a href="/notifications" class="btn btn-sm btn-ghost">View All</a>
</div>
<?php if (!empty($notifications)): ?>
<ul class="task-list">
<?php foreach ($notifications as $n): ?>
<li class="task-item">
<span class="badge <?= $n['tier'] === 'blocking' ? 'badge-danger' : ($n['tier'] === 'important' ? 'badge-warning' : 'badge-muted') ?>"><?= strtoupper($n['tier']) ?></span>
<span class="task-title"><?= htmlspecialchars($n['title'] ?? '') ?></span>
<span class="task-meta"><?= date('M j, g:ia', strtotime($n['created_at'] ?? 'now')) ?></span>
</li>
<?php endforeach; ?>
</ul>
<?php else: ?>
<div class="empty-state">
<div class="empty-state-icon">🔕</div>
<div class="empty-state-text">No recent notifications</div>
</div>
<?php endif; ?>
</div>
<div class="card">
<div class="card-header">
<span class="card-title">⚡ Quick Access</span>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;">
<a href="/boards" class="btn btn-ghost btn-block">📋 Boards</a>
<a href="/users" class="btn btn-ghost btn-block">👥 Directory</a>
<a href="/deductions" class="btn btn-ghost btn-block">⚠️ Deductions</a>
<a href="/evaluations" class="btn btn-ghost btn-block">📊 Evaluations</a>
<a href="/analytics" class="btn btn-ghost btn-block">📈 Analytics</a>
<a href="/control-panel" class="btn btn-ghost btn-block">⚙️ Control Panel</a>
<a href="/settings" class="btn btn-ghost btn-block">🔧 Settings</a>
<a href="/system-health" class="btn btn-ghost btn-block">🏥 System Health</a>
</div> </div>
</div> </div>
</div> </div>
\ No newline at end of file
<?php /** @var string $content */ ?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en" data-theme="dark">
<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">
<title><?= $__engine->yield('title', 'AL-ARCADE HR Platform - Login') ?></title> <title>The Grind — AL-ARCADE HR</title>
<link rel="stylesheet" href="/assets/css/app.css"> <link rel="stylesheet" href="/assets/css/app.css">
</head> </head>
<body class="auth-page"> <body class="auth-page">
<div class="auth-container"> <div class="auth-container">
<div class="auth-logo">🎮 The Grind</div> <?= $content ?? '' ?>
<?= $__engine->content() ?>
</div> </div>
</body> </body>
</html> </html>
\ 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