aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Fischer <florian.fischer@muhq.space>2026-08-10 14:27:35 +0200
committerFlorian Fischer <florian.fischer@muhq.space>2026-08-10 14:29:09 +0200
commitb8e99e574b8ef818a2bf428c8d92fb82e7796170 (patch)
tree19e1d1ae5432b7f67e72e91fd244fe22bd55ab89
parent7c585983d91a691401d626b55583f1917c7b1328 (diff)
downloadbycs-sol-b8e99e574b8ef818a2bf428c8d92fb82e7796170.tar.gz
bycs-sol-b8e99e574b8ef818a2bf428c8d92fb82e7796170.zip
cockpit: improve graduation history
* Allow change deletion if the current user issued the change * Only the assigned coach is allowed to change the level
-rw-r--r--cockpit.css29
-rw-r--r--cockpit.html30
-rw-r--r--src/cockpit.js70
3 files changed, 104 insertions, 25 deletions
diff --git a/cockpit.css b/cockpit.css
index 6b6295a..c9b529e 100644
--- a/cockpit.css
+++ b/cockpit.css
@@ -234,3 +234,32 @@
margin-top: 1rem;
margin-bottom: 1rem;
}
+
+#graduation-level-change,
+#graduation-history {
+ border: black solid 0.2pt;
+ border-radius: 10px;
+ padding-left: 1rem;
+ padding-bottom: 1rem;
+ margin-top: 1rem;
+ margin-bottom: 1rem;
+}
+
+.graduation-delete-state {
+ margin-left: 1rem;
+ position: relative;
+}
+
+.graduation-delete-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;
+ right: -1rem;
+ box-sizing: border-box;
+ animation: rotation 1s linear infinite;
+}
diff --git a/cockpit.html b/cockpit.html
index 8e77ffe..bda0322 100644
--- a/cockpit.html
+++ b/cockpit.html
@@ -71,23 +71,27 @@
<details id="graduation">
<summary>Graduierung</summary>
- <div id="graduation-level-change">
+ <div id="graduation-level-change" data-availability="if-coach">
+ <h4 id="graduation-level-change-headline">Graduierung abgeschlossen?</h4>
<button id="graduation-level-change-submit">Ändere Level</button>
<input type="number" min="1" max="4" id="graduation-level-change-level" />
</div>
- <table class="table table-striped">
- <thead>
- <tr>
- <th>Änderung</th>
- <th>Datum</th>
- <th>Grund</th>
- <th>Coach</th>
- <th>Context</th>
- </tr>
- </thead>
- <tbody id="graduation-changes"></tbody>
- </table>
+ <div id="graduation-history">
+ <h4 id="graduation-history-headline">Änderungsverlauf</h4>
+ <table class="table table-striped">
+ <thead>
+ <tr>
+ <th>Änderung</th>
+ <th>Datum</th>
+ <th>Grund</th>
+ <th>Coach</th>
+ <th>Context</th>
+ </tr>
+ </thead>
+ <tbody id="graduation-changes"></tbody>
+ </table>
+ </div>
</details>
</div>
</body>
diff --git a/src/cockpit.js b/src/cockpit.js
index 6b2ac35..ed04cde 100644
--- a/src/cockpit.js
+++ b/src/cockpit.js
@@ -1,4 +1,10 @@
-import { GraduationChange, fetchGraduation, submitGraduationChange } from "./graduation-ENV.ts";
+import { assert } from "./assert.ts";
+import {
+ GraduationChange,
+ fetchGraduation,
+ submitGraduationChange,
+ deleteGraduationChange,
+} from "./graduation-ENV.ts";
import { fetchStudent, fetchStudents } from "./student-ENV.ts";
import { getName, DATETIME_FMT } from "./utils-ENV.js";
@@ -133,19 +139,24 @@ function updateStudentInfo() {
}
async function updateStudentGraduation() {
- console.assert(student.graduation, "Student has no graduation");
+ assert(student.graduation, "Student has no graduation");
const graduation = student.graduation;
- const levelChangeLvl = document.getElementById("graduation-level-change-level");
- levelChangeLvl.value = Math.min(graduation.level + 1, 4);
-
- const levelChangeBtn = document.getElementById("graduation-level-change-submit");
- levelChangeBtn.onclick = () => {
- const change = GraduationChange.newLevelChange(Number(levelChangeLvl.value));
- submitGraduationChange(student.graduation, change);
- updateStudentInfo();
- updateStudentGraduation();
- };
+ // Only the coach is allowed to graduate
+ if (name === student.graduation.coach) {
+ const levelChangeLvl = document.getElementById("graduation-level-change-level");
+ levelChangeLvl.value = Math.min(graduation.level + 1, 4);
+
+ const levelChangeBtn = document.getElementById("graduation-level-change-submit");
+ levelChangeBtn.onclick = () => {
+ const change = GraduationChange.newLevelChange(Number(levelChangeLvl.value), "", name);
+ submitGraduationChange(student.graduation, change);
+ updateStudentInfo();
+ updateStudentGraduation();
+ };
+ } else {
+ document.getElementById("graduation-level-change").remove();
+ }
const changesTbl = document.getElementById("graduation-changes");
changesTbl.innerHTML = "";
@@ -164,10 +175,12 @@ async function updateStudentGraduation() {
break;
}
+ const tds = [];
const addTd = (s) => {
const changeTd = document.createElement("td");
changeTd.innerHTML = s;
changeTr.appendChild(changeTd);
+ tds.push(changeTd);
};
addTd(change.change);
@@ -176,6 +189,39 @@ async function updateStudentGraduation() {
addTd(change.coach);
addTd(JSON.stringify(change.context));
+ if (change.coach === name) {
+ const delBtn = document.createElement("button");
+ delBtn.innerHTML = "Löschen";
+
+ delBtn.addEventListener("click", () => {
+ const state = document.createElement("span");
+ state.classList.add("graduation-delete-state");
+
+ const inprogress = document.createElement("span");
+ inprogress.classList.add("graduation-delete-inprogress");
+ state.appendChild(inprogress);
+
+ delBtn.replaceWith(state);
+ deleteGraduationChange(graduation, change)
+ .then(() => {
+ updateStudentInfo();
+ state.style.display = "inline";
+ state.innerHTML = "✓";
+ for (const td of tds) {
+ td.style["text-decoration"] = "line-through";
+ }
+ })
+ .catch(() => {
+ state.style.display = "inline";
+ state.innerHTML = "X";
+ })
+ .finally(() => {
+ inprogress.remove();
+ });
+ });
+ changeTr.appendChild(delBtn);
+ }
+
changesTbl.appendChild(changeTr);
}
}