RECOM / templates /admin_dashboard.html
Ryugen22's picture
Upload admin_dashboard.html
0431e83 verified
Raw
History Blame Contribute Delete
12.2 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>RECOM β€” Admin Dashboard</title>
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@700&family=DM+Sans:wght@300;400;500&display=swap" rel="stylesheet"/>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root { --orange: #F26419; --peach: #F9A87E; --cream: #FFF3EC; --deep: #3D1A00; --mid: #7A3B10; --light-orange: #FDDCC4; }
body { font-family: 'DM Sans', sans-serif; background: var(--cream); min-height: 100vh; padding-bottom: 60px; }
header {
background: #fff; border-bottom: 1.5px solid var(--light-orange);
padding: 18px 32px; display: flex; align-items: center; justify-content: space-between;
position: sticky; top: 0; z-index: 10;
box-shadow: 0 2px 16px rgba(242,100,25,0.07);
}
.logo { display: flex; align-items: center; gap: 12px; }
.logo-mark {
width: 38px; height: 38px; background: var(--orange); border-radius: 10px;
display: flex; align-items: center; justify-content: center;
color: #fff; font-family: 'Playfair Display', serif; font-size: 18px; font-weight: 700;
}
.logo-text { font-family: 'Playfair Display', serif; font-size: 18px; font-weight: 700; color: var(--deep); }
.logo-text span { color: var(--orange); }
.admin-badge {
background: var(--light-orange); color: var(--orange);
font-size: 11px; font-weight: 600; letter-spacing: 1.5px;
text-transform: uppercase; padding: 4px 12px; border-radius: 100px;
border: 1px solid var(--peach);
}
.logout {
background: none; border: 1.5px solid var(--light-orange); color: var(--mid);
padding: 8px 16px; border-radius: 10px; font-family: 'DM Sans', sans-serif;
font-size: 13px; cursor: pointer; text-decoration: none;
transition: border-color 0.2s, color 0.2s;
}
.logout:hover { border-color: var(--orange); color: var(--orange); }
.container { max-width: 900px; margin: 0 auto; padding: 32px 20px; }
.stats {
display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: 16px; margin-bottom: 32px;
}
.stat-card {
background: #fff; border: 1.5px solid var(--light-orange); border-radius: 16px;
padding: 20px 22px; animation: fadeUp 0.4s ease both;
}
.stat-label { font-size: 12px; letter-spacing: 1px; text-transform: uppercase; color: var(--mid); margin-bottom: 8px; font-weight: 500; }
.stat-value { font-family: 'Playfair Display', serif; font-size: 32px; color: var(--orange); font-weight: 700; }
.section-title {
font-family: 'Playfair Display', serif; font-size: 20px; color: var(--deep);
margin-bottom: 16px; display: flex; align-items: center; gap: 10px;
}
.empty {
background: #fff; border: 1.5px solid var(--light-orange); border-radius: 18px;
padding: 60px 20px; text-align: center; color: var(--mid);
}
.empty-icon { font-size: 40px; margin-bottom: 12px; }
.empty p { font-size: 15px; font-weight: 300; }
.cards-grid { display: grid; gap: 16px; }
.reg-card {
background: #fff; border: 1.5px solid var(--light-orange); border-radius: 18px;
padding: 22px 24px; animation: fadeUp 0.4s ease both;
transition: box-shadow 0.2s, opacity 0.3s, transform 0.3s;
position: relative;
}
.reg-card:hover { box-shadow: 0 8px 28px rgba(242,100,25,0.12); }
.reg-card.removing { opacity: 0; transform: scale(0.96); }
.reg-header {
display: flex; align-items: flex-start; justify-content: space-between;
margin-bottom: 14px; flex-wrap: wrap; gap: 8px;
}
.reg-name { font-family: 'Playfair Display', serif; font-size: 20px; color: var(--deep); }
.reg-id {
background: var(--light-orange); color: var(--orange);
font-size: 12px; font-weight: 600; padding: 4px 12px; border-radius: 100px;
}
.reg-chips { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 14px; }
.chip {
background: var(--cream); border: 1px solid var(--light-orange);
border-radius: 100px; padding: 4px 12px; font-size: 13px; color: var(--mid);
}
.chip strong { color: var(--deep); }
.reg-details { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.detail { display: flex; align-items: flex-start; gap: 8px; font-size: 14px; color: var(--mid); }
.detail-icon { font-size: 16px; flex-shrink: 0; }
.detail-text { line-height: 1.5; }
.reg-footer {
display: flex; align-items: center; justify-content: space-between;
margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--light-orange);
flex-wrap: wrap; gap: 10px;
}
.reg-date { font-size: 12px; color: var(--mid); font-weight: 300; }
/* Delete button */
.delete-btn {
display: flex; align-items: center; gap: 6px;
background: none; border: 1.5px solid #ffcccc; color: #cc3300;
padding: 7px 14px; border-radius: 10px; font-family: 'DM Sans', sans-serif;
font-size: 13px; font-weight: 500; cursor: pointer;
transition: background 0.2s, border-color 0.2s, transform 0.15s;
}
.delete-btn:hover { background: #fff0f0; border-color: #cc3300; transform: translateY(-1px); }
.delete-btn:active { transform: translateY(0); }
/* Confirm overlay */
.confirm-overlay {
display: none; position: fixed; inset: 0;
background: rgba(61,26,0,0.35); backdrop-filter: blur(4px);
z-index: 100; align-items: center; justify-content: center;
}
.confirm-overlay.show { display: flex; animation: fadeIn 0.2s ease; }
.confirm-box {
background: #fff; border-radius: 20px; padding: 32px 28px;
width: min(360px, 90vw); box-shadow: 0 20px 60px rgba(61,26,0,0.2);
animation: popUp 0.25s cubic-bezier(0.34,1.56,0.64,1) both;
text-align: center;
}
.confirm-icon { font-size: 36px; margin-bottom: 14px; }
.confirm-box h3 { font-family: 'Playfair Display', serif; font-size: 20px; color: var(--deep); margin-bottom: 8px; }
.confirm-box p { font-size: 14px; color: var(--mid); font-weight: 300; margin-bottom: 24px; line-height: 1.6; }
.confirm-name { color: var(--orange); font-weight: 600; }
.confirm-actions { display: flex; gap: 10px; }
.btn-cancel {
flex: 1; padding: 12px; background: var(--cream); border: 1.5px solid var(--light-orange);
border-radius: 12px; font-family: 'DM Sans', sans-serif; font-size: 14px;
color: var(--mid); cursor: pointer; transition: border-color 0.2s;
}
.btn-cancel:hover { border-color: var(--mid); }
.btn-confirm-delete {
flex: 1; padding: 12px; background: #cc3300; border: none;
border-radius: 12px; font-family: 'DM Sans', sans-serif; font-size: 14px;
font-weight: 600; color: #fff; cursor: pointer;
transition: background 0.2s, transform 0.15s;
box-shadow: 0 4px 14px rgba(204,51,0,0.3);
}
.btn-confirm-delete:hover { background: #aa2800; transform: translateY(-1px); }
/* Toast */
.toast {
position: fixed; bottom: 28px; left: 50%; transform: translateX(-50%) translateY(80px);
background: var(--deep); color: #fff; padding: 12px 24px;
border-radius: 100px; font-size: 14px; font-weight: 500;
z-index: 200; transition: transform 0.3s cubic-bezier(0.34,1.56,0.64,1);
white-space: nowrap;
}
.toast.show { transform: translateX(-50%) translateY(0); }
@keyframes fadeUp { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: translateY(0); } }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes popUp { from { transform: scale(0.85); opacity: 0; } to { transform: scale(1); opacity: 1; } }
</style>
</head>
<body>
<header>
<div class="logo">
<div class="logo-mark">R</div>
<div class="logo-text">RE<span>COM</span></div>
<div class="admin-badge">Admin</div>
</div>
<a href="/admin/logout" class="logout">Logout</a>
</header>
<div class="container">
<div class="stats">
<div class="stat-card" style="animation-delay:0.1s">
<div class="stat-label">Total Registrations</div>
<div class="stat-value" id="totalCount">{{ students|length }}</div>
</div>
<div class="stat-card" style="animation-delay:0.2s">
<div class="stat-label">Total Revenue</div>
<div class="stat-value" id="totalRevenue">β‚Ή{{ students|length * 200 }}</div>
</div>
</div>
<div class="section-title">πŸ“‹ All Registrations</div>
{% if students|length == 0 %}
<div class="empty" id="emptyState">
<div class="empty-icon">πŸ“­</div>
<p>No registrations yet. They'll appear here once students sign up.</p>
</div>
{% else %}
<div class="empty" id="emptyState" style="display:none">
<div class="empty-icon">πŸ“­</div>
<p>No registrations yet. They'll appear here once students sign up.</p>
</div>
<div class="cards-grid" id="cardsGrid">
{% for s in students|reverse %}
<div class="reg-card" id="card-{{ s.id }}" style="animation-delay: {{ loop.index0 * 0.05 }}s">
<div class="reg-header">
<div class="reg-name">{{ s.name }}</div>
<div class="reg-id">#{{ s.id }}</div>
</div>
<div class="reg-chips">
<div class="chip">Class <strong>{{ s.class }}</strong></div>
<div class="chip">Section <strong>{{ s.section }}</strong></div>
</div>
<div class="reg-details">
<div class="detail">
<span class="detail-icon">πŸ“ž</span>
<div class="detail-text">{{ s.get('phone', 'β€”') }}</div>
</div>
<div class="detail">
<span class="detail-icon">πŸ’¬</span>
<div class="detail-text">{{ s.get('whatsapp', 'β€”') }}</div>
</div>
<div class="detail" style="grid-column: 1 / -1">
<span class="detail-icon">πŸ“</span>
<div class="detail-text">{{ s.get('address', 'β€”') }}</div>
</div>
</div>
<div class="reg-footer">
<div class="reg-date">πŸ• Registered on {{ s.get('registered_at', 'β€”') }}</div>
<button class="delete-btn" onclick="confirmDelete({{ s.id }}, '{{ s.name }}')">
πŸ—‘οΈ Mark Complete & Delete
</button>
</div>
</div>
{% endfor %}
</div>
{% endif %}
</div>
<!-- Confirm delete overlay -->
<div class="confirm-overlay" id="confirmOverlay">
<div class="confirm-box">
<div class="confirm-icon">βœ…</div>
<h3>Mark as Complete?</h3>
<p>This will remove <span class="confirm-name" id="confirmName"></span> from the list. Only do this once their record is fully done and delivered.</p>
<div class="confirm-actions">
<button class="btn-cancel" onclick="closeConfirm()">Cancel</button>
<button class="btn-confirm-delete" onclick="doDelete()">Yes, Delete</button>
</div>
</div>
</div>
<!-- Toast notification -->
<div class="toast" id="toast">βœ… Record marked as complete!</div>
<script>
let pendingId = null;
function confirmDelete(id, name) {
pendingId = id;
document.getElementById("confirmName").textContent = name;
document.getElementById("confirmOverlay").classList.add("show");
}
function closeConfirm() {
document.getElementById("confirmOverlay").classList.remove("show");
pendingId = null;
}
async function doDelete() {
if (!pendingId) return;
closeConfirm();
const card = document.getElementById("card-" + pendingId);
card.classList.add("removing");
const res = await fetch("/admin/delete/" + pendingId, { method: "POST" });
const result = await res.json();
setTimeout(() => {
card.remove();
updateStats(result.total);
showToast();
if (result.total === 0) {
document.getElementById("emptyState").style.display = "block";
}
}, 300);
pendingId = null;
}
function updateStats(total) {
document.getElementById("totalCount").textContent = total;
document.getElementById("totalRevenue").textContent = "β‚Ή" + (total * 200);
}
function showToast() {
const toast = document.getElementById("toast");
toast.classList.add("show");
setTimeout(() => toast.classList.remove("show"), 3000);
}
// Close overlay on background click
document.getElementById("confirmOverlay").addEventListener("click", function(e) {
if (e.target === this) closeConfirm();
});
</script>
</body>
</html>