This commit is contained in:
nate@necloud.us committed 2025-07-07 08:23:02 -04:00
1 parent c00d10c8f4
commit d0e72d0bfe
5 files changed
+158 -50

No files matched your search

+42 -36
View File
@@ -1,38 +1,44 @@
{ {
"student1": { "student1": [
"score": 8, {
"completed": true, "score": 8,
"answers": [ "completed": true,
{ "answers": [
"word": "apple", {
"correct": true "word": "apple",
}, "correct": true
{ },
"word": "banana", {
"correct": false "word": "banana",
}, "correct": false
{ },
"word": "grape", {
"correct": true "word": "grape",
} "correct": true
] }
}, ],
"nate": { "timestamp": "2025-07-07T12:14:21.057Z"
"score": 1, }
"completed": true, ],
"answers": [ "nate": [
{ {
"word": "not", "score": 1,
"correct": false "completed": true,
}, "answers": [
{ {
"word": "bot", "word": "not",
"correct": false "correct": false
}, },
{ {
"word": "snot", "word": "bot",
"correct": true "correct": false
} },
] {
} "word": "snot",
"correct": true
}
],
"timestamp": "2025-07-07T12:14:21.058Z"
}
]
} }
+16
View File
@@ -0,0 +1,16 @@
const fs = require('fs');
const path = require('path');
const resultsPath = path.join(__dirname, 'data', 'results.json');
const existingData = JSON.parse(fs.readFileSync(resultsPath));
for (const username in existingData) {
const singleResult = existingData[username];
existingData[username] = [{
...singleResult,
timestamp: new Date().toISOString()
}];
}
fs.writeFileSync(resultsPath, JSON.stringify(existingData, null, 2));
console.log("✅ Migration complete. Results are now session arrays.");
+14
View File
@@ -52,6 +52,7 @@
<button onclick="switchTab('tabAnalytics')">📊 Analytics</button> <button onclick="switchTab('tabAnalytics')">📊 Analytics</button>
<button onclick="switchTab('tabResults')">📊 Results</button> <button onclick="switchTab('tabResults')">📊 Results</button>
<button onclick="switchTab('tabReports')">📘 Reports</button> <button onclick="switchTab('tabReports')">📘 Reports</button>
<button onclick="switchTab('tabSessions')">📆 Sessions</button>
<button onclick="switchTab('tabBadges')">🎖 Badges</button> <button onclick="switchTab('tabBadges')">🎖 Badges</button>
<button onclick="switchTab('tabTheme')">🎨 Theme</button> <button onclick="switchTab('tabTheme')">🎨 Theme</button>
</div> </div>
@@ -121,6 +122,7 @@
<button onclick="refreshAnalytics()">🔄 Refresh</button> <button onclick="refreshAnalytics()">🔄 Refresh</button>
</div> </div>
<!-- 🎖 Badges --> <!-- 🎖 Badges -->
<div id="tabBadges" class="adminTab hidden"> <div id="tabBadges" class="adminTab hidden">
<h3>🎖 Badge Viewer</h3> <h3>🎖 Badge Viewer</h3>
@@ -130,6 +132,7 @@
<ul id="badgeUserBadges"></ul> <ul id="badgeUserBadges"></ul>
</div> </div>
</div> </div>
<!-- 🎨 Theme Tab --> <!-- 🎨 Theme Tab -->
<div id="tabTheme" class="adminTab hidden"> <div id="tabTheme" class="adminTab hidden">
<h3>🎨 Select Theme</h3> <h3>🎨 Select Theme</h3>
@@ -163,6 +166,17 @@
</div> </div>
</div> </div>
<div id="tabSessions" class="adminTab hidden">
<h3>📘 Archived Sessions</h3>
<label for="sessionUserSelect">Select Student:</label>
<select id="sessionUserSelect" onchange="loadUserSessions(this.value)">
<option value="">-- Select --</option>
</select>
<ul id="userSessionList"></ul>
</div>
<!-- 👩‍🎓 Student Panel --> <!-- 👩‍🎓 Student Panel -->
<div id="studentPanel" class="hidden"> <div id="studentPanel" class="hidden">
<div id="toast" class="toast">Welcome, student!</div> <div id="toast" class="toast">Welcome, student!</div>
+78 -8
View File
@@ -189,25 +189,36 @@ async function loadStudentResults() {
// 🧭 Admin tab switcher // 🧭 Admin tab switcher
function switchTab(targetId) { function switchTab(targetId) {
document.querySelectorAll('.adminTab').forEach(tab => tab.classList.add('hidden')); document.querySelectorAll('.adminTab').forEach(tab =>
document.querySelectorAll('#adminTabs button').forEach(btn => btn.classList.remove('active')); tab.classList.add('hidden')
);
document.querySelectorAll('#adminTabs button').forEach(btn =>
btn.classList.remove('active')
);
// Show the selected tab
document.getElementById(targetId).classList.remove('hidden'); document.getElementById(targetId).classList.remove('hidden');
const buttons = document.querySelectorAll('#adminTabs button'); // Highlight the active button
buttons.forEach(btn => { document.querySelectorAll('#adminTabs button').forEach(btn => {
if (btn.getAttribute('onclick')?.includes(targetId)) { if (btn.getAttribute('onclick')?.includes(targetId)) {
btn.classList.add('active'); btn.classList.add('active');
} }
}); });
// 🏆 Load badge viewer if needed // 🔁 Trigger tab-specific logic
if (targetId === 'tabSessions') {
loadSessionHistory();
}
if (targetId === 'tabReports') {
loadStudentNamesForReport();
}
if (targetId === 'tabBadges') { if (targetId === 'tabBadges') {
loadBadgeViewer(); loadBadgeViewer();
} }
// Reports if (targetId === 'tabSessions') {
if (targetId === 'tabReports') { populateSessionUserDropdown();
loadStudentNamesForReport(); document.getElementById("userSessionList").innerHTML = "";
} }
} }
async function loadUserDropdowns() { async function loadUserDropdowns() {
@@ -387,4 +398,63 @@ function copyReportToClipboard() {
const summary = `${name}\nScore: ${score}\n\nWords:\n${words}\n\nBadges:\n${badges}`; const summary = `${name}\nScore: ${score}\n\nWords:\n${words}\n\nBadges:\n${badges}`;
navigator.clipboard.writeText(summary); navigator.clipboard.writeText(summary);
alert("✅ Report copied to clipboard"); 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 = `<strong>${username}</strong>`;
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 = `<option value="">-- Select --</option>`;
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 = `<p>🗓️ <strong>${dateStr}</strong> — ${score}/${total} correct</p><ul>`;
answers.forEach(({ word, correct }) => {
html += `<li>${correct ? '✅' : '❌'} ${word}</li>`;
});
html += "</ul>";
sessionItem.innerHTML = html;
list.appendChild(sessionItem);
});
} }
+8 -6
View File
@@ -142,24 +142,26 @@ app.listen(PORT, () => {
}); });
app.post('/saveResults', (req, res) => { app.post('/saveResults', (req, res) => {
const { username, result } = req.body; const { username, result } = req.body;
if ( if (
typeof username !== 'string' || typeof username !== 'string' ||
typeof result !== 'object' || typeof result !== 'object' ||
typeof result.score !== 'number' || typeof result.score !== 'number' ||
typeof result.completed !== 'boolean' || typeof result.completed !== 'boolean' ||
!Array.isArray(result.answers) !Array.isArray(result.answers)
) { ) return res.status(400).send("Invalid result format");
return res.status(400).send("Invalid result format");
}
const resultsPath = path.join(DATA_DIR, files.results); const resultsPath = path.join(DATA_DIR, files.results);
const allResults = readJsonSafe(resultsPath); const allResults = readJsonSafe(resultsPath);
allResults[username] = result; if (!allResults[username]) allResults[username] = [];
allResults[username].push({
...result,
timestamp: new Date().toISOString()
});
fs.writeFileSync(resultsPath, JSON.stringify(allResults, null, 2)); fs.writeFileSync(resultsPath, JSON.stringify(allResults, null, 2));
res.send(`✅ Results saved for ${username}`); res.send(`✅ Results archived for ${username}`);
}); });
app.get('/getUsers', (req, res) => { app.get('/getUsers', (req, res) => {
const users = readJsonSafe(path.join(DATA_DIR, files.users), []); const users = readJsonSafe(path.join(DATA_DIR, files.users), []);