Files
uldp-degree-mangement-system/views/layouts/admin.ejs
2026-04-11 14:08:27 +07:00

404 lines
12 KiB
Plaintext

<!DOCTYPE html>
<html lang="en">
<head>
<link rel="icon" type="image/png" href="/img/favicon.png" />
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title><%= typeof title !== 'undefined' ? title + ' | ' : '' %>ULDP Management</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" 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="/assets/css/variables.css" />
<link rel="stylesheet" href="/assets/css/components/button.css" />
<link rel="stylesheet" href="/assets/css/components/card.css" />
<link rel="stylesheet" href="/assets/css/components/form.css" />
<link rel="stylesheet" href="/assets/css/components/modal.css" />
<link rel="stylesheet" href="/assets/css/components/table.css" />
<link rel="stylesheet" href="/assets/css/layout.css" />
<style>
:root {
--sidebar-width: 248px;
--topbar-height: 58px;
}
body {
display: flex;
flex-direction: column;
min-height: 100vh;
background-color: var(--bg-body);
font-family: var(--font-family);
}
/* ── Topbar ── */
.admin-topbar {
position: fixed;
top: 0; left: 0; right: 0;
height: var(--topbar-height);
background-color: #fff;
box-shadow: var(--shadow-header);
display: flex;
align-items: center;
padding: 0 1.25rem 0 0;
z-index: 1030;
}
.topbar-brand {
width: var(--sidebar-width);
flex-shrink: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 0.2rem;
padding: 0.4rem 1rem;
text-decoration: none;
background: var(--primary-color);
height: 100%;
}
.topbar-brand img {
width: 100px;
height: 80%;
object-fit: contain;
border-radius: 0;
margin-top: 20px;
}
.topbar-brand-text {
font-weight: var(--font-weight-semibold);
font-size: 0.7rem;
color: rgba(255,255,255,0.85);
line-height: 1;
letter-spacing: 0.04em;
text-transform: uppercase;
text-align: center;
}
.topbar-brand-sub {
display: none;
}
.topbar-body {
flex: 1;
display: flex;
align-items: center;
padding: 0 1.25rem;
gap: 0.75rem;
}
.topbar-page-title {
font-size: 0.9rem;
font-weight: var(--font-weight-semibold);
color: var(--text-main);
}
.topbar-right {
margin-left: auto;
display: flex;
align-items: center;
gap: 0.5rem;
}
.topbar-divider {
width: 1px;
height: 22px;
background: var(--border-color);
}
.topbar-user-btn {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.35rem 0.75rem;
border-radius: var(--border-radius-sm);
border: 1px solid var(--border-color);
background: #fff;
cursor: pointer;
transition: var(--transition-base);
text-decoration: none;
}
.topbar-user-btn:hover { background: #f8fafc; border-color: #cbd5e1; }
.topbar-avatar {
width: 28px;
height: 28px;
border-radius: 50%;
background: linear-gradient(135deg, var(--primary-color), var(--primary-light));
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.7rem;
font-weight: var(--font-weight-bold);
flex-shrink: 0;
}
.topbar-username {
font-size: 0.8125rem;
font-weight: var(--font-weight-medium);
color: var(--text-main);
}
/* ── Sidebar ── */
.admin-sidebar {
position: fixed;
top: var(--topbar-height);
left: 0;
bottom: 0;
width: var(--sidebar-width);
background: var(--sidebar-bg);
overflow-y: auto;
overflow-x: hidden;
z-index: 1020;
display: flex;
flex-direction: column;
}
.admin-sidebar::-webkit-scrollbar { width: 4px; }
.admin-sidebar::-webkit-scrollbar-track { background: transparent; }
.admin-sidebar::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.15); border-radius: 4px; }
.sidebar-nav-area { padding: 0.75rem 0; flex: 1; }
.sidebar-section-label {
padding: 0.6rem 1.25rem 0.3rem;
font-size: 0.65rem;
font-weight: var(--font-weight-bold);
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--sidebar-section-color);
margin-top: 0.25rem;
}
.admin-sidebar .nav-link {
display: flex;
align-items: center;
gap: 0.65rem;
padding: 0.6rem 1.25rem;
color: var(--sidebar-text);
font-size: 0.875rem;
font-weight: var(--font-weight-medium);
border-radius: 0;
transition: var(--transition-base);
border-left: 3px solid transparent;
text-decoration: none;
position: relative;
}
.admin-sidebar .nav-link .nav-icon {
width: 18px;
text-align: center;
font-size: 0.85rem;
opacity: 0.7;
transition: var(--transition-base);
flex-shrink: 0;
}
.admin-sidebar .nav-link:hover {
background-color: var(--sidebar-hover-bg);
color: #fff;
border-left-color: rgba(188,159,105,0.5);
}
.admin-sidebar .nav-link:hover .nav-icon { opacity: 1; }
.admin-sidebar .nav-link.active {
background-color: var(--sidebar-active-bg);
color: #fff !important;
border-left-color: var(--sidebar-active-border);
font-weight: var(--font-weight-semibold);
}
.admin-sidebar .nav-link.active .nav-icon {
opacity: 1;
color: var(--accent-color);
}
.sidebar-divider {
border-top: 1px solid rgba(255,255,255,0.07);
margin: 0.5rem 1.25rem;
}
.sidebar-footer {
padding: 1rem 1.25rem;
border-top: 1px solid rgba(255,255,255,0.07);
}
.sidebar-footer-text {
font-size: 0.65rem;
color: rgba(255,255,255,0.3);
text-align: center;
}
/* ── Main content ── */
.admin-main {
margin-left: var(--sidebar-width);
margin-top: var(--topbar-height);
min-height: calc(100vh - var(--topbar-height));
padding: 1.5rem 1.75rem;
}
/* ── Flash toast ── */
#flash-messages-data { display: none; }
.toast-container {
position: fixed;
top: calc(var(--topbar-height) + 1rem);
right: 1.25rem;
z-index: 9999;
}
/* ── Responsive ── */
@media (max-width: 767.98px) {
.admin-sidebar { transform: translateX(-100%); }
.admin-main { margin-left: 0; padding: 1rem; }
.topbar-brand { width: auto; }
.topbar-page-title { display: none; }
}
</style>
<%- style %>
</head>
<body>
<!-- Topbar -->
<header class="admin-topbar">
<a href="/admin/dashboard" class="topbar-brand">
<img src="/img/logo.png" alt="ULDP Logo" />
<div class="topbar-brand-text">ULDP Management</div>
</a>
<div class="topbar-body">
<span class="topbar-page-title"><%= typeof title !== 'undefined' ? title : 'Dashboard' %></span>
<div class="topbar-right">
<% if (locals.user) { %>
<a class="topbar-user-btn" href="#">
<div class="topbar-avatar">
<%= (locals.user.username || locals.user.email || 'A')[0].toUpperCase() %>
</div>
<span class="topbar-username d-none d-md-inline">
<%= locals.user.username || locals.user.email %>
</span>
</a>
<div class="topbar-divider"></div>
<a href="/auth/logout" class="btn btn-sm btn-outline-danger">
<i class="fas fa-sign-out-alt"></i>
<span class="d-none d-md-inline">Logout</span>
</a>
<% } %>
</div>
</div>
</header>
<!-- Sidebar -->
<aside class="admin-sidebar">
<div class="sidebar-nav-area">
<div class="sidebar-section-label">Main</div>
<nav>
<a class="nav-link <%= currentPath === '/admin/dashboard' ? 'active' : '' %>" href="/admin/dashboard">
<i class="fas fa-chart-pie nav-icon"></i>
<span>Dashboard</span>
</a>
<a class="nav-link <%= currentPath && currentPath.startsWith('/admin/qualification') ? 'active' : '' %>" href="/admin/qualification">
<i class="fas fa-graduation-cap nav-icon"></i>
<span>Qualifications</span>
</a>
<a class="nav-link <%= currentPath && currentPath.startsWith('/admin/certificate') ? 'active' : '' %>" href="/admin/certificate">
<i class="fas fa-certificate nav-icon"></i>
<span>Certificates</span>
</a>
</nav>
<div class="sidebar-divider"></div>
<div class="sidebar-section-label">Configuration</div>
<nav>
<a class="nav-link <%= currentPath === '/admin/department' ? 'active' : '' %>" href="/admin/department">
<i class="fas fa-building nav-icon"></i>
<span>Departments</span>
</a>
<a class="nav-link <%= currentPath === '/admin/level' ? 'active' : '' %>" href="/admin/level">
<i class="fas fa-layer-group nav-icon"></i>
<span>Levels</span>
</a>
</nav>
<div class="sidebar-divider"></div>
<div class="sidebar-section-label">System</div>
<nav>
<a class="nav-link <%= currentPath && currentPath.startsWith('/admin/audit-logs') ? 'active' : '' %>" href="/admin/audit-logs">
<i class="fas fa-shield-alt nav-icon"></i>
<span>Audit Logs</span>
</a>
</nav>
</div>
<div class="sidebar-footer">
<div class="sidebar-footer-text">ULDP &copy; <%= new Date().getFullYear() %></div>
</div>
</aside>
<!-- Main Content -->
<main class="admin-main">
<% if(typeof success_msg !== 'undefined' || typeof error_msg !== 'undefined' || typeof error !== 'undefined') { %>
<div id="flash-messages-data">
<%- JSON.stringify({
success_msg: typeof success_msg !== 'undefined' && success_msg ? success_msg : null,
error_msg: typeof error_msg !== 'undefined' && error_msg ? error_msg : null,
error: typeof error !== 'undefined' && error ? error : null
}) %>
</div>
<% } %>
<%- body %>
</main>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
// Modal cleanup
function forceCleanupModals() {
document.body.classList.remove('modal-open');
document.body.style.overflow = '';
document.body.style.paddingRight = '';
document.querySelectorAll('.modal-backdrop, .overlay, .loading').forEach(el => el.remove());
}
document.addEventListener('hidden.bs.modal', () => setTimeout(forceCleanupModals, 100));
setInterval(() => {
if (!document.querySelector('.modal.show') && document.querySelector('.modal-backdrop')) forceCleanupModals();
}, 2000);
window.addEventListener('load', forceCleanupModals);
// Flash toast
const flashData = document.getElementById('flash-messages-data');
if (flashData) {
try {
const msgs = JSON.parse(flashData.textContent);
const container = document.createElement('div');
container.className = 'toast-container';
document.body.appendChild(container);
const show = (msg, type) => {
if (!msg) return;
const icons = { success: 'fa-check-circle', danger: 'fa-exclamation-circle', warning: 'fa-exclamation-triangle' };
const t = document.createElement('div');
t.className = `toast align-items-center text-bg-${type} border-0 show mb-2`;
t.setAttribute('role', 'alert');
t.innerHTML = `<div class="d-flex"><div class="toast-body d-flex align-items-center gap-2"><i class="fas ${icons[type] || 'fa-info-circle'}"></i>${msg}</div><button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button></div>`;
container.appendChild(t);
setTimeout(() => t.remove(), 4500);
};
show(msgs.success_msg, 'success');
show(msgs.error_msg, 'danger');
show(msgs.error, 'danger');
} catch(e) {}
}
</script>
<%- script %>
</body>
</html>