aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Fischer <florian.fischer@muhq.space>2026-07-19 11:55:51 +0200
committerFlorian Fischer <florian.fischer@muhq.space>2026-07-19 12:07:12 +0200
commit369fbaa84bdb86d91752c53824bb3b13c45b483f (patch)
tree5c1dca9eb4581a1de8c0862ba776528a4bedcf41
parentdd52c913c454b9a6cf9ab7e7b102f9e7ede31754 (diff)
downloadbycs-sol-369fbaa84bdb86d91752c53824bb3b13c45b483f.tar.gz
bycs-sol-369fbaa84bdb86d91752c53824bb3b13c45b483f.zip
misc commit
Included changes: * Cache the fetched students * Add level icons * Improve mass-graduation interface * Move utility functions from config.js to utils-ENV.js
-rw-r--r--js/config.js29
-rw-r--r--js/database.js2
-rw-r--r--js/graduation-common.js17
-rw-r--r--js/group.js2
-rw-r--r--js/student-common.js48
-rw-r--r--js/utils-mock.js4
-rw-r--r--js/utils-prod.js11
-rw-r--r--mass-graducation/index.css31
-rw-r--r--mass-graducation/src/main.js89
-rw-r--r--mass-graducation/webpack.config.js8
10 files changed, 158 insertions, 83 deletions
diff --git a/js/config.js b/js/config.js
index 5fc456c..e69de29 100644
--- a/js/config.js
+++ b/js/config.js
@@ -1,29 +0,0 @@
-const courseId = 2466361;
-function getCourseId() {
- return courseId;
-}
-
-// TODO(muhq): retrieve when loading activities/DBs
-const cockpidId = 93017047;
-function getCockpitId() {
- return cockpidId;
-}
-
-function getCourseParticipantsUrl() {
- return `https://lernplattform.bycs.de/user/index.php?id=${courseId}`;
-}
-
-async function getUserIconUrl(userId) {
- console.assert(userId, "getUserIconUrl needs a userId");
- const userRes = await fetch(`https://lernplattform.bycs.de/user/profile.php?id=${userId}`)
- const userDoc = new DOMParser().parseFromString(await userRes.text(), 'text/html')
- const userpicture = userDoc.querySelector(".userpicture");
- return userpicture ? userpicture.src : "";
-}
-
-export {
- getCourseId,
- getCockpitId,
- getUserIconUrl,
- getCourseParticipantsUrl
-};
diff --git a/js/database.js b/js/database.js
index 8149c35..e929896 100644
--- a/js/database.js
+++ b/js/database.js
@@ -1,4 +1,4 @@
-import { getCourseId } from "./config.js";
+import { getCourseId } from "./utils-ENV.js";
const DB_TEMPLATE_LIST_HEADER = `
<table class="table table-striped">
diff --git a/js/graduation-common.js b/js/graduation-common.js
index 52c1bd4..0c28465 100644
--- a/js/graduation-common.js
+++ b/js/graduation-common.js
@@ -1,6 +1,18 @@
import { getSubmitDb, loadFromStudentDatabase, addEntryToStudentDatabase } from "./database.js"
-const LEVEL_NAMES = ["Neuling", "Entdecker", "Pionieer"];
+import NeulingPng from "../assets/Neuling.png";
+import EntdeckerPng from "../assets/Entdecker.png";
+import NavigatorPng from "../assets/Navigator.png";
+import PioneerPng from "../assets/Pioneer.png";
+
+const LEVEL_NAMES = ["Neuling", "Entdecker", "Navigator", "Pionieer"];
+
+const LEVEL_IMAGES = [
+ new URL(NeulingPng),
+ new URL(EntdeckerPng),
+ new URL(NavigatorPng),
+ new URL(PioneerPng),
+];
class GraduationChange {
constructor(date, change, reason, coach, context) {
@@ -210,7 +222,7 @@ class Graduation {
* Return the URI of the icon representing the graduation level.
*/
getIcon() {
- return "";
+ return LEVEL_IMAGES[this.level - 1];
}
/**
@@ -368,6 +380,7 @@ async function submitObservation(student, observation) {
export {
LEVEL_NAMES,
+ LEVEL_IMAGES,
GraduationChange,
Graduation,
fetchGraduation,
diff --git a/js/group.js b/js/group.js
index f9c31a2..ba5e1a0 100644
--- a/js/group.js
+++ b/js/group.js
@@ -1,4 +1,4 @@
-import { getCourseParticipantsUrl } from "./config.js";
+import { getCourseParticipantsUrl } from "./utils-ENV.js";
import { fetchStudents } from "./student-ENV.js";
/**
diff --git a/js/student-common.js b/js/student-common.js
index 778ff8d..2dfa35d 100644
--- a/js/student-common.js
+++ b/js/student-common.js
@@ -1,4 +1,4 @@
-import { getCourseId, getUserIconUrl, getCourseParticipantsUrl } from "./config.js";
+import { getCourseId, getUserIconUrl, getCourseParticipantsUrl } from "./utils-ENV.js";
import { fetchGraduation } from "./graduation-common.js";
/**
* Class to represent Students.
@@ -49,23 +49,15 @@ class Student {
}
}
-async function fetchStudent(idFilter, nameFilter, groupFilter) {
- const students = await fetchStudents(idFilter, nameFilter, groupFilter);
- return students[0];
-}
-
// Cache the fetched students.
let students = [];
const idRegex = /id=(\d+)/;
-async function fetchStudents(idFilter, nameFilter, groupFilter) {
- if (students.size) {
- return students;
- }
-
+async function _fetchStudents() {
const res = await fetch(getCourseParticipantsUrl());
const doc = new DOMParser().parseFromString(await res.text(), "text/html");
const table = doc.querySelector("tbody");
+ let fetchedStudents = [];
table.querySelectorAll("tr").forEach(row => {
if (row.classList.contains("emptyrow"))
return;
@@ -90,14 +82,34 @@ async function fetchStudents(idFilter, nameFilter, groupFilter) {
const groupCell = row.querySelector(".c5");
const group = groupCell.querySelector("a.quickeditlink").textContent;
- if ((!idFilter || id === idFilter) &&
- (!nameFilter || name.includes(nameFilter)) &&
- (!groupFilter || group.includes(groupFilter)))
- {
- students.push(new Student(id, name, group));
- }
+ fetchedStudents.push(new Student(id, name, group));
});
- return students;
+ return fetchedStudents;
+}
+
+async function fetchStudents(idFilter, nameFilter, groupFilter) {
+ if (!students.size) {
+ const studentsPromise = _fetchStudents()
+ const storedStudents = localStorage.getItem("students");
+ if (storedStudents) {
+ studentsPromise.then((fetched) => localStorage.setItem("students", JSON.stringify(fetched)));
+ for (const o of JSON.parse(storedStudents)) {
+ students.push(new Student(o.id, o.name, o.group));
+ }
+ } else {
+ students = await studentsPromise;
+ localStorage.setItem("students", JSON.stringify(students));
+ }
+ }
+
+ return students.filter((s) => (!idFilter || s.id === idFilter) &&
+ (!nameFilter || s.name.includes(nameFilter)) &&
+ (!groupFilter || s.group.includes(groupFilter)));
+}
+
+async function fetchStudent(idFilter, nameFilter, groupFilter) {
+ const students = await fetchStudents(idFilter, nameFilter, groupFilter);
+ return students[0];
}
export { Student, fetchStudents, fetchStudent };
diff --git a/js/utils-mock.js b/js/utils-mock.js
index f394183..da2f94b 100644
--- a/js/utils-mock.js
+++ b/js/utils-mock.js
@@ -35,3 +35,7 @@ export function getUserId() {
return fetchStudent(null, name).id;
}
+
+export function getCourseId() {
+ return "1337";
+}
diff --git a/js/utils-prod.js b/js/utils-prod.js
index 70e4980..ddde8a9 100644
--- a/js/utils-prod.js
+++ b/js/utils-prod.js
@@ -1,4 +1,4 @@
-export { DATE_FMT, DATETIME_FMT, dateStringNow, datetimeStringNow } from "./utils-common.js";
+export { DATE_FMT, DATETIME_FMT, dateStringNow, datetimeStringNow, getUserIconUrl } from "./utils-common.js";
export function isTeacher() {
return Boolean(document.querySelector('.editmode-switch-form'))
@@ -25,3 +25,12 @@ export function getUserId() {
// M is exported by Moodle
return String(M.cfg.userId);
}
+
+export function getCourseId() {
+ // M is exported by Moodle
+ return String(M.cfg.courseId);
+}
+
+export function getCourseParticipantsUrl() {
+ return `https://lernplattform.bycs.de/user/index.php?id=${getCourseId()}&perpage=5000`;
+}
diff --git a/mass-graducation/index.css b/mass-graducation/index.css
index c1c58a8..ef0e2ed 100644
--- a/mass-graducation/index.css
+++ b/mass-graducation/index.css
@@ -96,11 +96,19 @@
}
.group-member {
+ padding-left: 0;
display: grid;
- grid-template-columns: 30px 30px 200px repeat(3, 40px) auto 20px;
+ grid-template-columns: 30px 200px 30px repeat(3, 40px) auto 20px;
+}
+
+.group-member-icon {
+ width: 30px;
+ height: 30px;
+ align-self: center;
}
.group-member-name {
+ align-self: center;
}
.group-member-observation-icon {
@@ -144,7 +152,7 @@
.group-member-observation-container {
background: lightgray;
- width: 600px;
+ width: 100%;
}
.group-member-observation-table {
@@ -152,12 +160,27 @@
}
.group-member-observation-table-description {
- width: 80%;
+ max-width: 70%;
}
.group-member-observation-table-yes,
-.group-member-observation-table-no {
+.group-member-observation-table-no,
+.group-member-observation-table-maybe {
width: 10%;
text-align: center;
}
+.submit-mass-graduation-inprogress {
+ --size: 0.5px;
+ width: calc(48 * var(--size));
+ height: calc(48 * var(--size));
+ border: calc(5 * var(--size)) solid black;
+ border-bottom-color: transparent;
+ border-radius: 50%;
+ display: inline;
+ position: absolute;
+ margin-left: 0.25rem;
+ margin-top: 0.25rem;
+ box-sizing: border-box;
+ animation: rotation 1s linear infinite;
+}
diff --git a/mass-graducation/src/main.js b/mass-graducation/src/main.js
index 4224942..3ed011e 100644
--- a/mass-graducation/src/main.js
+++ b/mass-graducation/src/main.js
@@ -13,24 +13,27 @@ import { getName, DATETIME_FMT, datetimeStringNow } from "../../js/utils-ENV.js"
let group;
let name;
-function toggle(btn, other) {
+function toggle(btn, others) {
if (btn.innerHTML === "X") {
btn.innerHTML = "o";
} else {
btn.innerHTML = "X";
- if (other)
- other.innerHTML = "o";
+ if (others) {
+ for (const other of others)
+ other.innerHTML = "o";
+ }
}
document.getElementById("submit-mass-graduation").style.display = "inline";
+ document.getElementById("submit-mass-graduation-state").innerHTML = "";
}
-function observationClickHandler(ob, li) {
- ob.style.background = "lightgray";
-
+function observationClickHandler(li) {
let observationDiv = li.observationDiv;
// Build the observation container
if(!observationDiv) {
+ li.style.background = "lightgray";
+
observationDiv = document.createElement("div");
// Remember the observation div for this student
li.observationDiv = observationDiv;
@@ -48,6 +51,7 @@ function observationClickHandler(ob, li) {
<th></th>
<th class="group-member-observation-table-yes">Ja</th>
<th class="group-member-observation-table-no">Nein</th>
+ <th class="group-member-observation-table-maybe">Kann ich nicht beurteilen</th>
</tr>`;
obsTbl.appendChild(obsTh);
@@ -75,11 +79,20 @@ function observationClickHandler(ob, li) {
obsNo.classList.add("group-member-observation-table-no");
obsTr.appendChild(obsNo);
+ const obsMaybe = document.createElement("td");
+ obsMaybe.innerHTML = "X";
+ obsMaybe.dataset.maybe = "true";
+ obsMaybe.classList.add("group-member-observation-table-maybe");
+ obsTr.appendChild(obsMaybe);
+
obsYes.addEventListener("click", () => {
- toggle(obsYes, obsNo);
+ toggle(obsYes, [obsNo, obsMaybe]);
});
obsNo.addEventListener("click", () => {
- toggle(obsNo, obsYes);
+ toggle(obsNo, [obsYes, obsMaybe]);
+ });
+ obsMaybe.addEventListener("click", () => {
+ toggle(obsMaybe, [obsYes, obsNo]);
});
obsTbdy.appendChild(obsTr);
@@ -88,8 +101,11 @@ function observationClickHandler(ob, li) {
} else {
if (observationDiv.style.display === "none") {
observationDiv.style.display = "block";
+ li.style.background = "lightgray";
+
} else {
observationDiv.style.display = "none";
+ li.style.background = "unset";
}
}
}
@@ -105,38 +121,39 @@ async function loadGroup(selected) {
ul.innerHTML = "";
for (const student of students) {
+ const graduationPromise = student.getGraduation();
+
const li = document.createElement("li");
li.classList.add("group-member")
// Remember the student of the entry
li.student = student;
+ const i = document.createElement("img");
+ i.classList.add("group-member-icon")
+ graduationPromise.then((graduation) => {
+ i.src = graduation.getIcon();
+ });
+ li.appendChild(i);
+
+ const n = document.createElement("span");
+ n.classList.add("group-member-name")
+ n.innerHTML = `${student.name}`;
+ li.appendChild(n);
+
const ob = document.createElement("button");
ob.classList.add("group-member-observation-icon")
- const graduationPromise = student.getGraduation();
graduationPromise.then((graduation) => {
if (!graduation.isObserved()) {
return;
}
ob.innerHTML = "&#128065"
- ob.addEventListener("click", () => observationClickHandler(ob, li) );
+ ob.addEventListener("click", () => observationClickHandler(li) );
});
graduationPromises.push(graduationPromise);
li.appendChild(ob);
- const i = document.createElement("img");
- i.classList.add("group-member-icon")
- graduationPromise.then((graduation) => {
- i.src = graduation.getIcon();
- });
- li.appendChild(i);
-
- const n = document.createElement("span");
- n.classList.add("group-member-name")
- n.innerHTML = `${student.name}`;
- li.appendChild(n);
-
for (const col of ["green", "orange", "red"]) {
const b = document.createElement("button");
b.classList.add(`group-member-${col}`)
@@ -152,7 +169,7 @@ async function loadGroup(selected) {
const p = document.createElement("button");
p.classList.add("group-member-print")
p.innerHTML = "🖶";
- p.addEventListener("click", () => printRedPointNote(li) );
+ p.addEventListener("click", () => printNote(li) );
li.appendChild(p);
ul.appendChild(li);
@@ -232,6 +249,13 @@ async function loadGroup(selected) {
}
function submitMassGraduation() {
+ const state = document.getElementById("submit-mass-graduation-state");
+ state.innerHTML = "";
+ state.classList.add("submit-mass-graduation-inprogress");
+ state.style.display = "unset";
+
+ // TODO(muhq): await graduation promises
+
let promises = [];
for(const li of document.querySelectorAll("li.group-member")) {
const reason = li.querySelector(".group-member-point-reason").value;
@@ -259,7 +283,6 @@ function submitMassGraduation() {
}
}
- const state = document.getElementById("submit-mass-graduation-state");
Promise.all(promises).then(() => {
state.style.display = "inline";
state.innerHTML = "✓";
@@ -275,11 +298,13 @@ function submitMassGraduation() {
state.innerHTML = "X";
state.style.color = "red";
console.log(error);
+ }).finally(() => {
+ state.classList.remove("submit-mass-graduation-inprogress");
});
}
let prePrintBody = null;
-function printRedPointNote(li) {
+function printNote(li) {
// save the current DOM
prePrintBody = document.body;
@@ -287,14 +312,24 @@ function printRedPointNote(li) {
const printBdy = document.createElement("body");
const reason = li.querySelector(".group-member-point-reason").value;
const dateStr = datetimeStringNow();
+ let pointType = "";
+ if (li.querySelector(".group-member-orange").innerHTML === "X") {
+ pointType = "Oranger Punkt";
+ }
+ if (li.querySelector(".group-member-red").innerHTML === "X") {
+ pointType = "Roter Punkt";
+ }
printBdy.innerHTML = `
-<h2>Roter Punkt Dokumentation</h2>
+<h2>Dokuzettel</h2>
+<h3>${pointType}</h3>
<p>Schüler*in: ${li.student.name}</p>
<p>Datum: ${dateStr}</p>
+<p>Lehrkraft: ${name}</p>
+<p>Klassenleitung: TODO</p>
<h3>Begründung</h3>
<p>${reason}</p>
-<p>Lehrkraft: ${name}</p>`;
+`;
document.body = printBdy;
print();
diff --git a/mass-graducation/webpack.config.js b/mass-graducation/webpack.config.js
index 154c454..fae04c6 100644
--- a/mass-graducation/webpack.config.js
+++ b/mass-graducation/webpack.config.js
@@ -9,6 +9,14 @@ const config = {
output: {
path: path.resolve(__dirname, 'dist'),
},
+ module: {
+ rules: [
+ {
+ test: /\.png/,
+ type: 'asset/inline',
+ },
+ ],
+ },
plugins: [
new webpack.NormalModuleReplacementPlugin(
/-ENV.js/,