From 6839831af54cd9302119bcc3bffafe10475958d4 Mon Sep 17 00:00:00 2001 From: Florian Fischer Date: Mon, 3 Aug 2026 14:31:25 +0200 Subject: refactor project structure * merge sites into single project root * fix name of mass-graduation site * enable typescript support --- src/cockpit.js | 366 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 366 insertions(+) create mode 100644 src/cockpit.js (limited to 'src/cockpit.js') diff --git a/src/cockpit.js b/src/cockpit.js new file mode 100644 index 0000000..bd4c860 --- /dev/null +++ b/src/cockpit.js @@ -0,0 +1,366 @@ +// import { + // fetchSubjectsMock as fetchSubjects, + // fetchProgressMock as fetchProgress, +// } from "../../js/mock.js"; + +import { + OBSERVATION_TYPES, + LEVEL_NAMES, + GraduationChange, + fetchGraduation, + fetchPublicGraduation, + submitGraduationChange +} from "./graduation-ENV.js"; + +import { fetchStudent, fetchStudents, Student } from "./student-ENV.js"; +import { + isTeacher as getIsTeacher, + getName, + getUserId, + DATE_FMT, + DATETIME_FMT, +} from "./utils-ENV.js"; +import { initSelection } from "./selection.js"; + +let isTeacher; +let name; +let student; + +async function loadStudent(studentToLoad) { + student = studentToLoad; + await fetchAndUpdateStudentInfo(); + // updateStudentCalendar(); + // updateStudentProgress(); + updateStudentGraduation(); +} + +function initStudentSelection() { + initSelection( + "student-selection", + fetchStudents, + (student) => `${student.name} ${student.getGroup()}`, + loadStudent + ); +} + +function pointClickHandler(color) { + const textareaDiv = document.getElementById("student-info-point-reason-container"); + const textarea = document.getElementById("student-info-point-reason"); + const submitBtn = document.getElementById("student-info-point-reason-submit"); + + // TODO(muhq): Prefill the textarea with common reasons + switch (color) { + case "green": + textarea.style.background = "rgb(from green r g b / 20%)" + textarea.value = ""; + break; + case "orange": + textarea.style.background = "rgb(from orange r g b / 20%)" + textarea.value = ""; + break; + case "red": + textarea.style.background = "rgb(from red r g b / 20%)" + textarea.value = ""; + break; + } + textareaDiv.style.display = "block"; + textarea.focus(); + + const coach = name; + + let submit = function() { + let change; + switch (color) { + case "green": + change = GraduationChange.newGreenPointChange(textarea.value, coach); + break; + case "orange": + change = GraduationChange.newOrangePointChange(textarea.value, coach); + break; + case "red": + change = GraduationChange.newRedPointChange(textarea.value, coach); + break; + default: + alert("Invalid color"); + break; + } + submitGraduationChange(student, change); + textareaDiv.style.display = "none"; + updateStudentInfo(); + student.fetchGraduation().then(() => updateStudentGraduation() ); + } + // Use onEVENT to override any previous handler + textarea.onkeydown = function(e) { + if (e.shiftKey && e.key === "Enter") { + submit(); + } + if (e.key === "Escape") { + textareaDiv.style.display = "none"; + } + }; + submitBtn.onclick = submit; +} + +async function fetchAndUpdateStudentInfo() { + if (isTeacher) + student.graduation = await fetchGraduation(student); + else + student.graduation = await fetchPublicGraduation(student); + + updateStudentInfo(); +} + +let DEFAULT_STUDENT_ICON; + +function updateStudentInfo() { + // Reset the student icon + document.getElementById("student-info-icon").src = DEFAULT_STUDENT_ICON; + + // Try to replace the generic icon with the user's icon + var img = document.createElement('img'); + img.onload = function() { + document.getElementById("student-info-icon").src = img.src; + img = img.onload = null; + }; + student.getIconUrl().then( (url) => img.src = url ); + + document.getElementById("student-info-name").innerHTML = `${student.name}`; + + if (student.graduation) { + const tag = document.getElementById("student-info-tag"); + tag.innerHTML = student.graduation.getLevelName(); + tag.style.display = "block"; + + document.getElementById("student-info-points-green").innerHTML = student.graduation.green; + document.getElementById("student-info-points-orange").innerHTML = student.graduation.orange; + document.getElementById("student-info-points-red").innerHTML = student.graduation.red; + + if (isTeacher) { + document.getElementById("student-info-green").addEventListener('click', + () => pointClickHandler("green")); + document.getElementById("student-info-orange").addEventListener('click', + () => pointClickHandler("orange")); + document.getElementById("student-info-red").addEventListener('click', + () => pointClickHandler("red")); + } + + renderGraduationLevel(student.graduation); + + // renderGraduationStickers(student.graduation, document.getElementById("student-info-stickers")); + // renderGraduationBonus(student.graduation, document.getElementById("student-info-stickers")); + + renderObservationStatus(student.graduation, document.getElementById("student-observation")); + } + + document.getElementById("student-content").style.display = "block"; +} + +function renderGraduationLevel(graduation) { + const label = document.getElementById("student-info-level"); + label.innerHTML = graduation.getLevelName(); + const icon = document.getElementById("student-level-icon"); + icon.src = graduation.getIcon(); +} + +// function renderGraduationStickers(graduation, node) { + // node.innerHTML = ""; +// } + +// function renderGraduationBonus(graduation, node) { + // node.innerHTML = ""; +// } + +function renderObservationStatus(graduation, node) { + node.innerHTML = ""; + + let status; + if (isTeacher || graduation.isObserved() || graduation.observationRequested) { + status = document.createElement("span"); + status.classList.add("student-observation-status"); + if (graduation.isObserved()) { + status.innerHTML = '👁 Ich befinde mich in der Beobachtungsphase zum Levelaufstieg.'; + } else if (graduation.observationRequested) { + status.innerHTML = 'Beobachtungsphase noch nicht begonnen.'; + } else { + status.innerHTML = "Nicht in Beobachtung."; + } + } else { + status = document.createElement("button"); + status.classList.add("student-request-observation-button"); + // Level is 1-based + status.innerHTML = `Ich will ${LEVEL_NAMES[graduation.level]} werden`; + status.addEventListener("click", async function() { + await graduation.requestObservation(); + // Rerender the observation status + renderObservationStatus(graduation, node); + }); + } + node.appendChild(status); +} + +// function newProgressBar(subject) { + // let bar = document.createElement("div"); + // fetchProgress(subject).then((progress) => { + // bar.progress = progress; + // }); + + // let s1 = document.createElement("div"); + // s1.onhover + + // bar.appendChild(s1); + // let s2 = document.createElement("div"); + // bar.appendChild(s2); + // let s3 = document.createElement("div"); + // bar.appendChild(s3); + // return bar; +// } + +// let selectedSubjects; +// async function updateStudentProgress() { + // selectedSubjects = await fetchSubjects(student); + + // let prog = document.getElementById("progress"); + // for(const subj of selectedSubjects) { + // let subjDiv = document.createElement("div"); + // subjDiv.innerHTML = subj; + // subjDiv.appendChild(newProgressBar(subj)) + // prog.appendChild(subjDiv); + // } +// } + +// async function updateStudentCalendar() { + // let cal = document.getElementById("calendar"); +// } + +async function updateStudentGraduation() { + if (!isTeacher) return; + console.assert(student.graduation, "Student has no graduation"); + const graduation = student.graduation; + + const changesTbl = document.getElementById("graduation-changes"); + changesTbl.innerHTML = ""; + for (const change of graduation.changes.filter( + (ch) => ["Grün", "Rot", "Orange"].includes(ch.change) )) + { + const changeTr = document.createElement("tr"); + for (const key in change) { + if (key == "json" || key == "id" || key == "name") continue; + + if (key == "change") { + switch(change.change) { + case "Grün": + changeTr.style.background = "green"; + break; + case "Rot": + changeTr.style.background = "red"; + break; + case "Orange": + changeTr.style.background = "orange"; + break; + default: + alert(`unexpected change: ${change.change}`); + } + continue; + } + + const value = change[key]; + const fmt = key == "date" ? DATETIME_FMT.format(value) : value; + + const changeTd = document.createElement("td"); + changeTd.innerHTML = fmt; + changeTd.dataset.key = key; + changeTr.appendChild(changeTd); + } + changesTbl.appendChild(changeTr); + } + + const observtionsTbl = document.getElementById("graduation-current-observation-period"); + observtionsTbl.innerHTML = ""; + if (graduation.observation) { + const sums = graduation.observation.getObservationsSums(); + for (let i = 0; i < OBSERVATION_TYPES.length; i++) { + const tr = document.createElement("tr"); + const typeTd = document.createElement("td"); + typeTd.innerHTML = OBSERVATION_TYPES[i]; + tr.appendChild(typeTd); + + const yesTd = document.createElement("td"); + yesTd.innerHTML = sums[i].yes; + tr.appendChild(yesTd); + + const noTd = document.createElement("td"); + noTd.innerHTML = sums[i].no; + tr.appendChild(noTd); + + observtionsTbl.appendChild(tr); + } + + const endDiv = document.getElementById("graduation-current-observation-period-end"); + if (!graduation.observation.ready()) { + endDiv.style.display = "none"; + return; + } + + endDiv.style.display = "block"; + + const reason = document.getElementById("graduation-current-observation-period-end-reason").value; + const successBtn = document.getElementById("graduation-current-observation-period-end-success"); + successBtn.addEventListener("click", () => { + submitGraduationChange(graduation.student, GraduationChange.newObservationEnd(reason, true)); + }); + const failureBtn = document.getElementById("graduation-current-observation-period-end-failure"); + failureBtn.addEventListener("click", () => { + submitGraduationChange(graduation.student, GraduationChange.newObservationEnd(reason, false)); + }); + } + + const pastObservtionsTbl = document.getElementById("graduation-observation-periods"); + pastObservtionsTbl.innerHTML = ""; + if (graduation.pastObservations.length > 0) { + for (const period of graduation.pastObservations) { + const tr = document.createElement("tr"); + const periodTd = document.createElement("td"); + const startDate = DATE_FMT.format(period.start); + const endDate = DATE_FMT.format(period.end); + periodTd.innerHTML = `${startDate} - ${endDate}`; + tr.appendChild(periodTd); + + const successTd = document.createElement("td"); + if (period.success) { + successTd.innerHTML = "Graduierung erfolgreich" + } else { + successTd.innerHTML = "Graduierung nicht erfolgreich" + } + tr.appendChild(successTd); + + pastObservtionsTbl.appendChild(tr); + } + } +} + +async function initCockpit() { + if (isTeacher) { + initStudentSelection(); + const params = new URLSearchParams(location.search); + const userId = params.get("userToLoad"); + if (userId) { + student = await fetchStudent(userId); + await loadStudent(student); + } + } else { + // Remove all elements only suitable for teachers + for(const elem of document.querySelectorAll("[data-availability=if-lehrer]")) { + elem.remove(); + } + + const student = await Student.fetchById(getUserId()); + await loadStudent(student); + } +} + +window.onload = function() { + isTeacher = getIsTeacher(); + DEFAULT_STUDENT_ICON = document.getElementById("student-info-icon").src; + name = getName(); + initCockpit(); +} -- cgit v1.2.3