Sessions
This commit is contained in:
1 parent
c00d10c8f4
commit
d0e72d0bfe
5 files changed
+158
-50
No files matched your search
+42
-36
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
@@ -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('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
@@ -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);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
@@ -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), []);
|
||||||
|
|||||||
Reference in new issue
Block a user