aboutsummaryrefslogtreecommitdiff
path: root/cockpit/js/cockpit.js
diff options
context:
space:
mode:
authorFlorian Fischer <florian.fischer@muhq.space>2026-05-28 15:20:46 +0200
committerFlorian Fischer <florian.fischer@muhq.space>2026-05-28 15:20:46 +0200
commitf9a9fa15d6d61eb9730183193ce8af29bdfc17e7 (patch)
treec3b96a36623e52f22d281b518d7136795cf82b8a /cockpit/js/cockpit.js
downloadbycs-sol-f9a9fa15d6d61eb9730183193ce8af29bdfc17e7.tar.gz
bycs-sol-f9a9fa15d6d61eb9730183193ce8af29bdfc17e7.zip
cockpit: initial commit
Diffstat (limited to 'cockpit/js/cockpit.js')
-rw-r--r--cockpit/js/cockpit.js289
1 files changed, 289 insertions, 0 deletions
diff --git a/cockpit/js/cockpit.js b/cockpit/js/cockpit.js
new file mode 100644
index 0000000..3b54f67
--- /dev/null
+++ b/cockpit/js/cockpit.js
@@ -0,0 +1,289 @@
+import {
+ fetchSubjectsMock as fetchSubjects,
+ fetchProgressMock as fetchProgress,
+} from "./mock.js";
+
+import {
+ Graduation,
+ GraduationChange,
+ fetchGraduation,
+ submitGraduationChange
+} from "./graduation-ENV.js";
+
+import { Student, fetchStudents } from "./student-ENV.js";
+
+let isTeacher;
+let firstname;
+let lastname;
+let student;
+
+function getIsTeacher() {
+ return Boolean(document.querySelector('a[href*="templates.php"]')) || document.body.classList.contains('editing');
+}
+
+function initCurrentName() {
+ const autoNameDiv = document.getElementById("auto-name");
+ firstname = autoNameDiv.children[0].innerHTML;
+ lastname = autoNameDiv.children[1].innerHTML;
+ autoNameDiv.remove();
+}
+
+async function loadNewStudent(name, group) {
+ student = new Student(0, name, group);
+ await loadStudent(student);
+}
+
+async function loadStudent(studentToLoad) {
+ student = studentToLoad;
+ fetchAndUpdateStudentInfo();
+ updateStudentCalendar();
+ updateStudentProgress();
+ updateStudentGraduation();
+}
+
+async function initStudentSelection() {
+ const sel = document.getElementById("student-selection");
+ const list = document.getElementById('student-selection-list');
+ const noResults = document.getElementById('student-selection-noresults');
+
+ const students = await fetchStudents();
+
+ let items = [];
+
+ let currentFocus = -1;
+ let visibleItems = [];
+ function setActive() {
+ visibleItems.forEach(item => item.classList.remove("active"));
+ if (currentFocus >= 0 && visibleItems[currentFocus]) {
+ visibleItems[currentFocus].classList.add("active");
+ }
+ }
+
+ for (const student of students) {
+ let item = document.createElement("li");
+ item.student = student;
+ item.innerHTML = `${student.name} ${student.group}`;
+ item.style.display = 'none';
+
+ item.addEventListener('click', function() {
+ sel.value = "";
+ sel.dispatchEvent(new Event('keyup'));
+ loadStudent(student);
+ });
+
+ list.appendChild(item);
+ items.push(item);
+ }
+
+ sel.addEventListener('keyup', function(e) {
+ if ((["ArrowDown", "ArrowUp", "Enter"]).includes(e.key))
+ return;
+
+ const filter = sel.value.toLowerCase();
+ visibleItems = [];
+ for (const item of items) {
+ const itemText = item.textContent.trim().toLowerCase();
+
+ if (itemText.indexOf(filter) > -1 && filter !== "") {
+ item.style.display = "";
+ visibleItems.push(item);
+
+ // Highlight matched text
+ const regex = new RegExp(`(${filter})`, 'gi');
+ item.innerHTML = item.textContent.replace(
+ regex,
+ "<span class='highlight'>$1</span>"
+ );
+ } else {
+ item.style.display = "none";
+ }
+ }
+
+ // Select the first or none if there are no items visible
+ currentFocus = visibleItems.length ? 0 : -1;
+ setActive(visibleItems);
+ noResults.style.display = visibleItems.length || filter === "" ? "none" : "block";
+ });
+
+ const clearBtn = document.getElementById('student-selection-clear');
+
+ sel.addEventListener('input', function() {
+ clearBtn.style.display = sel.value ? "block" : "none";
+ });
+
+ clearBtn.addEventListener('click', function() {
+ sel.value = "";
+ clearBtn.style.display = "none";
+ sel.dispatchEvent(new Event('keyup')); // reset list
+ sel.focus();
+ });
+
+ sel.addEventListener('keydown', function(e) {
+ if (e.key === "ArrowDown" || e.key === "ArrowUp") {
+ if(e.key === "ArrowDown")
+ currentFocus = (currentFocus + 1) % visibleItems.length;
+ else
+ currentFocus = (currentFocus - 1 + visibleItems.length) % visibleItems.length;
+ setActive();
+ e.preventDefault();
+ } else if (e.key === "Enter") {
+ if (currentFocus > -1 && visibleItems[currentFocus]) {
+
+ const visibleItem = visibleItems[currentFocus];
+ sel.value = "";
+ sel.dispatchEvent(new Event('keyup'));
+ loadStudent(visibleItem.student);
+ }
+ e.preventDefault();
+ }
+ });
+}
+
+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();
+
+ let submit = function() {
+ let change;
+ switch (color) {
+ case "green":
+ change = GraduationChange.newGreenPointChange(student, textarea.value, `${firstname} ${lastname}`);
+ break;
+ case "orange":
+ change = GraduationChange.newOrangePointChange(student, textarea.value, `${firstname} ${lastname}`);
+ break;
+ case "red":
+ change = GraduationChange.newRedPointChange(student, textarea.value, `${firstname} ${lastname}`);
+ break;
+ default:
+ alert("Invalid color");
+ break;
+ }
+ submitGraduationChange(student, change);
+ textareaDiv.style.display = "none";
+ updateStudentInfo();
+ }
+ // 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() {
+ student.graduation = await fetchGraduation(student);
+ updateStudentInfo();
+}
+
+function updateStudentInfo() {
+ document.getElementById("student-info-icon").src = student.getIconUrl();
+ document.getElementById("student-info-name").innerHTML = `${student.name}`;
+
+ if (student.graduation) {
+ const tag = document.getElementById("student-info-tag");
+ tag.innerHTML = student.graduation.level;
+ tag.style.display = "block";
+
+ document.getElementById("student-info-green").addEventListener('click', () => pointClickHandler("green"));
+ document.getElementById("student-info-points-green").innerHTML = student.graduation.green;
+
+ document.getElementById("student-info-orange").addEventListener('click', () => pointClickHandler("orange"));
+ document.getElementById("student-info-points-orange").innerHTML = student.graduation.orange;
+
+ document.getElementById("student-info-red").addEventListener('click', () => pointClickHandler("red"));
+ document.getElementById("student-info-points-red").innerHTML = student.graduation.red;
+
+ renderGraduationLevel(student.graduation, document.getElementById("student-info-level"));
+
+ renderGraduationStickers(student.graduation, document.getElementById("student-info-stickers"));
+ renderGraduationBonus(student.graduation, document.getElementById("student-info-stickers"));
+ }
+}
+
+function renderGraduationLevel(graduation, node) {
+ node.innerHTML = graduation.level;
+}
+
+function renderGraduationStickers(graduation, node) {
+}
+
+function renderGraduationBonus(graduation, node) {
+}
+
+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() {
+ let graduation = document.getElementById("graduation");
+}
+
+async function initCockpit() {
+ if (isTeacher) {
+ await initStudentSelection();
+ } else {
+ loadNewStudent(`${firstname} ${lastname}`);
+ }
+ return;
+}
+
+window.onload = function() {
+ // isTeacher = getIsTeacher();
+ isTeacher = true;
+ initCurrentName();
+ initCockpit(isTeacher);
+}