diff --git a/data/results.json b/data/results.json
index 39d572a..ec3db51 100644
--- a/data/results.json
+++ b/data/results.json
@@ -1,38 +1,44 @@
{
- "student1": {
- "score": 8,
- "completed": true,
- "answers": [
- {
- "word": "apple",
- "correct": true
- },
- {
- "word": "banana",
- "correct": false
- },
- {
- "word": "grape",
- "correct": true
- }
- ]
- },
- "nate": {
- "score": 1,
- "completed": true,
- "answers": [
- {
- "word": "not",
- "correct": false
- },
- {
- "word": "bot",
- "correct": false
- },
- {
- "word": "snot",
- "correct": true
- }
- ]
- }
+ "student1": [
+ {
+ "score": 8,
+ "completed": true,
+ "answers": [
+ {
+ "word": "apple",
+ "correct": true
+ },
+ {
+ "word": "banana",
+ "correct": false
+ },
+ {
+ "word": "grape",
+ "correct": true
+ }
+ ],
+ "timestamp": "2025-07-07T12:14:21.057Z"
+ }
+ ],
+ "nate": [
+ {
+ "score": 1,
+ "completed": true,
+ "answers": [
+ {
+ "word": "not",
+ "correct": false
+ },
+ {
+ "word": "bot",
+ "correct": false
+ },
+ {
+ "word": "snot",
+ "correct": true
+ }
+ ],
+ "timestamp": "2025-07-07T12:14:21.058Z"
+ }
+ ]
}
\ No newline at end of file
diff --git a/migrateResults.js b/migrateResults.js
new file mode 100644
index 0000000..62b082b
--- /dev/null
+++ b/migrateResults.js
@@ -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.");
\ No newline at end of file
diff --git a/public/index.html b/public/index.html
index dd9551c..82e527f 100644
--- a/public/index.html
+++ b/public/index.html
@@ -52,6 +52,7 @@
+
@@ -121,6 +122,7 @@
+
🎖 Badge Viewer
@@ -130,6 +132,7 @@
+
🎨 Select Theme
@@ -163,6 +166,17 @@
+
+
📘 Archived Sessions
+
+
+
+
+
+
+
Welcome, student!
diff --git a/public/js/admin.js b/public/js/admin.js
index 1e9afac..ab5aa35 100644
--- a/public/js/admin.js
+++ b/public/js/admin.js
@@ -189,25 +189,36 @@ async function loadStudentResults() {
// 🧭 Admin tab switcher
function switchTab(targetId) {
- document.querySelectorAll('.adminTab').forEach(tab => tab.classList.add('hidden'));
- document.querySelectorAll('#adminTabs button').forEach(btn => btn.classList.remove('active'));
+ document.querySelectorAll('.adminTab').forEach(tab =>
+ tab.classList.add('hidden')
+ );
+ document.querySelectorAll('#adminTabs button').forEach(btn =>
+ btn.classList.remove('active')
+ );
+ // Show the selected tab
document.getElementById(targetId).classList.remove('hidden');
- const buttons = document.querySelectorAll('#adminTabs button');
- buttons.forEach(btn => {
+ // Highlight the active button
+ document.querySelectorAll('#adminTabs button').forEach(btn => {
if (btn.getAttribute('onclick')?.includes(targetId)) {
btn.classList.add('active');
}
});
- // 🏆 Load badge viewer if needed
+ // 🔁 Trigger tab-specific logic
+ if (targetId === 'tabSessions') {
+ loadSessionHistory();
+ }
+ if (targetId === 'tabReports') {
+ loadStudentNamesForReport();
+ }
if (targetId === 'tabBadges') {
loadBadgeViewer();
}
- // Reports
- if (targetId === 'tabReports') {
- loadStudentNamesForReport();
+ if (targetId === 'tabSessions') {
+ populateSessionUserDropdown();
+ document.getElementById("userSessionList").innerHTML = "";
}
}
async function loadUserDropdowns() {
@@ -387,4 +398,63 @@ function copyReportToClipboard() {
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
`;
+ answers.forEach(({ word, correct }) => {
+ html += `- ${correct ? '✅' : '❌'} ${word}
`;
+ });
+ html += "
";
+
+ sessionItem.innerHTML = html;
+ list.appendChild(sessionItem);
+ });
}
\ No newline at end of file
diff --git a/server.js b/server.js
index 4b93c3f..5213ad8 100644
--- a/server.js
+++ b/server.js
@@ -142,24 +142,26 @@ app.listen(PORT, () => {
});
app.post('/saveResults', (req, res) => {
const { username, result } = req.body;
-
if (
typeof username !== 'string' ||
typeof result !== 'object' ||
typeof result.score !== 'number' ||
typeof result.completed !== 'boolean' ||
!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 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));
- res.send(`✅ Results saved for ${username}`);
+ res.send(`✅ Results archived for ${username}`);
});
app.get('/getUsers', (req, res) => {
const users = readJsonSafe(path.join(DATA_DIR, files.users), []);