Add Graded sub-tab to student inbox

New grades show in the New tab on first visit; on return they move to the Graded tab so the inbox stays clean without losing the history.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 4.6 committed 2026-08-20 13:58:43 -04:00
1 parent 3a018a97bb
commit 43aa367f63
3 files changed
+50 -18

No files matched your search

+35 -16
View File
@@ -332,6 +332,21 @@ function markAllSeen(studentId, ids) {
localStorage.setItem(inboxSeenKey(studentId), JSON.stringify([...ids]));
}
function renderGradeCard(r, isNew) {
const pct = r.points_possible ? Math.round((r.score / r.points_possible) * 100) : null;
const scoreClass = pct !== null ? (pct >= 80 ? 'score-good' : 'score-bad') : '';
const date = new Date(r.graded_at + 'Z').toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
return `<div class="inbox-card${isNew ? ' inbox-new' : ''}">
<div class="inbox-card-header">
<span class="inbox-item-title">${esc(r.itemTitle)}</span>
${isNew ? '<span class="feedback-badge">New!</span>' : ''}
</div>
<div class="inbox-meta">${esc(r.courseName)} · ${esc(r.unitName)} · ${date}</div>
${r.points_possible !== null ? `<div class="inbox-score ${scoreClass}">🌟 ${r.score} / ${r.points_possible}${pct !== null ? ` (${pct}%)` : ''}</div>` : ''}
${r.parent_comment ? `<div class="parent-comment">💬 ${esc(r.parent_comment)}</div>` : ''}
</div>`;
}
async function loadInbox() {
const sid = currentStudent?.id;
if (!sid) return;
@@ -340,29 +355,33 @@ async function loadInbox() {
api(`/api/students/${sid}/messages`),
]);
const seen = getSeenIds(sid);
const newItems = items.filter((r) => !seen.has(r.submissionId));
const gradedItems = items.filter((r) => seen.has(r.submissionId));
$('#inbox-empty').hidden = items.length > 0;
$('#inbox-list').innerHTML = items.map((r) => {
const pct = r.points_possible ? Math.round((r.score / r.points_possible) * 100) : null;
const isNew = !seen.has(r.submissionId);
const scoreClass = pct !== null ? (pct >= 80 ? 'score-good' : 'score-bad') : '';
const date = new Date(r.graded_at + 'Z').toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
return `<div class="inbox-card${isNew ? ' inbox-new' : ''}">
<div class="inbox-card-header">
<span class="inbox-item-title">${esc(r.itemTitle)}</span>
${isNew ? '<span class="feedback-badge">New!</span>' : ''}
</div>
<div class="inbox-meta">${esc(r.courseName)} · ${esc(r.unitName)} · ${date}</div>
${r.points_possible !== null ? `<div class="inbox-score ${scoreClass}">🌟 ${r.score} / ${r.points_possible}${pct !== null ? ` (${pct}%)` : ''}</div>` : ''}
${r.parent_comment ? `<div class="parent-comment">💬 ${esc(r.parent_comment)}</div>` : ''}
</div>`;
}).join('');
$('#inbox-list').innerHTML = newItems.map((r) => renderGradeCard(r, true)).join('');
$('#inbox-empty').hidden = newItems.length > 0;
$('#graded-list').innerHTML = gradedItems.map((r) => renderGradeCard(r, false)).join('');
$('#graded-empty').hidden = gradedItems.length > 0;
const newBadge = $('#inbox-new-badge');
if (newBadge) { newBadge.hidden = newItems.length === 0; newBadge.textContent = newItems.length; }
markAllSeen(sid, items.map((r) => r.submissionId));
updateInboxBadge(sid, 0);
renderMsgThread(msgs);
}
document.querySelectorAll('.inbox-subtab').forEach((btn) => {
btn.addEventListener('click', () => {
document.querySelectorAll('.inbox-subtab').forEach((b) => b.classList.remove('active'));
btn.classList.add('active');
const sub = btn.dataset.sub;
$('#inbox-sub-new').hidden = sub !== 'new';
$('#inbox-sub-graded').hidden = sub !== 'graded';
});
});
function renderMsgThread(msgs) {
const thread = $('#msg-thread');
if (!thread) return;
+12 -2
View File
@@ -102,8 +102,18 @@
</div>
<div id="tab-inbox" class="tab-panel" hidden>
<div id="inbox-list"></div>
<p id="inbox-empty" class="hint" hidden>No grades yet — check back after your parent reviews your work!</p>
<div class="inbox-subtabs">
<button class="inbox-subtab active" data-sub="new">📬 New<span id="inbox-new-badge" class="feedback-badge" hidden></span></button>
<button class="inbox-subtab" data-sub="graded">📂 Graded</button>
</div>
<div id="inbox-sub-new">
<div id="inbox-list"></div>
<p id="inbox-empty" class="hint" hidden>No new grades — check back after your parent reviews your work!</p>
</div>
<div id="inbox-sub-graded" hidden>
<div id="graded-list"></div>
<p id="graded-empty" class="hint" hidden>No graded items yet.</p>
</div>
<div class="msg-thread-wrap">
<h2 class="msg-thread-title">💬 Messages</h2>
+3
View File
@@ -1251,6 +1251,9 @@ body.bg-bubbles {
border-left: 4px solid var(--good);
font-size: 1.05rem;
}
.inbox-subtabs { display: flex; gap: 0.5rem; margin-bottom: 1rem; }
.inbox-subtab { flex: 1; padding: 0.5rem 1rem; border: 1.5px solid #e2dccf; border-radius: 999px; background: var(--card); color: var(--text); font-size: 0.9rem; font-weight: 600; cursor: pointer; position: relative; }
.inbox-subtab.active { background: var(--accent); color: #fff; border-color: var(--accent); }
.inbox-card { background: var(--card); border: 1.5px solid #e2dccf; border-radius: 14px; padding: 1rem; margin-bottom: 0.75rem; }
.inbox-card.inbox-new { border-color: var(--accent); }
.inbox-card-header { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.25rem; }