]> git.laktatnebel.de Git - roadtokona.triathlon-coaching.com.git/commitdiff
version 1.0
authorOle B. Rosentreter <ole@laktatnebel.de>
Wed, 16 Jul 2025 08:51:24 +0000 (10:51 +0200)
committerOle B. Rosentreter <ole@laktatnebel.de>
Wed, 16 Jul 2025 08:51:24 +0000 (10:51 +0200)
12 files changed:
roadtokona-db/sql/schema.sql
roadtokona-web/src/webui/getRaceData.php [new file with mode: 0644]
roadtokona-web/src/webui/index.php [new file with mode: 0755]
roadtokona-web/src/webui/js/roadtokona.js [new file with mode: 0644]
roadtokona-web/src/webui/lib/database_functions.php [new file with mode: 0644]
roadtokona-web/src/webui/lib/db/database_functions_pgsql.php [new file with mode: 0644]
roadtokona-web/src/webui/lib/db/database_functions_select.php [new file with mode: 0644]
roadtokona-web/src/webui/lib/gui/gui_functions_select.php [new file with mode: 0644]
roadtokona-web/src/webui/lib/gui_functions.php [new file with mode: 0644]
roadtokona-web/src/webui/lib/util/util_functions.php [new file with mode: 0644]
roadtokona-web/src/webui/lib/util_functions.php [new file with mode: 0644]
roadtokona-web/src/webui/stylesheet/roadtokona.css [new file with mode: 0644]

index 102a4bf354f60c67afba612ab8aa6168c8103d9f..2cd89fb4f6fd82e402e22fb36ab1a9b2820dec13 100644 (file)
@@ -32,6 +32,8 @@ CREATE TABLE roadtokona.result (
 
 CREATE VIEW roadtokona.view_result_normalized AS
 SELECT
+       fk_race,
+       fk_division,
        race_date,
        race_location,
        division_gender,
@@ -42,17 +44,62 @@ SELECT
 FROM
        roadtokona.result re
 JOIN roadtokona.race ra ON (re.fk_race = ra.race_id)
-JOIN roadtokona.division di ON (re.fk_division = di.division_id);
+JOIN roadtokona.division di ON (re.fk_division = di.division_id)
+ORDER BY
+       result_normalized;
 
 CREATE VIEW roadtokona.view_division_factor AS
 SELECT
-       division_gender,
-       division_age,
-       factor_value
+       factor_value,
+       division_gender || division_age
 FROM
        roadtokona.factor fa
 JOIN roadtokona.division di ON (fa.fk_division = di.division_id);
 
+CREATE VIEW roadtokona.view_division AS
+SELECT
+       division_gender || division_age as agegroup,
+       division_gender || division_age
+FROM
+       roadtokona.factor fa
+JOIN roadtokona.division di ON (fa.fk_division = di.division_id);
+
+CREATE VIEW roadtokona.view_division_winner AS
+SELECT
+       min(result_finish) as finish_winner,
+       fk_division,
+       fk_race
+FROM
+       roadtokona.result
+GROUP BY
+       fk_division,
+       fk_race;
+
+CREATE VIEW roadtokona.view_race AS
+SELECT
+       race_id,
+       race_location || ' (' || to_char(race_date, 'DD.MM.YYYY') || ')'
+FROM
+       roadtokona.race
+ORDER BY
+       race_date;
+
+CREATE VIEW roadtokona.view_result_normalized_rolldown AS
+SELECT 
+       roadtokona.view_result_normalized.*
+FROM 
+       roadtokona.view_result_normalized
+WHERE NOT EXISTS (
+    SELECT 1
+    FROM roadtokona.view_division_winner
+    WHERE roadtokona.view_result_normalized.result_finish = roadtokona.view_division_winner.finish_winner 
+      AND roadtokona.view_result_normalized.fk_division = roadtokona.view_division_winner.fk_division 
+      AND roadtokona.view_result_normalized.fk_race = roadtokona.view_division_winner.fk_race
+)
+ORDER BY
+       result_normalized;
+
+
 insert into roadtokona.division values
 (default, 'M', 18),
 (default, 'F', 18),
@@ -121,6 +168,19 @@ insert into roadtokona.factor values
 --ALTER TABLE roadtokona.result OWNER TO laktatnebel;
 --ALTER TABLE roadtokona.view_result_normalized OWNER TO laktatnebel;
 
---GRANT ALL ON SCHEMA roadtokona TO oleb;
+ALTER SCHEMA roadtokona OWNER TO oleb;
+
+ALTER TABLE roadtokona.division OWNER TO oleb;
+ALTER TABLE roadtokona.race OWNER TO oleb;
+ALTER TABLE roadtokona.factor OWNER TO oleb;
+ALTER TABLE roadtokona.result OWNER TO oleb;
+ALTER TABLE roadtokona.view_result_normalized OWNER TO oleb;
+ALTER TABLE roadtokona.view_division OWNER TO oleb;
+ALTER TABLE roadtokona.view_division_factor OWNER TO oleb;
+ALTER TABLE roadtokona.view_race OWNER TO oleb;
+ALTER TABLE roadtokona.view_division_winner OWNER TO oleb;
+ALTER TABLE roadtokona.view_result_normalized_rolldown OWNER TO oleb;
+
+GRANT ALL ON SCHEMA roadtokona TO oleb;
 
 
diff --git a/roadtokona-web/src/webui/getRaceData.php b/roadtokona-web/src/webui/getRaceData.php
new file mode 100644 (file)
index 0000000..e6d9818
--- /dev/null
@@ -0,0 +1,46 @@
+
+<?php require_once '../roadtokona_glob_vars.php'; ?>
+<?php require_once 'lib/database_functions.php'; ?>
+<?php
+header('Content-Type: application/json');
+
+$str_connection = "host=".HOST." port=5432 dbname=".DB." user=".USER." password=".PASS." ";
+
+$race_id = $_GET['rid'] ?? '';
+
+try {
+    // PostgreSQL Verbindung
+    $db = pg_connect($str_connection);
+    if (!$db) {
+        throw new Exception('Verbindung zur Datenbank fehlgeschlagen');
+    }
+    
+    // Nur erlaubte Spalten selektieren (Security!)
+    //$query = "SELECT $columns FROM $source LIMIT 100";
+    
+    // Query ausführen
+    $result = pg_query($db, str_replace($race_id, "FK_RACE", $selectFromViewResultNormalizedRolldown));
+    if (!$result) {
+        throw new Exception('Datenbankabfrage fehlgeschlagen');
+    }
+    
+    // Daten sammeln
+    $data = [];
+    while ($row = pg_fetch_assoc($result)) {
+        $data[] = $row;
+    }
+    
+    // Ressourcen freigeben
+    pg_free_result($result);
+    pg_close($db);
+    
+    echo json_encode($data);
+    
+} catch (Exception $e) {
+    http_response_code(500);
+    echo json_encode([
+        'error' => 'Serverfehler',
+        'message' => $e->getMessage()
+    ]);
+}
+?>
\ No newline at end of file
diff --git a/roadtokona-web/src/webui/index.php b/roadtokona-web/src/webui/index.php
new file mode 100755 (executable)
index 0000000..e78a337
--- /dev/null
@@ -0,0 +1,123 @@
+<?php\r
+putenv("PGGSSENCMODE=disable");\r
+putenv("LC_ALL=C");\r
+?>\r
+<?php require_once '../roadtokona_glob_vars.php'; ?>\r
+<?php require_once 'lib/database_functions.php'; ?>\r
+<?php require_once 'lib/gui_functions.php'; ?>\r
+<?php require_once 'lib/util_functions.php'; ?>\r
+<?php
+$page = 'index.php';\r
+\r
+$j=0;\r
+\r
+$connect_dbms_handle = getDBConnection();\r
+\r
+$current_user=1;\r
+\r
+?>\r
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\r
+<html xmlns="http://www.w3.org/1999/xhtml" lang="de">\r
+\r
+<head>\r
+<meta http-equiv="content-type" content="text/html; charset=UTF-8" />\r
+<meta http-equiv="expires" content="0" /> \r
+\r
+<title>roadtokona.triathlon-coaching.com</title>\r
+\r
+<link rel="stylesheet" href="stylesheet/roadtokona.css" type="text/css" media="screen, projection\" />\r
+</head>\r
+\r
+<body>\r
+<header>\r
+<h1>#roadtokona</h1>\r
+</header>\r
+\r
+<main>\r
+<h2>Was ist neu?</h2>\r
+<p>Der Weg nach Kona ist anders geworden. Laut IRONMAN Cooperation fairer, aber definitiv weniger transparent.</p>\r
+<p>Bislang reichte ein bestimmter Platz in deienr Altersklasse für einen Slot, oder man konnte auf das Roll-down-verfahren hoffen und nachrücken.<br />\r
+Das war auch für den Support / Coach einfach: Am Live-Tracker war der aktuelle Stand der Dinge jederzeit ersichtlich.<br />"Platz 5 ist 2 Minuten vor Dir, gib Gas!" - diese Rufe hört man nun selten.</p>\r
+<p>Jetzt werden alle Zeiten im Ziel erst mit einem für jede Altersklasse spezifischen Faktor multipliziert und daraus eine Liste mit "normalisierten Zeiten" generiert.<br />Anhand derer werden dann die begehrten Slots vergeben.</p>\r
+<p>Ganz einfach, oder? Profis und PC/HC haben eigene Verfahren und die Ersten der jeweiligen Altersklassen sind auf jeden Fall qualifiziert.</p></section>\r
+\r
+\r
+<h2>Faktoren</h2>\r
+<p>Diese Faktoren hat die IRONMAN Cooperation veröffentlicht.<br />\r
+Da diese auf echten Ergebnissen beruhen, können sie sich jede Sasion auch ändern - ich werde hier immer nur die allerneusten Werte verwenden.</p>\r
+<div class="factors-container">\r
+<div><p>\r
+<?php\r
+$dataDivisionFactor = getData($connect_dbms_handle, $selectFromViewDivisionFactor);\r
+foreach ($dataDivisionFactor as $data) {\r
+    //print_r($d);\r
+    echo $data[1]." - ".$data[0];\r
+    if ($data[1] == "M85" ) {\r
+        echo "\n</p></div>\n<div><p>\n";\r
+//        echo "<br />";\r
+    } else {\r
+        echo "<br />\n";\r
+    }\r
+}\r
+?>\r
+* Mangels Daten in den Klassen F80 und älter gibt es dafür keine Faktoren.\r
+</p></div></div>\r
+\r
+<h2>Wie kann ich meine Zeit umrechnen?</h2>\r
+<form id="calculator">\r
+<fieldset>\r
+<legend>Rechner</legend>\r
+Deine Zeit:\r
+<?php\r
+makeSelect("yourhour", null, "yourhour", null, 0, 0, 24, -1, false, 1, null, null, null, false, false, 1);\r
+echo "h  ";\r
+makeSelect("yourminute", null, "yourminute", null, 2, 0, 60, -1, false, 1, null, null, null, false, false, 1);\r
+echo "min  ";\r
+makeSelect("yoursecond", null, "yoursecond", null, 3, 0, 60, -1, false, 1, null, null, null, false, false, 1);\r
+echo "sec  ";\r
+?>\r
+<br />Deine Altersklasse:\r
+<?php\r
+makeSelect("yourag", $dataDivisionFactor, "yourag", null, 4, 0, 0, -1, false, 1, null, null, null, false, false, 1);\r
+?>\r
+<div>\r
+<p id="result">Ergebnis: 00:00:00</p>\r
+</div>\r
+</fieldset>\r
+</form>\r
+\r
+<h2>Wie schnell hätte ich sein müssen?</h2>\r
+<p>Das ist die Gretchenfrage. :-) Da die Slotvergabe nach dem neuen System erst ab dem 16. August 2025 greift, sind die Ergebnisse aller anderen Wettkämpfe davor nur bedingt aussagekräftig.<br />\r
+Aber Du kannst ja gucken, welche Zeit in welcher Klasse zu welcher Platzierung in der "normalisierten Liste" gereicht hätte.<br />Die Schlüsse daraus kannst Du ja selber ziehen ;-)</p>\r
+<p>Du kannst den Wettkampf auswählen und bekommst die Rolldown-Liste. Das heißt die Liste ohne Profis, PC/HC und Ersten der jeweiligen Altersklasse.<br />\r
+Wenn es beispielsweise 75 Slots für die begehrte Insel gibt, sind davon schon maximal 26 durch die Ersten belegt. Bleiben noch 49.</p>\r
+<form id="raceselector">\r
+<fieldset>\r
+<legend>Wettkampfauswahl</legend>\r
+wähle aus: <?php\r
+$dataRaces = getData($connect_dbms_handle, $selectFromViewRaces);\r
+makeSelect("race", $dataRaces, "race", null, 5, 0, 0, -1, false, 1, null, null, null, false, false, 1);\r
+?>\r
+<br />... und markiere eine Altersklasse:\r
+<?php\r
+$dataDivision = getData($connect_dbms_handle, $selectFromViewDivision);\r
+makeSelect("ag", $dataDivision, "ag", null, 6, 0, 0, -1, false, 1, null, null, null, false, false, 1);\r
+?>\r
+<div>\r
+        <p id="results">Bitte Wettkampf auswählen.</p>\r
+</div>\r
+</fieldset>\r
+</form>\r
+\r
+</main>\r
+\r
+<footer>\r
+<a href="https://www.triathlon-coaching.com">Impressum, Datenschutz, Copyright, Kontakt ...</a>\r
+</footer>\r
+\r
+<script src="js/roadtokona.js"></script>\r
+</body>\r
+</html>\r
+<?php
+closeDBConnection ( $connect_dbms_handle );
+?>
diff --git a/roadtokona-web/src/webui/js/roadtokona.js b/roadtokona-web/src/webui/js/roadtokona.js
new file mode 100644 (file)
index 0000000..65b3fda
--- /dev/null
@@ -0,0 +1,164 @@
+/**
+ * 
+ */
+
+document.addEventListener('DOMContentLoaded', function() {
+            // Elemente auswählen
+            const hoursSelect = document.getElementById('yourhour');
+            const minutesSelect = document.getElementById('yourminute');
+            const secondsSelect = document.getElementById('yoursecond');
+            const factorSelect = document.getElementById('yourag');
+            const resultParagraph = document.getElementById('result');
+            
+            // Event-Listener für alle Auswahllisten
+            [hoursSelect, minutesSelect, secondsSelect, factorSelect].forEach(select => {
+                select.addEventListener('change', calculateResult);
+            });
+               
+            // Funktion zur Formatierung der Zeit im hh:mm:ss Format
+            function formatTime(totalSeconds) {
+                // Sicherstellen, dass wir mit einer ganzen Zahl arbeiten
+                totalSeconds = Math.round(totalSeconds);
+                
+                // Stunden, Minuten und Sekunden berechnen
+                const hours = Math.floor(totalSeconds / 3600);
+                const remainingSeconds = totalSeconds % 3600;
+                const minutes = Math.floor(remainingSeconds / 60);
+                const seconds = remainingSeconds % 60;
+                
+                // Führende Nullen hinzufügen
+                const pad = num => num.toString().padStart(2, '0');
+                
+                return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;
+            }
+            
+            // Funktion zur Berechnung des Ergebnisses
+            function calculateResult() {
+                // Werte auslesen
+                const hours = parseInt(hoursSelect.value) || 0;
+                const minutes = parseInt(minutesSelect.value) || 0;
+                const seconds = parseInt(secondsSelect.value) || 0;
+                const factor = parseFloat(factorSelect.value) || 1;
+                
+                // Gesamtzeit in Sekunden berechnen
+                const totalSeconds = hours * 3600 + minutes * 60 + seconds;
+                
+                // Ergebnis berechnen
+                const result = totalSeconds * factor;
+                
+                // Ergebnis formatieren und anzeigen
+                resultParagraph.textContent = `Ergebnis: ${formatTime(result)}`;
+            }
+            
+            // Initiale Berechnung
+            calculateResult();
+        });
+               
+               
+               
+document.getElementById('race').addEventListener('change', function() {
+    const selection = this.value;
+    const resultsDiv = document.getElementById('results');
+    
+    if (!selection) {
+        resultsDiv.innerHTML = '<p>Bitte Wettkampf auswählen.</p>';
+        return;
+    }
+    
+    resultsDiv.innerHTML = '<p class="loading">Daten werden geladen...</p>';
+    
+    // AJAX-Anfrage an den Server
+    fetch(`getRaceData.php?rid=${encodeURIComponent(selection)}`)
+        .then(response => {
+            if (!response.ok) {
+                throw new Error(`Serverfehler: ${response.status}`);
+            }
+            return response.json();
+        })
+        .then(data => {
+            if (!data || data.length === 0) {
+                resultsDiv.innerHTML = '<p>Keine Daten gefunden.</p>';
+                return;
+            }
+            
+            // Tabelle erstellen
+                       let html = '<p class="cta">OMG - ist das flott!<br /><a href="https://www.triathlon-coaching.com">Brauchst Du einen Coach?</a></p>'
+            html += '<table id="resultlist"><thead><tr><th>#</th>';
+            
+            // Spaltenüberschriften
+            const columns = Object.keys(data[0]);
+            columns.forEach(col => {
+                html += `<th>${col}</th>`;
+            });
+            html += '</tr></thead><tbody>';
+            
+                       ct=0;
+            // Datenzeilen
+            data.forEach(row => {
+                               ct++;
+                               html += '<tr>';
+                               html += '<td>'+ct+'</td>';
+                columns.forEach(col => {
+                    html += `<td>${row[col] ?? ''}</td>`;
+                });
+                html += '</tr>';
+            });
+            
+            html += '</tbody></table>';
+            resultsDiv.innerHTML = html;
+               // Nachdem die Tabelle geladen wurde, den Event-Listener für die Altersklassenauswahl setzen
+               setupAgeGroupHighlighting();
+        })
+        .catch(error => {
+            resultsDiv.innerHTML = `<p class="error">Fehler beim Laden der Daten: ${error.message}</p>`;
+            console.error('Fehler:', error);
+        });
+});
+               
+               
+               
+               
+// Funktion für die Markierung der Altersklassen
+function setupAgeGroupHighlighting() {
+    const auswahl = document.getElementById('ag');
+    const tabelle = document.getElementById('resultlist');
+    
+    // Event-Listener entfernen, falls bereits vorhanden
+    auswahl.removeEventListener('change', handleAgeGroupSelection);
+    
+    // Neuen Event-Listener hinzufügen
+    auswahl.addEventListener('change', handleAgeGroupSelection);
+}
+
+// Handler für die Altersklassenauswahl
+function handleAgeGroupSelection() {
+    const tabelle = document.getElementById('resultlist');
+    if (!tabelle) return;
+    
+    // Zuerst alle Markierungen entfernen
+    const bereitsMarkiert = tabelle.querySelectorAll('.highlight');
+    bereitsMarkiert.forEach(element => {
+        element.classList.remove('highlight');
+    });
+    
+    // Wenn eine Option ausgewählt wurde (nicht die leere Standardoption)
+    if (this.value) {
+        const selectedText = this.options[this.selectedIndex].text;
+               console.log('selectedText:', selectedText);
+               
+        const zellen = tabelle.querySelectorAll('td');
+        zellen.forEach(zelle => {
+            if (zelle.textContent === selectedText) {
+                zelle.closest('tr').classList.add('highlight');
+            }
+        });
+    }
+}              
+               
+// Initialen Event-Listener setzen (falls Tabelle bereits existiert)
+document.addEventListener('DOMContentLoaded', function() {
+    // Prüfen, ob die Tabelle bereits existiert (z.B. bei Seitenrefresh)
+    if (document.getElementById('resultlist')) {
+        setupAgeGroupHighlighting();
+    }
+});
\ No newline at end of file
diff --git a/roadtokona-web/src/webui/lib/database_functions.php b/roadtokona-web/src/webui/lib/database_functions.php
new file mode 100644 (file)
index 0000000..769cd86
--- /dev/null
@@ -0,0 +1,17 @@
+<?php
+
+require ("db/database_functions_pgsql.php");
+
+require ("db/database_functions_select.php");
+
+
+$selectFromViewRaces = "SELECT * FROM ".SCHEMA.".view_race;";
+
+$selectFromViewDivision = "SELECT * FROM ".SCHEMA.".view_division;";
+
+$selectFromViewDivisionFactor = "SELECT * FROM ".SCHEMA.".view_division_factor;";
+
+$selectFromViewResultNormalizedRolldown = "SELECT division_gender || division_age AS \"Agegroup\", result_finish AS \"Finish Time\", factor_value AS \"Faktor\", result_normalized AS \"Rolldown Time\" FROM ".SCHEMA.".view_result_normalized_rolldown WHERE fk_race=FK_RACE;";
+
+
+?>
\ No newline at end of file
diff --git a/roadtokona-web/src/webui/lib/db/database_functions_pgsql.php b/roadtokona-web/src/webui/lib/db/database_functions_pgsql.php
new file mode 100644 (file)
index 0000000..7d3ca8e
--- /dev/null
@@ -0,0 +1,104 @@
+<?php
+
+/**
+ * Mit PostgresSQL verbinden */
+function getDBConnection() {
+
+       $str_connection = "host=".HOST." port=5432 dbname=".DB." user=".USER." password=".PASS." ";
+       //echo $str_connection;
+       // mit DB verbinden
+       //$connect_dbms_handle = new PDO("pgsql:dbname=".DB, USER, PASS);
+       $connect_dbms_handle = @pg_connect($str_connection) or die('Verbindungsaufbau fehlgeschlagen: ' . pg_last_error());
+
+       if ($connect_dbms_handle) {
+               return $connect_dbms_handle;
+       } else {
+               echo "Datenbankabfrage fehlgeschlagen!";
+               echo pg_last_error($connect_dbms_handle);
+               return false;
+       }
+}
+
+/**
+ * PostgresSQL-Verbindung schliessen */
+function closeDBConnection($ref_connection) {
+       @pg_close($ref_connection) or die('Verbindungsclose fehlgeschlagen: ');
+       //echo pg_last_error($ref_connection);
+}
+
+
+// SQL an DB absetzen
+// Parameter: Tablle, Feld(er), Wert(e)
+// Rückgabewert: boolean
+function getBooleanData ($dbms_connection, $db_query) {
+       // Variablen
+       $return_bool_value = false ;
+
+       // DB Abfage starten
+       //echo $db_query;
+       $res_sql_result = pg_query ($dbms_connection, $db_query);
+       // Gültigkeit der DB Abfage testen
+       if ($res_sql_result) {
+               //echo "<li>".$res_sql_result;
+               $return_bool_value = true;
+       } else {
+               return "Datenbankabfrage fehlgeschlagen!";
+       }
+
+       return $return_bool_value;
+}
+
+
+// SQL an DB absetzen
+// Parameter: Tablle, Feld(er), Wert(e)
+// Rückgabewert: Array
+function getData ($dbms_connection, $db_query) {
+    $return_arr_data = array();                 // Rückgabewert als Array
+    //echo "\n<p> SQL:<br>".$ref_str_db_query."</p>";
+    
+    // DB Abfage starten
+    //echo $db_query;
+    $res_sql_result = pg_query ($dbms_connection, $db_query);
+    // Gültigkeit der DB Abfage testen
+    if ($res_sql_result) {
+        //echo "<p>".$res_sql_result."</p>";
+        while ($arr_sql_data = pg_fetch_row($res_sql_result)) {
+            //echo debugPrint($arr_sql_data);
+            array_push ($return_arr_data, $arr_sql_data);
+        }
+        //var_dump( $return_arr_data);
+    } else {
+        return "Datenbankabfrage fehlgeschlagen!";
+    }
+    
+    return $return_arr_data;
+}
+
+
+// SQL an DB absetzen
+// Parameter: Tablle, Feld(er), Wert(e)
+// Rückgabewert: Array
+function getDataReturnID ($dbms_connection, $db_query) {
+    $return_arr_data = array();                 // Rückgabewert als Array
+    //echo "\n<p> SQL:<br>".$ref_str_db_query."</p>";
+    
+    // DB Abfage starten
+    //echo $db_query;
+    $res_sql_result = pg_query ($dbms_connection, $db_query);
+    // Gültigkeit der DB Abfage testen
+    if ($res_sql_result) {
+        //echo "<p>".$res_sql_result."</p>";
+        while ($arr_sql_data = pg_fetch_row($res_sql_result)) {
+            //echo debugPrint($arr_sql_data);
+            array_push ($return_arr_data, $arr_sql_data);
+        }
+        //var_dump( $return_arr_data);
+    } else {
+        return "Datenbankabfrage fehlgeschlagen!";
+    }
+    
+    return $return_arr_data[0][0];
+}
+
+
+?>
\ No newline at end of file
diff --git a/roadtokona-web/src/webui/lib/db/database_functions_select.php b/roadtokona-web/src/webui/lib/db/database_functions_select.php
new file mode 100644 (file)
index 0000000..fa8e18a
--- /dev/null
@@ -0,0 +1,78 @@
+<?php
+function generateSelect ($selectedFields, $table) {
+       $select_str = "SELECT ";
+       
+       if (is_array($selectedFields)) {
+               //print_r($selectedFields);
+       $anzFields = count($selectedFields);
+       for ($i = 0 ; $i < $anzFields; $i++) {
+               $select_str .= $selectedFields[$i];
+               if ($i < $anzFields-1) {
+                       $select_str .= ", ";
+               }
+       }
+       } else {
+           $select_str .= "*";
+       }
+       
+       $select_str .= " FROM ".$table;
+       
+       $select_str .= ";";
+
+       //echo $select_str;
+       return $select_str;
+}
+
+
+function generateSelectWhereOrder ($selectedFields, $table, $whereClause, $orderClause) {
+       $select_str = "SELECT ";
+
+       if (is_array($selectedFields)) {
+           //print_r($selectedFields);
+       $anzFields = count($selectedFields);
+       for ($i = 0 ; $i < $anzFields; $i++) {
+               $select_str .= $selectedFields[$i];
+               if ($i < $anzFields-1) {
+                       $select_str .= ", ";
+               }
+       }
+       } else {
+           $select_str .= "*";
+       }
+
+       $select_str .= " FROM ".$table;
+
+       if ($whereClause != null) {
+               $select_str .= " WHERE ";
+
+               //print_r($whereClause);
+               $anzWheres = count($whereClause);
+               for ($i = 0 ; $i < $anzWheres; $i++) {
+                       $select_str .= $whereClause[$i];
+                       if ($i < $anzWheres-1) {
+                               $select_str .= " AND ";
+                       }
+               }
+       }
+
+       if ($orderClause != null) {
+               $select_str .= " ORDER BY ";
+
+               //print_r($orderClause);
+               $anzOrders = count($orderClause);
+               for ($i = 0 ; $i < $anzOrders; $i++) {
+                       $select_str .= $orderClause[$i];
+                       if ($i < $anzOrders-1) {
+                               $select_str .= ", ";
+                       }
+               }
+       }
+
+       $select_str .= ";";
+
+       //echo $select_str;
+       return $select_str;
+}
+
+
+?>
\ No newline at end of file
diff --git a/roadtokona-web/src/webui/lib/gui/gui_functions_select.php b/roadtokona-web/src/webui/lib/gui/gui_functions_select.php
new file mode 100644 (file)
index 0000000..6a6c248
--- /dev/null
@@ -0,0 +1,95 @@
+<?php
+
+/** $ref_select_name                Name des <select>
+ *  $ref_value_array                Daten-Array; null möglich
+ *  $ref_id                         Id des <select>
+ *  $ref_class                      Class des <select>; null möglich
+ *  $ref_tabindex                   Tabindex in der <form>
+
+ *  $ref_start                      Start-Index statt Datenarray falls Datenarray null
+ *  $ref_end                        End-Index statt Datenarray falls Datenarray null
+ *  $ref_int_preselect              Index vorausgewähltes Element; default -1
+ *  $ref_multiple                   Mehrfachauswahl <select multiple>; default false
+ *  $ref_int_size                   Für mehrzeilige Mehrfachauswahl > 1;
+ *  $ref_javascript                 Javascript; null möglich
+ *  $ref_mandantory                 Pflichtfeldmarkierung; null möglich
+ *  $ref_labeltitle                 <label>-Text; null möglich
+ *  $ref_p_flag                     true, wenn alles in <p> soll
+ *  $ref_newline_flag               true, wenn <label> ueber <select> stehen soll
+ *  $ref_intend                     Anz. Tabs einrücken
+ * */
+function makeSelect($ref_select_name, $ref_value_array, $ref_id, $ref_class, $ref_tabindex, $ref_start, $ref_end, $ref_int_preselect, $ref_multiple, $ref_int_size, $ref_javascript, $ref_mandantory, $ref_labeltitle, $ref_p_flag, $ref_newline_flag, $ref_intend) {
+
+       $intend = make_intend_str($ref_intend);
+
+    if ($ref_p_flag) {
+        echo $intend;
+        echo "<p>\n";
+    }
+
+       echo $intend."\t";
+
+    if ($ref_mandantory) {
+        echo "* ";
+    }
+    
+       if ($ref_labeltitle != null) {
+               echo "<label for=\"".$ref_select_name."\">".$ref_labeltitle."</label>";
+               if ($ref_newline_flag) {
+                       echo "<br />";
+               }
+               echo "\n";
+       }
+
+       $str_id = $ref_id == null ? " " : " id=\"".$ref_id."\" ";
+       $str_class = $ref_class == null ? " " : " class=\"".$ref_class."\" ";
+       $str_tabindex = $ref_tabindex == null ? " " : " tabindex=\"".$ref_tabindex."\" ";
+       $str_javascript = $ref_javascript == null ? " " : " onchange=\"".$ref_javascript."\" ";
+
+       echo $intend."\t";
+
+       echo "<select ".$str_id.$str_class.$str_tabindex.$str_javascript."name=\"".$ref_select_name."\" size=\"".$ref_int_size."\" "." ";
+       if ($ref_multiple == true) {
+               echo "multiple=\"multiple\" ";
+       }
+       echo ">\n";
+
+       if ($ref_value_array != null) {
+
+               // Flag: True, falls das Datenarray multidimensional ist. Dann wird das Attribut value in <option> mit dem wert aus dem Unterarray gefüllt, ansonsten wird der Schleifenindex verwendet.
+               $multiDimensionalArrayFlag = is_array($ref_value_array[0]);
+
+               for ($i=0; $i<count($ref_value_array); $i++) {
+                       echo $intend;
+                       echo "\t\t<option value=\"".($multiDimensionalArrayFlag ? $ref_value_array[$i][0] : $i)."\"";
+                       if (($multiDimensionalArrayFlag ? $ref_value_array[$i][0] : ($i+1)) == $ref_int_preselect) { // $i-1 deshalb, weil das Array mit dem 0-ten Element beginnt, der Value aber mit 1 anfängt
+                               echo " selected";
+                       }
+                       echo ">".($multiDimensionalArrayFlag ? $ref_value_array[$i][1] : $ref_value_array[$i])."</option>\n";
+               }
+       } else {
+               for ($i=$ref_start; $i<(1+$ref_end); $i++) {
+                       echo $intend;
+                       echo "\t\t<option value=\"".$i."\"";
+                       if ($i == $ref_int_preselect) {
+                               echo " selected";
+                       }
+                       echo ">".$i."</option>\n";
+               }
+       }
+
+       echo $intend."\t";
+       echo "</select>\n";
+
+       if ($ref_p_flag) {
+               echo $intend;
+               echo "</p>\n";
+       }
+}
+
+
+
+
+
+?>
\ No newline at end of file
diff --git a/roadtokona-web/src/webui/lib/gui_functions.php b/roadtokona-web/src/webui/lib/gui_functions.php
new file mode 100644 (file)
index 0000000..fd747a2
--- /dev/null
@@ -0,0 +1,7 @@
+<?php
+
+require ("gui/gui_functions_select.php");
+
+
+
+?>
\ No newline at end of file
diff --git a/roadtokona-web/src/webui/lib/util/util_functions.php b/roadtokona-web/src/webui/lib/util/util_functions.php
new file mode 100644 (file)
index 0000000..f0e70c7
--- /dev/null
@@ -0,0 +1,65 @@
+<?php
+
+function debugPrint($param) {
+       echo "<p>";
+       if (is_array($param)) {
+               if ($asList) {
+                   debugPrintAsList($param);
+               } else {
+                       echo "_";
+                       foreach ($param as $p) {
+                               echo $p."_";
+                       }
+               }
+       } else {
+               echo $param;
+       }
+       echo "</p>\n";
+}
+
+function debugPrintAsList($param) {
+       echo "<ol>\n";
+       foreach ($param as $p) {
+               echo "<li>".$p."_</li>\n";
+       }
+       echo "</ol>\n";
+}
+
+function coordinates ($ref_longitude, $ref_latitude) {
+       $return_geo = array("","");
+
+       $return_geo[0] = $ref_longitude[0] + $ref_longitude[1]/60 + $ref_longitude[2]/3600;
+       $return_geo[1] = $ref_latitude[0] + $ref_latitude[1]/60 + $ref_latitude[2]/3600;
+
+       return $return_geo;
+}
+
+function find_pos_in_array($ref_array, $ref_value) {
+       $pos = -1;
+       
+       for ($i=0; $i<count($ref_array); $i++) {
+               if ($ref_array[$i] == $ref_value) {
+                       $pos = $i;
+               }
+       }
+       
+       return $pos;
+}
+
+function make_intend($ref_count) {
+       for ($i=0; $i<$ref_count; $i++) {
+               echo "\t";
+       }
+}
+
+function make_intend_str($ref_count) {
+       $intend = "";
+       for ($i=0; $i<$ref_count; $i++) {
+               $intend .= "\t";
+       }
+       return $intend;
+}
+
+
+
+?>
\ No newline at end of file
diff --git a/roadtokona-web/src/webui/lib/util_functions.php b/roadtokona-web/src/webui/lib/util_functions.php
new file mode 100644 (file)
index 0000000..63b5bd3
--- /dev/null
@@ -0,0 +1,6 @@
+<?php
+
+require ("util/util_functions.php");
+
+
+?>
\ No newline at end of file
diff --git a/roadtokona-web/src/webui/stylesheet/roadtokona.css b/roadtokona-web/src/webui/stylesheet/roadtokona.css
new file mode 100644 (file)
index 0000000..a344930
--- /dev/null
@@ -0,0 +1,105 @@
+
+BODY {
+       font-size: 100.01%;
+       font-family: Verdana, sans-serif;
+       padding: 0;
+       margin: 0;
+}
+
+TABLE, TH, TD {
+  border: none;
+}
+
+TH, TD {
+  padding: 5px;
+  text-align: right;
+}
+
+H1 {
+       font-size: 1.5em;
+       margin: 0 0 0.5em 1em;
+       color: rgb(0, 121, 138);
+}
+
+H2 {
+       font-size: 1.25em;
+       margin: 1em 0 0.5em 1em;
+       color: rgb(0, 121, 138);
+}
+
+P {
+       margin: 0 2em;
+}
+
+P.cta,
+P.cta A {
+       margin: 0 2em;
+       color: rgb(166, 12, 36);
+    font-weight: 600;
+}
+
+FORM, SELECT, OPTION {
+       font-size: 1.150em;
+}
+
+SELECT, OPTION {
+       background-color: rgb(243, 173, 0);
+}
+
+FORM {
+       margin: 0 2em;
+       background-color: rgb(173, 218, 213);
+}
+
+LEGEND {
+       font-weight: bold;
+}
+
+.factors-container {
+       display: flex;
+       gap: 1rem;
+       margin: 0 2em;
+       margin-top: 1rem;
+       /*--font-size: 250%;*/
+}
+
+.factors-container div {
+       flex: 1;
+       padding: 1rem;
+       border: 1px solid #000;
+       background-color: rgb(226, 239, 243);
+}
+
+/* Mobile: Untereinander */
+@media (max-width: 480px) {
+       .factors-container {
+               flex-direction: column;
+               gap: 1rem;
+       }
+}
+
+#results,
+#result {
+       border: 1px solid #000;
+       background-color: rgb(226, 239, 243);
+       margin-top: 1rem;
+}
+
+.highlight {
+    background-color: yellow;
+    font-weight: bold;
+}
+
+FOOTER {
+       margin: 1rem 0 1em 1em;
+}
+
+A {
+       color: #000;
+}
+
+@keyframes pulse {
+    0% { transform: scale(1); }
+    50% { transform: scale(1.05); }
+    100% { transform: scale(1); }
+}