// Ensure tab event listeners are attached after everything is defined window.addEventListener('DOMContentLoaded', () => { setTimeout(() => { const tabButtons = document.querySelectorAll('#adminTabs button'); tabButtons.forEach(button => { button.addEventListener('click', (e) => { e.preventDefault(); const tabId = button.getAttribute('data-tab'); if (tabId && typeof window.switchTab === 'function') { window.switchTab(tabId); } }); }); }, 0); }); // Global variables for admin functionality // --- Weeks input logic --- // Populate active week dropdown from weeksContainer window.populateActiveWeekDropdown = function() { const select = document.getElementById('activeWeekSelect'); select.innerHTML = ''; document.querySelectorAll('#weeksContainer .week-input-card').forEach(card => { const date = card.querySelector('.week-date').value; if (date) { const option = document.createElement('option'); option.value = date; option.textContent = date; select.appendChild(option); } }); }; // Call populateActiveWeekDropdown whenever weeks change const observer = new MutationObserver(() => populateActiveWeekDropdown()); observer.observe(document.getElementById('weeksContainer'), { childList: true, subtree: true }); window.setActiveWeek = async function() { const selectedUser = document.getElementById('wordUserSelect')?.value; const activeWeek = document.getElementById('activeWeekSelect')?.value; if (!selectedUser || !activeWeek) { alert('⚠️ Please select a user and week.'); return; } await fetch('/setActiveWeek', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: selectedUser, activeWeek }) }); alert(`✅ Active week set for ${selectedUser}: ${activeWeek}`); } window.addWeekInput = function() { const container = document.getElementById('weeksContainer'); const weekIdx = container.children.length; const weekDiv = document.createElement('div'); weekDiv.className = 'week-input-card'; weekDiv.style = 'border:1px solid #ccc; padding:1rem; margin-bottom:1rem;'; weekDiv.innerHTML = `

`; container.appendChild(weekDiv); } window.saveWeeks = async function() { const selectedUser = document.getElementById('wordUserSelect')?.value; if (!selectedUser) { alert('⚠️ Please select a user before saving.'); return; } const weeks = []; document.querySelectorAll('#weeksContainer .week-input-card').forEach(card => { const date = card.querySelector('.week-date').value; const words = card.querySelector('.week-words').value.split('\n').map(w => w.trim()).filter(Boolean); if (date && words.length) { weeks.push({ date, words }); } }); if (!weeks.length) { alert('⚠️ Please enter at least one week with a date and words.'); return; } await fetch('/saveWeeksWordList', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: selectedUser, weeks }) }); alert(`✅ Weeks saved for ${selectedUser}`); } window.clearWeeks = function() { document.getElementById('weeksContainer').innerHTML = ''; } let allWords = []; let adminUsers = []; // 🧭 Admin tab switcher (make it globally accessible) window.switchTab = function switchTab(targetId) { console.log('🔄 Switching to tab:', targetId); // Hide all tabs document.querySelectorAll('.adminTab').forEach(tab => { tab.classList.add('hidden'); }); // Remove active class from all buttons document.querySelectorAll('#adminTabs button').forEach(btn => { btn.classList.remove('active'); }); // Show the selected tab const targetTab = document.getElementById(targetId); if (targetTab) { targetTab.classList.remove('hidden'); console.log('✅ Showing tab:', targetId); } else { console.error('❌ Tab not found:', targetId); } // Highlight the active button document.querySelectorAll('#adminTabs button').forEach(btn => { if (btn.getAttribute('data-tab') === targetId) { btn.classList.add('active'); } }); // 🔁 Trigger tab-specific logic switch(targetId) { case 'tabSessions': if (typeof populateSessionUserDropdown === 'function') { populateSessionUserDropdown(); } const sessionList = document.getElementById("userSessionList"); if (sessionList) sessionList.innerHTML = ""; break; case 'tabReports': if (typeof loadStudentNamesForReport === 'function') { loadStudentNamesForReport(); } break; case 'tabPasswords': populatePasswordUserDropdown(); break; case 'tabBadges': if (typeof loadBadgeViewer === 'function') { loadBadgeViewer(); } break; case 'tabAnalytics': if (typeof refreshAnalytics === 'function') { refreshAnalytics(); } break; case 'tabResults': if (typeof loadStudentResults === 'function') { loadStudentResults(); } break; case 'tabWords': loadUserDropdowns(); break; } // Attach admin tab event listeners after switchTab is defined window.addEventListener('DOMContentLoaded', () => { const tabButtons = document.querySelectorAll('#adminTabs button'); tabButtons.forEach(button => { button.addEventListener('click', (e) => { e.preventDefault(); const tabId = button.getAttribute('data-tab'); if (tabId) { window.switchTab(tabId); } }); }); }); }; // Load users from server async function loadUsers() { try { const res = await fetch('/getUsers'); adminUsers = await res.json(); } catch (err) { console.error("Failed to load users:", err); adminUsers = []; } } // ➕ Add a new user async function addNewUser() { const username = document.getElementById("newUsername").value.trim(); const password = document.getElementById("newPassword").value; const role = document.getElementById("newRole").value; const msg = document.getElementById("userAddMessage"); const hashPreview = document.getElementById("hashPreview"); if (!username || !password) { msg.textContent = "Please fill out all fields."; return; } const hash = hashPassword(password); if (hashPreview) hashPreview.textContent = hash; try { await fetch('/addUser', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, hash, role }) }); msg.textContent = `✅ User "${username}" added to server.`; document.getElementById("newUsername").value = ''; document.getElementById("newPassword").value = ''; if (hashPreview) hashPreview.textContent = '[auto]'; await loadUsers(); displayUserDropdown(); populateWordUserDropdown(); } catch (e) { msg.textContent = "❌ Failed to add user."; console.error("Add user error:", e); } } // 🗑 Populate and delete users function displayUserDropdown() { const dropdown = document.getElementById("userDropdown"); dropdown.innerHTML = ``; adminUsers.forEach(user => { dropdown.innerHTML += ``; }); } function selectUserAction(username) { if (!username) return; if (!confirm(`Are you sure you want to delete user "${username}"?`)) { document.getElementById("userDropdown").value = ""; return; } fetch("/deleteUser", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username }) }) .then(res => res.text()) .then(msg => { alert(msg); loadUsers().then(displayUserDropdown); }); } // 📚 Save or clear word list for current user async function saveWords() { const selectedUser = document.getElementById('wordUserSelect')?.value; if (!selectedUser) { alert("⚠️ Please select a user before saving."); return; } const text = document.getElementById('wordInput').value.trim(); allWords = text.split('\n').map(w => w.trim()).filter(Boolean); await fetch('/saveWordList', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: selectedUser, words: allWords }) }); displayWordList(); alert(`✅ Word list saved to server for ${selectedUser}`); } async function clearWords() { const selectedUser = document.getElementById('wordUserSelect')?.value; if (!selectedUser) return; allWords = []; await fetch('/saveWordList', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: selectedUser, words: [] }) }); displayWordList(); alert(`🗑 Word list cleared for ${selectedUser}`); } // 🗂 Load and display word lists async function loadWordsForSelectedUser() { const selectedUser = document.getElementById("wordUserSelect").value; if (!selectedUser) return; // Fetch the user's word list object (could be array or {weeks, activeWeek}) const res = await fetch(`/getWordList?username=${encodeURIComponent(selectedUser)}`); const data = await res.json(); allWords = data.words || []; // Now also fetch the raw wordlists object to get weeks/activeWeek const wordlistsRes = await fetch('/getWordlistsRaw'); const wordlists = await wordlistsRes.json(); const userList = wordlists[selectedUser]; // If userList is an object with weeks, populate week cards and dropdown if (userList && typeof userList === 'object' && Array.isArray(userList.weeks)) { // Clear current week cards document.getElementById('weeksContainer').innerHTML = ''; userList.weeks.forEach(week => { window.addWeekInput(); const cards = document.querySelectorAll('#weeksContainer .week-input-card'); const lastCard = cards[cards.length - 1]; if (lastCard) { lastCard.querySelector('.week-date').value = week.date; lastCard.querySelector('.week-words').value = (week.words || []).join('\n'); } }); // Repopulate the active week dropdown window.populateActiveWeekDropdown(); // Set the dropdown to the saved activeWeek if present if (userList.activeWeek) { const select = document.getElementById('activeWeekSelect'); select.value = userList.activeWeek; } } displayWordList(); } function displayWordList() { const ul = document.getElementById('wordListDisplay'); ul.innerHTML = allWords.map(word => `
  • ${word}
  • `).join(''); } function populateWordUserDropdown() { const selects = document.querySelectorAll(".word-user-dropdown"); selects.forEach(select => { select.innerHTML = ''; adminUsers.forEach(user => { const option = document.createElement("option"); option.value = user.username; option.textContent = user.username; select.appendChild(option); }); }); } async function displayWordListForSelectedUser() { const select = document.getElementById("viewUserSelect"); const username = select.value; const list = document.getElementById("userWordListDisplay"); list.innerHTML = ''; if (!username) return; try { const res = await fetch(`/getWordList?username=${username}`); const data = await res.json(); const words = data.words || []; if (!words.length) { list.innerHTML = "
  • No words found for this user.
  • "; return; } words.forEach(word => { const li = document.createElement("li"); li.textContent = word; list.appendChild(li); }); } catch (err) { console.error("Failed to load word list for user:", username, err); list.innerHTML = `
  • ❌ Error loading words
  • `; } } // 📊 Student result viewer window.loadStudentResults = async function() { const list = document.getElementById("resultsList"); list.innerHTML = ''; try { const res = await fetch('/getResults'); const results = await res.json(); if (!Object.keys(results).length) { list.innerHTML = '
  • No results available yet.
  • '; return; } for (const [username, userResults] of Object.entries(results)) { // Handle array of results per user if (Array.isArray(userResults) && userResults.length > 0) { userResults.forEach((result, index) => { const li = document.createElement("li"); let answersHTML = ''; // Check if result and answers exist and are valid if (result && result.answers && Array.isArray(result.answers)) { result.answers.forEach(entry => { const status = entry.correct ? '✅' : '❌'; answersHTML += `${status} ${entry.word}
    `; }); } else { answersHTML = 'No answer data available'; } const timestamp = result.timestamp ? new Date(result.timestamp).toLocaleString() : 'Unknown time'; // Label result type let typeLabel = ''; if (result.type === 'typing') { typeLabel = '[Typing Practice] '; } else { typeLabel = '[Game] '; } li.innerHTML = ` ${typeLabel}${username} (Session ${index + 1}) — Score: ${result?.score || 'N/A'}, Completed: ${result?.completed ? '✅' : '❌'}
    Date: ${timestamp}
    Answers:
    ${answersHTML}
    `; list.appendChild(li); }); } else { // Fallback for single result format const li = document.createElement("li"); li.innerHTML = `${username} — No valid results found`; list.appendChild(li); } } } catch (err) { console.error("🔴 Failed to load results:", err); list.innerHTML = '
  • Error loading results.
  • '; } } async function loadUserDropdowns() { try { const res = await fetch('/getUsers'); const users = await res.json(); const dropdowns = [ document.getElementById('userDropdown'), document.getElementById('wordUserSelect'), document.getElementById('viewUserSelect') ]; dropdowns.forEach(drop => { if (!drop) return; drop.innerHTML = ''; users.forEach(u => { const opt = document.createElement('option'); opt.value = u.username; opt.textContent = u.username; drop.appendChild(opt); }); }); console.log('✅ User dropdowns loaded'); } catch (err) { console.error("⚠️ Failed to load user list:", err); } } async function refreshAnalytics() { const summary = document.getElementById("analyticsSummary"); const breakdown = document.getElementById("studentBreakdown"); summary.innerHTML = "
  • Loading...
  • "; breakdown.innerHTML = ""; try { const res = await fetch('/getResults'); const data = await res.json(); let totalScore = 0, sessionCount = 0; const missMap = {}; for (const username in data) { const { score, completed, answers } = data[username]; if (!completed || !Array.isArray(answers)) continue; totalScore += score; sessionCount++; // Count missed words answers.forEach(({ word, correct }) => { if (!correct) { missMap[word] = (missMap[word] || 0) + 1; } }); // Per-student breakdown const li = document.createElement("li"); li.innerHTML = `${username}: ${score}/${answers.length} correct`; breakdown.appendChild(li); } const avgScore = sessionCount ? (totalScore / sessionCount).toFixed(2) : "0"; let mostMissed = "-", highestMisses = 0; for (const word in missMap) { if (missMap[word] > highestMisses) { highestMisses = missMap[word]; mostMissed = word; } } summary.innerHTML = `
  • Average Score: ${avgScore}
  • Total Completed Sessions: ${sessionCount}
  • Most Missed Word: ${mostMissed} (${highestMisses} misses)
  • `; } catch (err) { console.error("📉 Failed to refresh analytics:", err); summary.innerHTML = '
  • Error loading analytics.
  • '; } } async function loadBadgeViewer() { const res = await fetch('/getBadges'); const badgeData = await res.json(); const userList = document.getElementById("badgeUserList"); userList.innerHTML = ""; // Keep track of badge statistics let totalBadges = 0; let mostBadgesStudent = { name: '-', count: 0 }; let badgeCounts = {}; let badgesThisWeek = 0; let currentWeekStart = new Date(); currentWeekStart.setDate(currentWeekStart.getDate() - 7); // Process badge data for (const username in badgeData) { // Create student list items const li = document.createElement("li"); li.innerHTML = ``; userList.appendChild(li); // Calculate badge statistics let userBadgeCount = 0; if (Array.isArray(badgeData[username])) { // Old format userBadgeCount = badgeData[username].length; // Track badge types badgeData[username].forEach(badge => { if (!badgeCounts[badge]) badgeCounts[badge] = 0; badgeCounts[badge]++; }); } else if (badgeData[username] && badgeData[username].earned) { // New format userBadgeCount = badgeData[username].earned.length; // Track badge types badgeData[username].earned.forEach(badge => { // Count total badges totalBadges++; // Count by badge name const badgeName = badge.name; if (!badgeCounts[badgeName]) badgeCounts[badgeName] = 0; badgeCounts[badgeName]++; // Count recent badges if (badge.earnedAt) { const earnedDate = new Date(badge.earnedAt); if (earnedDate > currentWeekStart) { badgesThisWeek++; } } }); } // Track user with most badges if (userBadgeCount > mostBadgesStudent.count) { mostBadgesStudent = { name: username, count: userBadgeCount }; } } // Find most common badge let mostCommonBadge = { name: '-', count: 0 }; for (const badge in badgeCounts) { if (badgeCounts[badge] > mostCommonBadge.count) { mostCommonBadge = { name: badge, count: badgeCounts[badge] }; } } // Update badge statistics display document.getElementById('totalBadgesAwarded').textContent = totalBadges; document.getElementById('mostBadgesStudent').textContent = mostBadgesStudent.name !== '-' ? `${mostBadgesStudent.name} (${mostBadgesStudent.count})` : '-'; document.getElementById('mostCommonBadge').textContent = mostCommonBadge.name !== '-' ? `${mostCommonBadge.name} (${mostCommonBadge.count})` : '-'; document.getElementById('badgesThisWeek').textContent = badgesThisWeek; // Add search functionality const searchInput = document.getElementById('badgeStudentSearch'); searchInput.addEventListener('input', function() { const searchTerm = this.value.toLowerCase(); document.querySelectorAll('#badgeUserList li').forEach(li => { const username = li.textContent.toLowerCase(); if (username.includes(searchTerm)) { li.style.display = ''; } else { li.style.display = 'none'; } }); }); // Add filter functionality document.querySelectorAll('.badge-filters button').forEach(button => { button.addEventListener('click', function() { // Update active state document.querySelectorAll('.badge-filters button').forEach(btn => { btn.classList.remove('active'); }); this.classList.add('active'); // Apply filter const filter = this.getAttribute('data-filter'); const badgeCards = document.querySelectorAll('.admin-badge-card'); badgeCards.forEach(card => { if (filter === 'all' || card.getAttribute('data-category') === filter) { card.style.display = ''; } else { card.style.display = 'none'; } }); }); }); // Initialize print certificates functionality document.getElementById('printBadges').addEventListener('click', function() { const username = document.getElementById('badgeUserTitle').textContent.replace('Badges for ', ''); printBadgeCertificate(username); }); // Initialize custom badge award functionality document.getElementById('awardCustomBadge').addEventListener('click', function() { const username = document.getElementById('badgeUserTitle').textContent.replace('Badges for ', ''); showCustomBadgeModal(username); }); } function showUserBadges(username) { fetch('/getBadges') .then(res => res.json()) .then(data => { const userBadgeData = data[username]; const title = document.getElementById("badgeUserTitle"); const badgeList = document.getElementById("badgeUserBadges"); const details = document.getElementById("badgeDetails"); let badges = []; // Handle different badge data formats if (!userBadgeData) { badges = []; } else if (Array.isArray(userBadgeData)) { // Old format badges = userBadgeData.map(name => ({ name, icon: "🏅", color: "#4a5568" })); } else if (userBadgeData.earned) { // New format badges = userBadgeData.earned; } title.textContent = `Badges for ${username}`; badgeList.innerHTML = ""; // Create badge stats summary const badgeStats = document.createElement("div"); badgeStats.className = "badge-stats"; // Calculate badge counts by category const categoryStats = {}; if (userBadgeData && userBadgeData.counts) { Object.entries(userBadgeData.counts).forEach(([category, count]) => { categoryStats[category] = count; }); } else { // Calculate from badges array badges.forEach(badge => { const category = badge.category || 'other'; if (!categoryStats[category]) categoryStats[category] = 0; categoryStats[category]++; }); } // Create badge summary badgeStats.innerHTML = `

    Badge Summary

    ${badges.length} Total Badges
    ${Object.entries(categoryStats).map(([category, count]) => `
    ${category.charAt(0).toUpperCase() + category.slice(1)} ${count}
    `).join('')}
    `; badgeList.appendChild(badgeStats); if (!badges.length) { const emptyState = document.createElement("div"); emptyState.className = "empty-badges"; emptyState.innerHTML = `
    🏆

    No badges earned yet

    Complete spelling activities to earn badges!

    `; badgeList.appendChild(emptyState); } else { // Create badge grid for displaying badges const badgeGrid = document.createElement("div"); badgeGrid.className = "badge-grid"; badges.forEach(badge => { const badgeCard = document.createElement("div"); badgeCard.className = "admin-badge-card"; const badgeIcon = badge.icon || "🏅"; const badgeColor = badge.color || "#4a5568"; const badgeName = badge.name; const badgeDesc = badge.description || ""; const badgeDate = badge.earnedAt ? new Date(badge.earnedAt).toLocaleDateString() : ""; badgeCard.style.borderColor = badgeColor; badgeCard.innerHTML = `
    ${badgeIcon}

    ${badgeName}

    ${badgeDesc ? `

    ${badgeDesc}

    ` : ''} ${badgeDate ? `
    Earned: ${badgeDate}
    ` : ''}
    `; badgeGrid.appendChild(badgeCard); }); badgeList.appendChild(badgeGrid); } details.classList.remove("hidden"); }); } async function loadStudentNamesForReport() { const res = await fetch('/getResults'); const data = await res.json(); const select = document.getElementById("reportUser"); select.innerHTML = ``; Object.keys(data).forEach(username => { const opt = document.createElement("option"); opt.value = username; opt.textContent = username; select.appendChild(opt); }); } window.loadStudentReport = async function(username) { if (!username) return; try { const res1 = await fetch('/getResults'); const results = await res1.json(); const res2 = await fetch('/getBadges'); const badges = await res2.json(); const userResults = results[username]; const badgeList = badges[username] || []; // Handle case where userResults might be an array or undefined let latestReport = null; let allUserResults = []; if (Array.isArray(userResults) && userResults.length > 0) { // Get the most recent result latestReport = userResults[userResults.length - 1]; allUserResults = userResults; } else if (userResults && typeof userResults === 'object') { // Handle single result object latestReport = userResults; allUserResults = [userResults]; } if (!latestReport) { document.getElementById("reportContent").classList.add("hidden"); alert(`No results found for ${username}`); return; } document.getElementById("reportTitle").textContent = `Report for ${username}`; // Safe access to score and answers const score = latestReport.score || 0; const answers = latestReport.answers || []; document.getElementById("reportScore").textContent = `${score}/${answers.length} correct`; const wordList = document.getElementById("reportWords"); wordList.innerHTML = ""; if (answers.length > 0) { answers.forEach(({ word, correct }) => { const li = document.createElement("li"); li.textContent = `${word} — ${correct ? '✅' : '❌'}`; wordList.appendChild(li); }); } else { wordList.innerHTML = "
  • No word data available.
  • "; } const badgeContainer = document.getElementById("reportBadges"); badgeContainer.innerHTML = ""; if (badgeList.length) { badgeList.forEach(b => { const li = document.createElement("li"); li.textContent = `🏅 ${b}`; badgeContainer.appendChild(li); }); } else { badgeContainer.innerHTML = "
  • No badges earned yet.
  • "; } // Generate progress chart if function exists if (typeof window.createScoreChart === 'function') { createScoreChart(username, allUserResults); } // Generate word difficulty chart if function exists if (typeof window.createWordDifficultyChart === 'function') { createWordDifficultyChart(username, allUserResults); } document.getElementById("reportContent").classList.remove("hidden"); } catch (error) { console.error("Error loading student report:", error); alert("Error loading student report. Please try again."); } } window.copyReportToClipboard = function() { const name = document.getElementById("reportTitle").textContent; const score = document.getElementById("reportScore").textContent; const words = Array.from(document.querySelectorAll("#reportWords li")).map(li => li.textContent).join("\n"); const badges = Array.from(document.querySelectorAll("#reportBadges li")).map(li => li.textContent).join("\n"); const summary = `${name}\nScore: ${score}\n\nWords:\n${words}\n\nBadges:\n${badges}`; navigator.clipboard.writeText(summary); alert("✅ Report copied to clipboard"); } async function loadSessionHistory() { const res = await fetch('/getResults'); const allResults = await res.json(); const list = document.getElementById("sessionSummaryList"); list.innerHTML = ""; for (const username in allResults) { const sessions = allResults[username]; const header = document.createElement("li"); header.innerHTML = `${username}`; list.appendChild(header); sessions.forEach(({ score, answers, timestamp }, index) => { const li = document.createElement("li"); const dateStr = new Date(timestamp).toLocaleString(); const total = answers.length; li.textContent = `🗓️ ${dateStr} — ${score}/${total} correct`; list.appendChild(li); }); } } async function populateSessionUserDropdown() { const res = await fetch('/getResults'); const allResults = await res.json(); const select = document.getElementById("sessionUserSelect"); select.innerHTML = ``; Object.keys(allResults).forEach(username => { const opt = document.createElement("option"); opt.value = username; opt.textContent = username; select.appendChild(opt); }); } async function loadUserSessions(username) { const res = await fetch('/getResults'); const allResults = await res.json(); const userSessions = allResults[username] || []; const list = document.getElementById("userSessionList"); list.innerHTML = ""; userSessions.forEach(({ score, answers, timestamp }) => { const sessionItem = document.createElement("li"); const dateStr = new Date(timestamp).toLocaleString(); const total = answers.length; let html = `

    🗓️ ${dateStr} — ${score}/${total} correct

    "; sessionItem.innerHTML = html; list.appendChild(sessionItem); }); } // File upload handler for word lists function handleFileUpload(event) { const file = event.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = function(e) { const content = e.target.result; const words = content.split(/\r?\n/).map(w => w.trim()).filter(Boolean); document.getElementById('wordInput').value = words.join('\n'); showToast(`📁 Loaded ${words.length} words from file`); }; reader.readAsText(file); } // 🔐 Password Management Functions window.changeUserPassword = async function() { const username = document.getElementById('passwordUsername').value; const newPassword = document.getElementById('newUserPassword').value; const confirmPassword = document.getElementById('confirmPassword').value; if (!username) { alert('Please select a user'); return; } if (!newPassword || newPassword.length < 3) { alert('Password must be at least 3 characters long'); return; } if (newPassword !== confirmPassword) { alert('Passwords do not match'); return; } try { // Hash the new password (same way as registration) const encoder = new TextEncoder(); const data = encoder.encode(newPassword); const hashBuffer = await crypto.subtle.digest('SHA-256', data); const hashArray = Array.from(new Uint8Array(hashBuffer)); const newPasswordHash = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); // Send password change request const response = await fetch('/changePassword', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, newPasswordHash }) }); const message = await response.text(); if (response.ok) { alert(message); logPasswordChange(username); // Clear form document.getElementById('newUserPassword').value = ''; document.getElementById('confirmPassword').value = ''; document.getElementById('passwordUsername').value = ''; } else { alert('Error: ' + message); } } catch (error) { console.error('Error changing password:', error); alert('Failed to change password'); } } // Log password changes function logPasswordChange(username) { const passwordLog = document.getElementById('passwordLog'); const timestamp = new Date().toLocaleString(); const logEntry = document.createElement('div'); logEntry.style.padding = '0.5rem'; logEntry.style.marginBottom = '0.5rem'; logEntry.style.backgroundColor = 'var(--bg-primary)'; logEntry.style.borderRadius = 'var(--radius)'; logEntry.style.borderLeft = '4px solid var(--warning)'; logEntry.innerHTML = ` 🔐 Password Changed
    User: ${username}
    ${timestamp} `; // Remove "no changes" message if it exists if (passwordLog.querySelector('p')) { passwordLog.innerHTML = ''; } // Add new entry at the top passwordLog.insertBefore(logEntry, passwordLog.firstChild); } // Populate password username dropdown function populatePasswordUserDropdown() { const dropdown = document.getElementById('passwordUsername'); if (!dropdown || !adminUsers) return; dropdown.innerHTML = ''; adminUsers.forEach(user => { const option = document.createElement('option'); option.value = user.username; option.textContent = `${user.username} (${user.role})`; dropdown.appendChild(option); }); } // Add event listeners for admin tabs as backup to onclick window.addEventListener('DOMContentLoaded', () => { const tabButtons = document.querySelectorAll('#adminTabs button'); tabButtons.forEach(button => { button.addEventListener('click', (e) => { e.preventDefault(); const tabId = button.getAttribute('data-tab'); if (tabId) { console.log('Tab clicked:', tabId); if (typeof window.switchTab === 'function') { window.switchTab(tabId); } else { console.error('switchTab function not available'); } } }); }); console.log('✅ Tab event listeners added to', tabButtons.length, 'buttons'); });