aboutsummaryrefslogtreecommitdiff
path: root/src/cockpit.js
diff options
context:
space:
mode:
authorFlorian Fischer <florian.fischer@muhq.space>2026-08-03 14:31:25 +0200
committerFlorian Fischer <florian.fischer@muhq.space>2026-08-03 14:31:25 +0200
commit6839831af54cd9302119bcc3bffafe10475958d4 (patch)
tree12a1c1fbd864e9579cb9c3a09d4bf4686a280b73 /src/cockpit.js
parentfccb70cbd876785f6833dfe6efc10ec9e445556d (diff)
downloadbycs-sol-6839831af54cd9302119bcc3bffafe10475958d4.tar.gz
bycs-sol-6839831af54cd9302119bcc3bffafe10475958d4.zip
refactor project structure
* merge sites into single project root * fix name of mass-graduation site * enable typescript support
Diffstat (limited to 'src/cockpit.js')
-rw-r--r--src/cockpit.js366
1 files changed, 366 insertions, 0 deletions
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 = '<span style="font-size: larger;">&#128065</span> 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();
+}