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": {
"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"
}
]
}
+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('tabResults')">📊 Results</button>
<button onclick="switchTab('tabReports')">📘 Reports</button>
<button onclick="switchTab('tabSessions')">📆 Sessions</button>
<button onclick="switchTab('tabBadges')">🎖 Badges</button>
<button onclick="switchTab('tabTheme')">🎨 Theme</button>
</div>
@@ -121,6 +122,7 @@
<button onclick="refreshAnalytics()">🔄 Refresh</button>
</div>
<!-- 🎖 Badges -->
<div id="tabBadges" class="adminTab hidden">
<h3>🎖 Badge Viewer</h3>
@@ -130,6 +132,7 @@
<ul id="badgeUserBadges"></ul>
</div>
</div>
<!-- 🎨 Theme Tab -->
<div id="tabTheme" class="adminTab hidden">
<h3>🎨 Select Theme</h3>
@@ -163,6 +166,17 @@
</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 -->
<div id="studentPanel" class="hidden">
<div id="toast" class="toast">Welcome, student!</div>
+78 -8
View File
@@ -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 = `<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) => {
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), []);