import './style.css'; import './app.css'; import logoUrl from './assets/logo.jpeg'; const brandLogoHtml = `Simple Care`; // Trạng thái cục bộ let loggedIn = false; let studentInfo = null; let stats = { wifiSSID: '', onlineSecs: 0, offlineSecs: 0, wsConnected: false, serverReachable: false, allowedApps: '', monitorMode: '', monitorLabel: '', className: '', classCode: '', currentPeriod: 0, currentCourseName: '', shifts: [] }; let chatOpen = false; let chatMessages = []; let chatConversations = []; let activeStaffId = 0; // Quản lý webcam let webcamStream = null; let webcamInterval = null; const webcamVideo = document.createElement('video'); webcamVideo.autoplay = true; webcamVideo.playsInline = true; webcamVideo.style.display = 'none'; document.body.appendChild(webcamVideo); const webcamCanvas = document.createElement('canvas'); webcamCanvas.width = 320; webcamCanvas.height = 240; webcamCanvas.style.display = 'none'; document.body.appendChild(webcamCanvas); function init() { if (typeof window.go === 'undefined' || typeof window.go.main === 'undefined') { setTimeout(init, 200); return; } window.runtime.EventsOn('start_webcam_stream', startWebcam); window.runtime.EventsOn('stop_webcam_stream', stopWebcam); window.runtime.EventsOn('chat:message', () => { updateChatBadge(); loadChatConversations().catch(console.error); if (chatOpen && activeStaffId) loadChatMessages(activeStaffId).catch(console.error); }); window.runtime.EventsOn('chat:notify', (data) => { updateChatBadge(); showChatToast(data); }); checkLogin(); } async function checkLogin() { try { const isLogged = await window.go.main.App.CheckLoginStatus(); if (isLogged) { loggedIn = true; studentInfo = await window.go.main.App.GetStudentInfo(); renderDashboard(); startStatsTicker(); ensureChatWidget(); updateChatBadge(); } else { loggedIn = false; renderLoginPrompt(); } } catch (err) { console.error('Error checking login status:', err); } } function renderLoginPrompt() { document.querySelector('#app').innerHTML = `
`; document.getElementById('btn-goto-login').addEventListener('click', () => { window.go.main.App.NavigateToLogin(); }); } function monitorModeMeta(mode) { switch (mode) { case 'learning': return { icon: '🛡️', label: 'Đang giám sát', cls: 'mode-learning' }; case 'exam': return { icon: '📝', label: 'Phòng thi', cls: 'mode-exam' }; case 'outside_schedule': return { icon: '😴', label: 'Ngoài giờ học', cls: 'mode-outside' }; case 'not_configured': return { icon: '⚙️', label: 'Chưa sẵn sàng', cls: 'mode-config' }; default: return { icon: '⏳', label: 'Đang kết nối', cls: 'mode-pending' }; } } function attendanceClass(status) { if (status === 4) return 'att-ok'; if (status === 3) return 'att-late'; if (status === 1 || status === 2) return 'att-leave'; if (status === 0) return 'att-absent'; return 'att-pending'; } function renderShiftsTable(shifts) { if (!shifts || shifts.length === 0) { return '
Hôm nay không có ca học theo lịch lớp.
'; } return ` ${shifts.map(s => ` `).join('')}
Ca Môn học Giờ Trực tuyến Ngoại tuyến Điểm danh
Ca ${s.period}${s.isActiveNow ? ' đang học' : ''} ${s.courseName || '—'} ${s.startTime}–${s.endTime} ${formatDuration(s.onlineSeconds || 0)} ${formatDuration(s.offlineSeconds || 0)} ${s.attendanceLabel || 'Chưa tính'}
`; } function renderDashboard() { if (!studentInfo) return; const mode = monitorModeMeta(stats.monitorMode); const classLine = stats.className ? `${stats.className}${stats.classCode ? ` (${stats.classCode})` : ''}` : 'Đang xác định lớp...'; document.querySelector('#app').innerHTML = `
${brandLogoHtml}
Simple Care Rikkei Education
Avatar

${studentInfo.fullName}

${studentInfo.studentCode}

${studentInfo.email}

Lớp ${classLine}
Điện thoại ${studentInfo.phone || 'Chưa cung cấp'}
${mode.icon}
${mode.label} ${classLine}
${stats.monitorLabel || stats.statusMsg || 'Đang kết nối...'}
${stats.currentPeriod > 0 && stats.currentCourseName ? `Ca ${stats.currentPeriod} · ${stats.currentCourseName}` : 'Theo dõi theo lịch học của lớp'}
📡
WiFi
${stats.wifiSSID || 'Đang quét...'}
🖥️
Máy chủ
${stats.serverReachable ? 'Đã kết nối' : 'Mất kết nối'}
Hôm nay — theo ca
00:00:00 · 00:00:00 ${stats.sessionDate || ''}
${renderShiftsTable(stats.shifts)}
`; document.getElementById('btn-logout').addEventListener('click', async () => { if (confirm('Bạn có chắc chắn muốn đăng xuất khỏi ứng dụng giám sát?')) { loggedIn = false; studentInfo = null; stopWebcam(); const w = document.getElementById('student-chat-widget'); if (w) w.remove(); await window.go.main.App.Logout(); } }); } function ensureChatWidget() { if (document.getElementById('student-chat-widget')) return; const wrap = document.createElement('div'); wrap.id = 'student-chat-widget'; wrap.className = 'student-chat-dock'; wrap.innerHTML = ` `; document.body.appendChild(wrap); document.getElementById('student-chat-fab').addEventListener('click', toggleChat); document.getElementById('student-chat-close').addEventListener('click', () => setChatOpen(false)); document.getElementById('student-chat-send').addEventListener('click', sendStudentChat); document.getElementById('student-chat-input').addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); sendStudentChat(); } }); } function showChatToast(data) { const toast = document.getElementById('student-chat-toast'); if (!toast) return; const from = data?.from || 'Giảng viên'; const preview = data?.preview || 'Bạn có tin nhắn mới'; toast.innerHTML = `${escapeHtml(from)}${escapeHtml(preview)}`; toast.hidden = false; clearTimeout(showChatToast._t); showChatToast._t = setTimeout(() => { toast.hidden = true; }, 5000); } function setChatOpen(open) { chatOpen = open; const panel = document.getElementById('student-chat-messenger'); if (panel) panel.hidden = !open; if (open) { loadChatConversations().catch(console.error); if (activeStaffId) loadChatMessages(activeStaffId).catch(console.error); } } function toggleChat() { setChatOpen(!chatOpen); } async function loadChatConversations() { const rows = await window.go.main.App.GetChatConversations(); chatConversations = Array.isArray(rows) ? rows : []; renderChatConversations(); updateChatBadge(); } function renderChatConversations() { const list = document.getElementById('student-chat-conv-list'); if (!list) return; if (!chatConversations.length) { list.innerHTML = '
Chưa có tin nhắn
'; return; } list.innerHTML = chatConversations.map((c) => { const sid = Number(c.staffId || 0); const unread = Number(c.unread || 0); const active = sid === activeStaffId ? ' active' : ''; const name = escapeHtml(c.staffName || c.staffEmail || 'Giảng viên'); const preview = escapeHtml(c.lastMessage || ''); return ``; }).join(''); list.querySelectorAll('.student-chat-conv-btn').forEach((btn) => { btn.addEventListener('click', () => { const sid = Number(btn.getAttribute('data-staff-id')); pickStaffChat(sid); }); }); } async function pickStaffChat(staffId) { activeStaffId = staffId; const head = document.getElementById('student-chat-thread-head'); const conv = chatConversations.find((c) => Number(c.staffId) === staffId); if (head) head.textContent = conv?.staffName || conv?.staffEmail || 'Giảng viên'; const input = document.getElementById('student-chat-input'); const sendBtn = document.getElementById('student-chat-send'); if (input) input.disabled = !staffId; if (sendBtn) sendBtn.disabled = !staffId; renderChatConversations(); if (staffId) await loadChatMessages(staffId); } async function loadChatMessages(staffId) { const msgs = await window.go.main.App.GetChatMessages(staffId); chatMessages = Array.isArray(msgs) ? msgs : []; renderChatMessages(); updateChatBadge(); loadChatConversations().catch(console.error); } function renderChatMessages() { const box = document.getElementById('student-chat-messages'); if (!box) return; if (!chatMessages.length) { box.innerHTML = '
Chưa có tin nhắn
'; return; } box.innerHTML = chatMessages.map((m) => { const role = m.senderRole === 'student' ? 'student' : 'staff'; const time = m.createdAt ? new Date(m.createdAt).toLocaleTimeString('vi-VN', { hour: '2-digit', minute: '2-digit' }) : ''; const label = role === 'staff' ? (m.staffName || 'Giảng viên') : 'Bạn'; return `
${label}
${escapeHtml(m.body || '')}
${time}
`; }).join(''); box.scrollTop = box.scrollHeight; } function escapeHtml(s) { return String(s).replace(/&/g, '&').replace(//g, '>'); } async function sendStudentChat() { const input = document.getElementById('student-chat-input'); if (!input || !input.value.trim() || !activeStaffId) return; try { await window.go.main.App.SendChatMessage(input.value.trim()); input.value = ''; await loadChatMessages(activeStaffId); } catch (err) { alert(err?.message || err || 'Gửi tin thất bại'); } } async function updateChatBadge() { const badge = document.getElementById('student-chat-badge'); if (!badge || !window.go?.main?.App?.GetChatUnread) return; try { const n = await window.go.main.App.GetChatUnread(); if (n > 0) { badge.hidden = false; badge.textContent = n > 9 ? '9+' : String(n); } else { badge.hidden = true; } } catch { /* ignore */ } } function formatDuration(totalSeconds) { const hrs = Math.floor(totalSeconds / 3600); const mins = Math.floor((totalSeconds % 3600) / 60); const secs = totalSeconds % 60; const pad = (num) => String(num).padStart(2, '0'); return `${pad(hrs)}:${pad(mins)}:${pad(secs)}`; } function startStatsTicker() { const pullStats = async () => { if (!loggedIn) return; try { const freshStats = await window.go.main.App.GetStats(); stats = { ...stats, ...freshStats }; if (!Array.isArray(stats.shifts)) stats.shifts = []; const wifiEl = document.getElementById('wifi-ssid'); if (wifiEl) wifiEl.innerText = stats.wifiSSID || 'Không có WiFi'; const serverEl = document.getElementById('server-status'); if (serverEl) { serverEl.innerText = stats.serverReachable ? 'Đã kết nối' : 'Mất kết nối'; serverEl.style.color = stats.serverReachable ? 'var(--online-color)' : 'var(--offline-color)'; } const statusMsgEl = document.getElementById('status-message'); if (statusMsgEl) { statusMsgEl.innerText = stats.monitorLabel || stats.statusMsg || 'Đang kết nối...'; } const mode = monitorModeMeta(stats.monitorMode); const statusIconEl = document.getElementById('status-icon'); if (statusIconEl) statusIconEl.innerText = mode.icon; const modeTagEl = document.getElementById('monitor-mode-tag'); if (modeTagEl) modeTagEl.innerText = mode.label; const banner = document.querySelector('.status-banner'); if (banner) banner.className = `card status-banner ${mode.cls}`; const classLine = stats.className ? `${stats.className}${stats.classCode ? ` (${stats.classCode})` : ''}` : 'Đang xác định lớp...'; const classLineEl = document.getElementById('status-class-line'); if (classLineEl) classLineEl.innerText = classLine; const profileClassEl = document.getElementById('profile-class'); if (profileClassEl) profileClassEl.innerText = classLine; const statusSubEl = document.getElementById('status-sub'); if (statusSubEl) { statusSubEl.innerText = stats.currentPeriod > 0 && stats.currentCourseName ? `Ca ${stats.currentPeriod} · ${stats.currentCourseName}` : 'Theo dõi theo lịch học của lớp'; } const onlineEl = document.getElementById('clock-online'); if (onlineEl) onlineEl.innerText = formatDuration(stats.onlineSecs || 0); const offlineEl = document.getElementById('clock-offline'); if (offlineEl) offlineEl.innerText = formatDuration(stats.offlineSecs || 0); const shiftsWrap = document.getElementById('shifts-wrap'); if (shiftsWrap) shiftsWrap.innerHTML = renderShiftsTable(stats.shifts); const sessionDateEl = document.getElementById('session-date'); if (sessionDateEl && stats.sessionDate) sessionDateEl.innerText = stats.sessionDate; const indicator = document.querySelector('.status-indicator'); if (indicator) { indicator.className = `status-indicator ${stats.serverReachable ? 'online' : 'offline'}`; } updateChatBadge(); } catch (err) { console.error('Error fetching stats:', err); } }; pullStats(); setInterval(pullStats, 1000); } async function startWebcam() { if (webcamStream) return; try { webcamStream = await navigator.mediaDevices.getUserMedia({ video: { width: 320, height: 240, frameRate: { max: 10 } } }); webcamVideo.srcObject = webcamStream; webcamInterval = setInterval(() => { const ctx = webcamCanvas.getContext('2d'); if (ctx) { ctx.drawImage(webcamVideo, 0, 0, webcamCanvas.width, webcamCanvas.height); const dataUrl = webcamCanvas.toDataURL('image/jpeg', 0.4); window.go.main.App.SendWebcamFrame(dataUrl); } }, 250); } catch (err) { console.error('Failed to open webcam:', err); } } function stopWebcam() { if (webcamInterval) { clearInterval(webcamInterval); webcamInterval = null; } if (webcamStream) { webcamStream.getTracks().forEach(track => track.stop()); webcamStream = null; } webcamVideo.srcObject = null; } init();