diff options
| author | Florian Fischer <florian.fischer@muhq.space> | 2026-07-19 11:55:51 +0200 |
|---|---|---|
| committer | Florian Fischer <florian.fischer@muhq.space> | 2026-07-19 12:07:12 +0200 |
| commit | 369fbaa84bdb86d91752c53824bb3b13c45b483f (patch) | |
| tree | 5c1dca9eb4581a1de8c0862ba776528a4bedcf41 | |
| parent | dd52c913c454b9a6cf9ab7e7b102f9e7ede31754 (diff) | |
| download | bycs-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.js | 29 | ||||
| -rw-r--r-- | js/database.js | 2 | ||||
| -rw-r--r-- | js/graduation-common.js | 17 | ||||
| -rw-r--r-- | js/group.js | 2 | ||||
| -rw-r--r-- | js/student-common.js | 48 | ||||
| -rw-r--r-- | js/utils-mock.js | 4 | ||||
| -rw-r--r-- | js/utils-prod.js | 11 | ||||
| -rw-r--r-- | mass-graducation/index.css | 31 | ||||
| -rw-r--r-- | mass-graducation/src/main.js | 89 | ||||
| -rw-r--r-- | mass-graducation/webpack.config.js | 8 |
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 = "👁" - 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/, |
