champ-ed / static /evaluate.js
MalikS-343
squash
cbfe36d
Raw
History Blame Contribute Delete
6.62 kB
// evaluate.js - Supervisor page (/evaluate)
//
// Lists submitted critiques (fetched from the server), lets a supervisor
// select one, review the question / answer / critique, then write an
// evaluation and grade the critique 1-5. The evaluation is saved as one
// record and the form locks after submission.
import { Utils } from './utils.js';
const userId = Utils.getMachineId();
const sessionId = Utils.generateSessionId();
const RATING_LABELS = { good: 'Good', bad: 'Bad', mixed: 'Mixed' };
// -------------------- State --------------------
let critiques = [];
let currentCritique = null;
// -------------------- Elements --------------------
const listView = document.getElementById('critique-list-view');
const detailView = document.getElementById('critique-detail-view');
const critiqueList = document.getElementById('critique-list');
const critiqueListStatus = document.getElementById('critique-list-status');
const backBtn = document.getElementById('back-btn');
const detailQuestion = document.getElementById('detail-question');
const detailAnswer = document.getElementById('detail-answer');
const detailAnswerGrade = document.getElementById('detail-answer-grade');
const detailGeneralComment = document.getElementById('detail-general-comment');
const detailHighlights = document.getElementById('detail-highlights');
const evaluationForm = document.getElementById('evaluation-form');
const evaluationDone = document.getElementById('evaluation-done');
const evaluationCommentInput = document.getElementById('evaluation-comment-input');
const submitEvaluationBtn = document.getElementById('submit-evaluation-btn');
const evaluationStatus = document.getElementById('evaluation-status');
// -------------------- Helpers --------------------
async function postJson(url, payload) {
const res = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
if (!res.ok) {
throw new Error(`HTTP ${res.status}`);
}
return res;
}
function renderMarkdown(target, markdownText) {
const text = markdownText || '';
if (window.marked && window.DOMPurify) {
target.innerHTML = DOMPurify.sanitize(marked.parse(text));
} else {
target.textContent = text;
}
}
function truncate(text, maxLength) {
return text.length > maxLength ? `${text.slice(0, maxLength)}…` : text;
}
function getSelectedGrade() {
const checked = document.querySelector('input[name="critique-grade"]:checked');
return checked ? Number(checked.value) : null;
}
// -------------------- Load critique list --------------------
async function loadCritiques() {
critiqueListStatus.textContent = 'Loading critiques…';
try {
const res = await fetch('/critiques');
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
critiques = data.critiques || [];
renderCritiqueList();
} catch (err) {
critiqueListStatus.textContent = 'Could not load critiques. Please refresh the page.';
}
}
function renderCritiqueList() {
critiqueList.innerHTML = '';
if (critiques.length === 0) {
critiqueListStatus.textContent = 'No critiques have been submitted yet.';
return;
}
critiqueListStatus.textContent = '';
critiques.forEach((critique) => {
const item = document.createElement('li');
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'critique-select-btn';
const question = document.createElement('span');
question.textContent = truncate(critique.question || '(question unavailable)', 120);
const meta = document.createElement('span');
const count = (critique.highlights || []).length;
meta.textContent = ` — grade ${critique.answer_grade ?? '?'}/5, ${count} highlight review(s)`;
btn.append(question, meta);
btn.addEventListener('click', () => selectCritique(critique));
item.appendChild(btn);
critiqueList.appendChild(item);
});
}
// -------------------- Select a critique --------------------
function selectCritique(critique) {
currentCritique = critique;
detailQuestion.textContent = critique.question || '';
renderMarkdown(detailAnswer, critique.answer);
detailAnswerGrade.textContent = critique.answer_grade ?? '?';
detailGeneralComment.textContent = critique.general_comment || '(no general comment)';
detailHighlights.innerHTML = '';
const highlights = critique.highlights || [];
if (highlights.length === 0) {
const empty = document.createElement('li');
empty.textContent = '(no highlight reviews)';
detailHighlights.appendChild(empty);
} else {
highlights.forEach((hl) => {
const item = document.createElement('li');
const label = document.createElement('span');
label.textContent = `${RATING_LABELS[hl.rating] || hl.rating} — `;
const quote = document.createElement('q');
quote.textContent = truncate(hl.highlighted_text || '', 200);
item.append(label, quote);
if (hl.comment) {
item.append(` : ${hl.comment}`);
}
detailHighlights.appendChild(item);
});
}
// Reset the evaluation form.
evaluationForm.hidden = false;
evaluationDone.hidden = true;
evaluationCommentInput.value = '';
submitEvaluationBtn.disabled = false;
evaluationStatus.textContent = '';
document
.querySelectorAll('input[name="critique-grade"]')
.forEach((radio) => (radio.checked = false));
listView.hidden = true;
detailView.hidden = false;
window.scrollTo(0, 0);
}
backBtn.addEventListener('click', () => {
currentCritique = null;
detailView.hidden = true;
listView.hidden = false;
});
// -------------------- Submit evaluation --------------------
submitEvaluationBtn.addEventListener('click', async () => {
if (!currentCritique) return;
const grade = getSelectedGrade();
if (grade === null) {
evaluationStatus.textContent = 'Please grade the critique (1–5) before submitting.';
return;
}
submitEvaluationBtn.disabled = true;
evaluationStatus.textContent = 'Submitting…';
try {
await postJson('/evaluations', {
user_id: userId,
session_id: sessionId,
critique_id: currentCritique.critique_id,
critique_grade: grade,
comment: evaluationCommentInput.value.trim(),
});
// Lock the evaluation.
evaluationForm.hidden = true;
evaluationDone.hidden = false;
} catch (err) {
submitEvaluationBtn.disabled = false;
evaluationStatus.textContent = 'Could not submit the evaluation. Please try again.';
}
});
// -------------------- Init --------------------
loadCritiques();