feat(admin-dash): P0 — honest zeros, refresh+timestamp, hero hierarchy, stuck-sessions alert
- fmtNum: 0 no longer renders as "—" (muted "0" via .ov-zero instead) - backend: classesTotal (renamed from activeClasses — was already full count, label fixed) - backend: abandonedSessions24h (was failedSessions24h status!=completed; now only status=abandoned) - backend: stuckSessions[] — in_progress > 1h with user/subject join, limit 5 - header: timestamp + manual refresh button (.ov-header flex layout), updates every 30s via interval - newSessions24h card promoted to hero (2.6rem value, 52px icon, 2fr column ≥720px) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -35,8 +35,18 @@ const overviewStmts = {
|
||||
newUsers24h: db.prepare("SELECT COUNT(*) AS n FROM users WHERE created_at >= datetime('now', '-24 hours')"),
|
||||
newSessions24h: db.prepare("SELECT COUNT(*) AS n FROM test_sessions WHERE started_at >= datetime('now', '-24 hours')"),
|
||||
activeUsers24h: db.prepare("SELECT COUNT(*) AS n FROM users WHERE last_login IS NOT NULL AND last_login >= datetime('now', '-24 hours')"),
|
||||
failedSessions24h: db.prepare("SELECT COUNT(*) AS n FROM test_sessions WHERE started_at >= datetime('now', '-24 hours') AND status != 'completed'"),
|
||||
activeClasses: db.prepare('SELECT COUNT(*) AS n FROM classes'),
|
||||
abandonedSessions24h: db.prepare("SELECT COUNT(*) AS n FROM test_sessions WHERE started_at >= datetime('now', '-24 hours') AND status = 'abandoned'"),
|
||||
classesTotal: db.prepare('SELECT COUNT(*) AS n FROM classes'),
|
||||
stuckSessions: db.prepare(`
|
||||
SELECT ts.id, u.name AS user_name, s.name AS subject_name, ts.started_at
|
||||
FROM test_sessions ts
|
||||
JOIN users u ON u.id = ts.user_id
|
||||
LEFT JOIN subjects s ON s.id = ts.subject_id
|
||||
WHERE ts.status = 'in_progress'
|
||||
AND ts.started_at < datetime('now', '-1 hour')
|
||||
ORDER BY ts.started_at
|
||||
LIMIT 5
|
||||
`),
|
||||
// No banned_at column — fall back to audit log for recent bans (last 7 days)
|
||||
bannedThisWeek: db.prepare(`
|
||||
SELECT u.id, u.name, u.email, al.created_at AS banned_at
|
||||
@@ -72,10 +82,11 @@ function getOverview(_req, res) {
|
||||
newUsers24h: overviewStmts.newUsers24h.get().n,
|
||||
newSessions24h: overviewStmts.newSessions24h.get().n,
|
||||
activeUsers24h: overviewStmts.activeUsers24h.get().n,
|
||||
activeClasses: overviewStmts.activeClasses.get().n,
|
||||
failedSessions24h: overviewStmts.failedSessions24h.get().n,
|
||||
bannedThisWeek: overviewStmts.bannedThisWeek.all(),
|
||||
topSessions24h: overviewStmts.topSessions24h.all(),
|
||||
classesTotal: overviewStmts.classesTotal.get().n,
|
||||
abandonedSessions24h: overviewStmts.abandonedSessions24h.get().n,
|
||||
stuckSessions: overviewStmts.stuckSessions.all(),
|
||||
bannedThisWeek: overviewStmts.bannedThisWeek.all(),
|
||||
topSessions24h: overviewStmts.topSessions24h.all(),
|
||||
});
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message });
|
||||
|
||||
Reference in New Issue
Block a user