forked from UKSOURCE/cms.hailearning.edu.vn
404 lines
12 KiB
Plaintext
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 © <%= 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>
|