diff --git a/backend/src/controllers/classroom/sessions.js b/backend/src/controllers/classroom/sessions.js index 160a6d2..61fdb82 100644 --- a/backend/src/controllers/classroom/sessions.js +++ b/backend/src/controllers/classroom/sessions.js @@ -41,6 +41,15 @@ function createSession(req, res) { const session = db.prepare('SELECT * FROM classroom_sessions WHERE id=?').get(sessionId); emitToSession(sessionId, { type: 'classroom_started', sessionId, title, classId: class_id || null, teacherName: teacher.name }); + // Баннер «идёт онлайн-урок» на дашбордах — через SSE-канал (доска работает по WS, + // дашборд по SSE, поэтому нужен отдельный сигнал ученикам класса / приглашённым / учителю). + try { + const sse = require('../../sse'); + const payload = { type: 'classroom_live', state: 'started', sessionId, title, classId: class_id || null }; + if (class_id) sse.emitToClass(class_id, payload); + else if (user_ids) for (const uid of user_ids) sse.emit(uid, payload); + sse.emit(teacher.id, payload); + } catch { /* SSE недоступен — не критично */ } res.json(session); } @@ -74,6 +83,17 @@ function endSession(req, res) { db.prepare('DELETE FROM classroom_draw_permissions WHERE session_id=?').run(sessionId); db.prepare('DELETE FROM classroom_muted WHERE session_id=?').run(sessionId); emitToSession(sessionId, { type: 'classroom_ended', sessionId }); + // Снять баннер «идёт онлайн-урок» с дашбордов (SSE-канал). + try { + const sse = require('../../sse'); + const payload = { type: 'classroom_live', state: 'ended', sessionId }; + if (session.class_id) sse.emitToClass(session.class_id, payload); + else { + const invited = db.prepare('SELECT user_id FROM classroom_invites WHERE session_id=?').all(sessionId); + for (const r of invited) sse.emit(r.user_id, payload); + } + sse.emit(session.teacher_id, payload); + } catch { /* SSE недоступен — не критично */ } res.json({ ok: true }); } diff --git a/frontend/dashboard.html b/frontend/dashboard.html index 3a85a0a..99a6949 100644 --- a/frontend/dashboard.html +++ b/frontend/dashboard.html @@ -82,6 +82,32 @@ .ab-btn:hover { background: rgba(255,255,255,0.25); } /* ── Hero cards row (Reading · Lab of day · Pet) ── */ .hero-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 14px; } + + /* ── Live online-lesson banner ── */ + .live-lesson { + display: flex; align-items: center; gap: 14px; text-decoration: none; + background: linear-gradient(100deg, #059652, #06D6A0); color: #fff; + border-radius: 16px; padding: 14px 20px; margin-bottom: 18px; + box-shadow: 0 6px 22px rgba(5,150,82,0.28); transition: transform .15s, box-shadow .15s; + } + .live-lesson:hover { transform: translateY(-1px); box-shadow: 0 10px 28px rgba(5,150,82,0.34); } + .ll-dot { width: 12px; height: 12px; border-radius: 50%; background: #fff; flex-shrink: 0; + box-shadow: 0 0 0 0 rgba(255,255,255,0.7); animation: llPulse 1.6s infinite; } + @keyframes llPulse { + 0% { box-shadow: 0 0 0 0 rgba(255,255,255,0.6); } + 70% { box-shadow: 0 0 0 10px rgba(255,255,255,0); } + 100% { box-shadow: 0 0 0 0 rgba(255,255,255,0); } + } + .ll-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; } + .ll-text b { font-family: 'Unbounded', sans-serif; font-size: 0.92rem; font-weight: 800; + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } + .ll-text span { font-size: 0.78rem; opacity: 0.92; } + .ll-cta { flex-shrink: 0; background: rgba(255,255,255,0.95); color: #059652; + font-weight: 800; font-size: 0.82rem; padding: 8px 16px; border-radius: 10px; white-space: nowrap; } + @media (max-width: 480px) { + .live-lesson { padding: 12px 14px; gap: 10px; } + .ll-cta { padding: 7px 12px; font-size: 0.78rem; } + } .hero-card { position: relative; border-radius: 18px; padding: 18px 20px; display: flex; flex-direction: column; min-height: 196px; @@ -1532,6 +1558,13 @@
+ + +