Sessions
This commit is contained in:
1 parent
c00d10c8f4
commit
d0e72d0bfe
5 files changed
+127
-19
No files matched your search
+11
-5
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"student1": {
|
||||
"student1": [
|
||||
{
|
||||
"score": 8,
|
||||
"completed": true,
|
||||
"answers": [
|
||||
@@ -15,9 +16,12 @@
|
||||
"word": "grape",
|
||||
"correct": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"nate": {
|
||||
],
|
||||
"timestamp": "2025-07-07T12:14:21.057Z"
|
||||
}
|
||||
],
|
||||
"nate": [
|
||||
{
|
||||
"score": 1,
|
||||
"completed": true,
|
||||
"answers": [
|
||||
@@ -33,6 +37,8 @@
|
||||
"word": "snot",
|
||||
"correct": true
|
||||
}
|
||||
]
|
||||
],
|
||||
"timestamp": "2025-07-07T12:14:21.058Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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.");
|
||||
@@ -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
@@ -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() {
|
||||
@@ -388,3 +399,62 @@ function copyReportToClipboard() {
|
||||
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);
|
||||
});
|
||||
}
|
||||
@@ -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), []);
|
||||
|
||||
Reference in new issue
Block a user