From 9f7b24bb6177d65e0b9d1b77a578c50af9c7f493 Mon Sep 17 00:00:00 2001 From: Florian Fischer Date: Sun, 19 Oct 2025 15:04:15 +0200 Subject: =?UTF-8?q?unterst=C3=BCtzte=20konfigurierbare=20"Streaks"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ein Streak ist ein Zeitraum der zählt wie viele gleiche Ereignisse in zeitlicher Folge aufgetreten sind. Ein Streak wird durch ein nicht positives Ereignis beendet. --- config.php.sample | 22 ++++++++++++-------- index.css | 4 ++++ index.js | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++-- index.php | 29 +++++++++++++++++++------- lade_daten.php | 62 +++++++++++++++++++++++++++++++++++++++---------------- sql/dummydata.sql | 4 ++++ 6 files changed, 144 insertions(+), 37 deletions(-) diff --git a/config.php.sample b/config.php.sample index 2ca654f..3fed3bc 100644 --- a/config.php.sample +++ b/config.php.sample @@ -1,18 +1,22 @@ 'mysql', 'dbname' => 'punktestand', 'dbhost' => 'localhost', 'dbuser' => 'user', 'dbpassword' => 'password', - 'bilder_pfad_format' => 'img/$SCHULJAHR/$JAHRGANG/' -); -/* Konfiguration für eine sqlite Datenbank - * $CONFIG = array( - * 'dbtype' => 'sqlite', - * 'dbname' => './punktestand.sqlite', - * 'bilder_pfad_format' => 'img/$SCHULJAHR/$JAHRGANG/' - * ); - */ + /* Konfiguration für eine sqlite Datenbank + * 'dbtype' => 'sqlite', + * 'dbname' => './punktestand.sqlite', + */ + + 'bilder_pfad_format' => 'img/$SCHULJAHR/$JAHRGANG/', + + /* Konfiguration für die Streak-Anzeige */ + 'streak_anzahl' => 5, + 'streak_ziel' => '🍰', + 'streak_ereignis' => 'Kuchenpunkt', +); ?> diff --git a/index.css b/index.css index 31b82e5..fd4eba9 100644 --- a/index.css +++ b/index.css @@ -63,3 +63,7 @@ li.ereignis { margin-top: 2px; display: block; } + +td.streak { + text-align: center; +} diff --git a/index.js b/index.js index 8f145dc..55ce7b0 100644 --- a/index.js +++ b/index.js @@ -25,11 +25,16 @@ * */ +// Observer der auf 'resize' events unseres Graphs reagiert und die +// Streak Anzeige gegebenfalls anpasst. +let graphResizeObserver; + // Zeichne das Diagramm und erstelle eine Liste aller Events, // wenn die Seite komplett geladen wurde window.onload = function() { zeichne(); zeichneBilder(); + zeichneStreak(); listeEvents(); } @@ -90,7 +95,7 @@ async function zeichneBilder() { // Die Url des Klassenbildes ist der Klassenbezeichner unter dem Bilderpfad let bildUrl = `${bilder_pfad}/${klasse.bezeichner}.png`; - // Teste ob das Bild existiert + // Teste, ob das Bild existiert let ok = (await fetch(bildUrl, {method: 'HEAD'})).ok; // Das Bild existiert nicht if (!ok) { continue; } @@ -119,8 +124,59 @@ async function zeichneBilder() { diagramm.update(); } +// Zeichne den Streak für alle Klassen. +// Die globale Variable daten.streakEreignisse muss definiert sein und ein Iterable +// aller Ereignisse, die für den Streak relevant sind, enthalten. +// Ereignisse ohne positives Delta beenden einen Streak. +function zeichneStreak() { + // Es gibt keine Streak-Ereignisse + if (!daten.streakEreignisse) { + return; + } + + // Variable um den laufenden Streak per Klasse zu bestimmen + let streaks = Object.fromEntries(daten.klassen.map(klasse => [klasse.bezeichner, 0])); + + for (let ereignis of daten.streakEreignisse) { + const klasse = `${ereignis.Jahrgang}${ereignis.Bezeichner}`; + + // Das Streak Ereignis bringt keine Punkte und unterbricht damit den Streak. + if (ereignis.Delta == 0) { + streaks[klasse] = 0; + // Erhöhe den Streak. + } else { + streaks[klasse] = streaks[klasse] + 1; + } + } + + let streakNodes = document.getElementById('streaks'); + for (const klasse of Object.keys(streaks)) { + const streak = streaks[klasse]; + let streakNode = document.createElement("td"); + streakNode.classList.add("streak"); + streakNode.innerHTML = `${streak}/${streak_anzahl} ${streak_ziel}`; + // Füge den Streak-Node der Klasse den Streak Nodes auf der Webseite hinzu. + streakNodes.appendChild(streakNode); + } + + const zeichenflaeche = document.getElementById('zeichenflaeche'); + let streakTabelle = document.getElementById('streaks-tabelle'); + // Setze die Breite passend zum Graphen + streakTabelle.setAttribute("style", `width:${zeichenflaeche.style.width}px`); + + // Erzeuge ein Observer der auf Größenveränderungen des Graphen reagiert. + graphResizeObserver = new ResizeObserver((entries) => { + for (const entry of entries) { + // Passe die Breite unserer Streak-Tabelle an die des Graphen an. + streakTabelle.setAttribute("style", `width:${entry.contentRect.width}px`); + } + }); + + graphResizeObserver.observe(zeichenflaeche); +} + // Erzeuge eine Liste aller Ereignisse. -// Die globale Variable ereignisse muss definiert sein und ein Iterable aller +// Die globale Variable daten.ereignisse muss definiert sein und ein Iterable aller // Ereignisse, die angezeigt werden sollen, enthalten. function listeEvents() { let liste = document.getElementById("ereignisse"); diff --git a/index.php b/index.php index 8f8e101..fa1bb83 100644 --- a/index.php +++ b/index.php @@ -40,18 +40,22 @@ // Bereite die Klassen und Ereignisse für Javascript vor $daten_json = json_encode($daten); - // Ersetze die $JAHRGANG und $SCHULJAHR Platzhalter durch die werte der Variablen + // Ersetze die $JAHRGANG und $SCHULJAHR Platzhalter durch die Werte der Variablen $bilder_pfad = str_replace( array('$JAHRGANG', '$SCHULJAHR'), array($JAHRGANG, $SCHULJAHR), $CONFIG['bilder_pfad_format']); $bilder_pfad_json = json_encode($bilder_pfad); + $streak_anzahl = json_encode($CONFIG['streak_anzahl'] ?? ''); + $streak_ziel = json_encode($CONFIG['streak_ziel'] ?? ''); // Schreibe den Javascript Code mit unseren Daten in die Webseite echo ""; ?> @@ -66,12 +70,21 @@
- - - -
- - +
+ + + +
+ + +
+ + + + + + +
diff --git a/lade_daten.php b/lade_daten.php index 67134be..ac15510 100644 --- a/lade_daten.php +++ b/lade_daten.php @@ -12,6 +12,24 @@ * punktestand. If not, see . */ +// PHP Klasse, die alle Daten einer Schulklasse enthält. +class Klasse { + public string $bezeichner; + public int $punkte; + public string $farbe; + + public function __construct(string $bezeichner, ?string $farbe) { + $this->bezeichner = $bezeichner; + $this->punkte = 0; + if (is_null($farbe)) { + // Die Standardfarbe ist ein leichtes Blau + $this->farbe = 'rgb(54, 162, 235)'; + } else { + $this->farbe = $farbe; + } + } +} + // Die Funktion lade_daten liefert die Klassen und ihren Punktestand, sowie alle // aufgetretenen Ereignisse in einem array zurück. function lade_daten($config, $schuljahr, $jahrgang): array { @@ -40,24 +58,6 @@ function lade_daten($config, $schuljahr, $jahrgang): array { // Die Verbindung war erfolgreich - // PHP Klasse, die alle Daten einer Schulklasse enthält. - class Klasse { - public string $bezeichner; - public int $punkte; - public string $farbe; - - public function __construct(string $bezeichner, ?string $farbe) { - $this->bezeichner = $bezeichner; - $this->punkte = 0; - if (is_null($farbe)) { - // Die Standardfarbe ist ein leichtes Blau - $this->farbe = 'rgb(54, 162, 235)'; - } else { - $this->farbe = $farbe; - } - } - } - // Bereite unsere Klassenabfrage vor. // Dies verhindert SQL-Injection Angriffe, da die Parameter Schuljahr und // Jahrgang über die URL kontrollierbar sind. @@ -84,6 +84,29 @@ function lade_daten($config, $schuljahr, $jahrgang): array { // Alle Ereignisse der ausgewählten Klassen $ereignisse = $ereignis_abfrage->fetchAll(); + // Deklaration der streak_ereignisse Variable, die nur unter bestimmten + // Umständen mit einem Wert gefüllt wird. Die Forward-Deklaration ermöglicht es + // die Variable danach zu benutzen unabhängig, davon ob sie mit einem sinnvollen + // Wert gefüllt wurde. + $streak_ereignisse = null; + $streak_ereignis_beschreibung = $config['streak_ereignis']; + if ($streak_ereignis_beschreibung != '') { + // Bereite die Streak-Ereignisabfrage vor. Sicherheitshinweis siehe: Klassenabfrage. + // Nur Ereignisse mit dem konfigurierten Namen werden ausgewählt. + // Zusätzlich werden nur Ereignisse mit einem Datum betrachtet, da zur Streak + // Berechnung die Ereignisse in eine zeitliche Reihenfolge gebracht werden + // müssen. + $streak_ereignis_abfrage = $verbindung->prepare('SELECT * FROM Ereignis WHERE Schuljahr = ? AND Jahrgang = ? AND Beschreibung = ? ORDER BY Datum ASC'); + $streak_ereignis_abfrage->bindValue(1, $schuljahr, PDO::PARAM_STR); + $streak_ereignis_abfrage->bindValue(2, $jahrgang, PDO::PARAM_INT); + $streak_ereignis_abfrage->bindValue(3, $streak_ereignis_beschreibung, PDO::PARAM_STR); + $streak_ereignis_abfrage->execute(); + + // Alle Streak-Ereignisse der ausgewählten Klassen + $streak_ereignisse = $streak_ereignis_abfrage->fetchAll(); + } + + // Schließe die Datenbank Verbindung $verbindung = null; @@ -96,6 +119,9 @@ function lade_daten($config, $schuljahr, $jahrgang): array { $daten = array(); $daten['klassen'] = array_values($klassen); $daten['ereignisse'] = $ereignisse; + if ($streak_ereignisse != null) { + $daten['streakEreignisse'] = $streak_ereignisse; + } return $daten; } ?> diff --git a/sql/dummydata.sql b/sql/dummydata.sql index 410a3ea..dd21854 100644 --- a/sql/dummydata.sql +++ b/sql/dummydata.sql @@ -1,3 +1,7 @@ INSERT INTO Ereignis (Jahrgang, Bezeichner, Schuljahr, Datum, Delta, Beschreibung) VALUES (7, 'a', '2526', '2025-09-21 10:32:17', 10, 'Aufpassen'); INSERT INTO Ereignis (Jahrgang, Bezeichner, Schuljahr, Delta, Beschreibung) VALUES (7, 'a', '2526', -2, 'Nerven'); INSERT INTO Ereignis (Jahrgang, Bezeichner, Schuljahr, Delta, Beschreibung) VALUES (7, 'b', '2526', 5, 'Quizzen'); +INSERT INTO Ereignis (Jahrgang, Bezeichner, Schuljahr, Delta, Beschreibung, Datum) VALUES (7, 'a', '2526', 1, 'Kuchenpunkt', '2025-10-12 13:20'); +INSERT INTO Ereignis (Jahrgang, Bezeichner, Schuljahr, Delta, Beschreibung, Datum) VALUES (7, 'a', '2526', 1, 'Kuchenpunkt', '2025-10-19 13:20'); +INSERT INTO Ereignis (Jahrgang, Bezeichner, Schuljahr, Delta, Beschreibung, Datum) VALUES (7, 'c', '2526', 1, 'Kuchenpunkt', '2025-10-12 13:20'); +INSERT INTO Ereignis (Jahrgang, Bezeichner, Schuljahr, Delta, Beschreibung, Datum) VALUES (7, 'c', '2526', 0, 'Kuchenpunkt', '2025-10-19 13:20'); -- cgit v1.2.3