👨👩👧 Parent Tracking Link
—
🎛️ Display Management
×
🔑 Student Passcode
Each student needs a unique passcode for parent login. Quick setup below:
⏳ Loading student list...
🔗 Parent Portal URL
Parents access this link → enter Student ID (NEX-XXXXXX) + Passcode to view progress
🎛️ Manage Content Visible to Parents
×
⚙️ Class-wide Settings
Show Teacher Comments
Teacher feedback and comments
💾 Save Class Settings
✏️ Adjust Display Scores
Original scores in DB are NOT changed . Only changes what parents see on the portal.
── Select student ──
Load
Select a student to view and adjust displayed scores
💾 Save Adjustments
📱
Telegram Notifications
Class Name
×
VARIABLES (click to insert)
{{className}}
{{sessNum}}
{{date}}
{{present}}
{{total}}
{{pct}}
{{absentList}}
{{closeNote}}
{{remaining}}
{{studentName}}
{{lectureName}}
{{score}}
{{submittedAt}}
🧪 Test
💾 Save Settings
SELECT THEME — 10 THEMES
All themes are eye-friendly
🏛️
NEXUS OFFICE OF ADMINISTRATION
Restricted area. Enter your Firebase account password.
🔑 VERIFY & ENTER
✕ Close
🏛️
NEXUS OFFICE OF ADMINISTRATION
Supreme Administration Office · Super Admin
✕ Close
📊 Overview
📝 Activity Logs
🗄️ Data Manager
⚙️ System Config
Nexus Office Viewer v1.0 — More features coming soon
🏗️ ROADMAP — This section will be expanded
✅ Storage Monitor
Firebase quota tracking · Completed
🔜 Tuition Manager
Tuition management · Coming soon
🔜 Attendance Dashboard
System-wide attendance reports
🔜 AI Report Generator
Auto-generate academic reports
⏳ Click on the Logs tab to load data...
⚠️ Data Manager — Danger Zone
All operations here directly affect Firestore Production. Will be built in the next session.
[ COMING SOON — Data Cleanup · Bulk Export · Migration Tools ]
⚙️ System Config
Global settings · Feature flags · API configs. Will be built in the next session.
const NEXUS_THEMES = [
// DARK THEMES (Mệnh Kim)
{ id:'mk_dark', name:'Mệnh Kim Tối', dark:true, swatch:'linear-gradient(135deg,#0a0b0e,#D4AF37)',
vars:{ '--bg-dark':'#0a0b0e','--bg-light':'#16181d','--accent':'#D4AF37','--accent-glow':'rgba(212,175,55,0.4)','--text':'#F8F9FA','--text-muted':'#94a3b8','--card-bg':'rgba(22,24,29,0.7)','--border-color':'rgba(255,255,255,0.08)','--border-accent':'rgba(212,175,55,0.3)' },
body:'radial-gradient(circle at 50% 0%, #1e2128 0%, #0a0b0e 100%)' },
{ id:'mk_night', name:'Đêm Bạch Kim', dark:true, swatch:'linear-gradient(135deg,#080a14,#C0C0C0)',
vars:{ '--bg-dark':'#080a14','--bg-light':'#111525','--accent':'#C0C0C0','--accent-glow':'rgba(192,192,192,0.35)','--text':'#e2e8f0','--text-muted':'#64748b','--card-bg':'rgba(17,21,37,0.8)','--border-color':'rgba(255,255,255,0.07)','--border-accent':'rgba(192,192,192,0.25)' },
body:'radial-gradient(circle at 50% 0%, #141928 0%, #080a14 100%)' },
{ id:'mk_emerald', name:'Lam Ngọc Tối', dark:true, swatch:'linear-gradient(135deg,#091210,#10b981)',
vars:{ '--bg-dark':'#091210','--bg-light':'#0f1f1a','--accent':'#10b981','--accent-glow':'rgba(16,185,129,0.4)','--text':'#f0fdf4','--text-muted':'#6b9e8a','--card-bg':'rgba(15,31,26,0.8)','--border-color':'rgba(16,185,129,0.08)','--border-accent':'rgba(16,185,129,0.25)' },
body:'radial-gradient(circle at 50% 0%, #0f2a20 0%, #091210 100%)' },
{ id:'mk_sapphire',name:'Sapphire Tối', dark:true, swatch:'linear-gradient(135deg,#080c18,#38bdf8)',
vars:{ '--bg-dark':'#080c18','--bg-light':'#0f1629','--accent':'#38bdf8','--accent-glow':'rgba(56,189,248,0.4)','--text':'#f0f9ff','--text-muted':'#5e8fb5','--card-bg':'rgba(15,22,41,0.8)','--border-color':'rgba(56,189,248,0.07)','--border-accent':'rgba(56,189,248,0.25)' },
body:'radial-gradient(circle at 50% 0%, #0a1a35 0%, #080c18 100%)' },
{ id:'mk_purple', name:'Tím Huyền Bí', dark:true, swatch:'linear-gradient(135deg,#0c0814,#a78bfa)',
vars:{ '--bg-dark':'#0c0814','--bg-light':'#160f2a','--accent':'#a78bfa','--accent-glow':'rgba(167,139,250,0.4)','--text':'#f5f3ff','--text-muted':'#7c6b9e','--card-bg':'rgba(22,15,42,0.8)','--border-color':'rgba(139,92,246,0.08)','--border-accent':'rgba(139,92,246,0.25)' },
body:'radial-gradient(circle at 50% 0%, #1c0f35 0%, #0c0814 100%)' },
{ id:'mk_obsidian',name:'Obsidian', dark:true, swatch:'linear-gradient(135deg,#000000,#222)',
vars:{ '--bg-dark':'#000000','--bg-light':'#0a0a0a','--accent':'#D4AF37','--accent-glow':'rgba(212,175,55,0.3)','--text':'#e5e5e5','--text-muted':'#555','--card-bg':'rgba(10,10,10,0.9)','--border-color':'rgba(255,255,255,0.05)','--border-accent':'rgba(212,175,55,0.2)' },
body:'#000' },
// LIGHT THEMES (Mệnh Kim · Eye-friendly)
{ id:'mk_light', name:'Mệnh Kim Sáng', dark:false, swatch:'linear-gradient(135deg,#faf7f0,#D4AF37)',
vars:{ '--bg-dark':'#f5f0e8','--bg-light':'#faf7f0','--accent':'#B8860B','--accent-glow':'rgba(184,134,11,0.35)','--text':'#1a1208','--text-muted':'#7a6840','--card-bg':'rgba(255,250,240,0.9)','--border-color':'rgba(0,0,0,0.08)','--border-accent':'rgba(184,134,11,0.3)' },
body:'linear-gradient(135deg,#faf7f0 0%,#f0ead8 100%)', light:true },
{ id:'mk_pearl', name:'Ngọc Trai Trắng',dark:false, swatch:'linear-gradient(135deg,#f8f9fa,#C0C0C0)',
vars:{ '--bg-dark':'#f1f3f5','--bg-light':'#f8f9fa','--accent':'#475569','--accent-glow':'rgba(71,85,105,0.3)','--text':'#1e293b','--text-muted':'#64748b','--card-bg':'rgba(248,249,250,0.95)','--border-color':'rgba(0,0,0,0.07)','--border-accent':'rgba(100,116,139,0.3)' },
body:'#f1f3f5', light:true },
{ id:'mk_rose', name:'Hồng Ngọc Nhẹ', dark:false, swatch:'linear-gradient(135deg,#fef2f2,#fb7185)',
vars:{ '--bg-dark':'#fdf2f2','--bg-light':'#fef8f8','--accent':'#e11d48','--accent-glow':'rgba(225,29,72,0.3)','--text':'#1c0a10','--text-muted':'#9f5060','--card-bg':'rgba(255,248,249,0.95)','--border-color':'rgba(0,0,0,0.06)','--border-accent':'rgba(225,29,72,0.2)' },
body:'linear-gradient(135deg,#fef2f2 0%,#fde8ec 100%)', light:true },
{ id:'mk_mint', name:'Bạc Hà Xanh', dark:false, swatch:'linear-gradient(135deg,#f0fdf4,#10b981)',
vars:{ '--bg-dark':'#f0fdf4','--bg-light':'#f6fefb','--accent':'#059669','--accent-glow':'rgba(5,150,105,0.3)','--text':'#052e16','--text-muted':'#3d7a5a','--card-bg':'rgba(240,253,244,0.95)','--border-color':'rgba(0,0,0,0.06)','--border-accent':'rgba(5,150,105,0.2)' },
body:'linear-gradient(135deg,#f0fdf4 0%,#dcfce7 100%)', light:true },
];
let _activeTheme = localStorage.getItem('nexus_theme') || 'mk_dark';
let _themePickerOpen = false;
function applyTheme(id) {
const t = NEXUS_THEMES.find(x => x.id === id);
if(!t) return;
const root = document.documentElement.style;
Object.entries(t.vars).forEach(([k,v]) => root.setProperty(k, v));
document.body.style.background = t.body;
// Light themes: adjust scrollbar
if(t.light) {
document.documentElement.style.setProperty('--scrollbar-thumb','rgba(0,0,0,0.2)');
document.body.classList.add('theme-light');
} else {
document.body.classList.remove('theme-light');
}
_activeTheme = id;
localStorage.setItem('nexus_theme', id);
// Update active state in dropdown
document.querySelectorAll('.theme-btn').forEach(b => {
b.classList.toggle('active', b.dataset.tid === id);
});
}
function buildThemeGrid() {
const grid = document.getElementById('themeGrid');
if(!grid) return;
grid.innerHTML = NEXUS_THEMES.map(t => `
${t.name}
${t.dark===false ? '☀️ ' : '🌙 '}
`).join('');
}
window.toggleThemePicker = function(e) {
e.stopPropagation();
const dd = document.getElementById('themePickerDropdown');
_themePickerOpen = !_themePickerOpen;
if(_themePickerOpen) {
const btn = document.getElementById('btnThemePicker');
const rect = btn.getBoundingClientRect();
dd.style.top = (rect.bottom + 8) + 'px';
dd.style.right = (window.innerWidth - rect.right) + 'px';
dd.style.left = 'auto';
dd.style.display = 'block';
buildThemeGrid();
} else { dd.style.display = 'none'; }
};
document.addEventListener('click', () => {
if(_themePickerOpen) {
document.getElementById('themePickerDropdown').style.display = 'none';
_themePickerOpen = false;
}
});
document.getElementById('themePickerDropdown')?.addEventListener('click', e => e.stopPropagation());
// Apply saved theme on load
applyTheme(_activeTheme);
// ═══════════════════════════════════════════════════════════════
// 🔐 NEXUS OFFICE GATE
// Uses Firebase reauthentication with the admin's actual password
// ═══════════════════════════════════════════════════════════════
window.openOfficeGate = function() {
const modal = document.getElementById('officeGateModal');
modal.style.display = 'flex';
document.getElementById('officePassInput').value = '';
document.getElementById('officePassErr').innerHTML = '';
setTimeout(() => document.getElementById('officePassInput').focus(), 100);
};
window.closeOfficeGate = function() {
document.getElementById('officeGateModal').style.display = 'none';
};
window.verifyOfficePass = async function() {
const pass = document.getElementById('officePassInput').value;
const errEl = document.getElementById('officePassErr');
const btn = document.getElementById('btnOfficeVerify');
if(!pass) { errEl.innerHTML = '⚠️ Nhập mật khẩu để tiếp tục'; return; }
btn.textContent = '⏳ Đang xác thực...'; btn.disabled = true;
errEl.innerHTML = '';
try {
// Import Firebase auth modules (already loaded in main script)
const { EmailAuthProvider, reauthenticateWithCredential } = await import('https://www.gstatic.com/firebasejs/10.12.0/firebase-auth.js');
const user = window._nexusCurrentUser; // set by main auth listener
if(!user) throw new Error('Chưa đăng nhập');
const cred = EmailAuthProvider.credential(user.email, pass);
await reauthenticateWithCredential(user, cred);
// SUCCESS
closeOfficeGate();
openOfficeViewer(user);
} catch(e) {
const msg = e.code === 'auth/wrong-password' || e.code === 'auth/invalid-credential'
? '❌ Sai mật khẩu. Thử lại.'
: e.code === 'auth/too-many-requests'
? '🔒 Quá nhiều lần thử. Đợi vài phút.'
: '❌ ' + e.message;
errEl.innerHTML = msg;
document.getElementById('officePassInput').select();
} finally {
btn.textContent = '🔑 XÁC THỰC & VÀO'; btn.disabled = false;
}
};
function openOfficeViewer(user) {
const viewer = document.getElementById('nexusOfficeViewer');
viewer.style.display = 'flex';
document.getElementById('officeAdminEmail').textContent = user.email;
officeLoadOverviewStats();
}
window.closeOfficeViewer = function() {
document.getElementById('nexusOfficeViewer').style.display = 'none';
};
window.officeTab = function(tab, el) {
document.querySelectorAll('.off-tab').forEach(b => b.classList.remove('active'));
el.classList.add('active');
document.querySelectorAll('.off-pane').forEach(p => p.style.display = 'none');
document.getElementById('offTab' + tab.charAt(0).toUpperCase() + tab.slice(1)).style.display = '';
if(tab === 'logs') officeLoadLogs();
};
async function officeLoadOverviewStats() {
try {
const { collection, getDocs } = await import('https://www.gstatic.com/firebasejs/10.12.0/firebase-firestore.js');
const db2 = window._nexusDB; // reuse existing db
const [stu, lec, res, cls] = await Promise.all([
getDocs(collection(db2,'students')), getDocs(collection(db2,'lectures')),
getDocs(collection(db2,'results')), getDocs(collection(db2,'classes'))
]);
document.getElementById('offStat1').textContent = stu.size;
document.getElementById('offStat2').textContent = lec.size;
document.getElementById('offStat3').textContent = res.size;
document.getElementById('offStat4').textContent = cls.size;
} catch(e) { console.error('Office stats', e); }
}
async function officeLoadLogs() {
const el = document.getElementById('offLogsContent');
el.innerHTML = '
⏳ Đang tải logs...
';
try {
const { collection, getDocs, orderBy, query, limit } = await import('https://www.gstatic.com/firebasejs/10.12.0/firebase-firestore.js');
const snap = await getDocs(query(collection(window._nexusDB,'activity_logs'), orderBy('timestamp','desc'), limit(50)));
if(snap.empty) { el.innerHTML = '
No logs yet.
'; return; }
let rows = '';
snap.forEach(d => {
const data = d.data();
const time = data.timestamp?.toDate?.()?.toLocaleString('vi-VN') || '—';
const roleColor = data.role==='Học viên'?'#38bdf8':'#f59e0b';
rows += `
${time}
${data.userName||'—'}
${data.role||'—'}
${data.action||''} · ${data.details||''}
`;
});
el.innerHTML = `
${rows}
`;
} catch(e) { el.innerHTML = `
❌ ${e.message}
`; }
}