// 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();