246 lines
16 KiB
PHP
246 lines
16 KiB
PHP
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Task Management</title>
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||
<link rel="stylesheet" href="public/css/style.css">
|
||
</head>
|
||
<body>
|
||
<script>
|
||
if(localStorage.getItem('theme') === 'dark') document.body.classList.add('dark-mode');
|
||
if(localStorage.getItem('sidebar_collapsed') === '1') document.body.classList.add('sidebar-collapsed');
|
||
</script>
|
||
<div id="toast"></div>
|
||
|
||
<!-- Profile Picture Modal -->
|
||
<div class="modal-overlay" id="profile-pic-modal">
|
||
<div class="modal-content" style="max-width:420px;">
|
||
<h3 style="margin-bottom:1rem; font-size:1.1rem;">Profile Picture</h3>
|
||
<div style="display:flex; flex-direction:column; align-items:center; gap:1rem; margin-bottom:1.5rem;">
|
||
<?php
|
||
try { $pdo->exec("ALTER TABLE users ADD COLUMN profile_pic TEXT DEFAULT NULL"); } catch(Exception $e) {}
|
||
$pic = $current_user['profile_pic'] ?? null;
|
||
?>
|
||
<?php if($pic && file_exists($pic)): ?>
|
||
<img src="<?= htmlspecialchars($pic) ?>" alt="Profile" style="width:100px; height:100px; border-radius:50%; object-fit:cover; border:3px solid var(--primary);">
|
||
<?php else: ?>
|
||
<div style="width:100px; height:100px; border-radius:50%; background:var(--primary); display:flex; align-items:center; justify-content:center; font-size:2.5rem; color:#fff; font-weight:700;">
|
||
<?= strtoupper(mb_substr($current_user['username'],0,1)) ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
<form method="POST" enctype="multipart/form-data" style="margin-bottom:1rem;">
|
||
<input type="hidden" name="upload_profile_pic" value="1">
|
||
<input type="hidden" name="redirect_to" value="<?= htmlspecialchars($action) ?>">
|
||
<div class="form-group">
|
||
<label style="font-size:0.875rem; color:var(--text-light);">Upload New Picture (JPG, PNG, GIF, WebP)</label>
|
||
<input type="file" name="profile_pic" accept="image/*" class="form-control" style="margin-top:0.5rem;" required>
|
||
</div>
|
||
<button type="submit" class="btn" style="width:100%;">Upload Picture</button>
|
||
</form>
|
||
<?php if($pic && file_exists($pic)): ?>
|
||
<form method="POST">
|
||
<input type="hidden" name="delete_profile_pic" value="1">
|
||
<input type="hidden" name="redirect_to" value="<?= htmlspecialchars($action) ?>">
|
||
<button type="submit" class="btn btn-danger" style="width:100%;" onclick="return confirm('Delete profile picture?')">Delete Picture</button>
|
||
</form>
|
||
<?php endif; ?>
|
||
<button class="btn btn-secondary" style="width:100%; margin-top:0.5rem;" onclick="document.getElementById('profile-pic-modal').classList.remove('active')">Close</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Change Password Modal -->
|
||
<div class="modal-overlay" id="change-pw-modal">
|
||
<div class="modal-content" style="max-width:400px;">
|
||
<h3 style="margin-bottom:1rem; font-size:1.1rem;">Change Password</h3>
|
||
<?php
|
||
$pw_msg = $_SESSION['pw_change_msg'] ?? null;
|
||
unset($_SESSION['pw_change_msg']);
|
||
if ($pw_msg === 'success'): ?>
|
||
<div class="alert" style="background:rgba(16,185,129,0.12); color:#10B981; border:1px solid #10B981;">Password changed successfully!</div>
|
||
<?php elseif($pw_msg): ?>
|
||
<div class="alert"><?= htmlspecialchars($pw_msg) ?></div>
|
||
<?php endif; ?>
|
||
<form method="POST">
|
||
<input type="hidden" name="change_password" value="1">
|
||
<input type="hidden" name="redirect_to" value="<?= htmlspecialchars($action) ?>">
|
||
<div class="form-group">
|
||
<label>Current Password</label>
|
||
<input type="password" name="current_password" class="form-control" required>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>New Password</label>
|
||
<input type="password" name="new_password" class="form-control" required minlength="6">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Confirm New Password</label>
|
||
<input type="password" name="confirm_password" class="form-control" required minlength="6">
|
||
</div>
|
||
<button type="submit" class="btn" style="width:100%;">Change Password</button>
|
||
</form>
|
||
<button class="btn btn-secondary" style="width:100%; margin-top:0.5rem;" onclick="document.getElementById('change-pw-modal').classList.remove('active')">Cancel</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Notifications Modal -->
|
||
<?php
|
||
// Fetch notifications for popup
|
||
$notif_list = [];
|
||
try {
|
||
$stmt_n = $pdo->prepare("SELECT * FROM notifications WHERE user_id=? ORDER BY created_at DESC LIMIT 30");
|
||
$stmt_n->execute([$user_id]);
|
||
$notif_list = $stmt_n->fetchAll();
|
||
} catch(Exception $e) {}
|
||
?>
|
||
<div class="modal-overlay" id="notifications-modal">
|
||
<div class="modal-content" style="max-width:460px; max-height:80vh; display:flex; flex-direction:column; padding:0; overflow:hidden;">
|
||
<div style="display:flex; justify-content:space-between; align-items:center; padding:1.25rem 1.5rem; border-bottom:1px solid var(--border);">
|
||
<h3 style="font-size:1.05rem; font-weight:700;"><i class="fa fa-bell" style="color:var(--primary);"></i> Notifications</h3>
|
||
<div style="display:flex; gap:0.5rem; align-items:center;">
|
||
<?php if($unread_notifications > 0): ?>
|
||
<form method="POST" id="mark-all-read-form" style="display:inline;">
|
||
<input type="hidden" name="mark_all_read" value="1">
|
||
<input type="hidden" name="redirect_to" value="<?= htmlspecialchars($action) ?>">
|
||
<button type="submit" class="btn btn-sm btn-secondary" style="font-size:0.75rem;">Mark all read</button>
|
||
</form>
|
||
<?php endif; ?>
|
||
<button onclick="document.getElementById('notifications-modal').classList.remove('active')" style="background:none;border:none;color:var(--text-light);cursor:pointer;font-size:1.2rem;padding:4px;">×</button>
|
||
</div>
|
||
</div>
|
||
<div style="overflow-y:auto; flex:1; padding:0.75rem 0;">
|
||
<?php if(empty($notif_list)): ?>
|
||
<div style="text-align:center; padding:2.5rem 1rem; color:var(--text-light);">
|
||
<i class="fa fa-bell-slash" style="font-size:2rem; display:block; margin-bottom:0.75rem;"></i>
|
||
No notifications yet.
|
||
</div>
|
||
<?php else: foreach($notif_list as $n):
|
||
$t = strtotime($n['created_at']);
|
||
$diff = time()-$t;
|
||
$ago = $diff<60?'just now':($diff<3600?floor($diff/60).'m ago':($diff<86400?floor($diff/3600).'h ago':floor($diff/86400).'d ago'));
|
||
?>
|
||
<div style="display:flex; align-items:flex-start; gap:0.75rem; padding:0.65rem 1.5rem; border-bottom:1px solid var(--border); transition:background 0.15s; <?= !$n['is_read']?'background:rgba(var(--primary-rgb),0.04);':'' ?>">
|
||
<div style="width:8px; height:8px; border-radius:50%; background:<?= !$n['is_read']?'var(--primary)':'transparent' ?>; margin-top:6px; flex-shrink:0;"></div>
|
||
<div style="flex:1; min-width:0;">
|
||
<div style="font-size:0.875rem; color:var(--text); <?= !$n['is_read']?'font-weight:500;':'' ?>"><?= htmlspecialchars($n['message']) ?></div>
|
||
<div style="font-size:0.72rem; color:var(--text-light); margin-top:3px;"><?= $ago ?></div>
|
||
</div>
|
||
</div>
|
||
<?php endforeach; endif; ?>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="app-container">
|
||
<!-- Sidebar -->
|
||
<aside class="sidebar" id="sidebar">
|
||
<!-- Toggle button on the edge -->
|
||
<button class="sidebar-toggle-btn" id="sidebar-toggle-btn" onclick="toggleSidebar()" title="Collapse / Expand">
|
||
<i class="fa fa-chevron-left" id="sidebar-toggle-icon"></i>
|
||
</button>
|
||
|
||
<h2 class="sidebar-brand">
|
||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0;"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline></svg>
|
||
<span class="sidebar-label sidebar-brand-text">TaskManager</span>
|
||
</h2>
|
||
|
||
<nav style="display:flex; flex-direction:column; gap:0.25rem;">
|
||
<a href="?action=dashboard" class="nav-link <?= $action=='dashboard'?'active':'' ?>" title="Dashboard">
|
||
<i class="fa fa-gauge-high nav-icon"></i>
|
||
<span class="sidebar-label">Dashboard</span>
|
||
</a>
|
||
<a href="?action=tasks" class="nav-link <?= $action=='tasks'?'active':'' ?>" title="Tasks">
|
||
<i class="fa fa-list-check nav-icon"></i>
|
||
<span class="sidebar-label">Tasks</span>
|
||
</a>
|
||
<a href="?action=notes" class="nav-link <?= $action=='notes'?'active':'' ?>" title="Notes">
|
||
<i class="fa fa-note-sticky nav-icon"></i>
|
||
<span class="sidebar-label">Notes</span>
|
||
</a>
|
||
<a href="?action=passwords" class="nav-link <?= $action=='passwords'?'active':'' ?>" title="Passwords">
|
||
<i class="fa fa-lock nav-icon"></i>
|
||
<span class="sidebar-label">Passwords</span>
|
||
</a>
|
||
<a href="?action=projects" class="nav-link <?= $action=='projects'?'active':'' ?>" title="Files">
|
||
<i class="fa fa-folder-open nav-icon"></i>
|
||
<span class="sidebar-label">Files</span>
|
||
</a>
|
||
<a href="?action=tools" class="nav-link <?= $action=='tools'?'active':'' ?>" title="Tools">
|
||
<i class="fa fa-toolbox nav-icon"></i>
|
||
<span class="sidebar-label">Tools</span>
|
||
</a>
|
||
<?php if(!empty($_SESSION['is_admin'])): ?>
|
||
<a href="?action=admin" class="nav-link <?= $action=='admin'?'active':'' ?>" title="Admin Panel">
|
||
<i class="fa fa-shield-halved nav-icon"></i>
|
||
<span class="sidebar-label">Admin Panel</span>
|
||
</a>
|
||
<?php endif; ?>
|
||
</nav>
|
||
</aside>
|
||
|
||
<main class="main-content">
|
||
<!-- Top Bar -->
|
||
<div style="display:flex; justify-content:flex-end; align-items:center; gap:0.75rem; margin-bottom:1.5rem;">
|
||
<!-- Dark Mode Toggle -->
|
||
<button id="theme-toggle" class="btn btn-secondary" title="Toggle dark mode" style="border-radius:50%; padding:0; width:38px; height:38px; display:flex; align-items:center; justify-content:center; flex-shrink:0;">
|
||
<span id="theme-icon-light" style="display:none;"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg></span>
|
||
<span id="theme-icon-dark" style="display:none;"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg></span>
|
||
</button>
|
||
|
||
<!-- User Avatar Dropdown -->
|
||
<div class="user-dropdown-wrap" id="user-dropdown-wrap">
|
||
<!-- Avatar with notification badge -->
|
||
<div style="position:relative; display:inline-flex;">
|
||
<button class="user-avatar-btn" id="user-avatar-btn" onclick="toggleUserDropdown()" title="<?= htmlspecialchars($current_user['username']) ?>">
|
||
<?php
|
||
try { $pdo->exec("ALTER TABLE users ADD COLUMN profile_pic TEXT DEFAULT NULL"); } catch(Exception $e) {}
|
||
$pic_top = $current_user['profile_pic'] ?? null;
|
||
?>
|
||
<?php if($pic_top && file_exists($pic_top)): ?>
|
||
<img src="<?= htmlspecialchars($pic_top) ?>?v=<?= time() ?>" alt="avatar" style="width:100%; height:100%; object-fit:cover; border-radius:50%;">
|
||
<?php else: ?>
|
||
<?= strtoupper(mb_substr($current_user['username'],0,1)) ?>
|
||
<?php endif; ?>
|
||
</button>
|
||
<?php if($unread_notifications > 0): ?>
|
||
<span class="avatar-notif-badge" onclick="openNotifications()"><?= $unread_notifications > 9 ? '9+' : $unread_notifications ?></span>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<div class="user-dropdown-menu" id="user-dropdown-menu">
|
||
<div class="user-dropdown-header">
|
||
<div class="user-dropdown-name"><?= htmlspecialchars($current_user['username']) ?></div>
|
||
<?php if(!empty($_SESSION['is_admin'])): ?>
|
||
<div class="user-dropdown-role">Administrator</div>
|
||
<?php else: ?>
|
||
<div class="user-dropdown-role">Member</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
<div class="user-dropdown-divider"></div>
|
||
<button class="user-dropdown-item" onclick="openNotifications()">
|
||
<i class="fa fa-bell"></i>
|
||
Notifications
|
||
<?php if($unread_notifications > 0): ?>
|
||
<span class="badge" style="margin-left:auto; font-size:0.65rem;"><?= $unread_notifications ?></span>
|
||
<?php endif; ?>
|
||
</button>
|
||
<div class="user-dropdown-divider"></div>
|
||
<button class="user-dropdown-item" onclick="document.getElementById('profile-pic-modal').classList.add('active'); closeUserDropdown();">
|
||
<i class="fa fa-image"></i> Set Profile Picture
|
||
</button>
|
||
<button class="user-dropdown-item" onclick="document.getElementById('change-pw-modal').classList.add('active'); closeUserDropdown();">
|
||
<i class="fa fa-key"></i> Change Password
|
||
</button>
|
||
<div class="user-dropdown-divider"></div>
|
||
<a href="?action=logout" class="user-dropdown-item user-dropdown-logout" onclick="return confirm('Logout?')">
|
||
<i class="fa fa-right-from-bracket"></i> Logout
|
||
</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<?php if (isset($pw_msg) && $pw_msg): ?>
|
||
<script>document.addEventListener('DOMContentLoaded',()=>{ document.getElementById('change-pw-modal').classList.add('active'); });</script>
|
||
<?php endif; ?>
|