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:
1 parent
3a018a97bb
commit
43aa367f63
3 files changed
+50
-18
No files matched your search
+35
-16
@@ -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
@@ -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>
|
||||
|
||||
@@ -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; }
|
||||
|
||||
Reference in new issue
Block a user