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);
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
{
private string $templateDir;
private string $cacheDir;
private array $globals = [];
private array $sections = [];
private array $sectionStack = [];
private ?string $layout = null;
private string $childContent = '';
private string $basePath;
private string $cachePath;
private string $defaultLayout = 'layouts/app';
public function __construct(string $templateDir, string $cacheDir)
public function __construct(string $basePath = '', string $cachePath = '')
{
$this->templateDir = rtrim($templateDir, '/');
$this->cacheDir = rtrim($cacheDir, '/');
if (!is_dir($this->cacheDir)) {
mkdir($this->cacheDir, 0755, true);
}
$this->basePath = $basePath ?: (defined('ROOT_PATH') ? ROOT_PATH . '/templates' : __DIR__ . '/../../templates');
$this->cachePath = $cachePath ?: (defined('ROOT_PATH') ? ROOT_PATH . '/storage/cache/templates' : '/tmp');
}
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;
if (!file_exists($templateFile)) {
throw new \RuntimeException("Template not found: {$template} (looked in {$templateFile})");
}
// Render the child template first
$content = $this->renderFile($templateFile, $data);
// Determine layout
$layoutName = $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;
}
}
$content = $this->renderFile($template, $data);
if ($layoutName === 'none' || $layoutName === false || $layoutName === '') {
return $content;
}
if ($this->layout) {
$layoutTemplate = $this->layout;
$this->childContent = $content;
$this->layout = null;
$content = $this->renderFile($layoutTemplate, $data);
$layoutFile = $this->basePath . '/' . $layoutName . '.php';
if (!file_exists($layoutFile)) {
// No layout file? Just return the content directly.
return $content;
}
return $content;
// Render layout with content injected
$layoutData = array_merge($data, ['content' => $content]);
return $this->renderFile($layoutFile, $layoutData);
}
private function renderFile(string $template, array $data): string
/**
* Render a partial (no layout wrapping).
*/
public function partial(string $template, array $data = []): string
{
$file = $this->templateDir . '/' . str_replace('.', '/', $template) . '.php';
$file = $this->basePath . '/' . $template . '.php';
if (!file_exists($file)) {
throw new \RuntimeException("Template not found: {$file}");
return "<!-- partial not found: {$template} -->";
}
return $this->renderFile($file, $data);
}
extract(array_merge($this->globals, $data));
$__engine = $this;
/**
* Render a PHP file with extracted data and return output as string.
*/
private function renderFile(string $file, array $data): string
{
extract($data, EXTR_SKIP);
ob_start();
try {
include $file;
require $file;
} catch (\Throwable $e) {
ob_end_clean();
throw $e;
throw new \RuntimeException("Template render error in {$file}: " . $e->getMessage(), 0, $e);
}
return ob_get_clean();
}
public function extend(string $layout): void
{
$this->layout = $layout;
}
public function section(string $name): void
{
$this->sectionStack[] = $name;
ob_start();
}
public function endSection(): void
{
$name = array_pop($this->sectionStack);
$this->sections[$name] = ob_get_clean();
}
public function yield(string $name, string $default = ''): string
{
return $this->sections[$name] ?? $default;
}
public function content(): string
{
return $this->childContent;
}
public function partial(string $template, array $data = []): string
{
return $this->renderFile($template, $data);
}
public function e(mixed $value): string
{
return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
}
public function csrf(): string
{
$token = $_SESSION['csrf_token'] ?? ($_COOKIE['csrf_token'] ?? '');
return '<input type="hidden" name="_csrf_token" value="' . $this->e($token) . '">';
}
public function csrfMeta(): string
{
$token = $_SESSION['csrf_token'] ?? ($_COOKIE['csrf_token'] ?? '');
return '<meta name="csrf-token" content="' . $this->e($token) . '">';
}
}
\ No newline at end of file
/* ======================================================
AL-ARCADE HR PLATFORM v3.0 — "THE GRIND"
Core Stylesheet — Phase 1
====================================================== */
/* ============================================================================
* AL-ARCADE HR PLATFORM v3.0 — "THE GRIND"
* Complete Application Stylesheet
* ============================================================================ */
/* ─── RESET & BASE ─── */
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
:root {
--primary: #6366F1;
--primary-dark: #4F46E5;
--success: #22C55E;
--warning: #EAB308;
--danger: #EF4444;
--gold: #F59E0B;
--bg: #F8FAFC;
--bg-card: #FFFFFF;
--text: #1E293B;
--text-muted: #64748B;
--border: #E2E8F0;
--shadow: 0 1px 3px rgba(0,0,0,0.1);
/* Colors */
--bg-primary: #0f1117;
--bg-secondary: #1a1d27;
--bg-tertiary: #242836;
--bg-card: #1e2230;
--bg-hover: #2a2f3f;
--bg-input: #1a1d27;
--text-primary: #e8eaed;
--text-secondary: #9aa0a6;
--text-muted: #5f6368;
--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;
--font: 'Segoe UI', system-ui, -apple-system, sans-serif;
}
[data-theme="dark"] {
--bg: #0F172A;
--bg-card: #1E293B;
--text: #E2E8F0;
--text-muted: #94A3B8;
--border: #334155;
--shadow: 0 1px 3px rgba(0,0,0,0.3);
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: var(--font); background: var(--bg); color: var(--text); line-height: 1.6; }
/* 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; }
.nav-brand a { text-decoration: none; font-size: 1.2em; font-weight: 700; color: var(--primary); }
.nav-search { flex: 1; max-width: 400px; }
.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; }
.nav-actions { display: flex; align-items: center; gap: 12px; margin-left: auto; }
.nav-btn { background: none; border: none; cursor: pointer; font-size: 1.1em; padding: 6px; border-radius: var(--radius); color: var(--text); position: relative; }
.nav-btn:hover { background: var(--bg); }
.nav-user { display: flex; flex-direction: column; align-items: end; }
.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; }
.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; }
/* HUD */
.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; }
.hud-month { font-weight: 700; }
.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; }
.hud-exceptional .hud-bar, [data-color="hud-exceptional"] .hud-bar { background: linear-gradient(90deg, var(--gold), #FBBF24); }
.hud-healthy .hud-bar, [data-color="hud-healthy"] .hud-bar { background: var(--success); }
.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; }
.hud-secondary { display: flex; gap: 16px; margin-top: 4px; font-size: 0.85em; color: var(--text-muted); }
.hud-deductions { color: var(--danger); }
.hud-bounties { color: var(--success); }
/* Main Content */
.main-content { padding: 24px; max-width: 1400px; margin: 0 auto; }
.container { max-width: 1200px; margin: 0 auto; }
/* Cards */
.card { background: var(--bg-card); border-radius: var(--radius); border: 1px solid var(--border); padding: 20px; box-shadow: var(--shadow); }
.card h3 { margin-bottom: 12px; font-size: 1em; color: var(--text-muted); }
.card-wide { grid-column: span 2; }
/* Dashboard Grid */
.dashboard-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap: 20px; margin-top: 20px; }
.stat-card { text-align: center; }
.stat-number { font-size: 2.5em; font-weight: 800; color: var(--primary); }
.stat-sub { font-size: 0.85em; color: var(--text-muted); }
/* Forms */
.form-group { margin-bottom: 16px; }
.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; }
/* Buttons */
.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; }
.btn-primary { background: var(--primary); color: white; }
.btn-primary:hover { background: var(--primary-dark); }
.btn-secondary { background: var(--border); color: var(--text); }
.btn-danger { background: var(--danger); color: white; }
.btn-success { background: var(--success); color: white; }
.btn-sm { padding: 6px 12px; font-size: 0.8em; }
.btn-lg { padding: 14px 28px; font-size: 1.1em; }
.btn-block { display: block; width: 100%; text-align: center; }
.btn-link { background: none; color: var(--primary); padding: 0; }
/* Alerts */
.alert { padding: 12px 16px; border-radius: var(--radius); margin-bottom: 16px; font-size: 0.9em; }
.alert-error { background: #FEF2F2; color: #991B1B; border: 1px solid #FECACA; }
.alert-success { background: #F0FDF4; color: #166534; border: 1px solid #BBF7D0; }
.alert-warning { background: #FFFBEB; color: #92400E; border: 1px solid #FDE68A; }
/* Auth Page */
.auth-page { display: flex; align-items: center; justify-content: center; min-height: 100vh; }
.auth-container { width: 100%; max-width: 420px; padding: 20px; }
.auth-logo { text-align: center; font-size: 2em; font-weight: 800; margin-bottom: 30px; color: var(--primary); }
.auth-form { background: var(--bg-card); padding: 30px; border-radius: var(--radius); box-shadow: var(--shadow); border: 1px solid var(--border); }
.auth-form h2 { text-align: center; margin-bottom: 24px; }
/* Blocking Notification */
.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; }
.blocking-icon { font-size: 3em; margin-bottom: 16px; }
.blocking-content { text-align: left; margin: 20px 0; padding: 16px; background: var(--bg); border-radius: var(--radius); }
.blocking-note { font-size: 0.85em; color: var(--text-muted); margin: 16px 0; font-style: italic; }
/* Notifications */
.notification-list { display: flex; flex-direction: column; gap: 8px; }
.notification-item { padding: 16px; background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); }
.notification-item.unread { border-left: 3px solid var(--primary); background: #F0F4FF; }
.notification-item.tier-blocking { border-left-color: var(--danger); }
.notif-header { display: flex; justify-content: space-between; margin-bottom: 4px; }
.notif-time { font-size: 0.8em; color: var(--text-muted); }
/* Tasks */
.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; }
.task-key { font-weight: 700; color: var(--primary); font-size: 0.8em; white-space: nowrap; }
.task-title { flex: 1; }
.task-status { padding: 2px 8px; border-radius: 12px; font-size: 0.75em; font-weight: 600; }
.overdue { color: var(--danger); font-weight: 600; }
/* Badges */
.badge-doing { background: #DBEAFE; color: #1E40AF; }
.badge-todo { background: #FEF9C3; color: #854D0E; }
.badge-in_review { background: #E0E7FF; color: #4338CA; }
.badge-frozen { background: #E0F2FE; color: #075985; }
.badge-done { background: #DCFCE7; color: #166534; }
.badge-backlog { background: var(--border); color: var(--text-muted); }
.badge-success { background: #DCFCE7; color: #166534; }
.badge-warning { background: #FEF9C3; color: #854D0E; }
.badge-danger { background: #FEF2F2; color: #991B1B; }
/* Page Header */
.page-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
/* Team Status */
.team-member-row { display: flex; justify-content: space-between; align-items: center; padding: 8px 0; border-bottom: 1px solid var(--border); }
/* Toast */
#toast-container { position: fixed; bottom: 20px; right: 20px; z-index: 9999; display: flex; flex-direction: column; gap: 8px; }
.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); }
.toast-success { background: var(--success); }
.toast-error { background: var(--danger); }
.toast-warning { background: var(--warning); color: #000; }
.toast-info { background: var(--primary); }
.toast-gold { background: linear-gradient(135deg, #F59E0B, #D97706); }
@keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
/* Error Pages */
.error-page { display: flex; align-items: center; justify-content: center; min-height: 100vh; }
.error-container { text-align: center; }
.error-container h1 { font-size: 6em; font-weight: 800; color: var(--primary); }
/* Responsive */
--radius-lg: 12px;
--radius-sm: 6px;
/* Shadows */
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.3);
--shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.4);
/* Transitions */
--transition: all 0.2s ease;
/* Typography */
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
--font-mono: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace;
}
/* Light theme overrides */
[data-theme="light"] {
--bg-primary: #f8f9fa;
--bg-secondary: #ffffff;
--bg-tertiary: #f1f3f5;
--bg-card: #ffffff;
--bg-hover: #e9ecef;
--bg-input: #f1f3f5;
--text-primary: #1a1a2e;
--text-secondary: #495057;
--text-muted: #868e96;
--text-inverse: #ffffff;
--border-color: #dee2e6;
--border-light: #e9ecef;
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.06);
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08);
--shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.12);
}
html {
font-size: 14px;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
font-family: var(--font-sans);
background: var(--bg-primary);
color: var(--text-primary);
line-height: 1.6;
min-height: 100vh;
overflow-x: hidden;
}
a {
color: var(--accent-primary);
text-decoration: none;
transition: var(--transition);
}
a:hover {
color: var(--accent-primary-hover);
}
img { max-width: 100%; height: auto; }
/* ─── TYPOGRAPHY ─── */
h1, h2, h3, h4, h5, h6 {
font-weight: 600;
line-height: 1.3;
color: var(--text-primary);
}
h1 { font-size: 1.75rem; }
h2 { font-size: 1.4rem; }
h3 { font-size: 1.15rem; }
h4 { font-size: 1rem; }
/* ─── TOP NAVIGATION ─── */
.top-nav {
position: fixed;
top: 0;
left: 0;
right: 0;
height: var(--nav-height);
background: var(--bg-secondary);
border-bottom: 1px solid var(--border-color);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20px;
z-index: 1000;
box-shadow: var(--shadow-sm);
}
.nav-left {
display: flex;
align-items: center;
gap: 20px;
}
.nav-brand {
font-size: 1.2rem;
font-weight: 700;
color: var(--text-primary) !important;
display: flex;
align-items: center;
gap: 8px;
white-space: nowrap;
}
.nav-brand:hover { color: var(--accent-primary) !important; }
.search-bar input {
background: var(--bg-input);
border: 1px solid var(--border-color);
border-radius: var(--radius);
padding: 8px 14px;
color: var(--text-primary);
font-size: 0.9rem;
width: 280px;
transition: var(--transition);
}
.search-bar input:focus {
outline: none;
border-color: var(--accent-primary);
box-shadow: 0 0 0 3px var(--accent-primary-bg);
width: 360px;
}
.search-bar input::placeholder {
color: var(--text-muted);
}
.nav-right {
display: flex;
align-items: center;
gap: 16px;
}
.nav-icon {
position: relative;
font-size: 1.3rem;
color: var(--text-secondary) !important;
padding: 6px;
border-radius: var(--radius-sm);
transition: var(--transition);
}
.nav-icon:hover {
background: var(--bg-hover);
color: var(--text-primary) !important;
}
.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) {
.top-nav { flex-wrap: wrap; gap: 8px; padding: 8px 12px; }
.nav-search { order: 3; max-width: 100%; flex-basis: 100%; }
.dashboard-grid { grid-template-columns: 1fr; }
.card-wide { grid-column: span 1; }
.hud { padding: 8px 12px; }
.main-content { padding: 12px; }
}
\ No newline at end of file
.grid-4, .grid-3, .grid-2 { grid-template-columns: 1fr; }
.sidebar { display: none; }
.main-content { margin-left: 0; }
.search-bar input { width: 160px; }
.search-bar input:focus { width: 200px; }
}
/* ─── 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 $__engine->section('title'); ?>Admin Dashboard<?php $__engine->endSection(); ?>
<?php
/** @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">
<h1>Admin Dashboard</h1>
<div class="dashboard-grid">
<div class="card stat-card">
<h3>👥 Active Contractors</h3>
<div class="stat-number"><?= $active_contractors ?? 0 ?></div>
</div>
<div class="card stat-card">
<h3>📋 Onboarding</h3>
<div class="stat-number"><?= $onboarding_count ?? 0 ?></div>
</div>
<div class="card stat-card">
<h3>⚠️ Deductions This Month</h3>
<div class="stat-number"><?= $month_deductions['cnt'] ?? 0 ?></div>
<div class="stat-sub">EGP <?= number_format((float)($month_deductions['total'] ?? 0), 0) ?></div>
</div>
<div class="card stat-card">
<h3>💰 Bounties This Month</h3>
<div class="stat-number"><?= $month_bounties['cnt'] ?? 0 ?></div>
<div class="stat-sub">EGP <?= number_format((float)($month_bounties['total'] ?? 0), 0) ?></div>
$month = date('F Y');
?>
<div class="dashboard-header">
<h1>📊 Dashboard</h1>
<span class="dashboard-date"><?= date('l, F j, Y') ?></span>
</div>
<div class="welcome-banner">
<h2>Welcome back, <?= htmlspecialchars($user['full_name_en'] ?? 'Admin') ?></h2>
<p>Administration overview for <?= $month ?>.</p>
</div>
<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-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 ?> 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 ?> 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>
<div class="card stat-card">
<h3>📊 Active PIPs</h3>
<div class="stat-number"><?= $active_pips ?? 0 ?></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>
\ No newline at end of file
<?php $__engine->extend('layouts/app'); ?>
<?php $__engine->section('title'); ?>Super Admin Dashboard<?php $__engine->endSection(); ?>
<div class="dashboard">
<h1>Super Admin Dashboard</h1>
<div class="dashboard-grid">
<div class="card stat-card">
<h3>👥 Active Contractors</h3>
<div class="stat-number"><?= $active_contractors ?? 0 ?></div>
</div>
<div class="card stat-card">
<h3>💵 Total Payroll</h3>
<div class="stat-number">EGP <?= number_format($total_payroll ?? 0, 0) ?></div>
</div>
<div class="card stat-card">
<h3>⚠️ Deductions</h3>
<div class="stat-number"><?= $month_deductions['cnt'] ?? 0 ?></div>
<div class="stat-sub">EGP <?= number_format((float)($month_deductions['total'] ?? 0), 0) ?></div>
</div>
<div class="card stat-card">
<h3>💰 Bounties</h3>
<div class="stat-number"><?= $month_bounties['cnt'] ?? 0 ?></div>
<div class="stat-sub">EGP <?= number_format((float)($month_bounties['total'] ?? 0), 0) ?></div>
</div>
<div class="card stat-card">
<h3>📋 Onboarding</h3>
<div class="stat-number"><?= $onboarding_count ?? 0 ?></div>
<?php
/** @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 array $payroll_status */
/** @var int $active_pips */
/** @var float $total_payroll */
/** @var int $expiring_contracts */
$month = date('F Y');
$greeting = match(true) {
date('H') < 12 => 'Good morning',
date('H') < 18 => 'Good afternoon',
default => 'Good evening',
};
?>
<div class="dashboard-header">
<h1>📊 Dashboard</h1>
<span class="dashboard-date"><?= date('l, F j, Y') ?></span>
</div>
<div class="welcome-banner">
<h2><?= $greeting ?>, <?= htmlspecialchars($user['full_name_en'] ?? 'Admin') ?>!</h2>
<p>Here's your operational overview for <?= $month ?>.</p>
<div class="quick-actions">
<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>
<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>
<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>
</div>
</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>
<div class="card stat-card">
<h3>📊 Active PIPs</h3>
<div class="stat-number"><?= $active_pips ?? 0 ?></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 class="card stat-card">
<h3>📄 Expiring Contracts</h3>
<div class="stat-number"><?= $expiring_contracts ?? 0 ?></div>
<div class="stat-sub">within 90 days</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>
\ No newline at end of file
<?php /** @var string $content */ ?>
<!DOCTYPE html>
<html lang="en">
<html lang="en" data-theme="dark">
<head>
<meta charset="UTF-8">
<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">
</head>
<body class="auth-page">
<div class="auth-container">
<div class="auth-logo">🎮 The Grind</div>
<?= $__engine->content() ?>
<?= $content ?? '' ?>
</div>
</body>
</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