Semi-stable version with old variable names
@@ -0,0 +1,9 @@
|
||||
RewriteEngine On
|
||||
|
||||
ErrorDocument 429 /error-pages/429.html
|
||||
ErrorDocument 403 /error-pages/403.html
|
||||
ErrorDocument 500 /error-pages/500.html
|
||||
ErrorDocument 400 /error-pages/400.html
|
||||
ErrorDocument 404 /error-pages/400.html
|
||||
|
||||
RewriteRule ^(riegeneinteilung)$ $1.php [L]
|
||||
@@ -0,0 +1,30 @@
|
||||
RewriteEngine On
|
||||
|
||||
ErrorDocument 503 /503.html
|
||||
|
||||
RewriteCond %{REQUEST_URI} !^/503\.html$
|
||||
|
||||
RewriteRule ^(index)$ $1.php [L]
|
||||
RewriteRule ^(404|503)$ $1.html [L]
|
||||
|
||||
# ----------------------------------------
|
||||
# 1) Root → index.php
|
||||
# ----------------------------------------
|
||||
RewriteRule ^$ router.php [L]
|
||||
|
||||
# ----------------------------------------
|
||||
# 2) Allow existing files
|
||||
# ----------------------------------------
|
||||
RewriteCond %{REQUEST_FILENAME} -f
|
||||
RewriteRule ^ - [L]
|
||||
|
||||
# ----------------------------------------
|
||||
# 3) Allow existing directories
|
||||
# ----------------------------------------
|
||||
RewriteCond %{REQUEST_FILENAME} -d
|
||||
RewriteRule ^ - [L]
|
||||
|
||||
# ----------------------------------------
|
||||
# 4) Everything else → router.php
|
||||
# ----------------------------------------
|
||||
RewriteRule ^ router.php [QSA,L]
|
||||
@@ -0,0 +1,345 @@
|
||||
const selecteddiscipline = window.FREIGABE;
|
||||
|
||||
const csrf_token = window.CSDR_TOKEN;
|
||||
|
||||
let ws;
|
||||
let firstConnect = true;
|
||||
let wsOpen = false;
|
||||
const RETRY_DELAY = 5000;
|
||||
|
||||
const WSaccesstype = "rankLive";
|
||||
const WSaccess = window.FREIGABE;
|
||||
const WSaccessPermission = "R";
|
||||
|
||||
const urlAjaxNewWSToken = '/intern/scripts/ajax-create-ws-token.php';
|
||||
|
||||
async function fetchNewWSToken(accesstype, access, accessPermission) {
|
||||
try {
|
||||
const response = await fetch(urlAjaxNewWSToken, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
csrf_token,
|
||||
accesstype,
|
||||
access,
|
||||
accessPermission
|
||||
})
|
||||
});
|
||||
|
||||
if (response.status === 403) {
|
||||
console.warn("Please Re-Autenithicate. Reloading page...");
|
||||
location.reload();
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!response.ok) return null;
|
||||
|
||||
const data = await response.json();
|
||||
return data.success ? data.token : null;
|
||||
} catch (error) {
|
||||
console.error("Token fetch failed:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function startWebSocket() {
|
||||
console.log("Attempting WebSocket connection...");
|
||||
|
||||
let token;
|
||||
if (firstConnect) {
|
||||
token = window.WS_ACCESS_TOKEN;
|
||||
} else {
|
||||
token = await fetchNewWSToken(WSaccesstype, WSaccess, WSaccessPermission);
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
console.error("No valid token available. Retrying...");
|
||||
scheduleRetry();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
ws = new WebSocket(`wss://${window.location.hostname}/ws/?access=token&token=${token}`);
|
||||
} catch (err) {
|
||||
console.error("Malformed WebSocket URL", err);
|
||||
scheduleRetry();
|
||||
return;
|
||||
}
|
||||
|
||||
ws.onopen = () => {
|
||||
console.log("WebSocket connected!");
|
||||
if (!firstConnect) {
|
||||
displayMsg(1, "Live Syncronisation wieder verfügbar");
|
||||
}
|
||||
firstConnect = true;
|
||||
wsOpen = true;
|
||||
};
|
||||
|
||||
ws.onerror = (event) => {
|
||||
console.error("WebSocket error observed." + JSON.stringify(event));
|
||||
};
|
||||
|
||||
ws.onclose = (event) => {
|
||||
|
||||
if (firstConnect) {
|
||||
displayMsg(0, "Live Syncronisation verloren");
|
||||
}
|
||||
firstConnect = false;
|
||||
wsOpen = false;
|
||||
|
||||
scheduleRetry();
|
||||
};
|
||||
}
|
||||
|
||||
function scheduleRetry() {
|
||||
console.log(`Retrying in ${RETRY_DELAY}ms...`);
|
||||
setTimeout(startWebSocket, RETRY_DELAY);
|
||||
}
|
||||
|
||||
// Start the initial connection attempt safely
|
||||
startWebSocket();
|
||||
|
||||
let rangNotenArray = JSON.parse(window.RANG_NOTEN_ARRAY) ?? [];
|
||||
|
||||
const rangNotenId = window.RANG_NOTE_ID ?? 0;
|
||||
|
||||
const rangSort = window.RANG_SORT ?? '';
|
||||
const isLive = parseInt(window.LIVE ?? 0);
|
||||
|
||||
ws.addEventListener("message", async function(event) {
|
||||
let msgOBJ;
|
||||
|
||||
try {
|
||||
msgOBJ = JSON.parse(event.data);
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure it's an UPDATE type (matches your sendToGroup logic)
|
||||
if (msgOBJ?.type === "UPDATE") {
|
||||
const data = msgOBJ.payload;
|
||||
|
||||
// Check access rights
|
||||
if (data.discipline === selecteddiscipline.toLowerCase() || selecteddiscipline === 'A') {
|
||||
const noten = data.rankLive;
|
||||
|
||||
const programmId = $(`tr[data-person-id="${data.personId}"]`).closest('tbody').attr('data-programm-id') ?? null;
|
||||
|
||||
if (!isLive && noten[0][1][rangNotenId] !== undefined && rangNotenArray[programmId][data.personId] !== undefined) {
|
||||
rangNotenArray[programmId][data.personId] = noten[0][1][rangNotenId];
|
||||
applyRanks(rankProgramm(rangNotenArray, programmId, rangSort), true);
|
||||
}
|
||||
|
||||
for (const [keyG, noteGroup] of Object.entries(noten)) {
|
||||
|
||||
for (const [run, runGroup] of Object.entries(noteGroup)) {
|
||||
|
||||
for (const [key, value] of Object.entries(runGroup)) {
|
||||
|
||||
const $elements = $(`.changebleValue[data-field-type-id="${key}"][data-geraet-id="${keyG}"][data-person-id="${data.personId}"][data-run="${run}"]`);
|
||||
$elements.each(function() {
|
||||
const $el = $(this);
|
||||
|
||||
const isNullable = ($el.attr('data-noten-nullable') ?? 'false') === 'true';
|
||||
const floatValZero = parseFloat(value.replace(",", ".").replace("'", "")) === 0.0;
|
||||
|
||||
if (isNullable && floatValZero || value === '') {
|
||||
$el.text('-').addClass('emtyPlaceholder');
|
||||
} else {
|
||||
$el.text(value).removeClass('emtyPlaceholder');
|
||||
}
|
||||
|
||||
$el.removeClass('updated');
|
||||
$el.addClass('updated');
|
||||
|
||||
updateDependentVisibility($el);
|
||||
|
||||
setTimeout(() => {
|
||||
$el.removeClass('updated');
|
||||
}, 2000);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (msgOBJ?.type === "UPDATE_RANKLIVE_C_SUBABT") {
|
||||
if (!(await displayConfirm('Eine neue Gruppe ist aktiv. Fenster aktualisieren?'))) return;
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
|
||||
function rankProgramm(dataObj, programmId, order = 'DESC') {
|
||||
if (!dataObj[programmId]) {
|
||||
console.error(`ID ${programmId} not found in data.`);
|
||||
return [];
|
||||
}
|
||||
|
||||
const values = dataObj[programmId];
|
||||
|
||||
// 1. Transform and Sort
|
||||
const sorted = Object.entries(values)
|
||||
.map(([id, val]) => ({ id, val }))
|
||||
.sort((a, b) => {
|
||||
// 1. Handle null values first (Always push them to the bottom)
|
||||
if (a.val === null && b.val === null) return 0;
|
||||
if (a.val === null) return 1; // Move 'a' to a higher index (down)
|
||||
if (b.val === null) return -1; // Move 'b' to a higher index (down)
|
||||
|
||||
// 2. Normal sorting logic for valid numbers
|
||||
return order.toUpperCase() === 'ASC'
|
||||
? a.val - b.val
|
||||
: b.val - a.val;
|
||||
});
|
||||
|
||||
let lastVal = null;
|
||||
let lastRank = 0;
|
||||
|
||||
let sortedMap = [];
|
||||
|
||||
sortedMap[programmId] = sorted.map((item, index) => {
|
||||
if (item.val !== lastVal) {
|
||||
lastRank = index + 1;
|
||||
}
|
||||
|
||||
lastVal = item.val;
|
||||
|
||||
return {
|
||||
rang: lastRank,
|
||||
...item
|
||||
};
|
||||
});
|
||||
|
||||
return sortedMap;
|
||||
}
|
||||
|
||||
function rankAll(dataObj, order = 'DESC') {
|
||||
return Object.fromEntries(
|
||||
Object.entries(dataObj).map(([groupKey, values]) => {
|
||||
const sorted = Object.entries(values)
|
||||
.map(([id, val]) => ({ id, val }))
|
||||
.sort((a, b) => {
|
||||
// 1. Handle null values first (Always push them to the bottom)
|
||||
if (a.val === null && b.val === null) return 0;
|
||||
if (a.val === null) return 1; // Move 'a' to a higher index (down)
|
||||
if (b.val === null) return -1; // Move 'b' to a higher index (down)
|
||||
|
||||
// 2. Normal sorting logic for valid numbers
|
||||
return order.toUpperCase() === 'ASC'
|
||||
? a.val - b.val
|
||||
: b.val - a.val;
|
||||
});
|
||||
|
||||
let lastVal = null;
|
||||
let lastRank = 0;
|
||||
|
||||
const ranked = sorted.map((item, index) => {
|
||||
if (item.val !== lastVal) {
|
||||
lastRank = index + 1;
|
||||
}
|
||||
lastVal = item.val;
|
||||
|
||||
return {
|
||||
rang: lastRank,
|
||||
...item
|
||||
};
|
||||
});
|
||||
|
||||
return [groupKey, ranked];
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function applyRanks(array, sort = false) {
|
||||
for (const [key, entries] of Object.entries(array)) {
|
||||
const $tbody = $(`tbody[data-programm-id="${key}"]`);
|
||||
|
||||
if ($tbody.length === 0) continue;
|
||||
|
||||
for (const [skey, entry] of Object.entries(entries)) {
|
||||
const $row = $tbody.find(`tr[data-person-id="${entry.id}"]`);
|
||||
|
||||
if ($row.length === 0) continue;
|
||||
|
||||
$row.attr('data-sort-rank', entry.rang);
|
||||
|
||||
const $cell = $row.find('.rangField');
|
||||
|
||||
const points = parseFloat(rangNotenArray[key][entry.id]);
|
||||
|
||||
const text = !isNaN(points) ? entry.rang : '';
|
||||
|
||||
$cell.text(text);
|
||||
}
|
||||
|
||||
if (sort) {
|
||||
const rows = $tbody.find('tr').get();
|
||||
|
||||
rows.sort((a, b) => {
|
||||
const rankA = parseFloat($(a).attr('data-sort-rank')) || 999;
|
||||
const rankB = parseFloat($(b).attr('data-sort-rank')) || 999;
|
||||
return rankA - rankB;
|
||||
});
|
||||
|
||||
// Re-append the sorted rows back into the tbody
|
||||
$.each(rows, (index, row) => {
|
||||
$tbody.append(row);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
applyRanks(rankAll(rangNotenArray, rangSort), true);
|
||||
|
||||
function updateDependentVisibility($changedEl) {
|
||||
const uid = $changedEl.attr('data-uid');
|
||||
if (uid === undefined || uid === null) return;
|
||||
|
||||
const $row = $changedEl.closest('tr');
|
||||
|
||||
if ($row.length !== 1) return;
|
||||
|
||||
const isVisible = $changedEl.hasClass('emtyPlaceholder');
|
||||
const doesExist = $changedEl.hasClass('nonExistentEl');
|
||||
|
||||
const $linkedEl = $row.find(`.singleValueSpan[data-linked-el-uid="${uid}"]`)
|
||||
|
||||
$linkedEl.toggleClass('emtyPlaceholder', isVisible).toggleClass('nonExistentEl', doesExist);
|
||||
}
|
||||
|
||||
function initConditionalVisibility() {
|
||||
const $table = $('table.customDisplayEditorTable');
|
||||
|
||||
const $rows = $table.find('tr');
|
||||
|
||||
$rows.each((ind, el) => {
|
||||
const $row = $(el);
|
||||
$row.find('.singleValueSpan[data-linked-el-uid]').each(function() {
|
||||
console.log('ja');
|
||||
const linkedUid = $(this).data('linked-el-uid');
|
||||
|
||||
const $linkedEl = $row.find(`.singleValueSpan[data-uid="${linkedUid}"]`);
|
||||
|
||||
if ($linkedEl.length === 1) {
|
||||
updateDependentVisibility($linkedEl);
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
initConditionalVisibility();
|
||||
initConditionalVisibility();
|
||||
|
||||
const $parent = $('.allAbtContainer');
|
||||
const $children = $parent.children('.shiftedGeraetTable');
|
||||
|
||||
$children.sort((a, b) => {
|
||||
const idA = parseFloat($(a).data('geraet-index'));
|
||||
const idB = parseFloat($(b).data('geraet-index'));
|
||||
|
||||
return idA - idB;
|
||||
});
|
||||
|
||||
$parent.append($children);
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
$request = trim(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), '/');
|
||||
$segments = $request === '' ? [] : explode('/', $request);
|
||||
|
||||
$segments = array_map('urldecode', $segments);
|
||||
|
||||
$type = $segments[1] ?? null;
|
||||
$slug = $segments[2] ?? null;
|
||||
|
||||
$allowed_types = ["programm", "live"];
|
||||
|
||||
if (($type !== null && !in_array($type, $allowed_types)) || count($segments) > 3) {
|
||||
http_response_code(400);
|
||||
include $baseDir . "/error-pages/400.html";
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
define('APP_ROUTER', true);
|
||||
|
||||
require __DIR__ . "/script.php";
|
||||
@@ -0,0 +1,655 @@
|
||||
<?php
|
||||
|
||||
ini_set('display_errors', 1);
|
||||
|
||||
if (!isset($segments) || !defined('APP_ROUTER')) {
|
||||
http_response_code(403);
|
||||
include $baseDir . "/error-pages/403.php";
|
||||
exit;
|
||||
}
|
||||
|
||||
$type = $segments[1] ?? null;
|
||||
$slug = $segments[2] ?? null;
|
||||
|
||||
$allowed_types = ["programm", "live"];
|
||||
|
||||
if ($type !== null && !in_array($type, $allowed_types)) {
|
||||
http_response_code(400);
|
||||
include $baseDir . "/error-pages/400.html";
|
||||
exit;
|
||||
}
|
||||
|
||||
$baseDir = $_SERVER['DOCUMENT_ROOT'];
|
||||
|
||||
require $baseDir . '/../scripts/db/db-verbindung-script-guest.php';
|
||||
|
||||
require $baseDir . "/../scripts/db/db-tables.php";
|
||||
require $baseDir . "/../scripts/db/db-functions.php";
|
||||
|
||||
$rankLive_public = db_get_variable($guest, $tableVar, ['rankLivePublic']) ?? 0;
|
||||
|
||||
require $baseDir . '/../scripts/websocket/ws-create-token.php';
|
||||
|
||||
require_once $baseDir . '/../scripts/session_functions.php';
|
||||
|
||||
ini_wkvs_session(true);
|
||||
|
||||
$csrf_token = $_SESSION['csrf_token'] ?? '';
|
||||
|
||||
if (!$rankLive_public) {
|
||||
check_user_permission('wk_leitung', false, true);
|
||||
}
|
||||
|
||||
$is_live = $type === 'live';
|
||||
|
||||
if ($slug !== null && !$is_live) {
|
||||
|
||||
$alle_programme_db = db_select($guest, $tableProgramme, '`programm`', 'aktiv = ?', ['1']);
|
||||
$raw_programme = array_column($alle_programme_db, 'programm', 'programm');
|
||||
|
||||
$alle_programme = [];
|
||||
foreach ($raw_programme as $prog) {
|
||||
$normalized_key = str_replace(' ', '-', strtolower($prog));
|
||||
$alle_programme[$normalized_key] = $prog;
|
||||
}
|
||||
|
||||
$req_prog = str_replace(' ', '-', strtolower($slug));
|
||||
|
||||
if (!isset($alle_programme[$req_prog])) {
|
||||
http_response_code(400);
|
||||
$message = 'Das gesuchte Programm kann nicht gefunden werden';
|
||||
include $baseDir . "/error-pages/400.php";
|
||||
exit;
|
||||
}
|
||||
|
||||
$validated_programm_name = $alle_programme[$req_prog];
|
||||
}
|
||||
|
||||
$selectedFreigabeId = 'A';
|
||||
|
||||
$json_type = $is_live ? 'rankLive-geraet' : 'rankLive-overview';
|
||||
|
||||
$jsonstr = db_select($guest, $tableRankLiveConfigs, 'json', 'type_slug = ?', [$json_type], '', 1);
|
||||
|
||||
$data = json_decode($jsonstr[0]['json'], true);
|
||||
|
||||
$headers = $data['header'] ?? [];
|
||||
$bodyColumns = $data['body'] ?? [];
|
||||
|
||||
$current_year = date('Y');
|
||||
$monat = date('n');
|
||||
if ($monat > 6) {
|
||||
$current_year = $current_year + 1;
|
||||
}
|
||||
|
||||
$alle_programme_db = db_select($guest, $tableProgramme, '`programm`, `id`', 'aktiv = ?', ['1']);
|
||||
$indexedProgrammes = array_column($alle_programme_db, 'programm', 'id');
|
||||
|
||||
$personData = [
|
||||
'geburtsdatum' => 'Geburtsdatum',
|
||||
'name' => 'Name',
|
||||
'vorname' => 'Vorname',
|
||||
'verein' => 'Verein',
|
||||
'programm' => 'Programm'
|
||||
];
|
||||
|
||||
$disciplines = db_select($guest, $tableGeraete, '*', '', [], 'start_index ASC');
|
||||
|
||||
$indexedArrayGeraete = array_column($disciplines, 'name', 'id');
|
||||
|
||||
if ($is_live) {
|
||||
$indexed_array_geraete_id_index = array_column($disciplines, 'start_index', 'id');
|
||||
|
||||
$indexed_array_geraete_index_id = array_flip($indexed_array_geraete_id_index);
|
||||
|
||||
$abt = (int) (db_get_variable($guest, $tableVar, ['wk_panel_current_abt']) ?? 1);
|
||||
$akt_subabt = (int) (db_get_variable($guest, $tableVar, ['wk_panel_current_subabt_admin']) ?? 1);
|
||||
|
||||
$max_subabt = count($disciplines);
|
||||
|
||||
$stmt = $guest->prepare("SELECT
|
||||
t.id,
|
||||
t.name,
|
||||
t.vorname,
|
||||
t.programm,
|
||||
t.verein,
|
||||
t.geburtsdatum,
|
||||
p.id AS programm_id,
|
||||
tabt.`turnerin_index` AS start_index,
|
||||
tabt.`geraet_id` AS startgeraet,
|
||||
tabt.`abteilung_id` AS abt_id
|
||||
FROM $tableTeilnehmende t
|
||||
LEFT JOIN $tableProgramme p ON p.programm = t.programm
|
||||
INNER JOIN $tableTeilnehmendeAbt tabt ON tabt.`turnerin_id` = t.`id`
|
||||
INNER JOIN $tableAbt abt ON abt.`id` = tabt.`abteilung_id`
|
||||
WHERE (t.bezahlt = 2 OR t.bezahltoverride = 5) AND abt.`order_index` = ?");
|
||||
|
||||
$stmt->bind_param("i", $abt);
|
||||
} elseif ($type === 'programm' && $slug !== null) {
|
||||
$stmt = $guest->prepare("SELECT
|
||||
u.id,
|
||||
u.name,
|
||||
u.vorname,
|
||||
u.programm,
|
||||
u.verein,
|
||||
u.geburtsdatum,
|
||||
p.id AS programm_id
|
||||
FROM $tableTeilnehmende u
|
||||
LEFT JOIN $tableProgramme p ON p.programm = u.programm
|
||||
WHERE (u.bezahlt = 2 OR u.bezahltoverride = 5) AND u.programm = ?");
|
||||
|
||||
$stmt->bind_param("s", $validated_programm_name);
|
||||
} else {
|
||||
$stmt = $guest->prepare("SELECT
|
||||
u.id,
|
||||
u.name,
|
||||
u.vorname,
|
||||
u.programm,
|
||||
u.verein,
|
||||
u.geburtsdatum,
|
||||
p.id AS programm_id
|
||||
FROM $tableTeilnehmende u
|
||||
LEFT JOIN $tableProgramme p ON p.programm = u.programm
|
||||
WHERE (u.bezahlt = 2 OR u.bezahltoverride = 5)");
|
||||
}
|
||||
|
||||
$stmt->execute();
|
||||
|
||||
$result = $stmt->get_result();
|
||||
$tures = $result->fetch_all(MYSQLI_ASSOC);
|
||||
|
||||
$stmt->close();
|
||||
|
||||
$indexedTures = [];
|
||||
|
||||
foreach ($tures as $s_tures) {
|
||||
$indexedTures[$s_tures['programm_id']][$s_tures['id']] = $s_tures;
|
||||
}
|
||||
|
||||
$stmt = $guest->prepare("SELECT `note_bezeichnung_id`, `geraet_id`, `person_id`, `run_number`, `public_value`
|
||||
FROM $tableNoten
|
||||
WHERE `is_public` = 1
|
||||
AND `jahr` = ?");
|
||||
|
||||
$stmt->bind_param("i", $current_year);
|
||||
|
||||
$stmt->execute();
|
||||
|
||||
$result = $stmt->get_result();
|
||||
$noten = $result->fetch_all(MYSQLI_ASSOC);
|
||||
|
||||
$stmt->close();
|
||||
|
||||
$indexedNoten = [];
|
||||
|
||||
$stmt = $guest->prepare("SELECT `id`, `geraete_json`, `pro_geraet`, `anzahl_laeufe_json`, `name`, `default_value` FROM $tableNotenBezeichnungen");
|
||||
|
||||
$stmt->execute();
|
||||
|
||||
$result = $stmt->get_result();
|
||||
$notenConfig = $result->fetch_all(MYSQLI_ASSOC);
|
||||
|
||||
$stmt->close();
|
||||
|
||||
$disciplines = db_select($guest, $tableGeraete, '*', '', [], 'start_index ASC');
|
||||
|
||||
$indexedNotenConfig = array_column($notenConfig, null, 'id');
|
||||
|
||||
$rangNote = intval(db_get_var($guest, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['rangNote']));
|
||||
|
||||
$orderBestRang = db_get_var($guest, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['orderBestRang']);
|
||||
|
||||
$okValuesOrderBestRang = ["ASC", "DESC"];
|
||||
|
||||
$rangOrderOk = in_array($orderBestRang, $okValuesOrderBestRang) && intval($rangNote) > 0;
|
||||
|
||||
$notenIndexed = [];
|
||||
|
||||
foreach ($noten as $sn) {
|
||||
$notenIndexed[$sn['person_id']][$sn['geraet_id']][$sn['note_bezeichnung_id']][$sn['run_number']] = $sn['public_value'];
|
||||
}
|
||||
|
||||
$disciplinesExtended = array_merge(
|
||||
[["id" => 0, "name" => "None"]],
|
||||
$disciplines
|
||||
);
|
||||
|
||||
$arrayIndexedNoten = [];
|
||||
|
||||
|
||||
$wkName = db_get_var($guest, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['wkName']);
|
||||
|
||||
$grouped = [];
|
||||
|
||||
function groop_by_key($array, $array_key, bool $int = false): array
|
||||
{
|
||||
if ($int) {
|
||||
foreach ($array as $entry) {
|
||||
$key = (int) $entry[$array_key];
|
||||
$grouped[$key][] = $entry;
|
||||
}
|
||||
} else {
|
||||
foreach ($array as $entry) {
|
||||
$key = $entry[$array_key];
|
||||
$grouped[$key][] = $entry;
|
||||
}
|
||||
}
|
||||
return $grouped;
|
||||
}
|
||||
|
||||
if ($is_live) {
|
||||
|
||||
$grouped = groop_by_key($tures, 'startgeraet', true);
|
||||
|
||||
} else {
|
||||
|
||||
$grouped = groop_by_key($tures, 'programm');
|
||||
|
||||
foreach ($grouped as $key => $group) {
|
||||
$orderIndexes[$key] = $indexedProgramme[$key]['order_index'] ?? 10000;
|
||||
}
|
||||
|
||||
if (isset($orderIndexes)) {
|
||||
uksort($grouped, function ($a, $b) use ($orderIndexes) {
|
||||
return $orderIndexes[$a] <=> $orderIndexes[$b];
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
foreach ($grouped as $entry_key => $entries_group):
|
||||
|
||||
|
||||
endforeach;
|
||||
|
||||
$timeZone = new \DateTimeZone('Europe/Zurich');
|
||||
|
||||
$formatter = new \IntlDateFormatter(
|
||||
'de_CH',
|
||||
\IntlDateFormatter::FULL,
|
||||
\IntlDateFormatter::NONE,
|
||||
$timeZone
|
||||
);
|
||||
|
||||
$display_over_array = [];
|
||||
|
||||
function constructSingleValueSpanPublic($token, $type, $row, $selected_geraet_id = 0)
|
||||
{
|
||||
global $formatter;
|
||||
global $personData;
|
||||
global $arrayIndexedNoten;
|
||||
global $timeZone;
|
||||
global $display_over_array;
|
||||
global $indexedNotenConfig;
|
||||
global $indexedProgramme;
|
||||
|
||||
$text = '';
|
||||
$classes = 'singleValueSpan';
|
||||
|
||||
$bold = $token['bold'] ?? false;
|
||||
|
||||
$font_size = intval($token['fontSize'] ?? 0);
|
||||
|
||||
$min_display_width = intval($token['minDisplayWidth'] ?? 1) - 1;
|
||||
|
||||
$linked_el_uid = (int) ($token['linkedElUid'] ?? 0);
|
||||
|
||||
$uid = (int) ($token['uid'] ?? 0);
|
||||
|
||||
|
||||
$style = '';
|
||||
|
||||
if ($font_size > 0) {
|
||||
$style = ' style="font-size: ' . $font_size . 'px"';
|
||||
}
|
||||
|
||||
if ($min_display_width > 0) {
|
||||
$classes .= ' displayOver' . $min_display_width . 'px';
|
||||
$display_over_array[] = $min_display_width;
|
||||
}
|
||||
|
||||
$dataAttributes = 'data-bold="' . htmlspecialchars($bold) . '" data-uid="'. $uid .'"';
|
||||
|
||||
if ($linked_el_uid > 0) {
|
||||
$dataAttributes .= ' data-linked-el-uid="' . $linked_el_uid . '"';
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'text':
|
||||
$text = $token['text'] ?? '';
|
||||
$classes .= ' staticText';
|
||||
break;
|
||||
|
||||
case 'sortData':
|
||||
$column = $token['column'] ?? '';
|
||||
switch ($column) {
|
||||
case "rang":
|
||||
$classes .= ' rangField';
|
||||
break;
|
||||
case "startindex":
|
||||
$text = $row['calculedstartindex'];
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'personData':
|
||||
$column = $token['column'] ?? '';
|
||||
|
||||
if ($column === 'jahrgang') {
|
||||
$geburtsdatum = $row['geburtsdatum'] ?? '';
|
||||
$date = new \DateTimeImmutable($geburtsdatum, $timeZone);
|
||||
$formatter->setPattern('yyyy');
|
||||
|
||||
$text = $formatter->format($date);
|
||||
} else {
|
||||
$text = array_key_exists($column, $personData) ? $row[$column] ?? '' : '';
|
||||
|
||||
if ($column === 'geburtsdatum') {
|
||||
$date = new \DateTimeImmutable($text, $timeZone);
|
||||
$formatter->setPattern(($token['phpDateFormat'] ?? '' !== '') ? $token['phpDateFormat'] : 'd. MMMM YYYY');
|
||||
|
||||
$text = $formatter->format($date);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'notenGeraetData':
|
||||
|
||||
$field_type_id = intval($token['field-type-id'] ?? 0);
|
||||
$run = intval($token['run'] ?? 0);
|
||||
$person_id = $row['id'];
|
||||
|
||||
$programm_id = $indexedProgramme[$row['programm']]['id'] ?? 0;
|
||||
|
||||
$runsJSON = json_decode($indexedNotenConfig[$field_type_id]['anzahl_laeufe_json'] ?? '', true) ?? [];
|
||||
|
||||
$max_run = $runsJSON[$selected_geraet_id][$programm_id] ?? $runsJSON[$selected_geraet_id]['all'] ?? $runsJSON['default'] ?? 0;
|
||||
|
||||
if ($run > $max_run) {
|
||||
$classes .= ' nonExistentEl';
|
||||
|
||||
} else {
|
||||
$notenNullable = ($token['notenNullable'] ?? 'false') === 'true';
|
||||
|
||||
$dataAttributes .= ' data-field-type-id="' . $field_type_id . '"';
|
||||
$dataAttributes .= ' data-geraet-id="' . $selected_geraet_id . '"';
|
||||
$dataAttributes .= ' data-person-id="' . $person_id . '"';
|
||||
$dataAttributes .= ' data-run="' . $run . '"';
|
||||
$dataAttributes .= ' data-noten-nullable="' . (($notenNullable) ? "true" : "false") . '"';
|
||||
|
||||
$text = $arrayIndexedNoten[$person_id][$selected_geraet_id][$field_type_id][$run]['value'] ?? null;
|
||||
|
||||
if ($notenNullable && $text !== null && floatval($text) === 0.0) {
|
||||
$text = ' ';
|
||||
}
|
||||
|
||||
if ($text === null) {
|
||||
$classes .= ' emtyPlaceholder';
|
||||
$text = '-';
|
||||
}
|
||||
|
||||
$classes .= ' changebleValue';
|
||||
}
|
||||
|
||||
|
||||
break;
|
||||
|
||||
|
||||
case 'notenData':
|
||||
$field_type_id = intval($token['field-type-id'] ?? 0);
|
||||
$geraet_id = intval($token['geraet-id'] ?? 0);
|
||||
$run = intval($token['run'] ?? 0);
|
||||
$person_id = $row['id'];
|
||||
$notenNullable = ($token['notenNullable'] ?? 'false') === 'true';
|
||||
|
||||
$dataAttributes .= ' data-field-type-id="' . $field_type_id . '"';
|
||||
$dataAttributes .= ' data-geraet-id="' . $geraet_id . '"';
|
||||
$dataAttributes .= ' data-person-id="' . $person_id . '"';
|
||||
$dataAttributes .= ' data-run="' . $run . '"';
|
||||
$dataAttributes .= ' data-noten-nullable="' . (($notenNullable) ? "true" : "false") . '"';
|
||||
|
||||
$text = $arrayIndexedNoten[$person_id][$geraet_id][$field_type_id][$run]['value'] ?? null;
|
||||
|
||||
if ($notenNullable && $text !== null && floatval($text) === 0.0) {
|
||||
$text = ' ';
|
||||
}
|
||||
|
||||
if ($text === null) {
|
||||
$classes .= ' emtyPlaceholder';
|
||||
$text = '-';
|
||||
}
|
||||
|
||||
$classes .= ' changebleValue';
|
||||
|
||||
break;
|
||||
}
|
||||
?>
|
||||
<span class="<?= $classes ?>" <?= $dataAttributes; ?><?= $style ?>><?= htmlspecialchars($text); ?></span>
|
||||
<?php }
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="de-ch">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="png" href="/intern/img/icon.png">
|
||||
<link rel="stylesheet" href="/intern/css/custom-msg-display.css">
|
||||
<link href="/files/fonts/fonts.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/css/custom-display-editor-required-css.css">
|
||||
<script src="/intern/js/jquery/jquery-3.7.1.min.js"></script>
|
||||
<script src="/intern/js/custom-msg-display.js"></script>
|
||||
<title>RankLive - <?= htmlspecialchars($wkName) ?></title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<?php if (count($indexedTures) < 1) : ?>
|
||||
<h3>Noch keine Daten verfügbar</h3>
|
||||
<?php endif; ?>
|
||||
<div class="allAbtContainer">
|
||||
<?php foreach ($grouped as $entry_key => $entries_group):
|
||||
|
||||
$table_data_geraet_label = '';
|
||||
$extra_table_classes = '';
|
||||
|
||||
if ($is_live) {
|
||||
$entries_with_calculated_index = [];
|
||||
|
||||
// Optional: Vorab-Caching der MAX-Indices, um DB-Abfragen im Loop zu vermeiden
|
||||
// (Nur aktivieren, wenn $entries_group sehr groß ist und Performance-Probleme auftreten)
|
||||
$maxStartIndexCache = [];
|
||||
|
||||
foreach ($entries_group as $row) {
|
||||
$old_geraet_id = $row['startgeraet'];
|
||||
|
||||
$old_geraet_index = $indexed_array_geraete_id_index[$row['startgeraet']];
|
||||
$shifted_geraet_index = $old_geraet_index + $akt_subabt - 1;
|
||||
if ($shifted_geraet_index > $max_subabt) {
|
||||
$shifted_geraet_index -= $max_subabt;
|
||||
}
|
||||
$row['startgeraet'] = $indexed_array_geraete_index_id[$shifted_geraet_index];
|
||||
|
||||
$rohstartindex = intval($row['start_index']);
|
||||
$abtId = intval($row['abt_id']);
|
||||
|
||||
// Hole maxStartIndex (mit Caching-Logik optional)
|
||||
if (!isset($maxStartIndexCache["$abtId-$old_geraet_id "])) {
|
||||
$maxStartIndexCache["$abtId-$old_geraet_id"] = db_get_var($guest, "SELECT COUNT(*) FROM `$tableTeilnehmendeAbt` WHERE abteilung_id = ? AND geraet_id = ?", [$abtId, $old_geraet_id]);
|
||||
}
|
||||
|
||||
$maxstartindex = $maxStartIndexCache["$abtId-$old_geraet_id"];
|
||||
|
||||
// Sicherheit: Vermeiden Sie Division durch Null oder Modulo durch 0
|
||||
if ($maxstartindex < 1) {
|
||||
$maxstartindex = 1;
|
||||
}
|
||||
|
||||
// The order shift depends on the number of rotations the group has made
|
||||
$rotation_shift = $akt_subabt - 1;
|
||||
|
||||
$calculedstartindex = $rohstartindex - $rotation_shift;
|
||||
|
||||
// Sicherstellen, dass das Ergebnis positiv ist (PHP Modulo kann negative Ergebnisse liefern)
|
||||
// Wenn das Ergebnis negativ ist, addieren wir maxstartindex
|
||||
if ($calculedstartindex < 1) {
|
||||
$calculedstartindex += $maxstartindex;
|
||||
}
|
||||
|
||||
// Kopie der Zeile erstellen, um Originaldaten nicht zu verändern
|
||||
$row['calculedstartindex'] = $calculedstartindex;
|
||||
$entries_with_calculated_index[] = $row;
|
||||
}
|
||||
|
||||
// Sortieren nach dem berechneten Startindex
|
||||
usort($entries_with_calculated_index, fn($a, $b) => $a['calculedstartindex'] <=> $b['calculedstartindex']);
|
||||
|
||||
$entries_to_display = $entries_with_calculated_index;
|
||||
|
||||
$shifted_geraet_index = $indexed_array_geraete_id_index[$entry_key] + $akt_subabt - 1;
|
||||
if ($shifted_geraet_index > $max_subabt) {
|
||||
$shifted_geraet_index -= $max_subabt;
|
||||
}
|
||||
$shifted_geraet_id = $indexed_array_geraete_index_id[$shifted_geraet_index];
|
||||
|
||||
$table_data_geraet_label = ' data-geraet-index="' . $shifted_geraet_index . '"';
|
||||
$extra_table_classes = ' shiftedGeraetTable';
|
||||
} else {
|
||||
$entries_to_display = $entries_group;
|
||||
}
|
||||
|
||||
foreach ($entries_to_display as $row):
|
||||
$rangNotenArray[$entry_key][$row['id']] = isset($notenIndexed[$row['id']][0][$rangNote][1]) ? floatval($notenIndexed[$row['id']][0][$rangNote][1]) : null;
|
||||
foreach ($disciplinesExtended as $discipline):
|
||||
foreach ($notenConfig as $snC):
|
||||
|
||||
if (intval($snC['pro_geraet']) === 1 && intval($discipline['id']) === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (intval($snC['pro_geraet']) !== 1) {
|
||||
$allowedGeraete = !empty($snC['geraete_json']) ? json_decode($snC['geraete_json'], true) : [];
|
||||
if (!in_array($discipline['id'], $allowedGeraete)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$runsJSON = !empty($snC['anzahl_laeufe_json']) ? json_decode($snC['anzahl_laeufe_json'], true) : [];
|
||||
|
||||
$runs = $runsJSON[$discipline['id']][$entry_key] ?? $runsJSON[$discipline['id']]['all'] ?? $runsJSON["default"] ?? 1;
|
||||
|
||||
for ($r = 1; $r <= $runs; $r++):
|
||||
$note = $notenIndexed[$row['id']][$discipline['id']][$snC['id']][$r] ?? null;
|
||||
$normalizedNote = ($note !== null) ? number_format($note, $snC['nullstellen'] ?? 2) : null;
|
||||
|
||||
$arrayIndexedNoten[intval($row['id'])][intval($discipline['id'])][intval($snC['id'])][intval($r)] = ["value" => $normalizedNote];
|
||||
endfor;
|
||||
endforeach;
|
||||
endforeach;
|
||||
endforeach; ?>
|
||||
<div class="singleAbtDiv<?= $extra_table_classes ?>" <?= $table_data_geraet_label ?>>
|
||||
<?php if ($is_live) : ?>
|
||||
<h3><?= $indexedArrayGeraete[$shifted_geraet_id] ?? '' ?></h3>
|
||||
<?php else: ?>
|
||||
<h3><?= $entry_key ?? '' ?></h3>
|
||||
<?php endif; ?>
|
||||
<?php $geraet_id_for_live_view = $is_live ? $shifted_geraet_id : 0 ?>
|
||||
<div class="scoreboard-container">
|
||||
<table class="customDisplayEditorTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<?php foreach ($headers as $header_data): ?>
|
||||
<?php
|
||||
$min_display_width = ($header_data['minDisplayWidth'] ?? 1) - 1;
|
||||
|
||||
$class = "";
|
||||
|
||||
if ($min_display_width > 0) {
|
||||
$class = 'displayOver' . $min_display_width . 'px';
|
||||
$display_over_array[] = $min_display_width;
|
||||
}
|
||||
?>
|
||||
|
||||
<th class="<?= $class ?>">
|
||||
<?= htmlspecialchars($header_data['title']) ?>
|
||||
</th>
|
||||
<?php endforeach; ?>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody <?= !$is_live ? 'data-programm-id="'. $entry_key .'"' : ''?>>
|
||||
<?php foreach ($entries_to_display as $row): ?>
|
||||
<tr data-person-id="<?= $row['id'] ?>">
|
||||
<?php foreach ($bodyColumns as $ind => $columnTokens): ?>
|
||||
<?php
|
||||
$class = '';
|
||||
|
||||
if (isset($headers[$ind]['minDisplayWidth']) && $headers[$ind]['minDisplayWidth'] > 1) {
|
||||
$min_display_width = ($headers[$ind]['minDisplayWidth'] ?? 1) - 1;
|
||||
|
||||
$class = 'displayOver' . $min_display_width . 'px';
|
||||
$display_over_array[] = $min_display_width;
|
||||
}
|
||||
?>
|
||||
<td class=<?= $class ?>>
|
||||
<span class="tdSpan">
|
||||
|
||||
<?php
|
||||
foreach ($columnTokens as $token):
|
||||
$type = $token['type'] ?? '';
|
||||
if ($type === 'layoutEls') :
|
||||
$column = $token['column'];
|
||||
$content = $token['content'] ?? []; ?>
|
||||
<div class="<?= $column ?>LayoutEl">
|
||||
<?php foreach ($content as $layout_row) : ?>
|
||||
<span class="<?= $column ?>LayoutElRow">
|
||||
<?php foreach ($layout_row as $el) :
|
||||
constructSingleValueSpanPublic($el, $el['type'], $row, $geraet_id_for_live_view);
|
||||
endforeach; ?>
|
||||
</span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php else:
|
||||
constructSingleValueSpanPublic($token, $type, $row, $geraet_id_for_live_view);
|
||||
endif;
|
||||
endforeach; ?>
|
||||
</span>
|
||||
</td>
|
||||
<?php endforeach; ?>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<style>
|
||||
<?php
|
||||
$unique_display_over_array = array_unique($display_over_array);
|
||||
|
||||
foreach ($unique_display_over_array as $s_size):
|
||||
$s_size = (int)$s_size;
|
||||
?>
|
||||
@media (max-width: <?= $s_size ?>px) {
|
||||
.displayOver<?= $s_size ?>px {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
<?php endforeach; ?>
|
||||
</style>
|
||||
<script>
|
||||
window.FREIGABE = "<?= $selectedFreigabeId; ?>";
|
||||
window.CSDR_TOKEN = "<?= $csrf_token; ?>";
|
||||
window.WS_ACCESS_TOKEN = "<?= generateWSReadToken('rankLive', $selectedFreigabeId) ?>";
|
||||
window.AKTUELLES_JAHR = "<?= $current_year ?>";
|
||||
window.RANG_NOTEN_ARRAY = '<?= json_encode($rangNotenArray, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT); ?>';
|
||||
window.RANG_SORT = "<?= $orderBestRang ?>";
|
||||
window.RANG_NOTE_ID = <?= $rangNote ?>;
|
||||
window.LIVE = <?= (int) $is_live ?>;
|
||||
</script>
|
||||
|
||||
<script src="/RankLive/js/script.js"></script>
|
||||
|
||||
</body>
|
||||
@@ -0,0 +1,125 @@
|
||||
|
||||
:root {
|
||||
--sb-bg: #f8fafc;
|
||||
--sb-card-bg: #ffffff;
|
||||
--sb-text-main: #36454F;
|
||||
--sb-text-muted: #64748b;
|
||||
--sb-primary: #1e40af; /* Deep athletic blue */
|
||||
--sb-accent: #2563eb; /* Vivid score blue */
|
||||
--sb-border: #e2e8f0;
|
||||
--sb-hover: #f3f3f3;
|
||||
--sb-radius: 12px;
|
||||
--font-numeric: 'Courier New', Courier, monospace; /* Clean alignment for scores */
|
||||
--padding-td: clamp(3px, 2.5vh, 12px) clamp(4px, 2vw, 20px);
|
||||
--padding-th: clamp(2px, 1.5vh, 6px) clamp(4px, 2vw, 20px);
|
||||
}
|
||||
|
||||
|
||||
/* Base Table Styling */
|
||||
.customDisplayEditorTable {
|
||||
width: 100%;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
font-size: 0.95rem;
|
||||
color: var(--sb-text-main);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.customDisplayEditorTable th {
|
||||
color: #1e293b;
|
||||
border-bottom: solid 1px #1e293b;
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
letter-spacing: 0.05em;
|
||||
padding: var(--padding-th);
|
||||
white-space: nowrap;
|
||||
position: sticky;
|
||||
background-color: #fff;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.customDisplayEditorTable th sup {
|
||||
font-size: 0.65rem;
|
||||
margin-left: 2px;
|
||||
vertical-align: super;
|
||||
}
|
||||
|
||||
.customDisplayEditorTable td {
|
||||
padding: var(--padding-td);
|
||||
border-bottom: 1px solid var(--sb-border);
|
||||
vertical-align: middle;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.customDisplayEditorTable tbody > tr:nth-child(even) td {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
.customDisplayEditorTable tbody > tr:hover td {
|
||||
background-color: var(--sb-hover) !important;
|
||||
}
|
||||
|
||||
|
||||
.customDisplayEditorTable td {
|
||||
color: var(--sb-text-main);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
|
||||
.customDisplayEditorTable td.changebleValue {
|
||||
font-family: var(--font-numeric);
|
||||
font-size: 1.05rem;
|
||||
color: var(--sb-text-main);
|
||||
background-color: rgb(37 99 235 / 0.02);
|
||||
}
|
||||
|
||||
.customDisplayEditorTable tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.changebleValue.updated {
|
||||
animation: flashHighlight 3.5s ease-out;
|
||||
}
|
||||
|
||||
@keyframes flashHighlight {
|
||||
0% { color: #00ff99; }
|
||||
100% { color: var(--sb-text-main); }
|
||||
}
|
||||
|
||||
.singleValueSpan[data-bold="true"] {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.staticText {
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.tdSpan {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.verticalLayoutEl {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.verticalLayoutElRow {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
}
|
||||
|
||||
.verticalLayoutEl .changebleValue {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.emtyPlaceholder {
|
||||
color: transparent;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.nonExistentEl {
|
||||
display: none !important;
|
||||
}
|
||||
@@ -0,0 +1,320 @@
|
||||
:root {
|
||||
--main: #2d2726;
|
||||
--bg-top-raw: 254 63 24;
|
||||
--main-box-shadow: rgb(from var(--main) r g b / 0.05);
|
||||
--accent: #fe3f18;
|
||||
--accent-hover: #c5e300;
|
||||
--paddingSite: 40px;
|
||||
--card-radius: 20px;
|
||||
--card-bg: #ffffff;
|
||||
--bg: #FDFDFD;
|
||||
--card-shadow: 0 10px 30px rgba(15, 23, 42, 0.08);
|
||||
--border-subtle: #d4d7e1;
|
||||
--text-main: #191919;
|
||||
--text-muted: #5e5e5e;
|
||||
}
|
||||
|
||||
/* --- BASE & TYPOGRAPHY --- */
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
b {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
width: 100vw;
|
||||
background: var(--bg);
|
||||
color: var(--text-main);
|
||||
overflow-x: hidden;
|
||||
overscroll-behavior: none;
|
||||
font-optical-sizing: auto;
|
||||
}
|
||||
|
||||
.bgSection {
|
||||
position: relative;
|
||||
width: 100vw;
|
||||
padding: var(--paddingSite);
|
||||
max-width: 1400px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.headerAbt {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.headerAbt h2 {
|
||||
margin: 0;
|
||||
font-weight: 400;
|
||||
color: var(--main);
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.textInputAbtTitel {
|
||||
margin: 0;
|
||||
font-weight: 400;
|
||||
border-top: none;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
border-bottom: 1px solid var(--main);
|
||||
font-size: 1.5rem;
|
||||
color: #191919;
|
||||
background: none;
|
||||
width: max-content;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
/* Sidebar Menu Button Styling */
|
||||
.header-text-conatiner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20px;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.header-text-conatiner button {
|
||||
cursor: auto;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s ease;
|
||||
padding: 12px 20px;
|
||||
border-radius: 100px;
|
||||
background: var(--accent);
|
||||
border: 1px solid transparent;
|
||||
font-size: 15px;
|
||||
color: #000;
|
||||
width: 100%;
|
||||
box-shadow: 0 4px 6px rgba(207, 239, 0, 0.2);
|
||||
}
|
||||
|
||||
.header-text-conatiner button:hover {
|
||||
background: var(--accent-hover);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 12px rgba(207, 239, 0, 0.3);
|
||||
}
|
||||
|
||||
.header-text-conatiner button:active {
|
||||
transform: scale(0.98) translateY(0);
|
||||
}
|
||||
|
||||
.header-text-conatiner label {
|
||||
font-weight: 400;
|
||||
color: var(--text-main);
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: -5px;
|
||||
}
|
||||
|
||||
.header-text-conatiner input[type="number"] {
|
||||
padding: 12px 15px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--border-subtle);
|
||||
background: #fff;
|
||||
font-size: 15px;
|
||||
outline: none;
|
||||
transition: all 0.2s ease;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.header-text-conatiner input[type="number"]:focus {
|
||||
border-color: var(--main);
|
||||
box-shadow: 0 0 0 3px rgba(40, 102, 110, 0.1);
|
||||
}
|
||||
|
||||
.geraet-table {
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: 16px;
|
||||
box-shadow: var(--card-shadow);
|
||||
min-width: 260px;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
.geraet-table thead {
|
||||
background: var(--main);
|
||||
}
|
||||
|
||||
.geraet-table thead th {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
color: #fff;
|
||||
padding: 14px 20px;
|
||||
font-weight: 300;
|
||||
font-size: 1.05rem;
|
||||
text-align: left;
|
||||
letter-spacing: 0.5px;
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
}
|
||||
|
||||
.geraet-table thead th i span {
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.8;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.geraet-table tbody {
|
||||
display: block;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* Custom Scrollbar for tbody */
|
||||
.geraet-table tbody::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.geraet-table tbody::-webkit-scrollbar-track {
|
||||
background: #f1f5f9;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.geraet-table tbody::-webkit-scrollbar-thumb {
|
||||
background: #cbd5e1;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.geraet-table tbody::-webkit-scrollbar-thumb:hover {
|
||||
background: #94a3b8;
|
||||
}
|
||||
|
||||
.turnerin-row {
|
||||
cursor: auto;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 8px;
|
||||
transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.2s ease;
|
||||
display: block;
|
||||
border: 1px solid transparent;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-webkit-touch-callout: none;
|
||||
}
|
||||
|
||||
.turnerin-row>td {
|
||||
padding: 12px 16px;
|
||||
border: none;
|
||||
display: block;
|
||||
width: 100%;
|
||||
color: var(--text-main);
|
||||
font-weight: 200;
|
||||
}
|
||||
|
||||
/* Grouping Row Styling */
|
||||
.group-row {
|
||||
background: transparent;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.group-row>td {
|
||||
padding: 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.group-header {
|
||||
padding: 12px 16px;
|
||||
cursor: auto;
|
||||
gap: 18px;
|
||||
border-radius: 10px;
|
||||
font-weight: 200;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
|
||||
margin-bottom: 6px;
|
||||
display: flex;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-webkit-touch-callout: none;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
|
||||
.group-inner {
|
||||
width: 100%;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.group-row .group-inner {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.allGeraeteDiv {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
margin-top: 24px;
|
||||
margin-bottom: 36px;
|
||||
}
|
||||
|
||||
.buttonsDiv {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
justify-content: flex-start;
|
||||
margin-bottom: 20px;
|
||||
position: fixed;
|
||||
bottom: 10px;
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
.buttonsDiv > button {
|
||||
background: var(--main);
|
||||
border: 1px solid #fff;
|
||||
color: #ffffff;
|
||||
padding: 10px 20px;
|
||||
border-radius: 10px;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.buttonsDiv > select {
|
||||
background: #fff;
|
||||
border: 1px solid var(--main);
|
||||
color: var(--main);
|
||||
padding: 10px 20px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.buttonsDiv > button:hover {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.buttonsDiv > button:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.filtered-out {
|
||||
opacity: 0.5;
|
||||
background: #4d4d4d22 !important;
|
||||
color: #4d4d4d !important;
|
||||
}
|
||||
|
||||
.filtered-out-border {
|
||||
border: 1px solid #4d4d4d !important;
|
||||
}
|
||||
|
||||
.timeSpan {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 4px 8px;
|
||||
background: #f1f5f9;
|
||||
border-radius: 8px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.stickyDivHeader {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: var(--bg);
|
||||
padding: 10px 0;
|
||||
z-index: 10;
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Remote Audioplayer</title>
|
||||
<meta name="robots" content="noindex">
|
||||
<style>
|
||||
body {
|
||||
background: #000 !important;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-family: sans-serif;
|
||||
cursor: pointer;
|
||||
}
|
||||
h1 {
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
const userAccess = "audio";
|
||||
|
||||
let ws;
|
||||
const RETRY_DELAY = 200; // ms
|
||||
|
||||
function startWebSocket() {
|
||||
|
||||
console.log("Attempting WebSocket connection...");
|
||||
|
||||
try {
|
||||
ws = new WebSocket(`wss://` + window.location.hostname + `/ws/?access=${userAccess}`);
|
||||
} catch (err) {
|
||||
scheduleRetry();
|
||||
return;
|
||||
}
|
||||
|
||||
ws.onopen = () => {
|
||||
console.log("WebSocket connected!");
|
||||
document.querySelector('.errorws').style.display = 'none';
|
||||
|
||||
// SAFE: this executes only on successful connect
|
||||
ws.send(JSON.stringify({
|
||||
type: "SELF",
|
||||
payload: {}
|
||||
}));
|
||||
};
|
||||
|
||||
ws.onerror = () => {
|
||||
document.querySelector('h1').innerText = 'WS ERROR';
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
document.querySelector('h1').innerText = 'WS DISCONECTED';
|
||||
scheduleRetry();
|
||||
};
|
||||
}
|
||||
|
||||
function scheduleRetry() {
|
||||
console.log(`Retrying in ${RETRY_DELAY}ms...`);
|
||||
setTimeout(startWebSocket, RETRY_DELAY);
|
||||
}
|
||||
|
||||
startWebSocket();
|
||||
</script>
|
||||
<h1>Der Audioplayer startet durch das Berühren des Displays</h1>
|
||||
|
||||
<audio id="peep" preload="auto"></audio>
|
||||
<audio id="musicPlayer" preload="auto"></audio>
|
||||
|
||||
<script>
|
||||
const peep = document.getElementById('peep');
|
||||
const music = document.getElementById('musicPlayer');
|
||||
|
||||
let lastMusicUrl = null;
|
||||
let pollingStarted = false; // ensure we start polling only once
|
||||
|
||||
async function fetchAndHandleMusic() {
|
||||
try {
|
||||
const response = await fetch('/displays/json/audio.json?t=' + Date.now(), { cache: 'no-store' });
|
||||
const data = await response.json();
|
||||
|
||||
// Stop everything if musik is "stop"
|
||||
if (!data.musik || data.musik === 'nan' || data.start == false) {
|
||||
if (!music.paused) {
|
||||
music.pause();
|
||||
music.currentTime = 0;
|
||||
}
|
||||
if (!peep.paused) {
|
||||
peep.pause();
|
||||
peep.currentTime = 0;
|
||||
}
|
||||
lastMusicUrl = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// Play only if new URL
|
||||
if (data.musik !== lastMusicUrl) {
|
||||
lastMusicUrl = data.musik;
|
||||
|
||||
// Play short peep first
|
||||
peep.src = '/files/music/piep.mp3';
|
||||
peep.play().then(() => {
|
||||
// After 2 seconds, play the main music
|
||||
setTimeout(() => {
|
||||
music.src = data.musik;
|
||||
music.play().catch(err => console.log('Music play error:', err));
|
||||
}, 2000);
|
||||
}).catch(err => console.log('Peep play error:', err));
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.error('Error fetching JSON:', err);
|
||||
}
|
||||
}
|
||||
|
||||
document.body.addEventListener('click', () => {
|
||||
if (!pollingStarted) {
|
||||
pollingStarted = true;
|
||||
ws.addEventListener("message", () => {
|
||||
fetchAndHandleMusic();
|
||||
});
|
||||
document.querySelector('h1').innerText = 'Player läuft'; // remove instruction
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,372 +0,0 @@
|
||||
:root {
|
||||
--bg: #111827;
|
||||
--panel: #020617;
|
||||
--panel-soft: #0b1120;
|
||||
--accent: #38bdf8;
|
||||
--accent-soft: rgba(56, 189, 248, 0.15);
|
||||
--text-main: #e5e7eb;
|
||||
--text-muted: #9ca3af;
|
||||
--danger: #ef4444;
|
||||
--success: #22c55e;
|
||||
--radius-lg: 18px;
|
||||
--border-subtle: 1px solid rgba(148, 163, 184, 0.35);
|
||||
--shadow-soft: 0 20px 45px rgba(15, 23, 42, 0.85);
|
||||
--transition-fast: 180ms ease-out;
|
||||
--colorStartDiv: #0b1120;
|
||||
--panelBgLogo: #4a2f96;
|
||||
--font-heading: clamp(1.5rem, 6vh, 4rem);
|
||||
--font-sub: clamp(1rem, 3.5vh, 2.2rem);
|
||||
--logo-size: clamp(150px, 40vh, 500px);
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
head > * {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
overflow: hidden;
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: stretch;
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
background-color: var(--bg);
|
||||
color: var(--text-main);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* WebSocket error overlay */
|
||||
.errorws {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 5vh 5vw;
|
||||
background: radial-gradient(circle at top, #020617 0, #020617 55%);
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
.errorws .logoimg {
|
||||
max-width: var(--logo-size);
|
||||
height: auto;
|
||||
margin-bottom: 2vh;
|
||||
filter: drop-shadow(0 12px 30px rgba(0, 0, 0, 0.65));
|
||||
}
|
||||
|
||||
.errortext {
|
||||
margin: 0;
|
||||
font-size: clamp(1.8rem, 8vw, 5rem);
|
||||
line-height: 1.1;
|
||||
max-width: 90vw;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-main);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.errortextSmall {
|
||||
margin: 10px;
|
||||
font-size: clamp(1rem, 3vw, 2rem);
|
||||
opacity: 0.8;
|
||||
font-weight: 300;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--text-main);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Startup logo overlay */
|
||||
.logobg {
|
||||
padding: 4vh;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
background-color: var(--panelBgLogo);
|
||||
z-index: 1000;
|
||||
transition: opacity 0.5s ease;
|
||||
}
|
||||
|
||||
.logoimg {
|
||||
width: auto;
|
||||
max-height: 45vh;
|
||||
max-width: 80vw;
|
||||
object-fit: contain;
|
||||
/* filter: drop-shadow(0 18px 40px rgba(15, 23, 42, 0.4)); */
|
||||
}
|
||||
|
||||
.logotext {
|
||||
margin: 0;
|
||||
font-size: clamp(1.8rem, 7vh, 6rem);
|
||||
text-align: center;
|
||||
width: 90vw;
|
||||
line-height: 1.2;
|
||||
/* Forces single line for names/titles to prevent overflow */
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.logotext, .logoctext {
|
||||
color: var(--text-color) !important;
|
||||
text-align: center;
|
||||
font-weight: 400;
|
||||
margin-top: 1vh;
|
||||
}
|
||||
|
||||
.logoctext {
|
||||
margin: 0;
|
||||
font-size: clamp(2rem, 7vw, 12rem);
|
||||
}
|
||||
|
||||
/* 4. Responsive adjustments for different screen ratios */
|
||||
@media (aspect-ratio: 4/3), (aspect-ratio: 10/7) {
|
||||
/* Tablet specific tweaks (iPad/Android Tabs) */
|
||||
:root {
|
||||
--logo-size: 30vh;
|
||||
}
|
||||
.logotext {
|
||||
font-size: 5vh;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1921px) {
|
||||
/* Ultra-large TV/4K tweaks */
|
||||
.logotext {
|
||||
letter-spacing: 0.25em; /* Better legibility at distance */
|
||||
}
|
||||
}
|
||||
|
||||
/* Main scoreboard container – full screen */
|
||||
.pagediv {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
width: 100vw;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2vh;
|
||||
padding: 2.2vh 2.4vw;
|
||||
box-sizing: border-box;
|
||||
background:
|
||||
radial-gradient(circle at top left, var(--bg-soft), transparent 60%),
|
||||
radial-gradient(circle at bottom right, var(--bg-soft), transparent 60%);
|
||||
}
|
||||
|
||||
.pagediv.manuel {
|
||||
padding-right: clamp(60px, 2.4vw, 2.4vw);
|
||||
}
|
||||
|
||||
/* Common row styling */
|
||||
.display-row {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 30px;
|
||||
border-radius: var(--radius-lg);
|
||||
background: linear-gradient(135deg, var(--panel-soft), var(--panel));
|
||||
border-bottom: var(--border-subtle);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-shadow: var(--shadow-soft);
|
||||
}
|
||||
|
||||
.display-row::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: radial-gradient(circle at top left, var(--bg-soft), transparent 55%);
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.row1text,
|
||||
.row2text p,
|
||||
.row3 > p,
|
||||
.start_text {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
/* Row 1: athlete name */
|
||||
.row1text {
|
||||
font-size: min(8vh, 4.6vw);
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Row 2: club, program, start/stop */
|
||||
.row2 {
|
||||
justify-content: space-between;
|
||||
gap: 2vw;
|
||||
}
|
||||
|
||||
.row2::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: radial-gradient(circle at bottom right, var(--colorStartDiv), transparent 55%);
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.row2text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.8vh;
|
||||
width: 50vw;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.row2_1text {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.row2_2text {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Start / stop pill */
|
||||
.start_div {
|
||||
width: 35vw;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.start_text {
|
||||
display: flex;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
/* Row 3: D-score and total */
|
||||
.row3 {
|
||||
justify-content: space-between;
|
||||
gap: 2vw;
|
||||
}
|
||||
|
||||
.row3 > p {
|
||||
font-size: min(9vh, 5vw);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.row3_1text {
|
||||
color: var(--color-note-l);
|
||||
}
|
||||
|
||||
.row3_2text {
|
||||
color: var(--color-note-r);
|
||||
}
|
||||
|
||||
/* Existing #score and char/row styles if you use them elsewhere */
|
||||
#score {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.char {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 40vw;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
font-size: 20vw;
|
||||
}
|
||||
|
||||
/* Landscape / portrait logo scaling */
|
||||
@media (orientation: landscape) {
|
||||
.logoimg {
|
||||
max-height: 60vh;
|
||||
}
|
||||
}
|
||||
|
||||
@media (orientation: portrait) {
|
||||
.logoimg {
|
||||
max-width: 60vw;
|
||||
}
|
||||
}
|
||||
|
||||
/* Small screens – keep everything readable */
|
||||
@media (max-width: 768px) {
|
||||
.pagediv {
|
||||
padding: 1.6vh 3vw;
|
||||
gap: 1.4vh;
|
||||
}
|
||||
|
||||
.display-row {
|
||||
padding: 1.4vh 3vw;
|
||||
}
|
||||
|
||||
.row2 {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.start_div {
|
||||
align-self: flex-end;
|
||||
min-width: 40vw;
|
||||
}
|
||||
}
|
||||
|
||||
.noWsConnection {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
|
||||
transform: rotate(270deg);
|
||||
transform-origin: right bottom;
|
||||
}
|
||||
|
||||
.rotator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
||||
.noWsConnection img {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.noWsConnection p {
|
||||
font-size: 20px;
|
||||
color: var(--text-muted);
|
||||
margin: 0;
|
||||
letter-spacing: 1.1px;
|
||||
}
|
||||
@@ -1,439 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? "https://" : "http://";
|
||||
$fullDomain = $protocol . $_SERVER['HTTP_HOST'];*/
|
||||
|
||||
$lastSegment = strtolower($_GET['geraet']) ?? '';
|
||||
|
||||
$baseDir = $_SERVER['DOCUMENT_ROOT'];
|
||||
|
||||
require_once $baseDir . '/../scripts/db/db-verbindung-script-guest.php';
|
||||
require_once $baseDir . '/../scripts/db/db-functions.php';
|
||||
require_once $baseDir . '/../scripts/db/db-tables.php';
|
||||
|
||||
$stmt = $guest->prepare("SELECT `name` FROM $tableGeraete ORDER BY start_index ASC");
|
||||
|
||||
if (!$stmt->execute()) {
|
||||
http_response_code(500);
|
||||
exit;
|
||||
}
|
||||
|
||||
$result = $stmt->get_result();
|
||||
$disciplines = array_map(
|
||||
'strtolower',
|
||||
array_column($result->fetch_all(MYSQLI_ASSOC), 'name')
|
||||
);
|
||||
|
||||
$stmt->close();
|
||||
|
||||
// Define a small helper function to keep the code DRY (Don't Repeat Yourself)
|
||||
function sanitize_hex($color) {
|
||||
return preg_replace('/[^0-9a-fA-F#]/', '', $color);
|
||||
}
|
||||
|
||||
// Fetch and Sanitize Brand Colors
|
||||
$wkName = db_get_var($guest, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['wkName']);
|
||||
$cleanColor = sanitize_hex(db_get_var($guest, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['displayColourLogo']));
|
||||
$cleanColorText = sanitize_hex(db_get_var($guest, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['displayTextColourLogo']));
|
||||
|
||||
// Fetch and Sanitize Layout Colors
|
||||
$displayColorScoringBg = sanitize_hex(db_get_var($guest, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['displayColorScoringBg']));
|
||||
$displayColorScoringBgSoft = sanitize_hex(db_get_var($guest, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['displayColorScoringBgSoft']));
|
||||
$displayColorScoringPanel = sanitize_hex(db_get_var($guest, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['displayColorScoringPanel']));
|
||||
$displayColorScoringPanelSoft = sanitize_hex(db_get_var($guest, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['displayColorScoringPanelSoft']));
|
||||
|
||||
// Fetch and Sanitize Text Colors
|
||||
$displayColorScoringPanelText = sanitize_hex(db_get_var($guest, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['displayColorScoringPanelText']));
|
||||
$displayColorScoringPanelTextSoft = sanitize_hex(db_get_var($guest, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['displayColorScoringPanelTextSoft']));
|
||||
|
||||
// Fetch and Sanitize Accent Colors (Note: fixed 'diplay' typo here)
|
||||
$displayColorScoringPanelTextNoteL = sanitize_hex(db_get_var($guest, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['displayColorScoringPanelTextNoteL']));
|
||||
$displayColorScoringPanelTextNoteR = sanitize_hex(db_get_var($guest, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['displayColorScoringPanelTextNoteR']));
|
||||
|
||||
$guest->close();
|
||||
|
||||
if (!isset($lastSegment) || !in_array($lastSegment, $disciplines)){
|
||||
echo 'kein Gerät';
|
||||
exit;
|
||||
}
|
||||
|
||||
$jsonUrlconfig = '/displays/json/config.json';
|
||||
$jsonUrl = '/displays/json/display_' . $lastSegment . '.json';
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title><?= $wkName ?> Anzeigen</title>
|
||||
<meta name="robots" content="noindex">
|
||||
<link rel="stylesheet" href="/displays/css/display.css">
|
||||
<style>
|
||||
:root {
|
||||
/* Brand */
|
||||
--panelBgLogo: <?= $cleanColor ?>;
|
||||
--text-color: <?= $cleanColorText ?>;
|
||||
|
||||
/* Backgrounds */
|
||||
--bg: <?= $displayColorScoringBg ?>;
|
||||
--bg-soft: <?= $displayColorScoringBgSoft ?>26;
|
||||
--panel: <?= $displayColorScoringPanel ?>;
|
||||
--panel-soft: <?= $displayColorScoringPanelSoft ?>;
|
||||
|
||||
/* Typography */
|
||||
--text-main: <?= $displayColorScoringPanelText ?>;
|
||||
--text-muted: <?= $displayColorScoringPanelTextSoft ?>;
|
||||
|
||||
/* Noten */
|
||||
--color-note-l: <?= $displayColorScoringPanelTextNoteL ?>;
|
||||
--color-note-r: <?= $displayColorScoringPanelTextNoteR ?>;
|
||||
|
||||
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="errorws">
|
||||
<img class="logoimg" src="https://cdn-icons-png.freepik.com/512/12890/12890341.png?ga=GA1.1.991281105.1761199359">
|
||||
<p class="errortext">Keine WebSocket Verbindung</p>
|
||||
<p class="errortextSmall">Versuche Verbindung... (Versuch <span id="counterTries"></span> / <span id="counterMaxTries"></span>)</p>
|
||||
</div>
|
||||
|
||||
<div class="noWsConnection">
|
||||
<div class="rotator">
|
||||
<img src="https://cdn-icons-png.freepik.com/512/12890/12890341.png?ga=GA1.1.991281105.1761199359">
|
||||
<p>Keine WebSocket Verbindung, Syncronisation via FETCH</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="logobg">
|
||||
<img id="jsImgLogo" class="logoimg" src="/intern/img/logo-normal.png">
|
||||
<p class="logotext"><?= $wkName ?></p>
|
||||
<p class="logoctext"></p>
|
||||
</div>
|
||||
|
||||
<div class="pagediv">
|
||||
<div class="display-row row1">
|
||||
<p class="row1text"></p>
|
||||
</div>
|
||||
<div class="display-row row2">
|
||||
<div class="row2text">
|
||||
<p class="row2_1text sds"></p>
|
||||
<p class="row2_2text"></p>
|
||||
</div>
|
||||
<div class="start_div">
|
||||
<p class="start_text"></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="display-row row3">
|
||||
<p class="row3_1text"></p>
|
||||
<p class="row3_2text"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
const userAccess = "<?php echo $lastSegment; ?>_display";
|
||||
const jsonUrl = "<?php echo $jsonUrl; ?>";
|
||||
const jsonUrlconfig = "<?php echo $jsonUrlconfig; ?>";
|
||||
|
||||
// --- State Management ---
|
||||
let ws;
|
||||
let filedConectCount = 1;
|
||||
const fallbackConectCount = 10;
|
||||
const RETRY_DELAY = 1000;
|
||||
const FALLBACK_POLL_INTERVAL = 3000; // Fetch every 3 seconds if WS is dead
|
||||
|
||||
let fallbackTimer = null;
|
||||
|
||||
// Hold our current data in memory
|
||||
let displayConfig = { type: 'logo', ctext: '' };
|
||||
let currentScore = {};
|
||||
let lastUniqueId = null;
|
||||
|
||||
// UI Elements
|
||||
const counterTriesEl = document.getElementById('counterTries');
|
||||
const counterMaxTriesEl = document.getElementById('counterMaxTries');
|
||||
|
||||
if(counterTriesEl) counterTriesEl.innerHTML = filedConectCount;
|
||||
if(counterMaxTriesEl) counterMaxTriesEl.innerHTML = fallbackConectCount;
|
||||
|
||||
// --- Fullscreen Listener ---
|
||||
const fs = document.documentElement;
|
||||
document.body.addEventListener('click', () => {
|
||||
if (fs.requestFullscreen) fs.requestFullscreen();
|
||||
else if (fs.webkitRequestFullscreen) fs.webkitRequestFullscreen();
|
||||
else if (fs.mozRequestFullScreen) fs.mozRequestFullScreen();
|
||||
else if (fs.msRequestFullscreen) fs.msRequestFullscreen();
|
||||
});
|
||||
|
||||
// --- WebSocket Logic ---
|
||||
function startWebSocket() {
|
||||
console.log("Attempting WebSocket connection...");
|
||||
|
||||
try {
|
||||
ws = new WebSocket(`wss://${window.location.hostname}/ws/?access=${userAccess}`);
|
||||
} catch (err) {
|
||||
scheduleRetry();
|
||||
return;
|
||||
}
|
||||
|
||||
ws.onopen = () => {
|
||||
console.log("WebSocket connected!");
|
||||
document.querySelector('.errorws').style.display = 'none';
|
||||
document.querySelector('.noWsConnection').style.display = 'none';
|
||||
document.querySelector('.pagediv').classList.remove("manuel");
|
||||
|
||||
filedConectCount = 1;
|
||||
if(counterTriesEl) counterTriesEl.innerHTML = filedConectCount;
|
||||
|
||||
// Stop fallback polling since WS is alive
|
||||
stopFallbackPolling();
|
||||
|
||||
// Do ONE initial fetch to sync current state
|
||||
fetchFullState();
|
||||
};
|
||||
|
||||
ws.onerror = () => {
|
||||
document.querySelector('.errorws').style.display = 'flex';
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
document.querySelector('.errorws').style.display = 'flex';
|
||||
scheduleRetry();
|
||||
};
|
||||
|
||||
ws.addEventListener("message", msg => {
|
||||
let msgJSON;
|
||||
try {
|
||||
msgJSON = JSON.parse(msg.data);
|
||||
} catch (error) {
|
||||
return; // Ignore malformed messages
|
||||
}
|
||||
|
||||
// Route the incoming push data
|
||||
switch (msgJSON.type) {
|
||||
case "EINSTELLUNGEN_DISPLAY_UPDATE":
|
||||
updateSettings(msgJSON.payload.key, msgJSON.payload.value);
|
||||
break;
|
||||
case "UPDATE_DISPLAYCONTROL":
|
||||
// Expecting payload: { type: 'logo'|'ctext'|'scoring', ctext: '...' }
|
||||
displayConfig = msgJSON.payload;
|
||||
renderDOM();
|
||||
break;
|
||||
case "UPDATE_SCORE":
|
||||
// Expecting payload: the exact same structure as your jsonUrl outputs
|
||||
currentScore = msgJSON.payload;
|
||||
renderDOM();
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function scheduleRetry() {
|
||||
console.log(`Retrying in ${RETRY_DELAY}ms...`);
|
||||
|
||||
if (filedConectCount >= fallbackConectCount) {
|
||||
// MAX RETRIES REACHED -> Enter Fallback Mode
|
||||
document.querySelector('.errorws').style.display = 'none';
|
||||
document.querySelector('.noWsConnection').style.display = 'flex';
|
||||
document.querySelector('.pagediv').classList.add("manuel");
|
||||
startFallbackPolling();
|
||||
} else {
|
||||
if(counterTriesEl) counterTriesEl.innerHTML = filedConectCount;
|
||||
setTimeout(startWebSocket, RETRY_DELAY);
|
||||
filedConectCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Fallback Polling Logic ---
|
||||
function startFallbackPolling() {
|
||||
if (fallbackTimer === null) {
|
||||
console.warn("Starting JSON fallback polling...");
|
||||
fetchFullState(); // Fetch immediately once
|
||||
fallbackTimer = setInterval(fetchFullState, FALLBACK_POLL_INTERVAL);
|
||||
|
||||
// Optionally, keep trying to reconnect the WS slowly in the background
|
||||
setTimeout(startWebSocket, 10000);
|
||||
}
|
||||
}
|
||||
|
||||
function stopFallbackPolling() {
|
||||
if (fallbackTimer !== null) {
|
||||
clearInterval(fallbackTimer);
|
||||
fallbackTimer = null;
|
||||
console.log("Stopped JSON fallback polling.");
|
||||
}
|
||||
}
|
||||
|
||||
// --- Data Fetching (Initial Sync & Fallback) ---
|
||||
async function fetchFullState() {
|
||||
try {
|
||||
// Fetch both config and score simultaneously
|
||||
const [resConfig, resScore] = await Promise.all([
|
||||
fetch(jsonUrlconfig + '?t=' + Date.now(), { cache: "no-store" }),
|
||||
fetch(jsonUrl + '?t=' + Date.now(), { cache: "no-store" })
|
||||
]);
|
||||
|
||||
if (resConfig.ok) displayConfig = await resConfig.json();
|
||||
if (resScore.ok) currentScore = await resScore.json();
|
||||
|
||||
renderDOM();
|
||||
} catch (err) {
|
||||
console.error("Error fetching JSON:", err);
|
||||
const container = document.getElementById('score');
|
||||
if(container) {
|
||||
container.innerHTML = "";
|
||||
container.style.backgroundColor = "black";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- The Master Renderer ---
|
||||
function renderDOM() {
|
||||
const logobg = document.querySelector('.logobg');
|
||||
const ctext = document.querySelector('.logoctext');
|
||||
|
||||
if (displayConfig.type === 'logo' || displayConfig.type === 'ctext') {
|
||||
// LOGO OR CUSTOM TEXT MODE
|
||||
if (logobg) logobg.style.opacity = "1";
|
||||
if (ctext) ctext.innerText = (displayConfig.type === 'ctext') ? (displayConfig.ctext || '') : '';
|
||||
|
||||
} else if (displayConfig.type === 'scoring') {
|
||||
// SCORING MODE
|
||||
if (logobg) logobg.style.opacity = "0";
|
||||
|
||||
if (currentScore.uniqueid !== lastUniqueId) {
|
||||
lastUniqueId = currentScore.uniqueid;
|
||||
// Reset any animation/repeat logic here if needed
|
||||
}
|
||||
|
||||
const safeText = (selector, text) => {
|
||||
const el = document.querySelector(selector);
|
||||
if (el) el.innerText = text !== undefined ? text : '';
|
||||
};
|
||||
|
||||
safeText('.row1text', `${currentScore.vorname || ''} ${currentScore.name || ''}`);
|
||||
safeText('.row2_1text', currentScore.verein ? `${currentScore.verein}, ` : '');
|
||||
safeText('.row2_2text', currentScore.programm || '');
|
||||
|
||||
const starttext = document.querySelector('.start_text');
|
||||
const row2El = document.querySelector('.row2');
|
||||
if (starttext && row2El) {
|
||||
const rootStyles = getComputedStyle(document.documentElement);
|
||||
const dangerColor = rootStyles.getPropertyValue('--danger').trim();
|
||||
const successColor = rootStyles.getPropertyValue('--success').trim();
|
||||
|
||||
if (currentScore.start === true) {
|
||||
row2El.style.setProperty('--colorStartDiv', successColor);
|
||||
starttext.innerHTML = 'Start';
|
||||
} else {
|
||||
row2El.style.setProperty('--colorStartDiv', dangerColor);
|
||||
starttext.innerHTML = 'Stop';
|
||||
}
|
||||
}
|
||||
|
||||
safeText('.row3_1text', currentScore.noteLinks);
|
||||
safeText('.row3_2text', currentScore.noteRechts);
|
||||
}
|
||||
|
||||
fitTextAll();
|
||||
}
|
||||
|
||||
// --- Settings & UI Handlers ---
|
||||
function updateSettings(type, value) {
|
||||
|
||||
const sanitizeHex = (val) => val.replace(/[^0-9a-fA-F#]/g, '');
|
||||
|
||||
switch (type) {
|
||||
case 'wkName':
|
||||
const logotext = document.querySelector('.logotext');
|
||||
if (logotext) logotext.innerHTML = value;
|
||||
break;
|
||||
case 'displayColourLogo':
|
||||
document.documentElement.style.setProperty('--panelBgLogo', sanitizeHex(value));
|
||||
break;
|
||||
case 'displayTextColourLogo':
|
||||
document.documentElement.style.setProperty('--text-color', sanitizeHex(value));
|
||||
break;
|
||||
case 'displayColorScoringBg':
|
||||
document.documentElement.style.setProperty('--bg', sanitizeHex(value));
|
||||
break;
|
||||
case 'displayColorScoringBgSoft':
|
||||
document.documentElement.style.setProperty('--bg-soft', sanitizeHex(value) + "26");
|
||||
break;
|
||||
case 'displayColorScoringPanel':
|
||||
document.documentElement.style.setProperty('--panel', sanitizeHex(value));
|
||||
break;
|
||||
case 'displayColorScoringPanelSoft':
|
||||
document.documentElement.style.setProperty('--panel-soft', sanitizeHex(value));
|
||||
break;
|
||||
case 'displayColorScoringPanelText':
|
||||
document.documentElement.style.setProperty('--text-main', sanitizeHex(value));
|
||||
break;
|
||||
case 'displayColorScoringPanelTextSoft':
|
||||
document.documentElement.style.setProperty('--text-muted', sanitizeHex(value));
|
||||
break;
|
||||
case 'displayColorScoringPanelTextNoteL': // Matching your PHP typo
|
||||
document.documentElement.style.setProperty('--color-note-l', sanitizeHex(value));
|
||||
break;
|
||||
case 'displayColorScoringPanelTextNoteR': // Matching your PHP typo
|
||||
document.documentElement.style.setProperty('--color-note-r', sanitizeHex(value));
|
||||
break;
|
||||
case 'logo-normal':
|
||||
const jsImgLogo = document.getElementById('jsImgLogo');
|
||||
if(jsImgLogo) jsImgLogo.src = '/intern/img/logo-normal.png?' + Date.now();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Text Resizing Engine ---
|
||||
function isOverflown(parent, elem, heightscale, widthscale, paddingtext) {
|
||||
return (
|
||||
(elem.scrollWidth + paddingtext) > (parent.clientWidth / widthscale) ||
|
||||
(elem.scrollHeight + paddingtext) > (parent.clientHeight / heightscale)
|
||||
);
|
||||
}
|
||||
|
||||
function fitTextElement(elem, { minSize = 10, maxSize = 1000, step = 1, unit = 'px' } = {}) {
|
||||
if (!elem) return;
|
||||
const parent = elem.parentElement;
|
||||
if (!parent) return;
|
||||
|
||||
// FIXED: Declare variables properly so they don't leak into the global scope
|
||||
let heightscale = 1;
|
||||
let widthscale = 1;
|
||||
let paddingtext = 60;
|
||||
|
||||
if (parent.classList.contains('row2text')) {
|
||||
heightscale = 2;
|
||||
paddingtext = 0;
|
||||
}
|
||||
if (parent.classList.contains('row3')) {
|
||||
widthscale = 2;
|
||||
}
|
||||
|
||||
let size = minSize;
|
||||
elem.style.whiteSpace = 'nowrap';
|
||||
elem.style.fontSize = size + unit;
|
||||
|
||||
while (size < maxSize && !isOverflown(parent, elem, heightscale, widthscale, paddingtext)) {
|
||||
size += step;
|
||||
elem.style.fontSize = size + unit;
|
||||
}
|
||||
|
||||
elem.style.fontSize = (size - step) + unit;
|
||||
}
|
||||
|
||||
function fitTextAll() {
|
||||
const paragraphs = document.querySelectorAll('.pagediv p');
|
||||
paragraphs.forEach(p => fitTextElement(p));
|
||||
}
|
||||
|
||||
window.addEventListener('resize', fitTextAll);
|
||||
|
||||
// --- Initialize ---
|
||||
startWebSocket();
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,35 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>400 - Bad Request</title>
|
||||
<link rel="icon" type="image/png" href="/intern/img/icon.png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="/error-pages/style.css">
|
||||
<script>
|
||||
window.ERROR_CODE = 400;
|
||||
</script>
|
||||
<script src="/error-pages/translations.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<section>
|
||||
<div class="header">
|
||||
<h1 id="title">Ungültige Anfrage</h1>
|
||||
<p id="subtitle">HTTP Fehlercode 400</p>
|
||||
</div>
|
||||
|
||||
<div class="lang-switcher">
|
||||
<select class="lang-select" id="langSelect" onchange="translatePage(this.value)">
|
||||
<option value="de">Deutsch</option>
|
||||
<option value="en">English</option>
|
||||
<option value="fr">Français</option>
|
||||
<option value="it">Italiano</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="content" id="message">
|
||||
Ihre Anfrage ist fehlerhaft und das gesuchtes Ziel kann nicht gefunden werden.
|
||||
</div>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,39 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>400 - Bad Request</title>
|
||||
<link rel="icon" type="image/png" href="/intern/img/icon.png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="/error-pages/style.css">
|
||||
<script>
|
||||
window.ERROR_CODE = 400;
|
||||
</script>
|
||||
<script src="/error-pages/translations.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<section>
|
||||
<div class="header">
|
||||
<h1 id="title">Ungültige Anfrage</h1>
|
||||
<p id="subtitle">HTTP Fehlercode 400</p>
|
||||
</div>
|
||||
|
||||
<div class="lang-switcher">
|
||||
<select class="lang-select" id="langSelect" onchange="translatePage(this.value)">
|
||||
<option value="de">Deutsch</option>
|
||||
<option value="en">English</option>
|
||||
<option value="fr">Français</option>
|
||||
<option value="it">Italiano</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="phpContent" id="phpMessage">
|
||||
<?= htmlspecialchars($message ?? '') ?>
|
||||
</div>
|
||||
|
||||
<div class="content" id="message">
|
||||
Ihre Anfrage ist fehlerhaft und das gesuchtes Ziel kann nicht gefunden werden.
|
||||
</div>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,35 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>403 - Forbidden</title>
|
||||
<link rel="icon" type="image/png" href="/intern/img/icon.png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="/error-pages/style.css">
|
||||
<script>
|
||||
window.ERROR_CODE = 403;
|
||||
</script>
|
||||
<script src="/error-pages/translations.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<section>
|
||||
<div class="header">
|
||||
<h1 id="title">Anfrage verboten</h1>
|
||||
<p id="subtitle">HTTP Fehlercode 403</p>
|
||||
</div>
|
||||
|
||||
<div class="lang-switcher">
|
||||
<select class="lang-select" id="langSelect" onchange="translatePage(this.value)">
|
||||
<option value="de">Deutsch</option>
|
||||
<option value="en">English</option>
|
||||
<option value="fr">Français</option>
|
||||
<option value="it">Italiano</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="content" id="message">
|
||||
Sie haben keine Berechtigung diesen Inhalt anzuzeigen.
|
||||
</div>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,35 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>429 - Too many Requests</title>
|
||||
<link rel="icon" type="image/png" href="/intern/img/icon.png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="/error-pages/style.css">
|
||||
<script>
|
||||
window.ERROR_CODE = 429;
|
||||
</script>
|
||||
<script src="/error-pages/translations.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<section>
|
||||
<div class="header">
|
||||
<h1 id="title">Zu viele Anfragen</h1>
|
||||
<p id="subtitle">HTTP Fehlercode 429</p>
|
||||
</div>
|
||||
|
||||
<div class="lang-switcher">
|
||||
<select class="lang-select" id="langSelect" onchange="translatePage(this.value)">
|
||||
<option value="de">Deutsch</option>
|
||||
<option value="en">English</option>
|
||||
<option value="fr">Français</option>
|
||||
<option value="it">Italiano</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="content" id="message">
|
||||
Ihr Netzwerk sendet aktuell zu viele Anfragen an diese Webseite, weshalb diese IP-Adresse temporär blockiert wurde. Versuchen Sie die Seite in wenigen Sekunden neu zu laden.
|
||||
</div>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,35 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>500 - Internal Server Error</title>
|
||||
<link rel="icon" type="image/png" href="/intern/img/icon.png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="/error-pages/style.css">
|
||||
<script>
|
||||
window.ERROR_CODE = 500;
|
||||
</script>
|
||||
<script src="/error-pages/translations.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<section>
|
||||
<div class="header">
|
||||
<h1 id="title">Kritischer Fehler beim Laden der Seite</h1>
|
||||
<p id="subtitle">HTTP Fehlercode 500</p>
|
||||
</div>
|
||||
|
||||
<div class="lang-switcher">
|
||||
<select class="lang-select" id="langSelect" onchange="translatePage(this.value)">
|
||||
<option value="de">Deutsch</option>
|
||||
<option value="en">English</option>
|
||||
<option value="fr">Français</option>
|
||||
<option value="it">Italiano</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="content" id="message">
|
||||
Diese Seite konnte aufgrund von Problemen auf dem Server nicht geladen werden. Bitte versuchen Sie es später erneut. Die Behebung dieses Fehlers kann einige Zeit in Anspruch nehmen.
|
||||
</div>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,104 @@
|
||||
/* Modern reset and centering */
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
color: #1f2937;
|
||||
min-height: 100vh;
|
||||
padding: 20vh 60px;
|
||||
}
|
||||
|
||||
section {
|
||||
padding: 40px;
|
||||
width: 100%;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
body {
|
||||
padding: 15vh 10px;
|
||||
}
|
||||
|
||||
section {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 32px !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Header section with warning color */
|
||||
.header h1 {
|
||||
font-size: 48px;
|
||||
letter-spacing: 2px;
|
||||
color: #36454F;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.header p {
|
||||
font-size: 14px;
|
||||
letter-spacing: 0.05em;
|
||||
color: #6b7280;
|
||||
font-weight: 600;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
/* Sleek Language Dropdown Container */
|
||||
.lang-switcher {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.lang-select {
|
||||
font-family: inherit;
|
||||
font-size: 14px;
|
||||
color: #4b5563;
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 6px;
|
||||
padding: 6px 32px 6px 12px;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
appearance: none;
|
||||
/* Removes native browser arrow */
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%236b7280'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 9l-7 7-7-7'/%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 8px center;
|
||||
background-size: 16px;
|
||||
transition: border-color 0.15s ease, color 0.15s ease;
|
||||
}
|
||||
|
||||
.lang-select:hover,
|
||||
.lang-select:focus {
|
||||
border-color: #01CB8E;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
/* Divider line */
|
||||
.header::after {
|
||||
content: "";
|
||||
display: block;
|
||||
width: min(120px, 50vw);
|
||||
height: 3px;
|
||||
background-color: #01CB8E;
|
||||
margin: 0 0 24px 0;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
/* PHP Text */
|
||||
.phpContent {
|
||||
font-size: 24px;
|
||||
line-height: 1.6;
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
/* Main text */
|
||||
.content {
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
color: #4b5563a2;
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
const translations = {
|
||||
429: {
|
||||
de: {
|
||||
title: "Zu viele Anfragen",
|
||||
subtitle: "HTTP Fehlercode 429",
|
||||
message: "Ihr Netzwerk sendet aktuell zu viele Anfragen an diese Webseite, weshalb diese IP-Adresse temporär blockiert wurde. Versuchen Sie die Seite in wenigen Sekunden neu zu laden."
|
||||
},
|
||||
en: {
|
||||
title: "Too Many Requests",
|
||||
subtitle: "HTTP Error Code 429",
|
||||
message: "Your network is currently sending too many requests to this website, which is why this IP address has been temporarily blocked. Please try reloading the page in a few seconds."
|
||||
},
|
||||
fr: {
|
||||
title: "Trop de requêtes",
|
||||
subtitle: "Code d'erreur HTTP 429",
|
||||
message: "Votre réseau envoie actuellement trop de requêtes à ce site web, c'est pourquoi cette adresse IP a été temporairement bloquée. Veuillez réessayer de charger la page dans quelques secondes."
|
||||
},
|
||||
it: {
|
||||
title: "Troppe richieste",
|
||||
subtitle: "Codice di errore HTTP 429",
|
||||
message: "La tua rete sta attualmente inviando troppe richieste a questo sito web, motivo per cui questo indirizzo IP è stato temporaneamente bloccato. Prova a ricaricare la pagina tra pochi secondi."
|
||||
}
|
||||
},
|
||||
403: {
|
||||
de: {
|
||||
title: "Anfrage verboten",
|
||||
subtitle: "HTTP Fehlercode 403",
|
||||
message: "Sie haben keine Berechtigung diesen Inhalt anzuzeigen."
|
||||
},
|
||||
en: {
|
||||
title: "Forbidden",
|
||||
subtitle: "HTTP Error Code 403",
|
||||
message: "You do not have permission to view this content."
|
||||
},
|
||||
fr: {
|
||||
title: "Accès interdit",
|
||||
subtitle: "Code d'erreur HTTP 403",
|
||||
message: "Vous n'avez pas l'autorisation d'afficher ce contenu."
|
||||
},
|
||||
it: {
|
||||
title: "Accesso vietato",
|
||||
subtitle: "Codice di errore HTTP 403",
|
||||
message: "Non hai il permesso di visualizzare questo contenuto."
|
||||
}
|
||||
},
|
||||
400: {
|
||||
de: {
|
||||
title: "Ungültige Anfrage",
|
||||
subtitle: "HTTP Fehlercode 400",
|
||||
message: "Ihre Anfrage ist fehlerhaft und das gesuchte Ziel kann nicht gefunden werden."
|
||||
},
|
||||
en: {
|
||||
title: "Bad Request",
|
||||
subtitle: "HTTP Error Code 400",
|
||||
message: "Your request is malformed and the destination you are looking for cannot be found."
|
||||
},
|
||||
fr: {
|
||||
title: "Requête incorrecte",
|
||||
subtitle: "Code d'erreur HTTP 400",
|
||||
message: "Votre requête est incorrecte et la destination recherchée est introuvable."
|
||||
},
|
||||
it: {
|
||||
title: "Richiesta errata",
|
||||
subtitle: "Codice di errore HTTP 400",
|
||||
message: "La tua richiesta è errata e la destinazione cercata non è rintracciabile."
|
||||
}
|
||||
},
|
||||
500: {
|
||||
de: {
|
||||
title: "Kritischer Fehler beim Laden der Seite",
|
||||
subtitle: "HTTP Fehlercode 500",
|
||||
message: "Diese Seite konnte aufgrund von Problemen auf dem Server nicht geladen werden. Bitte versuchen Sie es später erneut. Die Behebung dieses Fehlers kann einige Zeit in Anspruch nehmen."
|
||||
},
|
||||
en: {
|
||||
title: "Critical Error Loading Page",
|
||||
subtitle: "HTTP Error Code 500",
|
||||
message: "This page could not be loaded due to problems on the server. Please try again later. Resolving this error may take some time."
|
||||
},
|
||||
fr: {
|
||||
title: "Erreur critique lors du chargement de la page",
|
||||
subtitle: "Code d'erreur HTTP 500",
|
||||
message: "Cette page n'a pas pu être chargée en raison de problèmes sur le serveur. Veuillez réessayer plus tard. La résolution de cette erreur peut prendre un certain temps."
|
||||
},
|
||||
it: {
|
||||
title: "Errore critico durante il caricamento della pagina",
|
||||
subtitle: "Codice di errore HTTP 500",
|
||||
message: "Impossibile caricare la pagina a causa di problemi sul server. Si prega di riprovare più tardi. La risoluzione di questo errore potrebbe richiedere del tempo."
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const errortype = window.ERROR_CODE;
|
||||
|
||||
function translatePage(lang) {
|
||||
document.documentElement.lang = lang;
|
||||
|
||||
document.getElementById('title').textContent = translations[errortype][lang].title;
|
||||
document.getElementById('subtitle').textContent = translations[errortype][lang].subtitle;
|
||||
document.getElementById('message').textContent = translations[errortype][lang].message;
|
||||
}
|
||||
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
const browserLang = navigator.language || navigator.userLanguage;
|
||||
|
||||
const shortLang = browserLang.toLowerCase().split('-')[0];
|
||||
|
||||
const supportedLangs = ['de', 'en', 'fr', 'it'];
|
||||
|
||||
if (supportedLangs.includes(shortLang)) {
|
||||
const selectEl = document.getElementById('langSelect');
|
||||
selectEl.value = shortLang; // Changes the dropdown visual state
|
||||
translatePage(shortLang); // Runs your translation function
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
RewriteEngine On
|
||||
|
||||
# ----------------------------------------
|
||||
# 1) Root → router.php
|
||||
# ----------------------------------------
|
||||
RewriteRule ^$ router.php [L]
|
||||
|
||||
# ----------------------------------------
|
||||
# 2) Allow existing files
|
||||
# ----------------------------------------
|
||||
RewriteCond %{REQUEST_FILENAME} -f
|
||||
RewriteRule ^ - [L]
|
||||
|
||||
# ----------------------------------------
|
||||
# 3) Allow existing directories
|
||||
# ----------------------------------------
|
||||
RewriteCond %{REQUEST_FILENAME} -d
|
||||
RewriteRule ^ - [L]
|
||||
|
||||
# ----------------------------------------
|
||||
# 4) Everything else → router.php
|
||||
# ----------------------------------------
|
||||
RewriteRule ^ router.php [QSA,L]
|
||||
@@ -0,0 +1,322 @@
|
||||
<?php
|
||||
|
||||
if (!isset($segments) || !defined('APP_ROUTER')) {
|
||||
http_response_code(403);
|
||||
include $baseDir . "/error-pages/403.php";
|
||||
exit;
|
||||
}
|
||||
|
||||
$type = $segments[1] ?? null;
|
||||
$requested_geraet = htmlspecialchars($segments[2] ?? '');
|
||||
|
||||
$allowed_types = ["audioplayer"];
|
||||
|
||||
if (!in_array($type, $allowed_types)) {
|
||||
http_response_code(400);
|
||||
include $baseDir . "/error-pages/400.html";
|
||||
exit;
|
||||
}
|
||||
|
||||
$baseDir = $_SERVER['DOCUMENT_ROOT'];
|
||||
|
||||
require_once $baseDir . '/../scripts/session_functions.php';
|
||||
|
||||
ini_wkvs_session(true);
|
||||
|
||||
$csrf_token = $_SESSION['csrf_token'] ?? '';
|
||||
|
||||
require $baseDir . '/../scripts/websocket/ws-create-token.php';
|
||||
|
||||
$WSaccesstype = "audio";
|
||||
$WSaccessPermission = "R";
|
||||
|
||||
require_once $baseDir . '/../scripts/db/db-verbindung-script-guest.php';
|
||||
require_once $baseDir . '/../scripts/db/db-functions.php';
|
||||
require_once $baseDir . '/../scripts/db/db-tables.php';
|
||||
|
||||
$stmt = $guest->prepare("SELECT `name`, `id` FROM $tableGeraete WHERE `audiofile` = '1' ORDER BY start_index ASC");
|
||||
|
||||
if (!$stmt->execute()) {
|
||||
http_response_code(500);
|
||||
exit;
|
||||
}
|
||||
|
||||
$result = $stmt->get_result();
|
||||
$disciplines = $result->fetch_all(MYSQLI_ASSOC);
|
||||
|
||||
$lowerDisciplines = array_change_key_case(
|
||||
array_column($disciplines, 'id', 'name'),
|
||||
CASE_LOWER
|
||||
);
|
||||
|
||||
$disciplines_indexed = array_column($disciplines, 'name', 'id');
|
||||
|
||||
|
||||
if (!array_key_exists($requested_geraet, $lowerDisciplines)) {
|
||||
http_response_code(400);
|
||||
$message = 'Das gesuchte Gerät kann nicht gefunden werden';
|
||||
include $baseDir . "/error-pages/400.php";
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!isset($lowerDisciplines[$requested_geraet])) {
|
||||
http_response_code(500);
|
||||
include $baseDir . "/error-pages/500.html";
|
||||
exit;
|
||||
}
|
||||
|
||||
$geraetId = $lowerDisciplines[$requested_geraet];
|
||||
|
||||
$WSaccess = (string) $geraetId;
|
||||
|
||||
$jsonUrl = '/externe-geraete/json/audio-id-' . $geraetId . '.json';
|
||||
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Remote Audioplayer</title>
|
||||
<meta name="robots" content="noindex">
|
||||
<script src="/intern/js/jquery/jquery-3.7.1.min.js"></script>
|
||||
<script src="/intern/js/custom-msg-display.js"></script>
|
||||
<link rel="stylesheet" href="/intern/css/custom-msg-display.css">
|
||||
<link rel="stylesheet" href="/externe-geraete/css/audio.css">
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<section>
|
||||
<div class="header">
|
||||
<h1 id="title">Audioplayer</h1>
|
||||
<p id="subtitle"><?= $disciplines_indexed[$geraetId] ?></p>
|
||||
</div>
|
||||
|
||||
<button class="startButton">
|
||||
Audioplayer starten
|
||||
</button>
|
||||
|
||||
<div class="currentyPlaying hidden">
|
||||
Aktuell laufendes Audio:
|
||||
<div class="subDivCurrentyPlaying">
|
||||
<span>Dateiname: <span class="spanFilename"></span></span>
|
||||
<span class="spanPersonname">Name der Person:
|
||||
<span class="spanPersonnameName"></span>,
|
||||
<span class="spanPersonnameVorname"></span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="progressDiv">
|
||||
<div id="progressContainer">
|
||||
<div id="progressBar"></div>
|
||||
</div>
|
||||
|
||||
<span class="progressText"><span id="currentText">0:00</span> / <span id="durationText">0:00</span></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
Dies ist der Live Audioplayer. Audio kann über die Kampfrichteransicht gestartet werden.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<audio id="peep" preload="auto"></audio>
|
||||
<audio id="musicPlayer" preload="auto"></audio>
|
||||
<script>
|
||||
const WSaccesstype = "<?= $WSaccesstype ?>";
|
||||
const WSaccess = "<?= $WSaccess ?>";
|
||||
const csrf_token = "<?= $csrf_token ?>";
|
||||
const WSaccessPermission = "<?= $WSaccessPermission ?>"
|
||||
|
||||
const jsonUrl = "<?php echo $jsonUrl; ?>";
|
||||
|
||||
let ws;
|
||||
|
||||
let firstConnect = true;
|
||||
const RETRY_DELAY = 2000;
|
||||
|
||||
const urlAjaxNewWSToken = '/intern/scripts/ajax-create-ws-token.php';
|
||||
|
||||
async function fetchNewWSToken(accesstype, access, accessPermission) {
|
||||
try {
|
||||
const response = await fetch(urlAjaxNewWSToken, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
csrf_token,
|
||||
accesstype,
|
||||
access,
|
||||
accessPermission
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) return null;
|
||||
|
||||
const data = await response.json();
|
||||
return data.success ? data.token : null;
|
||||
} catch (error) {
|
||||
console.error("Token fetch failed:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function startWebSocket() {
|
||||
console.log("Attempting WebSocket connection...");
|
||||
|
||||
let token;
|
||||
if (firstConnect) {
|
||||
token = '<?= generateWSReadToken($WSaccesstype, $WSaccess) ?>';
|
||||
} else {
|
||||
token = await fetchNewWSToken(WSaccesstype, WSaccess, WSaccessPermission);
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
console.error("No valid token available. Retrying...");
|
||||
scheduleRetry();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
ws = new WebSocket(`wss://${window.location.hostname}/ws/?access=token&token=${token}`);
|
||||
} catch (err) {
|
||||
console.error("Malformed WebSocket URL", err);
|
||||
scheduleRetry();
|
||||
return;
|
||||
}
|
||||
|
||||
ws.onopen = () => {
|
||||
console.log("WebSocket connected!");
|
||||
if (!firstConnect) {
|
||||
displayMsg(1, "Live Syncronisation wieder verfügbar");
|
||||
}
|
||||
firstConnect = true;
|
||||
};
|
||||
|
||||
ws.onerror = (event) => {
|
||||
console.error("WebSocket error observed.");
|
||||
};
|
||||
|
||||
ws.onclose = (event) => {
|
||||
displayMsg(0, "Live Syncronisation verloren");
|
||||
firstConnect = false;
|
||||
|
||||
scheduleRetry();
|
||||
};
|
||||
}
|
||||
|
||||
function scheduleRetry() {
|
||||
console.log(`Retrying in ${RETRY_DELAY}ms...`);
|
||||
setTimeout(startWebSocket, RETRY_DELAY);
|
||||
}
|
||||
|
||||
startWebSocket();
|
||||
|
||||
const spanFilename = $('.spanFilename');
|
||||
const spanPersonname = $('.spanPersonname');
|
||||
const spanPersonnameName = $('.spanPersonnameName');
|
||||
const spanPersonnameVorname = $('.spanPersonnameVorname');
|
||||
|
||||
const currentyPlaying = $('.currentyPlaying');
|
||||
|
||||
const $progressBar = $('#progressBar');
|
||||
const $progressContainer = $('#progressContainer');
|
||||
|
||||
const peep = document.getElementById('peep');
|
||||
const music = document.getElementById('musicPlayer');
|
||||
const $audio = $('#musicPlayer');
|
||||
|
||||
let lastMusicUrl = null;
|
||||
let pollingStarted = false; // ensure we start polling only once
|
||||
|
||||
async function fetchAndHandleMusic() {
|
||||
try {
|
||||
const response = await fetch(jsonUrl + '?t=' + Date.now(), { cache: 'no-store' });
|
||||
const data = await response.json();
|
||||
|
||||
if (!data.audio_path || data.audio_path === 'nan' || data.start == false) {
|
||||
if (!music.paused) {
|
||||
music.pause();
|
||||
music.currentTime = 0;
|
||||
}
|
||||
if (!peep.paused) {
|
||||
peep.pause();
|
||||
peep.currentTime = 0;
|
||||
}
|
||||
lastMusicUrl = null;
|
||||
return;
|
||||
}
|
||||
|
||||
peep.src = '/files/music/piep.mp3';
|
||||
music.src = data.audio_path;
|
||||
music.currentTime = 0;
|
||||
peep.play().then(() => {
|
||||
setTimeout(() => {
|
||||
music.play().catch(err => console.log('Music play error:', err));
|
||||
}, 2000);
|
||||
}).catch(err => console.log('Peep play error:', err));
|
||||
|
||||
spanFilename.text(data.audio_name ?? '');
|
||||
spanPersonnameName.text(data.person.name ?? '');
|
||||
spanPersonnameVorname.text(data.person.vorname ?? '');
|
||||
|
||||
} catch (err) {
|
||||
console.error('Error fetching JSON:', err);
|
||||
}
|
||||
}
|
||||
|
||||
function formatTime(seconds) {
|
||||
if (isNaN(seconds)) return "0:00";
|
||||
const mins = Math.floor(seconds / 60);
|
||||
const secs = Math.floor(seconds % 60);
|
||||
return mins + ":" + (secs < 10 ? '0' : '') + secs;
|
||||
}
|
||||
|
||||
const audioDOM = $audio[0];
|
||||
|
||||
$audio.on('timeupdate', function() {
|
||||
if (audioDOM.duration) {
|
||||
const percentage = (audioDOM.currentTime / audioDOM.duration) * 100;
|
||||
$progressBar.css('width', percentage + '%');
|
||||
$('#currentText').text(formatTime(audioDOM.currentTime));
|
||||
}
|
||||
});
|
||||
|
||||
$audio.on('loadedmetadata', function() {
|
||||
$('#durationText').text(formatTime(audioDOM.duration));
|
||||
});
|
||||
|
||||
let isActive = false;
|
||||
|
||||
$('.startButton').on('click', function() {
|
||||
isActive = !isActive;
|
||||
if (isActive) {
|
||||
$(this).text('Audioplayer stoppen');
|
||||
} else {
|
||||
$(this).text('Audioplayer starten');
|
||||
currentyPlaying.addClass('hidden');
|
||||
if (!music.paused) {
|
||||
music.pause();
|
||||
music.currentTime = 0;
|
||||
}
|
||||
if (!peep.paused) {
|
||||
peep.pause();
|
||||
peep.currentTime = 0;
|
||||
}
|
||||
}
|
||||
$('section').toggleClass('playerActive', isActive);
|
||||
|
||||
|
||||
if (!pollingStarted) {
|
||||
pollingStarted = true;
|
||||
ws.addEventListener("message", () => {
|
||||
if (isActive) {
|
||||
fetchAndHandleMusic();
|
||||
currentyPlaying.removeClass('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,154 @@
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
color: #1f2937;
|
||||
min-height: 100vh;
|
||||
padding: 20vh 60px;
|
||||
}
|
||||
|
||||
section {
|
||||
padding: 40px;
|
||||
width: 100%;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
body {
|
||||
padding: 15vh 10px;
|
||||
}
|
||||
|
||||
section {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 32px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 48px;
|
||||
letter-spacing: 2px;
|
||||
color: #36454F;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.header p {
|
||||
font-size: 14px;
|
||||
letter-spacing: 0.05em;
|
||||
color: #6b7280;
|
||||
font-weight: 600;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
|
||||
.startButton {
|
||||
font-family: inherit;
|
||||
font-size: 28px;
|
||||
color: #4b5563;
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 6px;
|
||||
padding: 6px 12px 6px 12px;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
appearance: none;
|
||||
transition: border-color 0.15s ease, color 0.15s ease;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.startButton:hover,
|
||||
.startButton:focus {
|
||||
border-color: #01CB8E;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
section.playerActive .startButton:hover,
|
||||
section.playerActive .startButton:focus {
|
||||
border-color: #cb0198;
|
||||
}
|
||||
|
||||
.header::after {
|
||||
content: "";
|
||||
display: block;
|
||||
width: min(240px, 50vw);
|
||||
height: 8px;
|
||||
background-color: #cb0198;
|
||||
margin: 0 0 24px 0;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.5s ease;
|
||||
}
|
||||
|
||||
section.playerActive .header::after {
|
||||
background-color: #01CB8E;
|
||||
}
|
||||
|
||||
.currentyPlaying {
|
||||
font-size: 20px;
|
||||
line-height: 1.6;
|
||||
color: #4b5563;
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.subDivCurrentyPlaying {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.spanPersonnameName {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.subDivCurrentyPlaying {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.content {
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
color: #4b5563a2;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#progressContainer {
|
||||
width: 100%;
|
||||
background: #e0e0e0;
|
||||
height: 10px;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#progressBar {
|
||||
width: 0%;
|
||||
background: #2196F3;
|
||||
height: 100%;
|
||||
transition: width 0.1s linear;
|
||||
}
|
||||
|
||||
.progressDiv {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
width: min(100%, 300px);
|
||||
flex-direction: column;
|
||||
padding: 10px;
|
||||
border: 1px solid #a3a8af;
|
||||
border-radius: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.progressText {
|
||||
font-size: 12px;
|
||||
}
|
||||
@@ -0,0 +1,402 @@
|
||||
:root {
|
||||
--display-bg: #111827;
|
||||
--display-panel: #020617;
|
||||
--display-panel-soft: #0b1120;
|
||||
--display-accent: #38bdf8;
|
||||
--display-accent-soft: rgba(56, 189, 248, 0.15);
|
||||
--display-text-main: #e5e7eb;
|
||||
--display-text-muted: #9ca3af;
|
||||
--display-danger: #ef4444;
|
||||
--display-success: #22c55e;
|
||||
--display-shadow: #0f172ad9;
|
||||
--display-radius-lg: 18px;
|
||||
--display-border: #94a3b859;
|
||||
--display-transition-fast: 180ms ease-out;
|
||||
--display-colorStartDiv: #0b1120;
|
||||
--display-panelBgLogo: #4a2f96;
|
||||
--display-font-heading: clamp(1.5rem, 6vh, 4rem);
|
||||
--display-font-sub: clamp(1rem, 3.5vh, 2.2rem);
|
||||
--display-logo-size: clamp(150px, 40vh, 500px);
|
||||
--display-error-WS-text: #a7e1fa;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
head>* {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.displayDiv {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.displayDiv {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: stretch;
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
background-color: var(--display-bg);
|
||||
color: var(--display-text-main);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* WebSocket error overlay */
|
||||
.displayDiv.errorws {
|
||||
position: relative;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 0;
|
||||
background: radial-gradient(circle at top, #020617 0, #020617 55%);
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
.displayDiv.errorws svg {
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
position: absolute;
|
||||
top: 20vh;
|
||||
}
|
||||
|
||||
.displayDiv.errorws .logoimg {
|
||||
max-width: var(--display-logo-size);
|
||||
height: auto;
|
||||
margin-bottom: 2vh;
|
||||
filter: drop-shadow(0 12px 30px rgba(0, 0, 0, 0.65));
|
||||
}
|
||||
|
||||
.divErrorTextWs {
|
||||
position: absolute;
|
||||
bottom: 5vh;
|
||||
}
|
||||
|
||||
.errortext {
|
||||
margin: 0;
|
||||
font-size: clamp(1.8rem, 8vw, 5rem);
|
||||
line-height: 1.1;
|
||||
max-width: 90vw;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--display-error-WS-text);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.errortextSmall {
|
||||
margin: 10px;
|
||||
font-size: clamp(0.5rem, 3vw, 2rem);
|
||||
opacity: 0.8;
|
||||
font-weight: 300;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--display-error-WS-text);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Startup logo overlay */
|
||||
.logobg {
|
||||
padding: 4vh;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
background-color: var(--display-panelBgLogo);
|
||||
z-index: 1000;
|
||||
transition: opacity 0.5s ease;
|
||||
}
|
||||
|
||||
.logoimg {
|
||||
width: auto;
|
||||
max-height: min(45vh, 100%);
|
||||
max-width: min(80vw, 100%);
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.logotext {
|
||||
margin: 0;
|
||||
font-size: clamp(1.8rem, 7vh, 6rem);
|
||||
text-align: center;
|
||||
width: 90vw;
|
||||
line-height: 1.2;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.logotext,
|
||||
.logoctext {
|
||||
color: var(--display-text-color) !important;
|
||||
text-align: center;
|
||||
font-weight: 400;
|
||||
margin-top: 1vh;
|
||||
}
|
||||
|
||||
.logoctext {
|
||||
margin: calc(clamp(2rem, 7vw, 12rem) / calc(100% / 10)) 0;
|
||||
font-size: clamp(2rem, 7vw, 12rem);
|
||||
}
|
||||
|
||||
/* 4. Responsive adjustments for different screen ratios */
|
||||
@media (aspect-ratio: 4/3),
|
||||
(aspect-ratio: 10/7) {
|
||||
|
||||
/* Tablet specific tweaks (iPad/Android Tabs) */
|
||||
:root {
|
||||
--display-logo-size: 30vh;
|
||||
}
|
||||
|
||||
.logotext {
|
||||
font-size: 5vh;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1921px) {
|
||||
|
||||
/* Ultra-large TV/4K tweaks */
|
||||
.logotext {
|
||||
letter-spacing: 0.25em;
|
||||
/* Better legibility at distance */
|
||||
}
|
||||
}
|
||||
|
||||
/* Main scoreboard container – full screen */
|
||||
.pagediv {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2vh;
|
||||
padding: 2.2vh 2.4vw;
|
||||
box-sizing: border-box;
|
||||
background:
|
||||
radial-gradient(circle at top left, var(--display-bg-soft), transparent 60%),
|
||||
radial-gradient(circle at bottom right, var(--display-bg-soft), transparent 60%);
|
||||
}
|
||||
|
||||
.pagediv.manuel {
|
||||
padding-right: clamp(60px, 2.4vw, 2.4vw);
|
||||
}
|
||||
|
||||
/* Common row styling */
|
||||
.display-row {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 30px;
|
||||
border-radius: var(--display-radius-lg);
|
||||
background: linear-gradient(135deg, var(--display-panel-soft), var(--display-panel));
|
||||
border-bottom: 1px solid var(--display-border);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 20px 45px var(--display-shadow);
|
||||
}
|
||||
|
||||
.display-row::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: radial-gradient(circle at top left, var(--display-bg-soft), transparent 55%);
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.row1text,
|
||||
.row2text p,
|
||||
.row3>p,
|
||||
.start_text {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
line-height: 1.1;
|
||||
align-self: start;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* Row 1: athlete name */
|
||||
.row1text {
|
||||
font-size: min(8vh, 4.6vw);
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Row 2: club, program, start/stop */
|
||||
.row2 {
|
||||
justify-content: space-between;
|
||||
gap: 2vw;
|
||||
}
|
||||
|
||||
.row2::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: radial-gradient(circle at bottom right, var(--display-colorStartDiv), transparent 55%);
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.row2text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.8vh;
|
||||
width: 50vw;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.row2_1text {
|
||||
color: var(--display-text-muted);
|
||||
}
|
||||
|
||||
.row2_2text {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Start / stop pill */
|
||||
.start_div {
|
||||
width: 35vw;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.start_text {
|
||||
display: flex;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
margin: auto 0;
|
||||
}
|
||||
|
||||
/* Row 3: D-score and total */
|
||||
.row3 {
|
||||
justify-content: space-between;
|
||||
gap: 2vw;
|
||||
}
|
||||
|
||||
.row3>p {
|
||||
font-size: min(9vh, 5vw);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.row3_1text {
|
||||
color: var(--display-color-note-l);
|
||||
}
|
||||
|
||||
.row3_2text {
|
||||
color: var(--display-color-note-r);
|
||||
}
|
||||
|
||||
/* Existing #score and char/row styles if you use them elsewhere */
|
||||
#score {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.char {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 40vw;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
font-size: 20vw;
|
||||
}
|
||||
|
||||
/* Landscape / portrait logo scaling */
|
||||
@media (orientation: landscape) {
|
||||
.logoimg {
|
||||
max-height: min(60vh, 100%);
|
||||
}
|
||||
}
|
||||
|
||||
@media (orientation: portrait) {
|
||||
.logoimg {
|
||||
max-width: min(60vw, 100%);
|
||||
}
|
||||
}
|
||||
|
||||
/* Small screens – keep everything readable */
|
||||
@media (max-width: 768px) {
|
||||
.pagediv {
|
||||
padding: 1.6vh 3vw;
|
||||
gap: 1.4vh;
|
||||
}
|
||||
|
||||
.display-row {
|
||||
padding: 1.4vh 3vw;
|
||||
}
|
||||
|
||||
.row2 {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.start_div {
|
||||
align-self: flex-end;
|
||||
min-width: 40vw;
|
||||
}
|
||||
}
|
||||
|
||||
.noWsConnection {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
|
||||
transform: rotate(270deg);
|
||||
transform-origin: right bottom;
|
||||
}
|
||||
|
||||
.rotator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
||||
.noWsConnection img {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.noWsConnection p {
|
||||
font-size: 20px;
|
||||
color: var(--display-text-muted);
|
||||
margin: 0;
|
||||
letter-spacing: 1.1px;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.ctext .cTextHidden {
|
||||
display: none !important;
|
||||
}
|
||||
@@ -0,0 +1,814 @@
|
||||
<?php
|
||||
|
||||
if (!isset($segments) || !defined('APP_ROUTER')) {
|
||||
http_response_code(403);
|
||||
include $baseDir . "/error-pages/403.php";
|
||||
exit;
|
||||
}
|
||||
|
||||
$type = $segments[1] ?? null;
|
||||
$requested_geraet = htmlspecialchars($segments[2] ?? '');
|
||||
|
||||
$allowed_types = ["display"];
|
||||
|
||||
if (!in_array($type, $allowed_types)) {
|
||||
http_response_code(400);
|
||||
include $baseDir . "/error-pages/400.html";
|
||||
exit;
|
||||
}
|
||||
|
||||
$baseDir = $_SERVER['DOCUMENT_ROOT'];
|
||||
|
||||
require_once $baseDir . '/../scripts/session_functions.php';
|
||||
|
||||
ini_wkvs_session(true);
|
||||
|
||||
$csrf_token = $_SESSION['csrf_token'] ?? '';
|
||||
|
||||
require $baseDir . '/../scripts/websocket/ws-create-token.php';
|
||||
|
||||
$WSaccesstype = "display";
|
||||
$WSaccessPermission = "R";
|
||||
|
||||
require_once $baseDir . '/../scripts/db/db-verbindung-script-guest.php';
|
||||
require_once $baseDir . '/../scripts/db/db-functions.php';
|
||||
require_once $baseDir . '/../scripts/db/db-tables.php';
|
||||
|
||||
$stmt = $guest->prepare("SELECT `name`, `id` FROM $tableGeraete ORDER BY start_index ASC");
|
||||
|
||||
if (!$stmt->execute()) {
|
||||
http_response_code(500);
|
||||
exit;
|
||||
}
|
||||
|
||||
$result = $stmt->get_result();
|
||||
$disciplines = $result->fetch_all(MYSQLI_ASSOC);
|
||||
$lowerDisciplines = array_change_key_case(
|
||||
array_column($disciplines, 'id', 'name'),
|
||||
CASE_LOWER
|
||||
);
|
||||
|
||||
|
||||
if (!array_key_exists($requested_geraet, $lowerDisciplines)) {
|
||||
http_response_code(400);
|
||||
$message = 'Das gesuchte Gerät kann nicht gefunden werden';
|
||||
include $baseDir . "/error-pages/400.php";
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!isset($lowerDisciplines[$requested_geraet])) {
|
||||
http_response_code(500);
|
||||
include $baseDir . "/error-pages/500.html";
|
||||
exit;
|
||||
}
|
||||
|
||||
$geraetId = $lowerDisciplines[$requested_geraet];
|
||||
|
||||
$WSaccess = (string) $geraetId;
|
||||
|
||||
|
||||
$stmt->close();
|
||||
|
||||
// Define a small helper function to keep the code DRY (Don't Repeat Yourself)
|
||||
function sanitize_hex($color) {
|
||||
return preg_replace('/[^0-9a-fA-F#]/', '', $color);
|
||||
}
|
||||
|
||||
$keysNeeded = [
|
||||
'wkName', 'displayColourLogo', 'displayTextColourLogo', 'displayColorScoringBg',
|
||||
'displayColorScoringBgSoft', 'displayColorScoringPanel', 'displayColorScoringPanelSoft', 'displayColorScoringBorderColor',
|
||||
'displayColorScoringShadowColor', 'displayColorScoringPanelText', 'displayColorScoringPanelTextSoft',
|
||||
'displayColorScoringPanelTextNoteL', 'displayColorScoringPanelTextNoteR', 'displayIdNoteL',
|
||||
'displayIdNoteR', 'displayCTextLogo', 'displayCTextWKName'
|
||||
];
|
||||
|
||||
$placeholders = implode(',', array_fill(0, count($keysNeeded), '?'));
|
||||
|
||||
$query = "SELECT `name`, `value` FROM $tableVar WHERE `name` IN ($placeholders)";
|
||||
|
||||
$stmt = $guest->prepare($query);
|
||||
$params = str_repeat("s", count($keysNeeded));
|
||||
$stmt->bind_param($params, ...$keysNeeded);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
|
||||
$config = [];
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
$config[$row['name']] = $row['value'];
|
||||
}
|
||||
|
||||
$wkName = $config['wkName'] ?? null;
|
||||
$displayCTextDisplayLogo = $config['displayCTextLogo'] ?? null;
|
||||
$displayCTextDisplayWKName = $config['displayCTextWKName'] ?? null;
|
||||
$cleanColor = $config['displayColourLogo'] ?? null;
|
||||
$cleanColorText = $config['displayTextColourLogo'] ?? null;
|
||||
$displayColorScoringBg = $config['displayColorScoringBg'] ?? null;
|
||||
$displayColorScoringBgSoft = $config['displayColorScoringBgSoft'] ?? null;
|
||||
$displayColorScoringPanel = $config['displayColorScoringPanel'] ?? null;
|
||||
$displayColorScoringPanelSoft = $config['displayColorScoringPanelSoft'] ?? null;
|
||||
$displayColorScoringShadowColor = $config['displayColorScoringShadowColor'] ?? null;
|
||||
$displayColorScoringBorderColor = $config['displayColorScoringBorderColor'] ?? null;
|
||||
$displayColorScoringPanelText = $config['displayColorScoringPanelText'] ?? null;
|
||||
$displayColorScoringPanelTextSoft = $config['displayColorScoringPanelTextSoft'] ?? null;
|
||||
$displayColorScoringPanelTextNoteL = $config['displayColorScoringPanelTextNoteL'] ?? null;
|
||||
$displayColorScoringPanelTextNoteR = $config['displayColorScoringPanelTextNoteR'] ?? null;
|
||||
|
||||
$stmt->close();
|
||||
|
||||
$guest->close();
|
||||
|
||||
$jsonUrlconfig = '/externe-geraete/json/config.json';
|
||||
$jsonUrl = '/externe-geraete/json/display-id-' . $geraetId . '.json';
|
||||
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title><?= $wkName ?> Anzeigen</title>
|
||||
<meta name="robots" content="noindex">
|
||||
<link rel="stylesheet" href="/externe-geraete/css/display.css">
|
||||
<script src="/intern/js/gsap/gsap.min.js"></script>
|
||||
<script src="/intern/js/gsap/MorphSVGPlugin.min.js"></script>
|
||||
<style>
|
||||
:root {
|
||||
|
||||
/* Brand */
|
||||
--display-panelBgLogo: <?= $cleanColor ?>;
|
||||
--display-text-color: <?= $cleanColorText ?>;
|
||||
|
||||
/* Backgrounds */
|
||||
--display-bg: <?= $displayColorScoringBg ?>;
|
||||
--display-bg-soft: <?= $displayColorScoringBgSoft ?>;
|
||||
--display-panel: <?= $displayColorScoringPanel ?>;
|
||||
--display-panel-soft: <?= $displayColorScoringPanelSoft ?>;
|
||||
|
||||
/* Shadow */
|
||||
--display-shadow: <?= $displayColorScoringShadowColor ?>;
|
||||
|
||||
/* Border */
|
||||
--display-border: <?= $displayColorScoringBorderColor ?>;
|
||||
|
||||
/* Typography */
|
||||
--display-text-main: <?= $displayColorScoringPanelText ?>;
|
||||
--display-text-muted: <?= $displayColorScoringPanelTextSoft ?>;
|
||||
|
||||
/* Noten */
|
||||
--display-color-note-l: <?= $displayColorScoringPanelTextNoteL ?>;
|
||||
--display-color-note-r: <?= $displayColorScoringPanelTextNoteR ?>;
|
||||
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="displayDiv">
|
||||
<div class="errorws">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="errorSvg" viewBox="0 0 800 200">
|
||||
<defs>
|
||||
<filter id="a" x="-50%" y="-50%" width="200%" height="200%">
|
||||
<feGaussianBlur stdDeviation="8" result="blur"/>
|
||||
<feMerge>
|
||||
<feMergeNode in="blur"/>
|
||||
<feMergeNode in="SourceGraphic"/>
|
||||
</feMerge>
|
||||
</filter>
|
||||
<linearGradient id="myLinearGradient" x1="0%" y1="5%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#22c55e00" />
|
||||
<stop offset="100%" stop-color="#22c55e90" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path d="M150 100h200m100 0h200" id="errorLine" opacity=".3" stroke="#3b82f6" stroke-width="4" stroke-dasharray="10,15" stroke-linecap="round"/>
|
||||
<g transform="translate(150 100)">
|
||||
<circle r="45" fill="#1e293b" stroke="#3b82f6" stroke-width="3"/>
|
||||
<circle r="45" fill="#1e293b" stroke="#3b82f6" stroke-width="3"/>
|
||||
|
||||
<rect x="-30" y="-19" width="60" height="38" rx="2" fill="#334155"/>
|
||||
<rect x="-28" y="-17" width="56" height="10" rx="2" fill="#555"/>
|
||||
<rect x="-28" y="-5" width="56" height="10" rx="2" fill="#555"/>
|
||||
<rect x="-28" y="7" width="56" height="10" rx="2" fill="#555"/>
|
||||
<text x="-26" y="-11" fill="#94a3b8" font-size="4" font-family="monospace" letter-spacing="0.5">Test WKVS</text>
|
||||
|
||||
<text x="-26" y="-1" fill="#94a3b8" font-size="2.5" font-family="monospace" letter-spacing="0">WKVS,</text>
|
||||
<text x="-26" y="2.5" fill="#94a3b8" font-size="2" font-family="monospace" letter-spacing="0">EP</text>
|
||||
<rect x="0" y="-5" width="28" rx="2" height="10" fill="url(#myLinearGradient)" />
|
||||
|
||||
<text x="10" y="1" fill="#22c55e" font-size="4" font-family="monospace" letter-spacing="0.5">START</text>
|
||||
|
||||
<text x="-26" y="13" fill="#94a3b8" font-size="4" font-family="monospace" letter-spacing="0.5">D 1.40</text>
|
||||
<text x="15" y="13" fill="#94a3b8" font-size="4" font-family="monospace" letter-spacing="0.5">9.40</text>
|
||||
|
||||
|
||||
|
||||
<text y="70" text-anchor="middle" fill="#94a3b8" font-size="12" font-family="monospace" letter-spacing="1.5">Anzeige</text>
|
||||
</g>
|
||||
<g transform="translate(650 100)">
|
||||
<circle r="45" fill="#1e293b" stroke="#3b82f6" stroke-width="3"/>
|
||||
<rect x="-18" y="-15" width="36" height="10" rx="2" fill="#334155"/>
|
||||
<rect x="-18" y="2" width="36" height="10" rx="2" fill="#334155"/>
|
||||
<circle cx="10" cy="-10" r="2" fill="#10b981"/>
|
||||
<circle cx="10" cy="7" r="2" fill="#10b981"/>
|
||||
<text y="70" text-anchor="middle" fill="#94a3b8" font-size="12" font-family="monospace" letter-spacing="1.5">WKVS Server</text>
|
||||
</g>
|
||||
<path fill="#f43f5e" class="opacity0gsap" opacity=".4" d="m-14.142-70.71 5.657 5.656-5.657 5.657-5.657-5.657z" transform="translate(400 100)"/>
|
||||
<path fill="#f43f5e" class="opacity0gsap" opacity=".4" d="m42.67 39.74 5.869 1.247-1.248 5.87-5.868-1.248z" transform="translate(400 100)"/>
|
||||
<path fill="#3b82f6" class="opacity0gsap" opacity=".4" d="m-50.98 28.301 8.66 5-5 8.66-8.66-5z" transform="translate(400 100)"/>
|
||||
<g filter="url(#a)" transform="translate(400 100)">
|
||||
<path id="cross"
|
||||
d="M-30,-30 L30,30 M30,-30 L-30,30"
|
||||
stroke="#f43f5e"
|
||||
stroke-width="8"
|
||||
stroke-linecap="round"
|
||||
fill="none"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="okSvg" viewBox="0 0 800 200" style="display: none">
|
||||
<defs>
|
||||
<filter id="a" x="-50%" y="-50%" width="200%" height="200%">
|
||||
<feGaussianBlur stdDeviation="8" result="blur"/>
|
||||
<feMerge>
|
||||
<feMergeNode in="blur"/>
|
||||
<feMergeNode in="SourceGraphic"/>
|
||||
</feMerge>
|
||||
</filter>
|
||||
<linearGradient id="myLinearGradient" x1="0%" y1="5%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="#22c55e00" />
|
||||
<stop offset="100%" stop-color="#22c55e90" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path d="M150 100 h500" opacity=".3" stroke="#3b82f6" stroke-width="4" stroke-dasharray="10, 15" stroke-linecap="round"/>
|
||||
<g transform="translate(150 100)">
|
||||
<circle r="45" fill="#1e293b" stroke="#3b82f6" stroke-width="3"/>
|
||||
<circle r="45" fill="#1e293b" stroke="#3b82f6" stroke-width="3"/>
|
||||
|
||||
<rect x="-30" y="-19" width="60" height="38" rx="2" fill="#334155"/>
|
||||
<rect x="-28" y="-17" width="56" height="10" rx="2" fill="#555"/>
|
||||
<rect x="-28" y="-5" width="56" height="10" rx="2" fill="#555"/>
|
||||
<rect x="-28" y="7" width="56" height="10" rx="2" fill="#555"/>
|
||||
<text x="-26" y="-11" fill="#94a3b8" font-size="4" font-family="monospace" letter-spacing="0.5">Test WKVS</text>
|
||||
|
||||
<text x="-26" y="-1" fill="#94a3b8" font-size="2.5" font-family="monospace" letter-spacing="0">WKVS,</text>
|
||||
<text x="-26" y="2.5" fill="#94a3b8" font-size="2" font-family="monospace" letter-spacing="0">EP</text>
|
||||
<rect x="0" y="-5" width="28" rx="2" height="10" fill="url(#myLinearGradient)" />
|
||||
|
||||
<text x="10" y="1" fill="#22c55e" font-size="4" font-family="monospace" letter-spacing="0.5">START</text>
|
||||
|
||||
<text x="-26" y="13" fill="#94a3b8" font-size="4" font-family="monospace" letter-spacing="0.5">D 1.40</text>
|
||||
<text x="15" y="13" fill="#94a3b8" font-size="4" font-family="monospace" letter-spacing="0.5">9.40</text>
|
||||
|
||||
|
||||
|
||||
<text y="70" text-anchor="middle" fill="#94a3b8" font-size="12" font-family="monospace" letter-spacing="1.5">Anzeige</text>
|
||||
</g>
|
||||
<g transform="translate(650 100)">
|
||||
<circle r="45" fill="#1e293b" stroke="#3b82f6" stroke-width="3"/>
|
||||
<rect x="-18" y="-15" width="36" height="10" rx="2" fill="#334155"/>
|
||||
<rect x="-18" y="2" width="36" height="10" rx="2" fill="#334155"/>
|
||||
<circle cx="10" cy="-10" r="2" fill="#10b981"/>
|
||||
<circle cx="10" cy="7" r="2" fill="#10b981"/>
|
||||
<text y="70" text-anchor="middle" fill="#94a3b8" font-size="12" font-family="monospace" letter-spacing="1.5">WKVS Server</text>
|
||||
</g>
|
||||
|
||||
<g filter="url(#a)" transform="translate(400 100)">
|
||||
<path d="m-15-60 15 30 m30-60 -30 60" stroke="#0e0" stroke-width="8" stroke-linecap="round"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
||||
<div class="divErrorTextWs">
|
||||
<p class="errortext">Keine WebSocket Verbindung</p>
|
||||
<p class="errortextSmall">Versuche Verbindung... (Versuch <span id="counterTries"></span> / <span id="counterMaxTries"></span>)</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="noWsConnection">
|
||||
<div class="rotator">
|
||||
<p>Keine WebSocket Verbindung, Syncronisation via FETCH</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="logobg">
|
||||
<img id="jsImgLogo" class="logoimg <?= (!$displayCTextDisplayLogo) ? 'cTextHidden' : '' ?>" src="/intern/img/logo-normal.png">
|
||||
<p class="logotext <?= (!$displayCTextDisplayWKName) ? 'cTextHidden' : '' ?>"><?= $wkName ?></p>
|
||||
<p class="logoctext"></p>
|
||||
</div>
|
||||
|
||||
<div class="pagediv">
|
||||
<div class="display-row row1">
|
||||
<p class="row1text"></p>
|
||||
</div>
|
||||
<div class="display-row row2">
|
||||
<div class="row2text">
|
||||
<p class="row2_1text sds"></p>
|
||||
<p class="row2_2text"></p>
|
||||
</div>
|
||||
<div class="start_div">
|
||||
<p class="start_text"></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="display-row row3">
|
||||
<p class="row3_1text"></p>
|
||||
<p class="row3_2text"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
const userAccess = "<?php echo $lastSegment; ?>_display";
|
||||
const jsonUrl = "<?php echo $jsonUrl; ?>";
|
||||
const jsonUrlconfig = "<?php echo $jsonUrlconfig; ?>";
|
||||
|
||||
// --- State Management ---
|
||||
let ws;
|
||||
let filedConectCount = 1;
|
||||
const fallbackConectCount = 5;
|
||||
const RETRY_DELAY = 3000;
|
||||
const FALLBACK_POLL_INTERVAL = 3000; // Fetch every 3 seconds if WS is dead
|
||||
const LONG_TERM_RETRY_DELAY_MULTIPLIER = 5;
|
||||
|
||||
let morphOnConnect = false;
|
||||
let morphOk = true;
|
||||
|
||||
const WSaccesstype = "<?= $WSaccesstype ?>";
|
||||
const WSaccess = "<?= $WSaccess ?>";
|
||||
const csrf_token = "<?= $csrf_token ?>";
|
||||
const WSaccessPermission = "<?= $WSaccessPermission ?>";
|
||||
let firstConnect = true;
|
||||
let longTermConnection = false;
|
||||
const urlAjaxNewWSToken = '/intern/scripts/ajax-create-ws-token.php';
|
||||
|
||||
let fallbackTimer = null;
|
||||
|
||||
// Hold our current data in memory
|
||||
let displayConfig = { type: 'logo', ctext: '' };
|
||||
let currentScore = {};
|
||||
let lastUniqueId = null;
|
||||
|
||||
const logobg = document.querySelector('.logobg');
|
||||
const ctext = document.querySelector('.logoctext');
|
||||
|
||||
// UI Elements
|
||||
let counterTriesEl = document.getElementById('counterTries');
|
||||
let counterMaxTriesEl = document.getElementById('counterMaxTries');
|
||||
|
||||
if(counterTriesEl) counterTriesEl.innerHTML = filedConectCount;
|
||||
if(counterMaxTriesEl) counterMaxTriesEl.innerHTML = fallbackConectCount;
|
||||
|
||||
// --- Fullscreen Listener ---
|
||||
const fs = document.documentElement;
|
||||
document.body.addEventListener('click', () => {
|
||||
if (fs.requestFullscreen) fs.requestFullscreen();
|
||||
else if (fs.webkitRequestFullscreen) fs.webkitRequestFullscreen();
|
||||
else if (fs.mozRequestFullScreen) fs.mozRequestFullScreen();
|
||||
else if (fs.msRequestFullscreen) fs.msRequestFullscreen();
|
||||
});
|
||||
|
||||
async function fetchNewWSToken(accesstype, access, accessPermission) {
|
||||
try {
|
||||
const response = await fetch(urlAjaxNewWSToken, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
csrf_token,
|
||||
accesstype,
|
||||
access,
|
||||
accessPermission
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) return null;
|
||||
|
||||
const data = await response.json();
|
||||
return data.success ? data.token : null;
|
||||
} catch (error) {
|
||||
console.error("Token fetch failed:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// --- WebSocket Logic ---
|
||||
async function startWebSocket() {
|
||||
console.log("Attempting WebSocket connection...");
|
||||
|
||||
let token;
|
||||
if (firstConnect) {
|
||||
token = '<?= generateWSReadToken($WSaccesstype, $WSaccess) ?>';
|
||||
} else {
|
||||
token = await fetchNewWSToken(WSaccesstype, WSaccess, WSaccessPermission);
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
console.error("No valid token available. Retrying...");
|
||||
scheduleRetry();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
ws = new WebSocket(`wss://${window.location.hostname}/ws/?access=token&token=${token}`);
|
||||
} catch (err) {
|
||||
console.error("Malformed WebSocket URL", err);
|
||||
scheduleRetry();
|
||||
return;
|
||||
}
|
||||
|
||||
ws.onopen = () => {
|
||||
console.log("WebSocket connected!");
|
||||
|
||||
if (morphOnConnect) {
|
||||
morph();
|
||||
} else {
|
||||
document.querySelector('.errorws').style.display = 'none';
|
||||
document.querySelector('.noWsConnection').style.display = 'none';
|
||||
document.querySelector('.pagediv').classList.remove("manuel");
|
||||
}
|
||||
|
||||
filedConectCount = 1;
|
||||
if(counterTriesEl) counterTriesEl.innerHTML = filedConectCount;
|
||||
|
||||
firstConnect = false;
|
||||
longTermConnection = false;
|
||||
morphOnConnect = false;
|
||||
morphOk = true;
|
||||
|
||||
// Stop fallback polling since WS is alive
|
||||
stopFallbackPolling();
|
||||
|
||||
// Do ONE initial fetch to sync current state
|
||||
fetchFullState();
|
||||
};
|
||||
|
||||
ws.onerror = () => {
|
||||
if (!longTermConnection) {
|
||||
document.querySelector('.errorws').style.display = 'flex';
|
||||
document.querySelector('.errorws').style.opacity = '1';
|
||||
}
|
||||
morphOk = false;
|
||||
restore();
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
if (!longTermConnection) {
|
||||
document.querySelector('.errorws').style.display = 'flex';
|
||||
document.querySelector('.errorws').style.opacity = '1';
|
||||
}
|
||||
morphOnConnect = true;
|
||||
morphOk = false;
|
||||
restore();
|
||||
scheduleRetry();
|
||||
};
|
||||
|
||||
ws.addEventListener("message", msg => {
|
||||
let msgJSON;
|
||||
try {
|
||||
msgJSON = JSON.parse(msg.data);
|
||||
} catch (error) {
|
||||
return; // Ignore malformed messages
|
||||
}
|
||||
|
||||
// Route the incoming push data
|
||||
switch (msgJSON.type) {
|
||||
case "EINSTELLUNGEN_DISPLAY_UPDATE":
|
||||
updateSettings(msgJSON.payload.key, msgJSON.payload.value);
|
||||
break;
|
||||
case "UPDATE_DISPLAYCONTROL":
|
||||
// Expecting payload: { type: 'logo'|'ctext'|'scoring', ctext: '...' }
|
||||
displayConfig = msgJSON.payload;
|
||||
renderDOM();
|
||||
break;
|
||||
case "UPDATE_SCORE":
|
||||
// Expecting payload: the exact same structure as your jsonUrl outputs
|
||||
currentScore = msgJSON.payload;
|
||||
renderDOM();
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function scheduleRetry() {
|
||||
|
||||
if (longTermConnection) {
|
||||
setTimeout(startWebSocket, RETRY_DELAY * LONG_TERM_RETRY_DELAY_MULTIPLIER);
|
||||
} else {
|
||||
console.log(`Retrying in ${RETRY_DELAY}ms...`);
|
||||
|
||||
if (filedConectCount >= fallbackConectCount) {
|
||||
// MAX RETRIES REACHED -> Enter Fallback Mode
|
||||
longTermConnection = true;
|
||||
document.querySelector('.errorws').style.display = 'none';
|
||||
document.querySelector('.noWsConnection').style.display = 'flex';
|
||||
document.querySelector('.pagediv').classList.add("manuel");
|
||||
startFallbackPolling();
|
||||
} else {
|
||||
|
||||
if(counterTriesEl) counterTriesEl.innerHTML = filedConectCount;
|
||||
setTimeout(startWebSocket, RETRY_DELAY);
|
||||
filedConectCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Fallback Polling Logic ---
|
||||
function startFallbackPolling() {
|
||||
if (fallbackTimer === null) {
|
||||
console.warn("Starting JSON fallback polling...");
|
||||
fetchFullState(); // Fetch immediately once
|
||||
fallbackTimer = setInterval(fetchFullState, FALLBACK_POLL_INTERVAL);
|
||||
}
|
||||
}
|
||||
|
||||
function stopFallbackPolling() {
|
||||
if (fallbackTimer !== null) {
|
||||
clearInterval(fallbackTimer);
|
||||
fallbackTimer = null;
|
||||
console.log("Stopped JSON fallback polling.");
|
||||
}
|
||||
}
|
||||
|
||||
// --- Data Fetching (Initial Sync & Fallback) ---
|
||||
async function fetchFullState() {
|
||||
try {
|
||||
// Fetch both config and score simultaneously
|
||||
const [resConfig, resScore] = await Promise.all([
|
||||
fetch(jsonUrlconfig + '?t=' + Date.now(), { cache: "no-store" }),
|
||||
fetch(jsonUrl + '?t=' + Date.now(), { cache: "no-store" })
|
||||
]);
|
||||
|
||||
if (resConfig.ok) displayConfig = await resConfig.json();
|
||||
if (resScore.ok) currentScore = await resScore.json();
|
||||
|
||||
renderDOM();
|
||||
} catch (err) {
|
||||
console.error("Error fetching JSON:", err);
|
||||
const container = document.getElementById('score');
|
||||
if(container) {
|
||||
container.innerHTML = "";
|
||||
container.style.backgroundColor = "black";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- The Master Renderer ---
|
||||
function renderDOM() {
|
||||
|
||||
if (displayConfig.type === 'logo' || displayConfig.type === 'ctext') {
|
||||
// LOGO OR CUSTOM TEXT MODE
|
||||
if (logobg) {
|
||||
logobg.style.opacity = "1";
|
||||
|
||||
if (displayConfig.type === 'logo') logobg.classList.remove('ctext');
|
||||
if (displayConfig.type === 'ctext') logobg.classList.add('ctext');
|
||||
}
|
||||
if (ctext) {
|
||||
ctext.innerText = (displayConfig.type === 'ctext') ? (displayConfig.ctext || '') : '';
|
||||
resizeCText();
|
||||
}
|
||||
|
||||
} else if (displayConfig.type === 'scoring') {
|
||||
// SCORING MODE
|
||||
if (logobg) logobg.style.opacity = "0";
|
||||
|
||||
if (currentScore.uniqueid !== lastUniqueId) {
|
||||
lastUniqueId = currentScore.uniqueid;
|
||||
// Reset any animation/repeat logic here if needed
|
||||
}
|
||||
|
||||
const safeText = (selector, text) => {
|
||||
const el = document.querySelector(selector);
|
||||
if (el) el.innerText = text !== undefined ? text : '';
|
||||
};
|
||||
|
||||
safeText('.row1text', `${currentScore.vorname || ''} ${currentScore.name || ''}`);
|
||||
safeText('.row2_1text', currentScore.verein ? `${currentScore.verein}, ` : '');
|
||||
safeText('.row2_2text', currentScore.programm || '');
|
||||
|
||||
const starttext = document.querySelector('.start_text');
|
||||
const row2El = document.querySelector('.row2');
|
||||
if (starttext && row2El) {
|
||||
const rootStyles = getComputedStyle(document.documentElement);
|
||||
const dangerColor = rootStyles.getPropertyValue('--display-danger').trim();
|
||||
const successColor = rootStyles.getPropertyValue('--display-success').trim();
|
||||
|
||||
if (currentScore.start === true) {
|
||||
row2El.style.setProperty('--display-colorStartDiv', successColor);
|
||||
starttext.innerHTML = 'Start';
|
||||
} else {
|
||||
row2El.style.setProperty('--display-colorStartDiv', dangerColor);
|
||||
starttext.innerHTML = 'Stop';
|
||||
}
|
||||
}
|
||||
|
||||
safeText('.row3_1text', currentScore.noteLinks);
|
||||
safeText('.row3_2text', currentScore.noteRechts);
|
||||
}
|
||||
|
||||
fitTextAll();
|
||||
}
|
||||
|
||||
// --- Settings & UI Handlers ---
|
||||
function updateSettings(type, value) {
|
||||
|
||||
const sanitizeHex = (val) => val.replace(/[^0-9a-fA-F#]/g, '');
|
||||
|
||||
switch (type) {
|
||||
case 'wkName':
|
||||
const logotext = document.querySelector('.logotext');
|
||||
if (logotext) logotext.innerHTML = value;
|
||||
break;
|
||||
case 'displayColourLogo':
|
||||
document.documentElement.style.setProperty('--display-panelBgLogo', sanitizeHex(value));
|
||||
break;
|
||||
case 'displayTextColourLogo':
|
||||
document.documentElement.style.setProperty('--display-text-color', sanitizeHex(value));
|
||||
break;
|
||||
case 'displayCTextLogo':
|
||||
document.querySelector('.logoimg').classList.toggle('cTextHidden');
|
||||
break;
|
||||
case 'displayCTextWKName':
|
||||
document.querySelector('.logotext').classList.toggle('cTextHidden');
|
||||
break;
|
||||
case 'displayColorScoringBg':
|
||||
document.documentElement.style.setProperty('--display-bg', sanitizeHex(value));
|
||||
break;
|
||||
case 'displayColorScoringBgSoft':
|
||||
document.documentElement.style.setProperty('--display-bg-soft', sanitizeHex(value));
|
||||
break;
|
||||
case 'displayColorScoringShadowColor':
|
||||
document.documentElement.style.setProperty('--display-shadow', sanitizeHex(value));
|
||||
break;
|
||||
case 'displayColorScoringBorderColor':
|
||||
document.documentElement.style.setProperty('--display-border', sanitizeHex(value));
|
||||
break;
|
||||
case 'displayColorScoringPanel':
|
||||
document.documentElement.style.setProperty('--display-panel', sanitizeHex(value));
|
||||
break;
|
||||
case 'displayColorScoringPanelSoft':
|
||||
document.documentElement.style.setProperty('--display-panel-soft', sanitizeHex(value));
|
||||
break;
|
||||
case 'displayColorScoringPanelText':
|
||||
document.documentElement.style.setProperty('--display-text-main', sanitizeHex(value));
|
||||
break;
|
||||
case 'displayColorScoringPanelTextSoft':
|
||||
document.documentElement.style.setProperty('--display-text-muted', sanitizeHex(value));
|
||||
break;
|
||||
case 'displayColorScoringPanelTextNoteL': // Matching your PHP typo
|
||||
document.documentElement.style.setProperty('--display-color-note-l', sanitizeHex(value));
|
||||
break;
|
||||
case 'displayColorScoringPanelTextNoteR': // Matching your PHP typo
|
||||
document.documentElement.style.setProperty('--display-color-note-r', sanitizeHex(value));
|
||||
break;
|
||||
case 'logo-normal':
|
||||
const jsImgLogo = document.getElementById('jsImgLogo');
|
||||
if(jsImgLogo) jsImgLogo.src = '/intern/img/logo-normal.png?' + Date.now();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Text Resizing Engine ---
|
||||
function isOverflown(parent, elem, heightscale, widthscale, paddingtext) {
|
||||
return (
|
||||
(elem.scrollWidth + paddingtext) > (parent.clientWidth / widthscale) ||
|
||||
(elem.scrollHeight + paddingtext) > (parent.clientHeight / heightscale)
|
||||
);
|
||||
}
|
||||
|
||||
function fitTextElement(elem, { minSize = 10, maxSize = 1000, step = 1, unit = 'px' } = {}) {
|
||||
if (!elem) return;
|
||||
const parent = elem.parentElement;
|
||||
if (!parent) return;
|
||||
|
||||
// FIXED: Declare variables properly so they don't leak into the global scope
|
||||
let heightscale = 1;
|
||||
let widthscale = 1;
|
||||
let paddingtext = 60;
|
||||
|
||||
if (parent.classList.contains('row2text')) {
|
||||
heightscale = 2;
|
||||
paddingtext = 0;
|
||||
}
|
||||
if (parent.classList.contains('row3')) {
|
||||
widthscale = 2;
|
||||
}
|
||||
|
||||
let size = minSize;
|
||||
elem.style.whiteSpace = 'nowrap';
|
||||
elem.style.fontSize = size + unit;
|
||||
|
||||
while (size < maxSize && !isOverflown(parent, elem, heightscale, widthscale, paddingtext)) {
|
||||
size += step;
|
||||
elem.style.fontSize = size + unit;
|
||||
}
|
||||
|
||||
elem.style.fontSize = (size - step) + unit;
|
||||
}
|
||||
|
||||
function resizeCText() {
|
||||
ctext.style.fontSize = '';
|
||||
if (isOverflown(logobg, ctext, 1, 1, 0)) fitTextElement(ctext);
|
||||
}
|
||||
|
||||
function fitTextAll() {
|
||||
const paragraphs = document.querySelectorAll('.pagediv p');
|
||||
paragraphs.forEach(p => fitTextElement(p));
|
||||
resizeCText();
|
||||
}
|
||||
|
||||
window.addEventListener('resize', fitTextAll);
|
||||
|
||||
// --- Initialize ---
|
||||
startWebSocket();
|
||||
|
||||
async function morph() {
|
||||
gsap.to("#cross", {
|
||||
duration: 1,
|
||||
attr: {
|
||||
d: "M-15,-60 L0,-30 M30,-90 L0,-30"
|
||||
},
|
||||
stroke: "#0e0",
|
||||
ease: "power2.inOut"
|
||||
});
|
||||
|
||||
gsap.to(".opacity0gsap", {
|
||||
duration: 1,
|
||||
opacity: "0",
|
||||
ease: "back.out(1.7)"
|
||||
});
|
||||
|
||||
const pathStart = "M150 100 h200 m100 0 h200";
|
||||
|
||||
const pathEnd = "M150 100 h250 m0 0 h250";
|
||||
|
||||
gsap.to("#errorLine", {
|
||||
duration: 1,
|
||||
attr: { d: pathEnd },
|
||||
ease: "power2.inOut"
|
||||
});
|
||||
|
||||
gsap.to(".errortext", {
|
||||
duration: 1,
|
||||
textContent: "Verbindung Wiederhergestellt",
|
||||
ease: "back.out(1.7)"
|
||||
});
|
||||
|
||||
gsap.to(".errortextSmall", {
|
||||
duration: 1,
|
||||
textContent: "Stelle auf Wettkampfbetrieb um...",
|
||||
ease: "back.out(1.7)"
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
if (morphOk) {
|
||||
gsap.to('.errorws', {
|
||||
opacity: 0,
|
||||
duration: 0.5,
|
||||
overwrite: "auto"
|
||||
});
|
||||
}
|
||||
}, 3000);
|
||||
|
||||
setTimeout(() => {
|
||||
if (morphOk) {
|
||||
document.querySelector('.errorws').style.display = 'none';
|
||||
}
|
||||
}, 3800);
|
||||
}
|
||||
|
||||
function restore() {
|
||||
// 1. Reset the Cross/Check path and color
|
||||
gsap.set("#cross", {
|
||||
attr: { d: "M-30,-30 L30,30 M30,-30 L-30,30" },
|
||||
stroke: "#f43f5e"
|
||||
});
|
||||
|
||||
// 2. Reset opacity for elements with that class
|
||||
gsap.set(".opacity0gsap", {
|
||||
opacity: ".4"
|
||||
});
|
||||
|
||||
// 3. Reset the Error Line path
|
||||
gsap.set("#errorLine", {
|
||||
attr: { d: "M150 100 h200 m100 0 h200" }
|
||||
});
|
||||
|
||||
gsap.to(".errortext", {
|
||||
duration: 1,
|
||||
textContent: "Keine WebSocket Verbindung",
|
||||
ease: "back.out(1.7)"
|
||||
});
|
||||
|
||||
document.querySelector(".errortextSmall").innerHTML = 'Versuche Verbindung... (Versuch <span id="counterTries"></span> / <span id="counterMaxTries"></span>)';
|
||||
|
||||
counterTriesEl = document.getElementById('counterTries');
|
||||
counterMaxTriesEl = document.getElementById('counterMaxTries');
|
||||
|
||||
if(counterTriesEl) counterTriesEl.innerHTML = filedConectCount;
|
||||
if(counterMaxTriesEl) counterMaxTriesEl.innerHTML = fallbackConectCount;
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
$request = trim(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), '/');
|
||||
$segments = $request === '' ? [] : explode('/', $request);
|
||||
|
||||
$segments = array_map('urldecode', $segments);
|
||||
|
||||
$type = $segments[1] ?? null;
|
||||
$slug = $segments[2] ?? null;
|
||||
|
||||
$allowed_types = ["display", "audioplayer"];
|
||||
|
||||
if (($type !== null && !in_array($type, $allowed_types)) || count($segments) > 3) {
|
||||
http_response_code(400);
|
||||
include $baseDir . "/error-pages/400.html";
|
||||
exit;
|
||||
}
|
||||
|
||||
define('APP_ROUTER', true);
|
||||
|
||||
if ($type === "display") {
|
||||
require __DIR__ . "/display.php";
|
||||
} elseif ($type === "audioplayer") {
|
||||
require __DIR__ . "/audio.php";
|
||||
}
|
||||
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 200 KiB |
@@ -76,4 +76,131 @@
|
||||
|
||||
.confirmNoButton:hover {
|
||||
background-color: rgba(255, 0, 0, 1);
|
||||
}
|
||||
|
||||
.confirmImportantHeading {
|
||||
font-size: 22px;
|
||||
margin: 0;
|
||||
letter-spacing: 1px;
|
||||
color: #36454F;
|
||||
}
|
||||
|
||||
.confirmImportantText {
|
||||
font-size: 16px;
|
||||
margin: 0;
|
||||
color: #36454F;
|
||||
}
|
||||
|
||||
.confirmImportantLabel {
|
||||
font-size: 12px;
|
||||
margin: 0;
|
||||
color: #36454F;
|
||||
}
|
||||
|
||||
.confirmImportantInput {
|
||||
border: 1px solid #36454F;
|
||||
padding: 12px;
|
||||
width: 100%;
|
||||
border-radius: 12px;
|
||||
color: #36454F;
|
||||
}
|
||||
|
||||
.confirmImportantInput.notOk {
|
||||
color: #692727;
|
||||
}
|
||||
|
||||
.confirmImportantInput.ok {
|
||||
color: #2b6927;
|
||||
}
|
||||
|
||||
.confirmImportantInput:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.confirmImportantInput.notOk:focus {
|
||||
color: #8b0000;
|
||||
border-color: #692727;
|
||||
}
|
||||
|
||||
.confirmImportantInput.ok:focus {
|
||||
color: #008b00;
|
||||
border-color: #2b6927;
|
||||
}
|
||||
|
||||
|
||||
.confirmImportantInputDiv {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.confirmImportantConfrimBtn, .confirmImportantCancelBtn {
|
||||
background: #25789e;
|
||||
border: 1px solid #fff;
|
||||
color: #ffffff;
|
||||
padding: 10px 20px;
|
||||
border-radius: 10px;
|
||||
transition: transform 0.3s ease filter 0.5s ease;
|
||||
letter-spacing: 1px;
|
||||
display: flex;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.confirmImportantConfrimBtn {
|
||||
background: #25789e;
|
||||
}
|
||||
|
||||
.confirmImportantCancelBtn {
|
||||
background: #9e253d;
|
||||
}
|
||||
|
||||
.blackBtn:hover {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.blackBtn:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.notValidBtn {
|
||||
user-select: none;
|
||||
cursor: not-allowed;
|
||||
filter: grayscale(1);
|
||||
}
|
||||
|
||||
.confirmImportantBtnDiv {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.confirmImportantTextDiv {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.confirmImportantBox {
|
||||
display: grid;
|
||||
padding: 20px;
|
||||
grid-template-columns: 1fr;
|
||||
position: relative;
|
||||
border-radius: 10px;
|
||||
background-color: #fff;
|
||||
width: min(500px, 90vw);
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.confirmImportantBg {
|
||||
display: flex;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
z-index: 10000;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
backdrop-filter: blur(2px);
|
||||
background-color: #36454f2f;
|
||||
}
|
||||
@@ -23,6 +23,7 @@ table input {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.selectOptions,
|
||||
@@ -86,6 +87,6 @@ table input {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.selectLabel {
|
||||
.selectLabel:not(.sidebar-links .selectLabel) {
|
||||
font-size: 1rem;
|
||||
}
|
||||
@@ -65,7 +65,8 @@ body {
|
||||
.controls-wrapper>form {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 10px
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* From Trainer Dashboard - newBtn style */
|
||||
@@ -100,7 +101,7 @@ button[type="submit"]:active {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.change-type-form input {
|
||||
.change-type-form textarea {
|
||||
padding: 10px 15px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border-subtle);
|
||||
@@ -111,7 +112,7 @@ button[type="submit"]:active {
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.change-type-form input:focus {
|
||||
.change-type-form textarea:focus {
|
||||
border-color: var(--main);
|
||||
}
|
||||
|
||||
@@ -131,6 +132,17 @@ button[type="submit"]:active {
|
||||
box-shadow: var(--card-shadow);
|
||||
}
|
||||
|
||||
@media (max-width: 500px) {
|
||||
:root {
|
||||
--paddingSite: 20px;
|
||||
}
|
||||
|
||||
.iframeWithTitle {
|
||||
max-width: calc(100vw - 2 * var(--paddingSite));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.iframeWithTitle h1 {
|
||||
color: #fff;
|
||||
font-size: 1.2rem;
|
||||
|
||||
@@ -36,7 +36,6 @@ body {
|
||||
margin: 0;
|
||||
width: 100vw;
|
||||
background: var(--bg);
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
::selection {
|
||||
@@ -190,13 +189,7 @@ input {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
#wsInfo {
|
||||
margin: 0;
|
||||
font-weight: 300;
|
||||
opacity: 0;
|
||||
transition: all 2s ease;
|
||||
}
|
||||
|
||||
/*
|
||||
td a {
|
||||
position: relative;
|
||||
color: var(--bg-top);
|
||||
@@ -219,15 +212,7 @@ td a::after {
|
||||
td a:hover::after {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
|
||||
.payed {
|
||||
color: #099d1f;
|
||||
}
|
||||
|
||||
.notPayed,
|
||||
.inProcess {
|
||||
color: #9d1d09;
|
||||
}
|
||||
*/
|
||||
|
||||
/* Kampfrichter header layout */
|
||||
|
||||
@@ -1166,6 +1151,7 @@ select:open {
|
||||
.settingsRowGroup {
|
||||
display: grid;
|
||||
gap: 18px;
|
||||
align-content: start;
|
||||
}
|
||||
span.light {
|
||||
font-weight: 300;
|
||||
@@ -1173,4 +1159,154 @@ span.light {
|
||||
|
||||
.tableNoten input {
|
||||
width: fit-content !important;
|
||||
}
|
||||
|
||||
#css-editor-container {
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.cm-editor {
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.cssCodeSaveBtn {
|
||||
padding: 6px 12px;
|
||||
background: var(--bg-top);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* From Uiverse.io by alexmaracinaru */
|
||||
.newBtn {
|
||||
cursor: pointer;
|
||||
font-weight: 300;
|
||||
transition: all 0.2s;
|
||||
padding: 10px 20px;
|
||||
border-radius: 100px;
|
||||
background: #1d6c6f;
|
||||
border: 1px solid transparent;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 15px;
|
||||
align-self: flex-start;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.newBtn:disabled {
|
||||
cursor: not-allowed;
|
||||
background: #ccc;
|
||||
}
|
||||
|
||||
.newBtn:not(:disabled):hover {
|
||||
background: #1c8284;
|
||||
}
|
||||
|
||||
.newBtn>svg {
|
||||
width: 34px;
|
||||
margin-left: 10px;
|
||||
transition: transform 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.newBtn:not(:disabled):hover svg {
|
||||
transform: translateX(5px);
|
||||
}
|
||||
|
||||
.newBtn:not(:disabled):active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
input[type="file"]#importPresetInput {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
padding: 10px;
|
||||
border: 2px dashed #cbd5e0;
|
||||
border-radius: 8px;
|
||||
background-color: #fafafa;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input[type="file"]#importPresetInput::file-selector-button {
|
||||
background: #1d6c6f;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
margin-right: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.gridWKVSConfig {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: 60px;
|
||||
}
|
||||
|
||||
.importWKVSConfigDiv {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.displayDiv.displayDivSettings {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
aspect-ratio: 16 / 11;
|
||||
}
|
||||
|
||||
.displayDivSettings * {
|
||||
transition: scale 0.6s ease;
|
||||
scale: 1;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.oneOneGridDisplay {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 450px;
|
||||
column-gap: 40px;
|
||||
}
|
||||
|
||||
@keyframes shakeHighlit {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
25% {
|
||||
transform: rotate(10deg);
|
||||
}
|
||||
75% {
|
||||
transform: rotate(-10deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
}
|
||||
|
||||
.scaled {
|
||||
scale: 1.05;
|
||||
animation-name: shakeHighlit;
|
||||
animation-duration: 0.5s;
|
||||
}
|
||||
|
||||
.subSettingsRowGroup .settingsRow span {
|
||||
color: #36454f;
|
||||
}
|
||||
|
||||
.subSettingsRowGroup {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
align-items: end;
|
||||
gap: 16px 22px;
|
||||
padding: 20px;
|
||||
border: 1px solid #000;
|
||||
}
|
||||
|
||||
.subSettingsRowGroup .settingsRow {
|
||||
|
||||
}
|
||||
@@ -11,6 +11,7 @@
|
||||
--text-muted: #5e5e5e;
|
||||
--disabled-bg: #f3f4f6;
|
||||
--disabled-border: #cbd5f5;
|
||||
--main-button: #36454F;
|
||||
}
|
||||
|
||||
/* Base */
|
||||
@@ -57,11 +58,6 @@ input {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* General numeric inputs in scoring area */
|
||||
.bgSection input[type="number"] {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.nopadding {
|
||||
height: 40px;
|
||||
}
|
||||
@@ -78,13 +74,6 @@ input {
|
||||
background: none;
|
||||
}
|
||||
|
||||
@media (min-width: 1000px) {
|
||||
.bgSection.open {
|
||||
width: calc(100vw - 380px);
|
||||
/* - 2 * var(--paddingSite) */
|
||||
}
|
||||
}
|
||||
|
||||
/* Cards */
|
||||
|
||||
.headerDivKampfrichter {
|
||||
@@ -220,15 +209,6 @@ table {
|
||||
}
|
||||
}
|
||||
|
||||
.allTurnerinenDiv tr:hover {
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
|
||||
/*.allTurnerinenDiv tr.notHeaderRow:hover td{
|
||||
-webkit-text-stroke: 0.75px currentColor;
|
||||
color: currentColor;
|
||||
}*/
|
||||
|
||||
/* Panel with inputs (new style, light) */
|
||||
|
||||
.div_edit_values_user {
|
||||
@@ -249,11 +229,15 @@ table {
|
||||
align-items: baseline;
|
||||
font-size: 1.15rem;
|
||||
letter-spacing: 0.01em;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.current-turnerin-name {
|
||||
font-weight: 600;
|
||||
color: var(--accent);
|
||||
font-weight: 700;
|
||||
color: #14141f;
|
||||
margin: 10px 0 20px 0;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.heading_fv_nextturnerin {
|
||||
@@ -289,7 +273,7 @@ table {
|
||||
.editkampfrichter_user {
|
||||
border-collapse: collapse;
|
||||
background: #f9fafb;
|
||||
border-radius: 12px;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
min-width: 180px;
|
||||
box-shadow: 0 0 0 1px rgba(209, 213, 219, 0.9);
|
||||
@@ -304,10 +288,10 @@ table {
|
||||
|
||||
.editkampfrichter_user th {
|
||||
font-size: 0.8rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.09em;
|
||||
color: var(--text-muted);
|
||||
background: #f3f4f6;
|
||||
color: #fff;
|
||||
background: #232431;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.table_endnote_edit {
|
||||
@@ -318,7 +302,7 @@ table {
|
||||
/* Tight cell for pure input */
|
||||
|
||||
.nopadding {
|
||||
padding: 0.25rem 0.5rem !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
/* Inputs inside edit card */
|
||||
@@ -326,20 +310,22 @@ table {
|
||||
.fullinput {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
/*padding: 0.45rem 0.6rem;*/
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border-subtle);
|
||||
border: 2px solid var(--border-subtle);
|
||||
background: #ffffff;
|
||||
color: var(--text-main);
|
||||
font-size: 0.9rem;
|
||||
color: #000;
|
||||
font-size: 1.2rem;
|
||||
line-height: 1.3;
|
||||
outline: none;
|
||||
transition: border-color 0.15s ease, box-shadow 0.15s ease, background-color 0.15s ease;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
border-radius: 0 0 5px 5px;
|
||||
height: 40px;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.fullinput:focus {
|
||||
border-color: var(--accent);
|
||||
border: 4px solid var(--accent);
|
||||
box-shadow: 0 0 0 1px rgba(45, 115, 172, 0.25);
|
||||
background: #ffffff;
|
||||
}
|
||||
@@ -348,11 +334,12 @@ table {
|
||||
|
||||
.fullinput:disabled,
|
||||
.fullinput[readonly] {
|
||||
background: none;
|
||||
background: #e2e2e2;
|
||||
border: none;
|
||||
color: #6b7280;
|
||||
color: #000;
|
||||
opacity: 1;
|
||||
cursor: auto;
|
||||
cursor: not-allowed;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.fullinput:disabled:hover,
|
||||
@@ -363,24 +350,6 @@ table {
|
||||
border-color: var(--disabled-border);
|
||||
}
|
||||
|
||||
/* Computed result fields even clearer */
|
||||
|
||||
/*[id^="e-note-"]:disabled {
|
||||
background: #fff7d6;
|
||||
background: repeating-linear-gradient(
|
||||
135deg,
|
||||
#ffefb0,
|
||||
#ffefb0 6px,
|
||||
#fff7d6 6px,
|
||||
#fff7d6 12px
|
||||
);
|
||||
border-color: #f59e0b;
|
||||
box-shadow: inset 0 0 0 1px rgba(245, 158, 11, 0.7);
|
||||
color: #92400e;
|
||||
font-weight: 800;
|
||||
text-align: center;
|
||||
}*/
|
||||
|
||||
[id^="note-user-"]:disabled {
|
||||
background: none;
|
||||
border: none;
|
||||
@@ -444,7 +413,7 @@ input[type="number"]::-webkit-outer-spin-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
align-self: baseline;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
/* Variants */
|
||||
@@ -482,8 +451,7 @@ input[type="number"]::-webkit-outer-spin-button {
|
||||
.submit-musik-start:hover,
|
||||
.submit-musik-stopp:hover,
|
||||
.submit-display-result:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 12px 30px rgba(15, 23, 42, 0.12);
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.submit-display-turnerin:active,
|
||||
@@ -491,8 +459,7 @@ input[type="number"]::-webkit-outer-spin-button {
|
||||
.submit-musik-start:active,
|
||||
.submit-musik-stopp:active,
|
||||
.submit-display-result:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 8px 18px rgba(15, 23, 42, 0.1);
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
/* Burger + menu (unchanged from your style) */
|
||||
@@ -523,15 +490,16 @@ input[type="number"]::-webkit-outer-spin-button {
|
||||
background-color: #fff;
|
||||
box-shadow: none;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100svh;
|
||||
right: 0;
|
||||
top: 150px;
|
||||
height: fit-content;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
transform: translateX(-100%);
|
||||
transform: translateX(100%);
|
||||
z-index: 100;
|
||||
padding: 20px;
|
||||
transition: transform 0.45s cubic-bezier(.4, 0, .2, 1);
|
||||
border-radius: 20px 0 0 20px;
|
||||
transition: transform 0.2s cubic-bezier(.4, 0, .2, 1);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@@ -544,9 +512,7 @@ input[type="number"]::-webkit-outer-spin-button {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
min-height: 100vh;
|
||||
overflow-y: auto;
|
||||
padding-bottom: 80px;
|
||||
}
|
||||
|
||||
.innerInternMenuDiv h3 {
|
||||
@@ -621,7 +587,7 @@ input[type="number"]::-webkit-outer-spin-button {
|
||||
|
||||
.button_gruppe {
|
||||
border: none;
|
||||
background: #2d73ac;
|
||||
background: var(--bg-top);
|
||||
color: white;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
@@ -633,7 +599,6 @@ input[type="number"]::-webkit-outer-spin-button {
|
||||
}
|
||||
|
||||
.button_gruppe:hover {
|
||||
background: #245d8a;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
@@ -681,28 +646,8 @@ input[type="number"]::-webkit-outer-spin-button {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.menuBg {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 98;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
/*background: rgba(0, 0, 0, 0.15);*/
|
||||
}
|
||||
|
||||
.menuBg.menuTransition {
|
||||
transition: opacity 0.6s ease-out;
|
||||
}
|
||||
|
||||
/*.menuBg.open {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
user-select: all;
|
||||
}*/
|
||||
|
||||
.menuTransition {
|
||||
transition: all 0.6s ease-out;
|
||||
transition: all 0.3s cubic-bezier(.4, 0, .2, 1);
|
||||
}
|
||||
|
||||
.menuTransition>.kampfrichterBurgerMenuLine {
|
||||
@@ -761,6 +706,7 @@ input[type="number"]::-webkit-outer-spin-button {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@media (min-width: 601px) {
|
||||
|
||||
.div_edit_values_user,
|
||||
@@ -804,30 +750,6 @@ input[type="number"]::-webkit-outer-spin-button {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.msgDiv {
|
||||
position: fixed;
|
||||
bottom: 50px;
|
||||
right: 20px;
|
||||
display: flex;
|
||||
align-items: end;
|
||||
flex-direction: column-reverse;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.msgBox {
|
||||
transform: translateX(calc(100% + 40px));
|
||||
padding: 10px 15px;
|
||||
color: #fff;
|
||||
background-color: #5b5b5b98;
|
||||
border-left: 4px solid;
|
||||
border-radius: 2px;
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
|
||||
.msgBox.show {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.playcontrolDiv {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@@ -853,7 +775,6 @@ input[type="number"]::-webkit-outer-spin-button {
|
||||
color: var(--bg-top);
|
||||
}
|
||||
|
||||
.adminButtonDiv input,
|
||||
.buttonNewAdminStyle {
|
||||
background-color: var(--bg-top);
|
||||
color: #fff;
|
||||
@@ -866,21 +787,16 @@ input[type="number"]::-webkit-outer-spin-button {
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.adminButtonDiv input {
|
||||
margin: 10px 0 0 10px;
|
||||
}
|
||||
|
||||
.adminButtonDiv input:hover {
|
||||
filter: brightness(1.2);
|
||||
}
|
||||
|
||||
.titleSingleAbt {
|
||||
font-weight: 400;
|
||||
color: var(--bg-top);
|
||||
}
|
||||
|
||||
.adminButtonDiv {
|
||||
display: block;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
table.widefat {
|
||||
@@ -928,6 +844,13 @@ table.widefat {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@media (max-width: 750px) {
|
||||
.editUserButtons {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.div-submit-display-result, .div-submit-display-start {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -937,6 +860,8 @@ table.widefat {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 3rem;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.singleNotentable {
|
||||
@@ -945,6 +870,46 @@ table.widefat {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.opacity50 {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.blackBtn {
|
||||
background: var(--main-button);
|
||||
border: 1px solid #fff;
|
||||
color: #ffffff;
|
||||
padding: 10px 20px;
|
||||
border-radius: 10px;
|
||||
transition: transform 0.3s ease;
|
||||
letter-spacing: 1px;
|
||||
display: flex;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.blackBtn:hover {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.blackBtn:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.customDisplayEditorTable tr:nth-child(odd).notpaid td {
|
||||
background-color: #fff0f0;
|
||||
}
|
||||
|
||||
.customDisplayEditorTable tr:nth-child(even).notpaid td {
|
||||
background-color: #ffe5e5;
|
||||
}
|
||||
|
||||
.customDisplayEditorTable tr.notpaid:hover td {
|
||||
background-color: #ffdbdb !important;
|
||||
}
|
||||
|
||||
select {
|
||||
appearance: none;
|
||||
/* Standard */
|
||||
@@ -961,10 +926,7 @@ select {
|
||||
border-radius: 4px;
|
||||
background-color: #fff;
|
||||
|
||||
background-image: url("data:image/svg+xml;utf8,\
|
||||
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none'>\
|
||||
<path d='M6 9L12 15L18 9' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/>\
|
||||
</svg>");
|
||||
background-image: url("data:image/svg+xml;utf8,\<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none'><path d='M6 9L12 15L18 9' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/>\</svg>");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 10px center;
|
||||
background-size: 20px;
|
||||
@@ -973,8 +935,10 @@ select {
|
||||
}
|
||||
|
||||
select:open {
|
||||
background-image: url("data:image/svg+xml;utf8,\
|
||||
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none'>\
|
||||
<path d='M18 15L12 9L6 15' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/>\
|
||||
</svg>");
|
||||
background-image: url("data:image/svg+xml;utf8,\<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none'>\<path d='M18 15L12 9L6 15' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/>\</svg>");
|
||||
}
|
||||
|
||||
.horizontalLabelWrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
body{
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.page-secure-login{
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.bg-secure-login-form {
|
||||
gap: 20px;
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.bg-picture-secure-login{
|
||||
width: calc(100vw - 550px);
|
||||
height: 100vh;
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
background-color: #fff;
|
||||
}
|
||||
.bg-picture-secure-login img{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 0 80px 0 0;
|
||||
display: block;
|
||||
}
|
||||
.bg-secure-login{
|
||||
display: flex;
|
||||
width: 100vw;
|
||||
max-width: 550px;
|
||||
height: 100vh;
|
||||
background-color: #fff;
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
top: 0px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 30px;
|
||||
}
|
||||
.bg-secure-login-form > h1{
|
||||
color: #36454F !important;
|
||||
font-size: 32px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
.bg-secure-login-form input[type=password], .bg-secure-login-form input[type=text]{
|
||||
padding: 5px;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
border-top: none !important;
|
||||
border-left: none !important;
|
||||
border-right: none !important;
|
||||
font-size: 16px;
|
||||
border-bottom: 1px solid #000 !important;
|
||||
border-radius: 0px !important;
|
||||
}
|
||||
#access_username {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.bg-secure-login-form input[type=password]:focus, .bg-secure-login-form input[type=text]:focus{
|
||||
outline: none;
|
||||
border-bottom: 1px solid #000 !important;
|
||||
}
|
||||
|
||||
.bg-secure-login-form input[type=password]::placeholder, .bg-secure-login-form input[type=text]::placeholder {
|
||||
color: #ccc !important;
|
||||
}
|
||||
|
||||
.bg-secure-login-form input[type=submit]{
|
||||
background-color: #fff !important;
|
||||
padding: 10px 20px !important;
|
||||
margin-top: 25px !important;
|
||||
border: 1px solid #000 !important;
|
||||
color: #36454F !important;
|
||||
transition: all 0.3s ease-out !important;
|
||||
border-radius: 0px !important;
|
||||
font-weight: 500;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.bg-secure-login-form input[type=submit]:hover{
|
||||
background-color: #36454F !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
.bg-secure-login-form > p{
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.seclog_home_link{
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
top: 30px;
|
||||
right: 30px;
|
||||
height: 48px;
|
||||
width: 48px;
|
||||
}
|
||||
|
||||
.seclog_home_link > img{
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
}
|
||||
#div_showpw, #access_username {
|
||||
margin-top: 10px;
|
||||
}
|
||||
#togglePassword {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
|
||||
#togglePassword:hover {
|
||||
transform: translateY(-50%) scale(1.15);
|
||||
}
|
||||
|
||||
#div_showpw{
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
label {
|
||||
color: #36454F !important;
|
||||
font-weight: 300;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
input:-webkit-autofill,
|
||||
input:-webkit-autofill:hover,
|
||||
input:-webkit-autofill:focus,
|
||||
input:-webkit-autofill:active {
|
||||
-webkit-box-shadow: 0 0 0 1000px #ffffff inset !important;
|
||||
box-shadow: 0 0 0 1000px #ffffff inset !important;
|
||||
-webkit-text-fill-color: #000000 !important;
|
||||
transition: background-color 5000s ease-in-out 0s;
|
||||
}
|
||||
@@ -9,10 +9,11 @@
|
||||
--accent: #4a5568;
|
||||
--accent-soft: #f0f5ff;
|
||||
--border-subtle: #d4d7e1;
|
||||
--text-main: #191919;
|
||||
--text-main: #36454F;
|
||||
--text-muted: #5e5e5e;
|
||||
--disabled-bg: #f3f4f6;
|
||||
--disabled-border: #cbd5f5;
|
||||
--main-button: #36454F;
|
||||
}
|
||||
|
||||
/* ── Base ─────────────────────────────────────────────── */
|
||||
@@ -71,6 +72,7 @@ input {
|
||||
width: 100vw;
|
||||
box-sizing: border-box !important;
|
||||
background: none;
|
||||
padding: 0 var(--paddingSite) 10px
|
||||
}
|
||||
|
||||
@media (min-width: 1000px) {
|
||||
@@ -112,7 +114,6 @@ input {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
padding: 0 var(--paddingSite) 10px;
|
||||
}
|
||||
|
||||
.bg-erfassen button[type="submit"] {
|
||||
@@ -139,7 +140,6 @@ input {
|
||||
|
||||
h3.vereine,
|
||||
h3.benutzer {
|
||||
padding: 0 var(--paddingSite);
|
||||
font-weight: 400;
|
||||
font-size: 1.15rem;
|
||||
color: var(--text-main);
|
||||
@@ -149,8 +149,7 @@ h3.benutzer {
|
||||
|
||||
/* ── Inner section (card list) ────────────────────────── */
|
||||
|
||||
.inner-pw-set-bg {
|
||||
padding: 0 var(--paddingSite);
|
||||
.benutzerGrid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
@@ -161,53 +160,62 @@ h3.benutzer {
|
||||
/* ── User / Verein card ───────────────────────────────── */
|
||||
|
||||
.single_pwedit,
|
||||
.newUserLink {
|
||||
.newUserLinkDiv {
|
||||
background: var(--card-bg);
|
||||
border-radius: var(--card-radius);
|
||||
box-shadow: var(--card-shadow);
|
||||
padding: 24px 28px;
|
||||
transition: box-shadow 0.25s ease;
|
||||
}
|
||||
|
||||
.single_pwedit:hover,
|
||||
.newUserLink:hover {
|
||||
.newUserLinkDiv:hover {
|
||||
box-shadow: 0 14px 40px rgba(15, 23, 42, 0.12);
|
||||
}
|
||||
|
||||
.single_pwedit.verein,
|
||||
.newUserLink.verein {
|
||||
.newUserLinkDiv.verein {
|
||||
border-left: 4px solid var(--bg-top);
|
||||
}
|
||||
|
||||
/* ── Form fields inside user card ─────────────────────── */
|
||||
|
||||
.single_pwedit form,
|
||||
.newUserLink form {
|
||||
.newUserLinkDiv form {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 14px 24px;
|
||||
align-items: flex-end;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.single_pwedit form {
|
||||
padding: 24px 28px 14px 28px;
|
||||
}
|
||||
|
||||
.newUserLinkDiv {
|
||||
padding: 24px 28px;
|
||||
}
|
||||
|
||||
.single_pwedit label,
|
||||
.newUserLink label {
|
||||
.newUserLinkDiv label {
|
||||
display: block;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--text-muted);
|
||||
letter-spacing: 1.5px;
|
||||
color: var(--text-main);
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.single_pwedit .field-group,
|
||||
.newUserLink .field-group {
|
||||
.newUserLinkDiv .field-group {
|
||||
flex: 1 1 200px;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.single_pwedit input[type="text"],
|
||||
.newUserLink input[type="text"] {
|
||||
.newUserLinkDiv input[type="text"] {
|
||||
width: 100%;
|
||||
padding: 9px 12px;
|
||||
border: 1px dashed #999;
|
||||
@@ -220,21 +228,21 @@ h3.benutzer {
|
||||
}
|
||||
|
||||
.single_pwedit input[type="text"]:focus,
|
||||
.newUserLink input[type="text"]:focus {
|
||||
.newUserLinkDiv input[type="text"]:focus {
|
||||
outline: none;
|
||||
border: 1px solid var(--bg-top);
|
||||
box-shadow: 0 0 0 3px rgba(var(--bg-top-raw) / 0.12);
|
||||
}
|
||||
|
||||
.single_pwedit input[type="text"]::placeholder,
|
||||
.newUserLink input[type="text"]::placeholder {
|
||||
.newUserLinkDiv input[type="text"]::placeholder {
|
||||
color: #b0b0b0;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* ── Buttons inside cards ─────────────────────────────── */
|
||||
|
||||
.single_pwedit button[type="submit"],
|
||||
/*.single_pwedit button[type="submit"],
|
||||
.newUserLink button[type="submit"] {
|
||||
appearance: none;
|
||||
border: 1px solid #7777778e;
|
||||
@@ -255,7 +263,7 @@ h3.benutzer {
|
||||
border-color: var(--bg-top);
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
}*/
|
||||
|
||||
.delete-user-btn,
|
||||
.delete-verein-btn {
|
||||
@@ -268,7 +276,6 @@ h3.benutzer {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
margin-top: 10px;
|
||||
transition: all 0.25s ease;
|
||||
}
|
||||
|
||||
@@ -289,9 +296,30 @@ h3.benutzer {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
margin-top: 10px;
|
||||
margin-left: 8px;
|
||||
transition: all 0.25s ease;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.userActionsDivLink {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px 10px;
|
||||
}
|
||||
|
||||
.userActionsDivLink:not(.mty) {
|
||||
padding: 14px 28px 24px 28px;
|
||||
border-top: 1px solid var(--main-button);
|
||||
}
|
||||
|
||||
.userActionsDivLink.mty {
|
||||
padding: 0 28px 10px 28px;
|
||||
}
|
||||
|
||||
.userActionsDivEdit {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.createOturl:hover {
|
||||
@@ -311,11 +339,12 @@ h3.benutzer {
|
||||
justify-content: center;
|
||||
background-color: #39393941;
|
||||
backdrop-filter: blur(2px);
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.newUserLink {
|
||||
max-width: 600px;
|
||||
max-height: 500px;
|
||||
.newUserLinkDiv {
|
||||
max-width: min(600px, 80vw);
|
||||
max-height: min(500px, 80vh);
|
||||
overflow-y: auto;
|
||||
z-index: 9999;
|
||||
}
|
||||
@@ -356,7 +385,7 @@ h3.benutzer {
|
||||
color: var(--text-main);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.perm-section-title .perm-badge {
|
||||
@@ -370,9 +399,17 @@ h3.benutzer {
|
||||
background: var(--bg-top);
|
||||
color: #fff;
|
||||
font-size: 0.7rem;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.permBadgeCount {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.permBadgeText {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.perm-section-chevron {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
@@ -386,14 +423,14 @@ h3.benutzer {
|
||||
|
||||
.perm-section-body {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
overflow: hidden auto;
|
||||
transition: max-height 0.35s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
padding 0.35s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
padding 0.35s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.perm-section.open .perm-section-body {
|
||||
max-height: 500px;
|
||||
max-height: 300px;
|
||||
padding: 12px 16px 16px;
|
||||
}
|
||||
|
||||
@@ -509,19 +546,36 @@ table {
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
|
||||
.inputPreisProgramm,
|
||||
.inputAktivProgramm {
|
||||
.inputAktivProgramm,
|
||||
.inputOrderIndexProgramm,
|
||||
.labelStartgebuerenWraper {
|
||||
padding: 6px 10px;
|
||||
border: 1px dashed #999;
|
||||
border-radius: 4px;
|
||||
font-size: 0.9rem;
|
||||
transition: border-color 0.2s ease, background-color 0.4s ease;
|
||||
}
|
||||
|
||||
.inputPreisProgramm:focus,
|
||||
.inputAktivProgramm:focus {
|
||||
.inputPreisProgramm,
|
||||
.inputAktivProgramm,
|
||||
.inputOrderIndexProgramm {
|
||||
max-width: 100px;
|
||||
font-size: 0.9rem;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.inputPreisProgramm {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.inputPreisProgramm:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.inputAktivProgramm:focus,
|
||||
.inputOrderIndexProgramm:focus {
|
||||
outline: none;
|
||||
border-color: var(--bg-top);
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
input[type="number"] {
|
||||
@@ -534,13 +588,6 @@ input[type="number"]::-webkit-outer-spin-button {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.deleteProgramm {
|
||||
appearance: none;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
select {
|
||||
appearance: none;
|
||||
@@ -562,7 +609,7 @@ select {
|
||||
/* ── One-time URL Modal ───────────────────────────────── */
|
||||
|
||||
.ot-modal {
|
||||
display: none;
|
||||
display: flex;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
@@ -888,9 +935,9 @@ select {
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.inner-pw-set-bg {
|
||||
.benutzerGrid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(420px, 1fr));
|
||||
grid-template-columns: repeat(auto-fill, minmax(480px, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -959,7 +1006,7 @@ select {
|
||||
}
|
||||
|
||||
.containerSection {
|
||||
padding: var(--paddingSite) calc(var(--paddingSite) * 1.5);
|
||||
padding: var(--paddingSite) calc(var(--paddingSite) * .5);
|
||||
margin-bottom: var(--paddingSite);
|
||||
}
|
||||
|
||||
@@ -982,4 +1029,58 @@ select {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 30px 0;
|
||||
}
|
||||
|
||||
.containerTrainerKampfrichterPermissions {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
width: 100%;
|
||||
gap: 16px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.inputPasswordUser {
|
||||
color: #fafafa !important;
|
||||
background-color:#fafafa !important;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.inputPasswordUser:hover, .inputPasswordUser:focus {
|
||||
color: #000000 !important;
|
||||
user-select: auto;
|
||||
}
|
||||
|
||||
.divAblaufdatumLink {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.blackBtn {
|
||||
background: var(--main-button);
|
||||
border: 1px solid #fff;
|
||||
color: #ffffff;
|
||||
padding: 10px 20px;
|
||||
border-radius: 10px;
|
||||
transition: transform 0.3s ease;
|
||||
letter-spacing: 1px;
|
||||
display: flex;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.blackBtn:hover {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.blackBtn:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
h3.normalTitle {
|
||||
color: var(--main-button);
|
||||
margin-bottom: 8px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.userNotActive {
|
||||
color: #286690;
|
||||
width: 100%;
|
||||
}
|
||||
@@ -0,0 +1,585 @@
|
||||
:root {
|
||||
--paddingSite: 40px;
|
||||
--card-radius: 24px;
|
||||
--card-bg: #ffffff;
|
||||
--bg: #F4F3EF;
|
||||
--card-shadow: 0 12px 40px rgba(0, 0, 0, 0.04);
|
||||
--accent: #FF5A5F;
|
||||
/* Soft energetic pink/red from Dribbble */
|
||||
--accent-secondary: #FF8A65;
|
||||
--bg-top: rgb(54, 137, 13);
|
||||
--bg-top-raw: 54 137 13;
|
||||
--accent-soft: #f0f5ff;
|
||||
--border-subtle: #eaeaea;
|
||||
--text-main: #1b1b1b;
|
||||
--text-muted: #8A8A8A;
|
||||
--disabled-bg: #f3f4f6;
|
||||
--disabled-border: #cbd5f5;
|
||||
--ui-border: #dfe4ea;
|
||||
--main-button: #36454F;
|
||||
}
|
||||
|
||||
/* Base */
|
||||
|
||||
*,
|
||||
*::after,
|
||||
*::before {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
section {
|
||||
margin: 0;
|
||||
overscroll-behavior: none;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
width: 100vw;
|
||||
background: var(--bg);
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: #0000003d;
|
||||
color: #ffffffff;
|
||||
}
|
||||
|
||||
::-moz-selection {
|
||||
background: #a4bf4a99;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
input {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* General numeric inputs in scoring area */
|
||||
.bgSection input[type="number"] {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.nopadding {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
/* Layout */
|
||||
|
||||
.bgSection {
|
||||
position: relative;
|
||||
margin-left: auto;
|
||||
/*margin: var(--paddingSite);
|
||||
width: calc(100vw - 2 * var(--paddingSite));*/
|
||||
width: 100vw;
|
||||
box-sizing: border-box !important;
|
||||
background: none;
|
||||
}
|
||||
|
||||
@media (min-width: 1081px) {
|
||||
.bgSection.open {
|
||||
width: calc(100vw - 380px);
|
||||
/* - 2 * var(--paddingSite) */
|
||||
}
|
||||
}
|
||||
|
||||
/* Cards */
|
||||
|
||||
.headerDivTrainer {
|
||||
padding: var(--paddingSite);
|
||||
padding-bottom: 10px;
|
||||
background: transparent;
|
||||
margin-bottom: 20px;
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.dashboardGrid {
|
||||
padding: 0 var(--paddingSite) var(--paddingSite);
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 30px;
|
||||
align-items: start;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.twoColumDiv {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 30px;
|
||||
align-items: start;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 1080px) {
|
||||
.twoColumDiv {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* headings */
|
||||
|
||||
.containerHeading {
|
||||
margin-top: 0;
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
margin-bottom: 24px;
|
||||
color: var(--text-main);
|
||||
}
|
||||
|
||||
.subSettingsRowGroup .settingsRow span {
|
||||
color: #36454f;
|
||||
}
|
||||
|
||||
.subSettingsRowGroup {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
align-items: end;
|
||||
gap: 16px 22px;
|
||||
padding: 20px;
|
||||
border: 1px solid #000;
|
||||
}
|
||||
|
||||
.customSelectSubGroupHeader {
|
||||
border-bottom: 1px solid #1c2d34;
|
||||
font-weight: 600;
|
||||
border-radius: 0 !important;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.customSelectAddOption {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.headerThRankLiveConfig > span {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
padding: 20px;
|
||||
border: 1px solid #00000046;
|
||||
}
|
||||
|
||||
#bodyRowRankLiveConfig > td > span {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 15px;
|
||||
position: relative;
|
||||
padding: 10px 20px;
|
||||
border: 1px solid #00000046;
|
||||
position: relative;
|
||||
min-height: 54.5px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.headerThRankLiveConfig .addColumnBtn {
|
||||
font-size: 24px;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
line-height: 0;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
background-color: #5d9ab9;
|
||||
border: 1px solid #3a6b83;
|
||||
color: #fff;
|
||||
border-radius: 15px;
|
||||
align-content: center;
|
||||
cursor: pointer;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.headerThRankLiveConfig .addColumnBtn.addColumnBtnAfter {
|
||||
right: 0;
|
||||
transform: translateX(50%) translateY(-50%);
|
||||
}
|
||||
|
||||
.headerThRankLiveConfig .addColumnBtn.addColumnBtnBefore {
|
||||
left: 0;
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
}
|
||||
|
||||
.headerThRankLiveConfig .addColumnBtn {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.headerThRankLiveConfig:not(.ui-sortable-helper) > span:hover .addColumnBtn.addColumnBtnAfter {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.headerThRankLiveConfig:not(.ui-sortable-helper):first-child > span:hover .addColumnBtn.addColumnBtnBefore {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.headerThRankLiveConfig:not(.ui-sortable-helper) > span:hover {
|
||||
border-right: 1px solid #3a6b83;
|
||||
}
|
||||
|
||||
.headerThRankLiveConfig:not(.ui-sortable-helper):first-child > span:hover {
|
||||
border-left: 1px solid #3a6b83;
|
||||
}
|
||||
|
||||
|
||||
.deleteColumnBtn {
|
||||
color: #6a2a2a;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.customSelectAddOption {
|
||||
max-width: min(100vw, 600px);
|
||||
border: 1px solid #777;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.rankLiveFieldContainerEmty {
|
||||
display: block;
|
||||
min-height: 30px;
|
||||
width: 100%;
|
||||
background: repeating-linear-gradient(
|
||||
45deg,
|
||||
#eeeeee50,
|
||||
#eeeeee50 5px,
|
||||
#dddddd50 5px,
|
||||
#dddddd50 10px
|
||||
);
|
||||
}
|
||||
|
||||
.singleValueSpan {
|
||||
position: relative;
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #777;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.staticText {
|
||||
border-color: #00f;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.staticText .textSingleValueSpan {
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.settingSpan {
|
||||
display: none;
|
||||
padding: 2px;
|
||||
border-radius: 3px;
|
||||
background-color: #09d1ce;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
transform: translateX(50%) translateY(-50%);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.headerThRankLiveConfig .settingSpan {
|
||||
right: 50%;
|
||||
}
|
||||
|
||||
.singleValueSpan .settingSpan {
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
.mainTable:not(.selectCondEl) .singleValueSpan:hover .settingSpan,
|
||||
.mainTable:not(.selectCondEl) .headerThRankLiveConfig:hover .settingSpan,
|
||||
.settingsDivSingleValueSpan:not(.hidden) + .settingSpan {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.settingSpan > svg {
|
||||
display: block;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.settingsDivSingleValueSpan {
|
||||
background-color: #fff;
|
||||
padding: 15px;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transform: translateY(100%);
|
||||
min-width: 100%;
|
||||
width: fit-content;
|
||||
gap: 12px;
|
||||
z-index: 10;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.settingsInputGroup {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.singleValueSpan[data-bold="true"] .textSingleValueSpan, .singleValueSpan[data-bold="true"] .statischerTextInput {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.addFieldBtn {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: #36e049;
|
||||
display: flex;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: #fff;
|
||||
font-weight: 800;
|
||||
border-radius: 0 0 10px 0;
|
||||
filter: grayscale(1);
|
||||
transform: translateY(-37.5%) translateX(-37.5%) scale(0.25);
|
||||
transition: transform 0.3s ease, scale 0.3s ease;
|
||||
cursor: pointer;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.mainTable.selectCondEl .addFieldBtn {
|
||||
transform: scale(0) translateY(-50%) translateX(-50%);
|
||||
scale: 1;
|
||||
}
|
||||
|
||||
.mainTable:not(.selectCondEl) .rankLiveFieldContainer:hover:not(:has(.singleValueSpan:hover)) .addFieldBtn {
|
||||
filter: grayscale(0);
|
||||
transform: scale(1) translateY(0) translateX(0);
|
||||
scale: 1;
|
||||
}
|
||||
|
||||
.headerThRankLiveConfig > span .deleteColumnBtn {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
background-color: #e03669;
|
||||
display: flex;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: #fff;
|
||||
font-weight: 800;
|
||||
border-radius: 0 0 0 10px;
|
||||
filter: grayscale(1);
|
||||
transform: translateY(-37.5%) translateX(37.5%) scale(0.25);
|
||||
transition: transform 0.3s ease, scale 0.3s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.headerThRankLiveConfig:not(.ui-sortable-helper) > span:hover .deleteColumnBtn {
|
||||
filter: grayscale(0);
|
||||
transform: scale(1) translateY(0) translateX(0);
|
||||
scale: 1;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: max-content;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.checkbox input {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.checkbox-ui {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
width: 42px;
|
||||
height: 24px;
|
||||
flex: 0 0 auto;
|
||||
background: var(--ui-border);
|
||||
border: 1px solid color-mix(in srgb, var(--ui-border) 80%, #000);
|
||||
border-radius: 999px;
|
||||
transition:
|
||||
background-color 180ms ease,
|
||||
border-color 180ms ease,
|
||||
box-shadow 180ms ease;
|
||||
}
|
||||
|
||||
.checkbox-ui::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 3px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background: #fff;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 2px 7px rgba(15, 23, 42, 0.22);
|
||||
transform: translateY(-50%);
|
||||
transition: transform 180ms ease;
|
||||
}
|
||||
|
||||
.checkbox input:checked + .checkbox-ui {
|
||||
background: var(--bg-top);
|
||||
border-color: var(--bg-top);
|
||||
}
|
||||
|
||||
.checkbox input:checked + .checkbox-ui::after {
|
||||
transform: translate(18px, -50%);
|
||||
}
|
||||
|
||||
.checkbox input:focus-visible + .checkbox-ui {
|
||||
box-shadow: 0 0 0 4px var(--ui-focus);
|
||||
}
|
||||
|
||||
.dropPlaceholder {
|
||||
border: 2px dashed #ccc;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.dropPlaceholderColumn {
|
||||
border: 2px dashed #ddd;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.statischerTextInput {
|
||||
border-bottom: 1px solid #000;
|
||||
}
|
||||
|
||||
.statischerTextInput:focus {
|
||||
border-color: var(--bg-top);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.columnTitleInput, .inputFontSize, .gebutsdatumFormatInput, .inputDisplayOnDisplayWidth {
|
||||
background: none;
|
||||
padding: 2px;
|
||||
margin-right: 10px;
|
||||
height: auto;
|
||||
width: 100%;
|
||||
max-width: 20vw;
|
||||
border-bottom: 1px solid #000;
|
||||
}
|
||||
|
||||
.columnTitleInput:focus {
|
||||
border-color: var(--bg-top);
|
||||
}
|
||||
|
||||
.customSelectAddOption .selectOptions {
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.customSelectAddOption {
|
||||
position: absolute !important;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.inputFontSize {
|
||||
max-width: 60px;
|
||||
}
|
||||
|
||||
.inputDisplayOnDisplayWidth {
|
||||
width: 90px;
|
||||
}
|
||||
|
||||
.autocomplete-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dropdown-list {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: white;
|
||||
border: 1px solid #ccc;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
padding: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dropdown-item:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.verticalLayoutEl {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.verticalLayoutEl > span.dragableDropArea {
|
||||
min-height: 20px;
|
||||
min-width: max(50px, 100%);
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
width: fit-content;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.verticalLayoutEl > span.dragableDropArea:not(:last-child) {
|
||||
border-bottom: 1px solid #000;
|
||||
}
|
||||
|
||||
.inputLabelWrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.selectSlugType {
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.customSelectSlugType {
|
||||
border: 1px solid #000;
|
||||
padding: 6px 12px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.selectCondEl.mainTable .singleValueSpan:not(.condEl) {
|
||||
border-color: #a5a5a5;
|
||||
color: #a5a5a5;
|
||||
background-color: #ececec;
|
||||
}
|
||||
|
||||
.selectCondEl.mainTable .singleValueSpan:not(.condEl):hover {
|
||||
border-color: #0fab91;
|
||||
color: #0fab91;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.selectCondEl.mainTable .singleValueSpan.selectedCondEl {
|
||||
border-color: #ab4b0f;
|
||||
color: #ab4b0f;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.blackBtn {
|
||||
background: var(--main-button);
|
||||
border: 1px solid #fff;
|
||||
color: #ffffff;
|
||||
padding: 10px 20px;
|
||||
border-radius: 10px;
|
||||
transition: transform 0.3s ease;
|
||||
letter-spacing: 1px;
|
||||
display: inline-flex;
|
||||
align-self: flex-start;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.blackBtn:hover {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.blackBtn:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
@@ -80,13 +80,6 @@ input {
|
||||
background: none;
|
||||
}
|
||||
|
||||
@media (min-width: 1000px) {
|
||||
.bgSection.open {
|
||||
width: calc(100vw - 380px);
|
||||
/* - 2 * var(--paddingSite) */
|
||||
}
|
||||
}
|
||||
|
||||
/* Cards */
|
||||
|
||||
.headerDivTrainer {
|
||||
@@ -97,8 +90,6 @@ input {
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* headings */
|
||||
|
||||
.containerHeading {
|
||||
@@ -539,47 +530,47 @@ input[type="number"]::-webkit-outer-spin-button {
|
||||
|
||||
/* Burger + menu (unchanged from your style) */
|
||||
|
||||
.trainerBurgerMenuDiv {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
.trainerBurgerMenuDiv, .trainerBurgerMenuDiv svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
cursor: pointer;
|
||||
z-index: 99;
|
||||
color: var(--bg);
|
||||
}
|
||||
|
||||
.trainerBurgerMenuDiv svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
stroke: currentColor;
|
||||
transition: transform 0.3s ease;
|
||||
stroke: #fff;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.trainerBurgerMenuDiv.open svg {
|
||||
transform: rotate(-90deg);
|
||||
.trainerBurgerMenuDiv:hover svg {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.trainerBurgerMenuDiv:active svg {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.internMenuDiv {
|
||||
background-color: #fff;
|
||||
box-shadow: none;
|
||||
box-shadow: 4px 0 20px rgba(0, 0, 0, 0.15);
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100svh;
|
||||
width: 100%;
|
||||
max-width: 380px;
|
||||
transform: translateX(-100%);
|
||||
right: 0;
|
||||
top: 150px;
|
||||
width: 90vw;
|
||||
height: 200px;
|
||||
max-width: 400px;
|
||||
transform: translateX(0%);
|
||||
z-index: 100;
|
||||
padding: 20px;
|
||||
transition: transform 0.45s cubic-bezier(.4, 0, .2, 1);
|
||||
opacity: 1;
|
||||
border-radius: 20px 0 0 20px;
|
||||
}
|
||||
|
||||
.internMenuDiv.open {
|
||||
transform: translateX(0);
|
||||
box-shadow: 4px 0 20px rgba(0, 0, 0, 0.15);
|
||||
transform: translateX(100%);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.innerInternMenuDiv {
|
||||
@@ -1022,6 +1013,93 @@ tr.rowStartgebuer::after {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.chartjs-tooltip {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
|
||||
.chartjs-tooltip th {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.chartjs-tooltip td {
|
||||
font-weight: 200;
|
||||
}
|
||||
|
||||
.customSelect {
|
||||
position: relative;
|
||||
width: 240px;
|
||||
font-family: system-ui, sans-serif;
|
||||
}
|
||||
|
||||
.customSelect > * {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.bulkSelectDiv {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.bulkSelect:focus .selectArrow {
|
||||
rotate: 180deg
|
||||
}
|
||||
|
||||
.selectTrigger {
|
||||
width: 100%;
|
||||
border: 1px solid #ccc;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.selectOptions {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 0;
|
||||
border: 1px solid #ccc;
|
||||
border-top: none;
|
||||
list-style: none;
|
||||
background: #fff;
|
||||
display: none;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.selectOptions li {
|
||||
padding: 10px 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.selectOptions li:hover,
|
||||
.selectOptions li.selected {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
.buttonDownload {
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
background-color: var(--bg-top);
|
||||
color: #fff;
|
||||
border-top: 2px solid #727272;
|
||||
border-left: 2px solid #727272;
|
||||
border-bottom: 2px solid #2c2c2c;
|
||||
border-right: 2px solid #2c2c2c;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.buttonDownload:hover {
|
||||
transform: scale(1.015);
|
||||
}
|
||||
|
||||
.buttonDownload:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
select {
|
||||
appearance: none;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
--main: #fe3f18;
|
||||
--bg-top-raw: 254 63 24;
|
||||
--main-box-shadow: rgb(from var(--main) r g b / 0.05);
|
||||
--accent: #cfef00;
|
||||
--accent: #fe3f18;
|
||||
--accent-hover: #c5e300;
|
||||
--paddingSite: 40px;
|
||||
--card-radius: 20px;
|
||||
@@ -12,6 +12,7 @@
|
||||
--border-subtle: #d4d7e1;
|
||||
--text-main: #191919;
|
||||
--text-muted: #5e5e5e;
|
||||
--main-button: #36454F;
|
||||
}
|
||||
|
||||
/* --- BASE & TYPOGRAPHY --- */
|
||||
@@ -62,7 +63,7 @@ body {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: var(--paddingSite);
|
||||
background: linear-gradient(135deg, var(--main), #d85a00);
|
||||
background: var(--main);
|
||||
color: #fff;
|
||||
margin-bottom: var(--paddingSite);
|
||||
position: relative;
|
||||
@@ -82,7 +83,7 @@ body {
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.allTurnerinenDiv {
|
||||
.mainContentDiv {
|
||||
padding: 0 var(--paddingSite);
|
||||
}
|
||||
|
||||
@@ -127,7 +128,7 @@ body {
|
||||
max-width: 450px;
|
||||
transform: translateX(-100%);
|
||||
z-index: 100;
|
||||
padding: 20px;
|
||||
padding: 30px;
|
||||
transition: transform 0.45s cubic-bezier(.4, 0, .2, 1);
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -141,9 +142,8 @@ body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
height: 100dvh;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
padding-bottom: 80px;
|
||||
}
|
||||
|
||||
.text_akt_abt {
|
||||
@@ -207,6 +207,20 @@ body {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.textInputAbtTitel {
|
||||
margin: 0;
|
||||
font-weight: 400;
|
||||
border-top: none;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
border-bottom: 1px solid var(--main);
|
||||
font-size: 1.5rem;
|
||||
color: #191919;
|
||||
background: none;
|
||||
width: max-content;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.deleteProgramm {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -243,45 +257,26 @@ body {
|
||||
}
|
||||
|
||||
/* Sidebar Menu Button Styling */
|
||||
.header-text-conatiner {
|
||||
.headerOptionConatiner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20px;
|
||||
padding-top: 20px;
|
||||
gap: 15px;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.header-text-conatiner button {
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s ease;
|
||||
padding: 12px 20px;
|
||||
border-radius: 100px;
|
||||
background: var(--accent);
|
||||
border: 1px solid transparent;
|
||||
font-size: 15px;
|
||||
color: #000;
|
||||
width: 100%;
|
||||
box-shadow: 0 4px 6px rgba(207, 239, 0, 0.2);
|
||||
.headerOptionConatiner > button {
|
||||
|
||||
}
|
||||
|
||||
.header-text-conatiner button:hover {
|
||||
background: var(--accent-hover);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 12px rgba(207, 239, 0, 0.3);
|
||||
}
|
||||
|
||||
.header-text-conatiner button:active {
|
||||
transform: scale(0.98) translateY(0);
|
||||
}
|
||||
|
||||
.header-text-conatiner label {
|
||||
.headerOptionConatiner label {
|
||||
font-weight: 400;
|
||||
color: var(--text-main);
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: -5px;
|
||||
}
|
||||
|
||||
.header-text-conatiner input[type="number"] {
|
||||
.headerOptionConatiner input[type="number"] {
|
||||
padding: 12px 15px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--border-subtle);
|
||||
@@ -292,7 +287,7 @@ body {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.header-text-conatiner input[type="number"]:focus {
|
||||
.headerOptionConatiner input[type="number"]:focus {
|
||||
border-color: var(--main);
|
||||
box-shadow: 0 0 0 3px rgba(40, 102, 110, 0.1);
|
||||
}
|
||||
@@ -309,6 +304,7 @@ body {
|
||||
transition: all 0.3s ease;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.geraet-table thead {
|
||||
@@ -465,45 +461,177 @@ body {
|
||||
|
||||
/* Invalid / Unassigned Wrapper */
|
||||
.invalidAbtDiv {
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
margin: 20px 20px;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.invalidAbtDiv .geraet-table {
|
||||
box-shadow: none;
|
||||
border-color: #fecdd3;
|
||||
border-color: #d93f4a;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.invalidAbtDiv .geraet-table thead {
|
||||
background: #ffe4e6;
|
||||
background: #d93f4a;
|
||||
}
|
||||
|
||||
.invalidAbtDiv .geraet-table thead th {
|
||||
color: #e11d48;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* Horizontal scroll area for departments */
|
||||
.allTurnerinenDiv>div {
|
||||
|
||||
.singleAbtDiv {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.allTurnerinenDiv>div>div:last-child {
|
||||
.singleAbtDataContainer {
|
||||
overflow-x: auto;
|
||||
padding: 10px 0 20px 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 16px;
|
||||
align-items: flex-start;
|
||||
margin-top: 24px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.allTurnerinenDiv>div>div:last-child::-webkit-scrollbar {
|
||||
.singleAbtDataContainer::-webkit-scrollbar {
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.allTurnerinenDiv>div>div:last-child::-webkit-scrollbar-track {
|
||||
.singleAbtDataContainer::-webkit-scrollbar-track {
|
||||
background: #f1f5f9;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.allTurnerinenDiv>div>div:last-child::-webkit-scrollbar-thumb {
|
||||
.singleAbtDataContainer::-webkit-scrollbar-thumb {
|
||||
background: #cbd5e1;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.buttonMoveDiv {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.buttonMove {
|
||||
background: #fff;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 6px;
|
||||
width: 24px;
|
||||
height: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
input.abtIndexInput::-webkit-outer-spin-button,
|
||||
input.abtIndexInput::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
input.abtIndexInput[type=number] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
.abtIndexInput {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nichtBezahlt {
|
||||
color: #e11d48;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.timePicker {
|
||||
border: none;
|
||||
border-bottom: 1px solid #000;
|
||||
background: none;
|
||||
padding: 4px 8px;
|
||||
font-size: 1rem;
|
||||
color: var(--main);
|
||||
width: 80px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.addTimetypeButton {
|
||||
color: #2dac79;
|
||||
}
|
||||
|
||||
.removeTimetypeButton {
|
||||
color: #e11d48;
|
||||
}
|
||||
|
||||
.subHeaderAbt {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 4px 8px;
|
||||
background: #f1f5f9;
|
||||
border-radius: 8px;
|
||||
font-size: 0.9rem;
|
||||
align-self: start;
|
||||
margin-top: 18px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.subHeaderAbt .customSelect {
|
||||
position: absolute;
|
||||
width: 160px;
|
||||
bottom: 0px;
|
||||
transform: translateY(120%) translateX(-10%);
|
||||
padding: 6px;
|
||||
background: #fff;
|
||||
border: 1px solid #7b7676;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.buttonsDiv {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
justify-content: flex-start;
|
||||
margin-bottom: 20px;
|
||||
position: fixed;
|
||||
bottom: 10px;
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
.blackBtn {
|
||||
background: var(--main-button);
|
||||
border: 1px solid #fff;
|
||||
color: #ffffff;
|
||||
padding: 10px 20px;
|
||||
border-radius: 10px;
|
||||
transition: transform 0.3s ease;
|
||||
letter-spacing: 1px;
|
||||
display: flex;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.blackBtn:hover {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.blackBtn:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
h3.normalTitle {
|
||||
color: var(--main-button);
|
||||
margin-bottom: 8px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
@@ -184,9 +184,15 @@
|
||||
}
|
||||
|
||||
.menuWrapper {
|
||||
position: fixed;
|
||||
right: 20px;
|
||||
top: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 20px;
|
||||
border-radius: 40px;
|
||||
background-color: rgb(var(--bg-top-raw));
|
||||
}
|
||||
|
||||
.abmeldenbutton {
|
||||
@@ -211,7 +217,7 @@
|
||||
padding: 20px 24px 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: rgba(45, 45, 45, 0.85);
|
||||
color: rgb(146 146 146 / 85%);
|
||||
}
|
||||
|
||||
.customSelect {
|
||||
@@ -230,8 +236,7 @@ table input {
|
||||
height: 38px;
|
||||
}
|
||||
|
||||
.selectTrigger,
|
||||
.selectTriggerBulk {
|
||||
.selectTriggerSidebar {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
@@ -241,21 +246,21 @@ table input {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sidebar-nav .selectOptions,
|
||||
.sidebar-nav .selectOptionsBulk {
|
||||
.sidebar-nav .selectOptionsSidebar {
|
||||
position: absolute;
|
||||
top: calc(100% + 4px);
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 4px;
|
||||
list-style: none;
|
||||
margin-top: 2px;
|
||||
|
||||
border: 1px solid;
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 12px;
|
||||
|
||||
background: var(--sidebar-bg);
|
||||
background: #fff;
|
||||
z-index: 1000;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
||||
|
||||
opacity: 0;
|
||||
transform: scaleY(0);
|
||||
@@ -265,8 +270,7 @@ table input {
|
||||
transition: opacity 0.2s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
.sidebar-nav .customSelect.open .selectOptions,
|
||||
.sidebar-nav .customSelect.open .selectOptionsBulk {
|
||||
.sidebar-nav .customSelect.open .selectOptionsSidebar {
|
||||
opacity: 1;
|
||||
transform: scaleY(1);
|
||||
pointer-events: auto;
|
||||
@@ -276,8 +280,7 @@ table input {
|
||||
rotate: 180deg
|
||||
}
|
||||
|
||||
.selectOptions li,
|
||||
.selectOptionsBulk li {
|
||||
.selectOptionsSidebar li {
|
||||
padding: 10px 14px;
|
||||
cursor: pointer;
|
||||
border-radius: 8px;
|
||||
@@ -285,8 +288,7 @@ table input {
|
||||
transition: background-color 0.15s ease;
|
||||
}
|
||||
|
||||
.selectOptions li:last-child,
|
||||
.selectOptionsBulk li:last-child {
|
||||
.selectOptionsSidebar li:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@@ -302,43 +304,36 @@ table input {
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
color: rgba(45, 45, 45, 0.45);
|
||||
color: #fff;
|
||||
margin-bottom: 6px;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.sidebar-li-freigaben .selectTrigger {
|
||||
.selectTriggerSidebar {
|
||||
width: 100%;
|
||||
padding: 9px 12px;
|
||||
background: #fff;
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
background: #171717;
|
||||
border: 1px solid #fcfcfc;
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--sidebar-text);
|
||||
color: #fcfcfc;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
|
||||
.sidebar-li-freigaben .selectTrigger:hover {
|
||||
border-color: rgba(0, 0, 0, 0.15);
|
||||
.selectTriggerSidebar:hover, .sidebar-li-freigaben .customSelect.open .selectTriggerSidebar {
|
||||
background: #fcfcfc;
|
||||
color: #171717;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.sidebar-li-freigaben .selectOptions {
|
||||
background: #fff;
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.sidebar-li-freigaben .selectOptions li {
|
||||
.selectOptionsSidebar li {
|
||||
font-size: 13px;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
.sidebar-li-freigaben .selectOptions li.selected {
|
||||
.selectOptionsSidebar li.selected {
|
||||
background: var(--sidebar-active);
|
||||
color: #111;
|
||||
font-weight: 600;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
--text-muted: #5e5e5e;
|
||||
--disabled-bg: #f3f4f6;
|
||||
--disabled-border: #cbd5f5;
|
||||
--color-excel: #1D6F42;
|
||||
}
|
||||
|
||||
/* Base */
|
||||
@@ -86,13 +87,6 @@ input {
|
||||
background: none;
|
||||
}
|
||||
|
||||
@media (min-width: 1500px) {
|
||||
.bgSection.open {
|
||||
width: calc(100vw - 380px);
|
||||
/* - 2 * var(--paddingSite) */
|
||||
}
|
||||
}
|
||||
|
||||
/* Cards */
|
||||
|
||||
.headerDivTrainer {
|
||||
@@ -112,6 +106,16 @@ input {
|
||||
.headingStartgebuerenTabelle {
|
||||
margin-top: 0;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.headingStartgebuerenTabelle {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.heading_edit_turnerin,
|
||||
.headingAlleTurnerinnen {
|
||||
margin-top: 0;
|
||||
font-weight: 400;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
@@ -185,6 +189,79 @@ input {
|
||||
transition: all 2s ease;
|
||||
}
|
||||
|
||||
/* Basis-Container-Positionierung */
|
||||
.tooltip-container {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Das gehoverte Element */
|
||||
.target-element {
|
||||
padding: 10px 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Der Tooltip selbst */
|
||||
.tooltip {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
|
||||
/* Positionierung: Oben über dem Element, zentriert */
|
||||
bottom: 120%; /* Abstand nach oben */
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
|
||||
/* Styling */
|
||||
background-color: #ffffff;
|
||||
color: #2f2f2f;
|
||||
text-align: center;
|
||||
padding: 8px 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
white-space: nowrap;
|
||||
box-shadow: 0 4px 6px rgba(162, 162, 162, 0.1);
|
||||
align-items: center;
|
||||
text-align: left;
|
||||
|
||||
/* Die 0.5s Verzögerung */
|
||||
transition: visibility 0s linear 0.5s, opacity 0.3s linear;
|
||||
}
|
||||
|
||||
/* Kleiner Pfeil nach unten */
|
||||
.tooltip::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
margin-left: -5px;
|
||||
border-width: 5px;
|
||||
border-style: solid;
|
||||
border-color: #ffffff transparent transparent transparent;
|
||||
}
|
||||
|
||||
/* Sichtbar machen beim Hovern über den Container */
|
||||
.tooltip-container:hover .tooltip {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
|
||||
/* Keine Verzögerung beim Verschwinden */
|
||||
transition-delay: 0s;
|
||||
transition: visibility 0s linear 0s, opacity 0.3s linear;
|
||||
}
|
||||
|
||||
.tooltipPaymentStatus {
|
||||
display: grid;
|
||||
grid-template-columns: 180px 1fr;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tooltipPaymentStatus > * {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Kampfrichter header layout */
|
||||
|
||||
.headerDivTrainer {
|
||||
@@ -369,19 +446,16 @@ div.bezahlstatus.paymentAdmin {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.allTurnerinenDiv .editTurnerin,
|
||||
.allTurnerinenDiv .editTurnerinAdmin {
|
||||
.allTurnerinenDiv .editPerson {
|
||||
background: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.allTurnerinenDiv .editTurnerin svg,
|
||||
.allTurnerinenDiv .editTurnerinAdmin svg {
|
||||
.allTurnerinenDiv .editPerson svg{
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.allTurnerinenDiv .editTurnerin svg:hover,
|
||||
.allTurnerinenDiv .editTurnerinAdmin svg:hover {
|
||||
.allTurnerinenDiv .editPerson svg:hover {
|
||||
transform: scale(1.2);
|
||||
animation-name: stiftWackler;
|
||||
animation-duration: 0.5s;
|
||||
@@ -447,6 +521,7 @@ div.bezahlstatus.paymentAdmin {
|
||||
.editContainerDivInner>form>input,
|
||||
.editContainerDivInner>form>select,
|
||||
.bulkSelect,
|
||||
.editContainerDivInner .customSelect,
|
||||
.emptyEditForm {
|
||||
border: 1px dashed #777;
|
||||
max-width: 300px;
|
||||
@@ -456,6 +531,16 @@ div.bezahlstatus.paymentAdmin {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.bulkSelectDiv .customSelect {
|
||||
border: 1px solid #777;
|
||||
max-width: 300px;
|
||||
border-radius: 3px;
|
||||
font-size: 16px;
|
||||
margin-bottom: 15px;
|
||||
font-weight: 300;
|
||||
min-width: min(250px, 100%);
|
||||
}
|
||||
|
||||
.editContainerDivInner>form>input:focus,
|
||||
.editContainerDivInner>form>select:focus,
|
||||
.bulkSelect:focus {
|
||||
@@ -466,6 +551,8 @@ div.bezahlstatus.paymentAdmin {
|
||||
}
|
||||
|
||||
.editContainerDivInner>form>input,
|
||||
.editContainerDivInner .customSelect,
|
||||
.bulkSelectDiv .customSelect,
|
||||
.emptyEditForm {
|
||||
padding: 6px 10px;
|
||||
}
|
||||
@@ -787,8 +874,12 @@ input[type="number"]::-webkit-outer-spin-button {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.trainerBurgerMenuDiv.open svg {
|
||||
transform: rotate(45deg);
|
||||
.trainerBurgerMenuDiv:hover svg, .deleteStartgebuerCell:hover svg {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.trainerBurgerMenuDiv:active svg, .deleteStartgebuerCell:active svg {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.internMenuDiv {
|
||||
@@ -796,8 +887,9 @@ input[type="number"]::-webkit-outer-spin-button {
|
||||
box-shadow: none;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100svh;
|
||||
border-radius: 0 40px 40px 0;
|
||||
bottom: 40px;
|
||||
height: calc(80svh - 80px);
|
||||
width: 100%;
|
||||
max-width: 450px;
|
||||
transform: translateX(-100%);
|
||||
@@ -805,6 +897,7 @@ input[type="number"]::-webkit-outer-spin-button {
|
||||
padding: 20px;
|
||||
transition: transform 0.45s cubic-bezier(.4, 0, .2, 1);
|
||||
opacity: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.internMenuDiv.open {
|
||||
@@ -816,9 +909,8 @@ input[type="number"]::-webkit-outer-spin-button {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
height: 100dvh;
|
||||
max-height: calc(100% - 150px);
|
||||
overflow-y: auto;
|
||||
padding-bottom: 80px;
|
||||
}
|
||||
|
||||
.text_akt_abt {
|
||||
@@ -828,12 +920,12 @@ input[type="number"]::-webkit-outer-spin-button {
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.innerInternMenuDiv form {
|
||||
.internMenuDiv form {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.innerInternMenuDiv input[type="submit"],
|
||||
.innerInternMenuDiv select {
|
||||
.internMenuDiv input[type="submit"],
|
||||
.internMenuDiv select {
|
||||
width: 100%;
|
||||
padding: 12px 14px;
|
||||
border-radius: 10px;
|
||||
@@ -842,28 +934,17 @@ input[type="number"]::-webkit-outer-spin-button {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.innerInternMenuDiv input[type="submit"]:focus,
|
||||
.innerInternMenuDiv select:focus {
|
||||
.internMenuDiv input[type="submit"]:focus,
|
||||
.internMenuDiv select:focus {
|
||||
outline: none;
|
||||
border-color: #2d73ac;
|
||||
box-shadow: 0 0 6px rgba(45, 115, 172, 0.3);
|
||||
}
|
||||
|
||||
.innerInternMenuDiv .button-secondary {
|
||||
background: linear-gradient(135deg, #2d73ac, #1f4f75);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
padding: 12px 18px;
|
||||
transition: transform 0.2s ease, background 0.3s ease;
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.innerInternMenuDiv .button-secondary:hover {
|
||||
background: linear-gradient(135deg, #245d8a, #163b5a);
|
||||
transform: translateY(-2px);
|
||||
.internMenuDiv button.newBtn {
|
||||
position: absolute;
|
||||
bottom: 30px;
|
||||
right: 30px;
|
||||
}
|
||||
|
||||
.gruppennav {
|
||||
@@ -1184,15 +1265,18 @@ tr.rowStartgebuer {
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
tr.rowStartgebuer:hover {
|
||||
background-color: #f9fafb;
|
||||
}
|
||||
|
||||
tr.rowStartgebuer {
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.tableStartgebueren {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
tr.rowStartgebuerTotal {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
font-weight: bold;
|
||||
border-top: 2px solid #ddd;
|
||||
background-color: #fdfdfd;
|
||||
@@ -1240,14 +1324,12 @@ tr.rowStartgebuerTotal {
|
||||
}
|
||||
|
||||
.tableStartgebueren {
|
||||
max-height: 60vh;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
|
||||
/* Prevent scroll chaining to body */
|
||||
overscroll-behavior-x: contain;
|
||||
overscroll-behavior-x: none;
|
||||
overscroll-behavior-y: none;
|
||||
|
||||
/* Smooth scrolling on touch devices */
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
@@ -1294,87 +1376,97 @@ tr.rowStartgebuerTotal {
|
||||
|
||||
/* Container styling */
|
||||
.form_excel {
|
||||
max-width: 400px;
|
||||
padding: 30px;
|
||||
max-width: 420px;
|
||||
padding: 18px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.05);
|
||||
text-align: center;
|
||||
border-top: 6px solid #1D6F42;
|
||||
/* Excel Green */
|
||||
border: 4px solid var(--color-excel);
|
||||
}
|
||||
|
||||
.form_excel h4 {
|
||||
.form_excel h3 {
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
margin: 0 0 20px 0;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* Download Template Section */
|
||||
|
||||
.listExcelForm {
|
||||
text-align: left;
|
||||
line-height: 1.25;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.form_excel a {
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
margin-bottom: 25px;
|
||||
display: inline-flex;
|
||||
color: var(--color-excel);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.form_excel button[type="button"] {
|
||||
background-color: #f3f3f3;
|
||||
color: #1D6F42;
|
||||
border: 1px solid #1D6F42;
|
||||
padding: 10px 20px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
.form_excel a::after {
|
||||
content: "";
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
background-color: var(--color-excel);
|
||||
bottom: -2px;
|
||||
left: 0;
|
||||
transform: scaleX(0);
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.form_excel button[type="button"]:hover {
|
||||
background-color: #e8f5e9;
|
||||
transform: translateY(-1px);
|
||||
.form_excel a:hover::after {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
|
||||
/* File Input Styling */
|
||||
.form_excel input[type="file"] {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
padding: 10px;
|
||||
border: 2px dashed #cbd5e0;
|
||||
border-radius: 8px;
|
||||
background-color: #fafafa;
|
||||
padding: 8px 0;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.form_excel input[type="file"]::file-selector-button {
|
||||
background: #1D6F42;
|
||||
color: white;
|
||||
border: none;
|
||||
background: none;
|
||||
color: var(--color-excel);
|
||||
border: 1px solid var(--color-excel);
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
margin-right: 10px;
|
||||
cursor: pointer;
|
||||
letter-spacing: 1px;
|
||||
transition: background 0.3s ease, color 0.3s ease;
|
||||
}
|
||||
|
||||
.form_excel input[type="file"]::file-selector-button:hover {
|
||||
background: var(--color-excel);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
|
||||
/* Submit Button */
|
||||
.form_excel button[type="submit"] {
|
||||
background-color: #1D6F42;
|
||||
/* Official Excel Green */
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 12px 24px;
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
background: none;
|
||||
color: var(--color-excel);
|
||||
border: 1px solid var(--color-excel);
|
||||
padding: 8px 16px;
|
||||
border-radius: 6px;
|
||||
margin-top: 8px;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 4px 6px rgba(29, 111, 66, 0.2);
|
||||
transition: background 0.3s ease;
|
||||
transition: background 0.3s ease, color 0.3s ease;
|
||||
}
|
||||
|
||||
.form_excel button[type="submit"]:hover {
|
||||
background-color: #155231;
|
||||
box-shadow: 0 6px 12px rgba(29, 111, 66, 0.3);
|
||||
background: var(--color-excel);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Payment Modal Overlay */
|
||||
@@ -1574,13 +1666,65 @@ span.flatpickr-weekday {
|
||||
color: #cfef00;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
color: #777;
|
||||
}
|
||||
|
||||
.tooltipPaymentStatusContainer {
|
||||
position: absolute;
|
||||
transform: translateY(-100%) translateX(-50%);
|
||||
background-color: #ffffff;
|
||||
color: #2f2f2f;
|
||||
text-align: center;
|
||||
padding: 8px 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
white-space: nowrap;
|
||||
box-shadow: 0 4px 6px rgba(162, 162, 162, 0.1);
|
||||
align-items: center;
|
||||
text-align: left;
|
||||
transition: opacity 0.5s ease;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.opacity0 {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.2s ease !important;
|
||||
}
|
||||
|
||||
.closeWarenkorb {
|
||||
color: #d30741;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
transform: scale(1);
|
||||
transition: transform 0.3s ease;
|
||||
display: inline-flex;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.closeWarenkorb:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.closeWarenkorb:active {
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
||||
.headerDivWarenkorb {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
|
||||
select:not(.flatpickr-monthDropdown-months) {
|
||||
appearance: none;
|
||||
/* Standard */
|
||||
-webkit-appearance: none;
|
||||
/* WebKit */
|
||||
-moz-appearance: none;
|
||||
/* Firefox */
|
||||
|
||||
width: 100%;
|
||||
padding: 12px 35px 12px 14px !important;
|
||||
@@ -1590,10 +1734,7 @@ select:not(.flatpickr-monthDropdown-months) {
|
||||
border-radius: 4px;
|
||||
background-color: #fff;
|
||||
|
||||
background-image: url("data:image/svg+xml;utf8,\
|
||||
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none'>\
|
||||
<path d='M6 9L12 15L18 9' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/>\
|
||||
</svg>");
|
||||
background-image: url("data:image/svg+xml;utf8,\<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none'>\<path d='M6 9L12 15L18 9' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/>\</svg>");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 10px center;
|
||||
background-size: 20px;
|
||||
@@ -1602,8 +1743,5 @@ select:not(.flatpickr-monthDropdown-months) {
|
||||
}
|
||||
|
||||
select:open {
|
||||
background-image: url("data:image/svg+xml;utf8,\
|
||||
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none'>\
|
||||
<path d='M18 15L12 9L6 15' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/>\
|
||||
</svg>");
|
||||
background-image: url("data:image/svg+xml;utf8,\<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none'>\<path d='M18 15L12 9L6 15' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/>\</svg>");
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
/* Dies ist eine CSS-Datei, welche mit eigener CSS-Logik gefüllt werden kann. Diese CSS Datei glit für alle Intern Seiten.*/
|
||||
@@ -1,77 +0,0 @@
|
||||
.welcomeScreen{
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: 10000;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.welcomeScreen{
|
||||
animation: fadeOut 0.9s forwards;
|
||||
animation-delay: 3s;
|
||||
}
|
||||
|
||||
.innerWelcomeScreen{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.textWelcomeScreen{
|
||||
text-align: center;
|
||||
}
|
||||
.textWelcomeScreen.text span {
|
||||
display: inline-block;
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
animation: rise 0.5s forwards;
|
||||
font-size: 60px;
|
||||
font-weight: 100;
|
||||
color: #8044A6;
|
||||
}
|
||||
|
||||
.textWelcomeScreen.title span {
|
||||
display: inline-block;
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
animation: fall 0.9s forwards;
|
||||
font-size: 120px;
|
||||
}
|
||||
|
||||
|
||||
@keyframes rise {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeOut {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
99% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fall {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 155 KiB After Width: | Height: | Size: 672 KiB |
|
Before Width: | Height: | Size: 399 KiB After Width: | Height: | Size: 357 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 9.6 KiB |
@@ -0,0 +1,632 @@
|
||||
ace.define("ace/mode/css_highlight_rules",["require","exports","module","ace/lib/oop","ace/lib/lang","ace/mode/text_highlight_rules"], function(require, exports, module){"use strict";
|
||||
var oop = require("../lib/oop");
|
||||
var lang = require("../lib/lang");
|
||||
var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules;
|
||||
var supportType = exports.supportType = "align-content|align-items|align-self|all|animation|animation-delay|animation-direction|animation-duration|animation-fill-mode|animation-iteration-count|animation-name|animation-play-state|animation-timing-function|backface-visibility|background|background-attachment|background-blend-mode|background-clip|background-color|background-image|background-origin|background-position|background-repeat|background-size|border|border-bottom|border-bottom-color|border-bottom-left-radius|border-bottom-right-radius|border-bottom-style|border-bottom-width|border-collapse|border-color|border-image|border-image-outset|border-image-repeat|border-image-slice|border-image-source|border-image-width|border-left|border-left-color|border-left-style|border-left-width|border-radius|border-right|border-right-color|border-right-style|border-right-width|border-spacing|border-style|border-top|border-top-color|border-top-left-radius|border-top-right-radius|border-top-style|border-top-width|border-width|bottom|box-shadow|box-sizing|caption-side|clear|clip|color|column-count|column-fill|column-gap|column-rule|column-rule-color|column-rule-style|column-rule-width|column-span|column-width|columns|content|counter-increment|counter-reset|cursor|direction|display|empty-cells|filter|flex|flex-basis|flex-direction|flex-flow|flex-grow|flex-shrink|flex-wrap|float|font|font-family|font-size|font-size-adjust|font-stretch|font-style|font-variant|font-weight|hanging-punctuation|height|justify-content|left|letter-spacing|line-height|list-style|list-style-image|list-style-position|list-style-type|margin|margin-bottom|margin-left|margin-right|margin-top|max-height|max-width|max-zoom|min-height|min-width|min-zoom|nav-down|nav-index|nav-left|nav-right|nav-up|opacity|order|outline|outline-color|outline-offset|outline-style|outline-width|overflow|overflow-x|overflow-y|padding|padding-bottom|padding-left|padding-right|padding-top|page-break-after|page-break-before|page-break-inside|perspective|perspective-origin|position|quotes|resize|right|tab-size|table-layout|text-align|text-align-last|text-decoration|text-decoration-color|text-decoration-line|text-decoration-style|text-indent|text-justify|text-overflow|text-shadow|text-transform|top|transform|transform-origin|transform-style|transition|transition-delay|transition-duration|transition-property|transition-timing-function|unicode-bidi|user-select|user-zoom|vertical-align|visibility|white-space|width|word-break|word-spacing|word-wrap|z-index";
|
||||
var supportFunction = exports.supportFunction = "rgb|rgba|url|attr|counter|counters";
|
||||
var supportConstant = exports.supportConstant = "absolute|after-edge|after|all-scroll|all|alphabetic|always|antialiased|armenian|auto|avoid-column|avoid-page|avoid|balance|baseline|before-edge|before|below|bidi-override|block-line-height|block|bold|bolder|border-box|both|bottom|box|break-all|break-word|capitalize|caps-height|caption|center|central|char|circle|cjk-ideographic|clone|close-quote|col-resize|collapse|column|consider-shifts|contain|content-box|cover|crosshair|cubic-bezier|dashed|decimal-leading-zero|decimal|default|disabled|disc|disregard-shifts|distribute-all-lines|distribute-letter|distribute-space|distribute|dotted|double|e-resize|ease-in|ease-in-out|ease-out|ease|ellipsis|end|exclude-ruby|flex-end|flex-start|fill|fixed|georgian|glyphs|grid-height|groove|hand|hanging|hebrew|help|hidden|hiragana-iroha|hiragana|horizontal|icon|ideograph-alpha|ideograph-numeric|ideograph-parenthesis|ideograph-space|ideographic|inactive|include-ruby|inherit|initial|inline-block|inline-box|inline-line-height|inline-table|inline|inset|inside|inter-ideograph|inter-word|invert|italic|justify|katakana-iroha|katakana|keep-all|last|left|lighter|line-edge|line-through|line|linear|list-item|local|loose|lower-alpha|lower-greek|lower-latin|lower-roman|lowercase|lr-tb|ltr|mathematical|max-height|max-size|medium|menu|message-box|middle|move|n-resize|ne-resize|newspaper|no-change|no-close-quote|no-drop|no-open-quote|no-repeat|none|normal|not-allowed|nowrap|nw-resize|oblique|open-quote|outset|outside|overline|padding-box|page|pointer|pre-line|pre-wrap|pre|preserve-3d|progress|relative|repeat-x|repeat-y|repeat|replaced|reset-size|ridge|right|round|row-resize|rtl|s-resize|scroll|se-resize|separate|slice|small-caps|small-caption|solid|space|square|start|static|status-bar|step-end|step-start|steps|stretch|strict|sub|super|sw-resize|table-caption|table-cell|table-column-group|table-column|table-footer-group|table-header-group|table-row-group|table-row|table|tb-rl|text-after-edge|text-before-edge|text-bottom|text-size|text-top|text|thick|thin|transparent|underline|upper-alpha|upper-latin|upper-roman|uppercase|use-script|vertical-ideographic|vertical-text|visible|w-resize|wait|whitespace|z-index|zero|zoom";
|
||||
var supportConstantColor = exports.supportConstantColor = "aliceblue|antiquewhite|aqua|aquamarine|azure|beige|bisque|black|blanchedalmond|blue|blueviolet|brown|burlywood|cadetblue|chartreuse|chocolate|coral|cornflowerblue|cornsilk|crimson|cyan|darkblue|darkcyan|darkgoldenrod|darkgray|darkgreen|darkgrey|darkkhaki|darkmagenta|darkolivegreen|darkorange|darkorchid|darkred|darksalmon|darkseagreen|darkslateblue|darkslategray|darkslategrey|darkturquoise|darkviolet|deeppink|deepskyblue|dimgray|dimgrey|dodgerblue|firebrick|floralwhite|forestgreen|fuchsia|gainsboro|ghostwhite|gold|goldenrod|gray|green|greenyellow|grey|honeydew|hotpink|indianred|indigo|ivory|khaki|lavender|lavenderblush|lawngreen|lemonchiffon|lightblue|lightcoral|lightcyan|lightgoldenrodyellow|lightgray|lightgreen|lightgrey|lightpink|lightsalmon|lightseagreen|lightskyblue|lightslategray|lightslategrey|lightsteelblue|lightyellow|lime|limegreen|linen|magenta|maroon|mediumaquamarine|mediumblue|mediumorchid|mediumpurple|mediumseagreen|mediumslateblue|mediumspringgreen|mediumturquoise|mediumvioletred|midnightblue|mintcream|mistyrose|moccasin|navajowhite|navy|oldlace|olive|olivedrab|orange|orangered|orchid|palegoldenrod|palegreen|paleturquoise|palevioletred|papayawhip|peachpuff|peru|pink|plum|powderblue|purple|rebeccapurple|red|rosybrown|royalblue|saddlebrown|salmon|sandybrown|seagreen|seashell|sienna|silver|skyblue|slateblue|slategray|slategrey|snow|springgreen|steelblue|tan|teal|thistle|tomato|turquoise|violet|wheat|white|whitesmoke|yellow|yellowgreen";
|
||||
var supportConstantFonts = exports.supportConstantFonts = "arial|century|comic|courier|cursive|fantasy|garamond|georgia|helvetica|impact|lucida|symbol|system|tahoma|times|trebuchet|utopia|verdana|webdings|sans-serif|serif|monospace";
|
||||
var numRe = exports.numRe = "\\-?(?:(?:[0-9]+(?:\\.[0-9]+)?)|(?:\\.[0-9]+))";
|
||||
var pseudoElements = exports.pseudoElements = "(\\:+)\\b(after|before|first-letter|first-line|moz-selection|selection)\\b";
|
||||
var pseudoClasses = exports.pseudoClasses = "(:)\\b(active|checked|disabled|empty|enabled|first-child|first-of-type|focus|hover|indeterminate|invalid|last-child|last-of-type|link|not|nth-child|nth-last-child|nth-last-of-type|nth-of-type|only-child|only-of-type|required|root|target|valid|visited)\\b";
|
||||
var CssHighlightRules = function () {
|
||||
var keywordMapper = this.createKeywordMapper({
|
||||
"support.function": supportFunction,
|
||||
"support.constant": supportConstant,
|
||||
"support.type": supportType,
|
||||
"support.constant.color": supportConstantColor,
|
||||
"support.constant.fonts": supportConstantFonts
|
||||
}, "text", true);
|
||||
this.$rules = {
|
||||
"start": [{
|
||||
include: ["strings", "url", "comments"]
|
||||
}, {
|
||||
token: "paren.lparen",
|
||||
regex: "\\{",
|
||||
next: "ruleset"
|
||||
}, {
|
||||
token: "paren.rparen",
|
||||
regex: "\\}"
|
||||
}, {
|
||||
token: "string",
|
||||
regex: "@(?!viewport)",
|
||||
next: "media"
|
||||
}, {
|
||||
token: "keyword",
|
||||
regex: "#[a-z0-9-_]+"
|
||||
}, {
|
||||
token: "keyword",
|
||||
regex: "%"
|
||||
}, {
|
||||
token: "variable",
|
||||
regex: "\\.[a-z0-9-_]+"
|
||||
}, {
|
||||
token: "string",
|
||||
regex: ":[a-z0-9-_]+"
|
||||
}, {
|
||||
token: "constant.numeric",
|
||||
regex: numRe
|
||||
}, {
|
||||
token: "constant",
|
||||
regex: "[a-z0-9-_]+"
|
||||
}, {
|
||||
caseInsensitive: true
|
||||
}],
|
||||
"media": [{
|
||||
include: ["strings", "url", "comments"]
|
||||
}, {
|
||||
token: "paren.lparen",
|
||||
regex: "\\{",
|
||||
next: "start"
|
||||
}, {
|
||||
token: "paren.rparen",
|
||||
regex: "\\}",
|
||||
next: "start"
|
||||
}, {
|
||||
token: "string",
|
||||
regex: ";",
|
||||
next: "start"
|
||||
}, {
|
||||
token: "keyword",
|
||||
regex: "(?:media|supports|document|charset|import|namespace|media|supports|document"
|
||||
+ "|page|font|keyframes|viewport|counter-style|font-feature-values"
|
||||
+ "|swash|ornaments|annotation|stylistic|styleset|character-variant)"
|
||||
}],
|
||||
"comments": [{
|
||||
token: "comment", // multi line comment
|
||||
regex: "\\/\\*",
|
||||
push: [{
|
||||
token: "comment",
|
||||
regex: "\\*\\/",
|
||||
next: "pop"
|
||||
}, {
|
||||
defaultToken: "comment"
|
||||
}]
|
||||
}],
|
||||
"ruleset": [{
|
||||
regex: "-(webkit|ms|moz|o)-",
|
||||
token: "text"
|
||||
}, {
|
||||
token: "punctuation.operator",
|
||||
regex: "[:;]"
|
||||
}, {
|
||||
token: "paren.rparen",
|
||||
regex: "\\}",
|
||||
next: "start"
|
||||
}, {
|
||||
include: ["strings", "url", "comments"]
|
||||
}, {
|
||||
token: ["constant.numeric", "keyword"],
|
||||
regex: "(" + numRe + ")(ch|cm|deg|em|ex|fr|gd|grad|Hz|in|kHz|mm|ms|pc|pt|px|rad|rem|s|turn|vh|vmax|vmin|vm|vw|%)"
|
||||
}, {
|
||||
token: "constant.numeric",
|
||||
regex: numRe
|
||||
}, {
|
||||
token: "constant.numeric", // hex6 color
|
||||
regex: "#[a-f0-9]{6}"
|
||||
}, {
|
||||
token: "constant.numeric", // hex3 color
|
||||
regex: "#[a-f0-9]{3}"
|
||||
}, {
|
||||
token: ["punctuation", "entity.other.attribute-name.pseudo-element.css"],
|
||||
regex: pseudoElements
|
||||
}, {
|
||||
token: ["punctuation", "entity.other.attribute-name.pseudo-class.css"],
|
||||
regex: pseudoClasses
|
||||
}, {
|
||||
include: "url"
|
||||
}, {
|
||||
token: keywordMapper,
|
||||
regex: "\\-?[a-zA-Z_][a-zA-Z0-9_\\-]*"
|
||||
}, {
|
||||
token: "paren.lparen",
|
||||
regex: "\\{"
|
||||
}, {
|
||||
caseInsensitive: true
|
||||
}],
|
||||
url: [{
|
||||
token: "support.function",
|
||||
regex: "(?:url(:?-prefix)?|domain|regexp)\\(",
|
||||
push: [{
|
||||
token: "support.function",
|
||||
regex: "\\)",
|
||||
next: "pop"
|
||||
}, {
|
||||
defaultToken: "string"
|
||||
}]
|
||||
}],
|
||||
strings: [{
|
||||
token: "string.start",
|
||||
regex: "'",
|
||||
push: [{
|
||||
token: "string.end",
|
||||
regex: "'|$",
|
||||
next: "pop"
|
||||
}, {
|
||||
include: "escapes"
|
||||
}, {
|
||||
token: "constant.language.escape",
|
||||
regex: /\\$/,
|
||||
consumeLineEnd: true
|
||||
}, {
|
||||
defaultToken: "string"
|
||||
}]
|
||||
}, {
|
||||
token: "string.start",
|
||||
regex: '"',
|
||||
push: [{
|
||||
token: "string.end",
|
||||
regex: '"|$',
|
||||
next: "pop"
|
||||
}, {
|
||||
include: "escapes"
|
||||
}, {
|
||||
token: "constant.language.escape",
|
||||
regex: /\\$/,
|
||||
consumeLineEnd: true
|
||||
}, {
|
||||
defaultToken: "string"
|
||||
}]
|
||||
}],
|
||||
escapes: [{
|
||||
token: "constant.language.escape",
|
||||
regex: /\\([a-fA-F\d]{1,6}|[^a-fA-F\d])/
|
||||
}]
|
||||
};
|
||||
this.normalizeRules();
|
||||
};
|
||||
oop.inherits(CssHighlightRules, TextHighlightRules);
|
||||
exports.CssHighlightRules = CssHighlightRules;
|
||||
|
||||
});
|
||||
|
||||
ace.define("ace/mode/matching_brace_outdent",["require","exports","module","ace/range"], function(require, exports, module){"use strict";
|
||||
var Range = require("../range").Range;
|
||||
var MatchingBraceOutdent = function () { };
|
||||
(function () {
|
||||
this.checkOutdent = function (line, input) {
|
||||
if (!/^\s+$/.test(line))
|
||||
return false;
|
||||
return /^\s*\}/.test(input);
|
||||
};
|
||||
this.autoOutdent = function (doc, row) {
|
||||
var line = doc.getLine(row);
|
||||
var match = line.match(/^(\s*\})/);
|
||||
if (!match)
|
||||
return 0;
|
||||
var column = match[1].length;
|
||||
var openBracePos = doc.findMatchingBracket({ row: row, column: column });
|
||||
if (!openBracePos || openBracePos.row == row)
|
||||
return 0;
|
||||
var indent = this.$getIndent(doc.getLine(openBracePos.row));
|
||||
doc.replace(new Range(row, 0, row, column - 1), indent);
|
||||
};
|
||||
this.$getIndent = function (line) {
|
||||
return line.match(/^\s*/)[0];
|
||||
};
|
||||
}).call(MatchingBraceOutdent.prototype);
|
||||
exports.MatchingBraceOutdent = MatchingBraceOutdent;
|
||||
|
||||
});
|
||||
|
||||
ace.define("ace/mode/css_completions",["require","exports","module"], function(require, exports, module){"use strict";
|
||||
var propertyMap = {
|
||||
"background": { "#$0": 1 },
|
||||
"background-color": { "#$0": 1, "transparent": 1, "fixed": 1 },
|
||||
"background-image": { "url('/$0')": 1 },
|
||||
"background-repeat": { "repeat": 1, "repeat-x": 1, "repeat-y": 1, "no-repeat": 1, "inherit": 1 },
|
||||
"background-position": { "bottom": 2, "center": 2, "left": 2, "right": 2, "top": 2, "inherit": 2 },
|
||||
"background-attachment": { "scroll": 1, "fixed": 1 },
|
||||
"background-size": { "cover": 1, "contain": 1 },
|
||||
"background-clip": { "border-box": 1, "padding-box": 1, "content-box": 1 },
|
||||
"background-origin": { "border-box": 1, "padding-box": 1, "content-box": 1 },
|
||||
"border": { "solid $0": 1, "dashed $0": 1, "dotted $0": 1, "#$0": 1 },
|
||||
"border-color": { "#$0": 1 },
|
||||
"border-style": { "solid": 2, "dashed": 2, "dotted": 2, "double": 2, "groove": 2, "hidden": 2, "inherit": 2, "inset": 2, "none": 2, "outset": 2, "ridged": 2 },
|
||||
"border-collapse": { "collapse": 1, "separate": 1 },
|
||||
"bottom": { "px": 1, "em": 1, "%": 1 },
|
||||
"clear": { "left": 1, "right": 1, "both": 1, "none": 1 },
|
||||
"color": { "#$0": 1, "rgb(#$00,0,0)": 1 },
|
||||
"cursor": { "default": 1, "pointer": 1, "move": 1, "text": 1, "wait": 1, "help": 1, "progress": 1, "n-resize": 1, "ne-resize": 1, "e-resize": 1, "se-resize": 1, "s-resize": 1, "sw-resize": 1, "w-resize": 1, "nw-resize": 1 },
|
||||
"display": { "none": 1, "block": 1, "inline": 1, "inline-block": 1, "table-cell": 1 },
|
||||
"empty-cells": { "show": 1, "hide": 1 },
|
||||
"float": { "left": 1, "right": 1, "none": 1 },
|
||||
"font-family": { "Arial": 2, "Comic Sans MS": 2, "Consolas": 2, "Courier New": 2, "Courier": 2, "Georgia": 2, "Monospace": 2, "Sans-Serif": 2, "Segoe UI": 2, "Tahoma": 2, "Times New Roman": 2, "Trebuchet MS": 2, "Verdana": 1 },
|
||||
"font-size": { "px": 1, "em": 1, "%": 1 },
|
||||
"font-weight": { "bold": 1, "normal": 1 },
|
||||
"font-style": { "italic": 1, "normal": 1 },
|
||||
"font-variant": { "normal": 1, "small-caps": 1 },
|
||||
"height": { "px": 1, "em": 1, "%": 1 },
|
||||
"left": { "px": 1, "em": 1, "%": 1 },
|
||||
"letter-spacing": { "normal": 1 },
|
||||
"line-height": { "normal": 1 },
|
||||
"list-style-type": { "none": 1, "disc": 1, "circle": 1, "square": 1, "decimal": 1, "decimal-leading-zero": 1, "lower-roman": 1, "upper-roman": 1, "lower-greek": 1, "lower-latin": 1, "upper-latin": 1, "georgian": 1, "lower-alpha": 1, "upper-alpha": 1 },
|
||||
"margin": { "px": 1, "em": 1, "%": 1 },
|
||||
"margin-right": { "px": 1, "em": 1, "%": 1 },
|
||||
"margin-left": { "px": 1, "em": 1, "%": 1 },
|
||||
"margin-top": { "px": 1, "em": 1, "%": 1 },
|
||||
"margin-bottom": { "px": 1, "em": 1, "%": 1 },
|
||||
"max-height": { "px": 1, "em": 1, "%": 1 },
|
||||
"max-width": { "px": 1, "em": 1, "%": 1 },
|
||||
"min-height": { "px": 1, "em": 1, "%": 1 },
|
||||
"min-width": { "px": 1, "em": 1, "%": 1 },
|
||||
"overflow": { "hidden": 1, "visible": 1, "auto": 1, "scroll": 1 },
|
||||
"overflow-x": { "hidden": 1, "visible": 1, "auto": 1, "scroll": 1 },
|
||||
"overflow-y": { "hidden": 1, "visible": 1, "auto": 1, "scroll": 1 },
|
||||
"padding": { "px": 1, "em": 1, "%": 1 },
|
||||
"padding-top": { "px": 1, "em": 1, "%": 1 },
|
||||
"padding-right": { "px": 1, "em": 1, "%": 1 },
|
||||
"padding-bottom": { "px": 1, "em": 1, "%": 1 },
|
||||
"padding-left": { "px": 1, "em": 1, "%": 1 },
|
||||
"page-break-after": { "auto": 1, "always": 1, "avoid": 1, "left": 1, "right": 1 },
|
||||
"page-break-before": { "auto": 1, "always": 1, "avoid": 1, "left": 1, "right": 1 },
|
||||
"position": { "absolute": 1, "relative": 1, "fixed": 1, "static": 1 },
|
||||
"right": { "px": 1, "em": 1, "%": 1 },
|
||||
"table-layout": { "fixed": 1, "auto": 1 },
|
||||
"text-decoration": { "none": 1, "underline": 1, "line-through": 1, "blink": 1 },
|
||||
"text-align": { "left": 1, "right": 1, "center": 1, "justify": 1 },
|
||||
"text-transform": { "capitalize": 1, "uppercase": 1, "lowercase": 1, "none": 1 },
|
||||
"top": { "px": 1, "em": 1, "%": 1 },
|
||||
"vertical-align": { "top": 1, "bottom": 1 },
|
||||
"visibility": { "hidden": 1, "visible": 1 },
|
||||
"white-space": { "nowrap": 1, "normal": 1, "pre": 1, "pre-line": 1, "pre-wrap": 1 },
|
||||
"width": { "px": 1, "em": 1, "%": 1 },
|
||||
"word-spacing": { "normal": 1 },
|
||||
"filter": { "alpha(opacity=$0100)": 1 },
|
||||
"text-shadow": { "$02px 2px 2px #777": 1 },
|
||||
"text-overflow": { "ellipsis-word": 1, "clip": 1, "ellipsis": 1 },
|
||||
"-moz-border-radius": 1,
|
||||
"-moz-border-radius-topright": 1,
|
||||
"-moz-border-radius-bottomright": 1,
|
||||
"-moz-border-radius-topleft": 1,
|
||||
"-moz-border-radius-bottomleft": 1,
|
||||
"-webkit-border-radius": 1,
|
||||
"-webkit-border-top-right-radius": 1,
|
||||
"-webkit-border-top-left-radius": 1,
|
||||
"-webkit-border-bottom-right-radius": 1,
|
||||
"-webkit-border-bottom-left-radius": 1,
|
||||
"-moz-box-shadow": 1,
|
||||
"-webkit-box-shadow": 1,
|
||||
"transform": { "rotate($00deg)": 1, "skew($00deg)": 1 },
|
||||
"-moz-transform": { "rotate($00deg)": 1, "skew($00deg)": 1 },
|
||||
"-webkit-transform": { "rotate($00deg)": 1, "skew($00deg)": 1 }
|
||||
};
|
||||
var CssCompletions = function () {
|
||||
};
|
||||
(function () {
|
||||
this.completionsDefined = false;
|
||||
this.defineCompletions = function () {
|
||||
if (document) {
|
||||
var style = document.createElement('c').style;
|
||||
for (var i in style) {
|
||||
if (typeof style[i] !== 'string')
|
||||
continue;
|
||||
var name = i.replace(/[A-Z]/g, function (x) {
|
||||
return '-' + x.toLowerCase();
|
||||
});
|
||||
if (!propertyMap.hasOwnProperty(name))
|
||||
propertyMap[name] = 1;
|
||||
}
|
||||
}
|
||||
this.completionsDefined = true;
|
||||
};
|
||||
this.getCompletions = function (state, session, pos, prefix) {
|
||||
if (!this.completionsDefined) {
|
||||
this.defineCompletions();
|
||||
}
|
||||
if (state === 'ruleset' || session.$mode.$id == "ace/mode/scss") {
|
||||
var line = session.getLine(pos.row).substr(0, pos.column);
|
||||
var inParens = /\([^)]*$/.test(line);
|
||||
if (inParens) {
|
||||
line = line.substr(line.lastIndexOf('(') + 1);
|
||||
}
|
||||
if (/:[^;]+$/.test(line)) {
|
||||
/([\w\-]+):[^:]*$/.test(line);
|
||||
return this.getPropertyValueCompletions(state, session, pos, prefix);
|
||||
}
|
||||
else {
|
||||
return this.getPropertyCompletions(state, session, pos, prefix, inParens);
|
||||
}
|
||||
}
|
||||
return [];
|
||||
};
|
||||
this.getPropertyCompletions = function (state, session, pos, prefix, skipSemicolon) {
|
||||
skipSemicolon = skipSemicolon || false;
|
||||
var properties = Object.keys(propertyMap);
|
||||
return properties.map(function (property) {
|
||||
return {
|
||||
caption: property,
|
||||
snippet: property + ': $0' + (skipSemicolon ? '' : ';'),
|
||||
meta: "property",
|
||||
score: 1000000
|
||||
};
|
||||
});
|
||||
};
|
||||
this.getPropertyValueCompletions = function (state, session, pos, prefix) {
|
||||
var line = session.getLine(pos.row).substr(0, pos.column);
|
||||
var property = (/([\w\-]+):[^:]*$/.exec(line) || {})[1];
|
||||
if (!property)
|
||||
return [];
|
||||
var values = [];
|
||||
if (property in propertyMap && typeof propertyMap[property] === "object") {
|
||||
values = Object.keys(propertyMap[property]);
|
||||
}
|
||||
return values.map(function (value) {
|
||||
return {
|
||||
caption: value,
|
||||
snippet: value,
|
||||
meta: "property value",
|
||||
score: 1000000
|
||||
};
|
||||
});
|
||||
};
|
||||
}).call(CssCompletions.prototype);
|
||||
exports.CssCompletions = CssCompletions;
|
||||
|
||||
});
|
||||
|
||||
ace.define("ace/mode/behaviour/css",["require","exports","module","ace/lib/oop","ace/mode/behaviour","ace/mode/behaviour/cstyle","ace/token_iterator"], function(require, exports, module){"use strict";
|
||||
var oop = require("../../lib/oop");
|
||||
var Behaviour = require("../behaviour").Behaviour;
|
||||
var CstyleBehaviour = require("./cstyle").CstyleBehaviour;
|
||||
var TokenIterator = require("../../token_iterator").TokenIterator;
|
||||
var CssBehaviour = function () {
|
||||
this.inherit(CstyleBehaviour);
|
||||
this.add("colon", "insertion", function (state, action, editor, session, text) {
|
||||
if (text === ':' && editor.selection.isEmpty()) {
|
||||
var cursor = editor.getCursorPosition();
|
||||
var iterator = new TokenIterator(session, cursor.row, cursor.column);
|
||||
var token = iterator.getCurrentToken();
|
||||
if (token && token.value.match(/\s+/)) {
|
||||
token = iterator.stepBackward();
|
||||
}
|
||||
if (token && token.type === 'support.type') {
|
||||
var line = session.doc.getLine(cursor.row);
|
||||
var rightChar = line.substring(cursor.column, cursor.column + 1);
|
||||
if (rightChar === ':') {
|
||||
return {
|
||||
text: '',
|
||||
selection: [1, 1]
|
||||
};
|
||||
}
|
||||
if (/^(\s+[^;]|\s*$)/.test(line.substring(cursor.column))) {
|
||||
return {
|
||||
text: ':;',
|
||||
selection: [1, 1]
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
this.add("colon", "deletion", function (state, action, editor, session, range) {
|
||||
var selected = session.doc.getTextRange(range);
|
||||
if (!range.isMultiLine() && selected === ':') {
|
||||
var cursor = editor.getCursorPosition();
|
||||
var iterator = new TokenIterator(session, cursor.row, cursor.column);
|
||||
var token = iterator.getCurrentToken();
|
||||
if (token && token.value.match(/\s+/)) {
|
||||
token = iterator.stepBackward();
|
||||
}
|
||||
if (token && token.type === 'support.type') {
|
||||
var line = session.doc.getLine(range.start.row);
|
||||
var rightChar = line.substring(range.end.column, range.end.column + 1);
|
||||
if (rightChar === ';') {
|
||||
range.end.column++;
|
||||
return range;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
this.add("semicolon", "insertion", function (state, action, editor, session, text) {
|
||||
if (text === ';' && editor.selection.isEmpty()) {
|
||||
var cursor = editor.getCursorPosition();
|
||||
var line = session.doc.getLine(cursor.row);
|
||||
var rightChar = line.substring(cursor.column, cursor.column + 1);
|
||||
if (rightChar === ';') {
|
||||
return {
|
||||
text: '',
|
||||
selection: [1, 1]
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
this.add("!important", "insertion", function (state, action, editor, session, text) {
|
||||
if (text === '!' && editor.selection.isEmpty()) {
|
||||
var cursor = editor.getCursorPosition();
|
||||
var line = session.doc.getLine(cursor.row);
|
||||
if (/^\s*(;|}|$)/.test(line.substring(cursor.column))) {
|
||||
return {
|
||||
text: '!important',
|
||||
selection: [10, 10]
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
oop.inherits(CssBehaviour, CstyleBehaviour);
|
||||
exports.CssBehaviour = CssBehaviour;
|
||||
|
||||
});
|
||||
|
||||
ace.define("ace/mode/folding/cstyle",["require","exports","module","ace/lib/oop","ace/range","ace/mode/folding/fold_mode"], function(require, exports, module){"use strict";
|
||||
var oop = require("../../lib/oop");
|
||||
var Range = require("../../range").Range;
|
||||
var BaseFoldMode = require("./fold_mode").FoldMode;
|
||||
var FoldMode = exports.FoldMode = function (commentRegex) {
|
||||
if (commentRegex) {
|
||||
this.foldingStartMarker = new RegExp(this.foldingStartMarker.source.replace(/\|[^|]*?$/, "|" + commentRegex.start));
|
||||
this.foldingStopMarker = new RegExp(this.foldingStopMarker.source.replace(/\|[^|]*?$/, "|" + commentRegex.end));
|
||||
}
|
||||
};
|
||||
oop.inherits(FoldMode, BaseFoldMode);
|
||||
(function () {
|
||||
this.foldingStartMarker = /([\{\[\(])[^\}\]\)]*$|^\s*(\/\*)/;
|
||||
this.foldingStopMarker = /^[^\[\{\(]*([\}\]\)])|^[\s\*]*(\*\/)/;
|
||||
this.singleLineBlockCommentRe = /^\s*(\/\*).*\*\/\s*$/;
|
||||
this.tripleStarBlockCommentRe = /^\s*(\/\*\*\*).*\*\/\s*$/;
|
||||
this.startRegionRe = /^\s*(\/\*|\/\/)#?region\b/;
|
||||
this._getFoldWidgetBase = this.getFoldWidget;
|
||||
this.getFoldWidget = function (session, foldStyle, row) {
|
||||
var line = session.getLine(row);
|
||||
if (this.singleLineBlockCommentRe.test(line)) {
|
||||
if (!this.startRegionRe.test(line) && !this.tripleStarBlockCommentRe.test(line))
|
||||
return "";
|
||||
}
|
||||
var fw = this._getFoldWidgetBase(session, foldStyle, row);
|
||||
if (!fw && this.startRegionRe.test(line))
|
||||
return "start"; // lineCommentRegionStart
|
||||
return fw;
|
||||
};
|
||||
this.getFoldWidgetRange = function (session, foldStyle, row, forceMultiline) {
|
||||
var line = session.getLine(row);
|
||||
if (this.startRegionRe.test(line))
|
||||
return this.getCommentRegionBlock(session, line, row);
|
||||
var match = line.match(this.foldingStartMarker);
|
||||
if (match) {
|
||||
var i = match.index;
|
||||
if (match[1])
|
||||
return this.openingBracketBlock(session, match[1], row, i);
|
||||
var range = session.getCommentFoldRange(row, i + match[0].length, 1);
|
||||
if (range && !range.isMultiLine()) {
|
||||
if (forceMultiline) {
|
||||
range = this.getSectionRange(session, row);
|
||||
}
|
||||
else if (foldStyle != "all")
|
||||
range = null;
|
||||
}
|
||||
return range;
|
||||
}
|
||||
if (foldStyle === "markbegin")
|
||||
return;
|
||||
var match = line.match(this.foldingStopMarker);
|
||||
if (match) {
|
||||
var i = match.index + match[0].length;
|
||||
if (match[1])
|
||||
return this.closingBracketBlock(session, match[1], row, i);
|
||||
return session.getCommentFoldRange(row, i, -1);
|
||||
}
|
||||
};
|
||||
this.getSectionRange = function (session, row) {
|
||||
var line = session.getLine(row);
|
||||
var startIndent = line.search(/\S/);
|
||||
var startRow = row;
|
||||
var startColumn = line.length;
|
||||
row = row + 1;
|
||||
var endRow = row;
|
||||
var maxRow = session.getLength();
|
||||
while (++row < maxRow) {
|
||||
line = session.getLine(row);
|
||||
var indent = line.search(/\S/);
|
||||
if (indent === -1)
|
||||
continue;
|
||||
if (startIndent > indent)
|
||||
break;
|
||||
var subRange = this.getFoldWidgetRange(session, "all", row);
|
||||
if (subRange) {
|
||||
if (subRange.start.row <= startRow) {
|
||||
break;
|
||||
}
|
||||
else if (subRange.isMultiLine()) {
|
||||
row = subRange.end.row;
|
||||
}
|
||||
else if (startIndent == indent) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
endRow = row;
|
||||
}
|
||||
return new Range(startRow, startColumn, endRow, session.getLine(endRow).length);
|
||||
};
|
||||
this.getCommentRegionBlock = function (session, line, row) {
|
||||
var startColumn = line.search(/\s*$/);
|
||||
var maxRow = session.getLength();
|
||||
var startRow = row;
|
||||
var re = /^\s*(?:\/\*|\/\/|--)#?(end)?region\b/;
|
||||
var depth = 1;
|
||||
while (++row < maxRow) {
|
||||
line = session.getLine(row);
|
||||
var m = re.exec(line);
|
||||
if (!m)
|
||||
continue;
|
||||
if (m[1])
|
||||
depth--;
|
||||
else
|
||||
depth++;
|
||||
if (!depth)
|
||||
break;
|
||||
}
|
||||
var endRow = row;
|
||||
if (endRow > startRow) {
|
||||
return new Range(startRow, startColumn, endRow, line.length);
|
||||
}
|
||||
};
|
||||
}).call(FoldMode.prototype);
|
||||
|
||||
});
|
||||
|
||||
ace.define("ace/mode/css",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/css_highlight_rules","ace/mode/matching_brace_outdent","ace/worker/worker_client","ace/mode/css_completions","ace/mode/behaviour/css","ace/mode/folding/cstyle"], function(require, exports, module){"use strict";
|
||||
var oop = require("../lib/oop");
|
||||
var TextMode = require("./text").Mode;
|
||||
var CssHighlightRules = require("./css_highlight_rules").CssHighlightRules;
|
||||
var MatchingBraceOutdent = require("./matching_brace_outdent").MatchingBraceOutdent;
|
||||
var WorkerClient = require("../worker/worker_client").WorkerClient;
|
||||
var CssCompletions = require("./css_completions").CssCompletions;
|
||||
var CssBehaviour = require("./behaviour/css").CssBehaviour;
|
||||
var CStyleFoldMode = require("./folding/cstyle").FoldMode;
|
||||
var Mode = function () {
|
||||
this.HighlightRules = CssHighlightRules;
|
||||
this.$outdent = new MatchingBraceOutdent();
|
||||
this.$behaviour = new CssBehaviour();
|
||||
this.$completer = new CssCompletions();
|
||||
this.foldingRules = new CStyleFoldMode();
|
||||
};
|
||||
oop.inherits(Mode, TextMode);
|
||||
(function () {
|
||||
this.foldingRules = "cStyle";
|
||||
this.blockComment = { start: "/*", end: "*/" };
|
||||
this.getNextLineIndent = function (state, line, tab) {
|
||||
var indent = this.$getIndent(line);
|
||||
var tokens = this.getTokenizer().getLineTokens(line, state).tokens;
|
||||
if (tokens.length && tokens[tokens.length - 1].type == "comment") {
|
||||
return indent;
|
||||
}
|
||||
var match = line.match(/^.*\{\s*$/);
|
||||
if (match) {
|
||||
indent += tab;
|
||||
}
|
||||
return indent;
|
||||
};
|
||||
this.checkOutdent = function (state, line, input) {
|
||||
return this.$outdent.checkOutdent(line, input);
|
||||
};
|
||||
this.autoOutdent = function (state, doc, row) {
|
||||
this.$outdent.autoOutdent(doc, row);
|
||||
};
|
||||
this.getCompletions = function (state, session, pos, prefix) {
|
||||
return this.$completer.getCompletions(state, session, pos, prefix);
|
||||
};
|
||||
this.createWorker = function (session) {
|
||||
var worker = new WorkerClient(["ace"], "ace/mode/css_worker", "Worker");
|
||||
worker.attachToDocument(session.getDocument());
|
||||
worker.on("annotate", function (e) {
|
||||
session.setAnnotations(e.data);
|
||||
});
|
||||
worker.on("terminate", function () {
|
||||
session.clearAnnotations();
|
||||
});
|
||||
return worker;
|
||||
};
|
||||
this.$id = "ace/mode/css";
|
||||
this.snippetFileId = "ace/snippets/css";
|
||||
}).call(Mode.prototype);
|
||||
exports.Mode = Mode;
|
||||
|
||||
}); (function() {
|
||||
ace.require(["ace/mode/css"], function(m) {
|
||||
if (typeof module == "object" && typeof exports == "object" && module) {
|
||||
module.exports = m;
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -65,33 +65,3 @@ function deleteCookieBanner() {
|
||||
$div.remove();
|
||||
}, 400);
|
||||
}
|
||||
|
||||
/* function hasSessionCookie(callback) {
|
||||
$.ajax({
|
||||
url: '/myjupa/api/check-php-session.php',
|
||||
method: 'GET',
|
||||
cache: false, // Ensure we don't get a cached result
|
||||
success: function(response) {
|
||||
callback(response.cookieExists);
|
||||
},
|
||||
error: function() {
|
||||
callback(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Usage in your click handler
|
||||
$(document).on('click', 'a[href*="/myjupa/"]', function (el) {
|
||||
el.preventDefault();
|
||||
const target = $(this).attr("href");
|
||||
|
||||
hasSessionCookie(function(exists) {
|
||||
if (exists) {
|
||||
window.location.assign(target);
|
||||
} else {
|
||||
deleteCookieBanner();
|
||||
|
||||
displayCookieBanner(target);
|
||||
}
|
||||
});
|
||||
}); */
|
||||
@@ -23,6 +23,8 @@ jQuery(document).ready(function ($) {
|
||||
|
||||
});
|
||||
|
||||
const colors = ["rgb(255, 0, 0)", "rgb(0, 255, 0)", "#ff9d00"];
|
||||
|
||||
function displayMsg(type, msg) {
|
||||
const now = Date.now();
|
||||
|
||||
@@ -37,7 +39,6 @@ function displayMsg(type, msg) {
|
||||
lastMsgTime = now;
|
||||
lastMsgContent = msg;
|
||||
|
||||
const colors = ["rgb(255, 0, 0)", "rgb(0, 255, 0)", "#ff9d00"];
|
||||
if (type !== 0 && type !== 1 && type !== 2) return;
|
||||
|
||||
const d = new Date();
|
||||
@@ -58,33 +59,95 @@ function displayMsg(type, msg) {
|
||||
setTimeout(() => fadeOutDiv($div), 5000);
|
||||
}
|
||||
|
||||
function displayConfirm(type, msg) {
|
||||
function displayConfirm(msg = null) {
|
||||
return new Promise((resolve) => {
|
||||
const colors = ["rgb(255, 0, 0)", "rgb(0, 255, 0)", "#ff9d00"];
|
||||
if (![0, 1, 2].includes(type)) return resolve(false);
|
||||
|
||||
const $div = $('<div class="confirmBox"></div>')
|
||||
.css({ 'border-color': colors[type] })
|
||||
.text(msg);
|
||||
// Created clean layout references
|
||||
const $heading = $('<h3>', { class: 'confirmImportantHeading', text: 'Bitte bestätige diese Aktion' });
|
||||
const $text = $('<p>', { class: 'confirmImportantText', text: msg || 'Diese Aktion ist unumkehrbar.' });
|
||||
|
||||
const $buttonDiv = $('<div class="buttonConfirmDiv"></div>');
|
||||
const $buttonYes = $('<button class="confirmYesButton">Ja</button>');
|
||||
const $buttonNo = $('<button class="confirmNoButton">Nein</button>');
|
||||
const $submitBtn = $('<button>', { class: 'confirmImportantConfrimBtn', text: "Bestätigen" });
|
||||
const $cancelBtn = $('<button>', { class: 'confirmImportantCancelBtn', text: "Abbrechen" });
|
||||
const $btnDiv = $('<div>', { class: 'confirmImportantBtnDiv' }).append($cancelBtn, $submitBtn);
|
||||
|
||||
$buttonDiv.append($buttonNo, $buttonYes);
|
||||
$div.append($buttonDiv);
|
||||
$msgDiv.append($div);
|
||||
const $textDiv = $('<div>', { class: 'confirmImportantTextDiv' }).append($heading, $text);
|
||||
const $box = $('<div>', { class: 'confirmImportantBox' }).append($textDiv, $btnDiv);
|
||||
const $displayBg = $('<div>', { class: 'confirmImportantBg' }).append($box);
|
||||
|
||||
$buttonYes.on('click', function () {
|
||||
fadeOutDiv($div);
|
||||
resolve(true);
|
||||
$('body').append($displayBg);
|
||||
|
||||
const closeModal = (result) => {
|
||||
$(document).off('keydown', handleEscape);
|
||||
$displayBg.remove();
|
||||
resolve(result);
|
||||
};
|
||||
|
||||
const handleEscape = function (e) {
|
||||
if (e.key === 'Escape' || e.key === 'Esc') {
|
||||
closeModal(false);
|
||||
}
|
||||
};
|
||||
$(document).on('keydown', handleEscape);
|
||||
|
||||
$submitBtn.on('click', function () {
|
||||
closeModal(true);
|
||||
});
|
||||
|
||||
$buttonNo.on('click', function () {
|
||||
fadeOutDiv($div);
|
||||
resolve(false);
|
||||
$cancelBtn.on('click', function () {
|
||||
closeModal(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function displayConfirmImportant(msg = null, deleteString = "LOESCHEN") {
|
||||
return new Promise((resolve) => {
|
||||
|
||||
const trimmedString = deleteString.trim();
|
||||
|
||||
// Created clean layout references
|
||||
const $heading = $('<h3>', { class: 'confirmImportantHeading', text: 'Bitte bestätige diese Aktion' });
|
||||
const $text = $('<p>', { class: 'confirmImportantText', text: msg || 'Diese Aktion ist unumkehrbar.' });
|
||||
|
||||
const $label = $('<label>', { class: 'confirmImportantLabel', for: 'confirmImportantInput', text: `Bitte geben Sie im untenstehenden Feld "${deleteString}" ein, um fortzufahren` });
|
||||
const $input = $('<input>', { class: 'confirmImportantInput', id: 'confirmImportantInput', type: "text", placeholder: `Hier "${deleteString}" eingeben` });
|
||||
const $inputDiv = $('<div>', { class: 'confirmImportantInputDiv' }).append($label, $input);
|
||||
|
||||
const $submitBtn = $('<button>', { class: 'confirmImportantConfrimBtn notValidBtn', text: "Bestätigen" });
|
||||
const $cancelBtn = $('<button>', { class: 'confirmImportantCancelBtn', text: "Abbrechen" });
|
||||
const $btnDiv = $('<div>', { class: 'confirmImportantBtnDiv' }).append($cancelBtn, $submitBtn);
|
||||
|
||||
const $textDiv = $('<div>', { class: 'confirmImportantTextDiv' }).append($heading, $text);
|
||||
const $box = $('<div>', { class: 'confirmImportantBox' }).append($textDiv, $inputDiv, $btnDiv);
|
||||
const $displayBg = $('<div>', { class: 'confirmImportantBg' }).append($box);
|
||||
|
||||
$('body').append($displayBg);
|
||||
|
||||
const closeModal = (result) => {
|
||||
$(document).off('keydown', handleEscape);
|
||||
$displayBg.remove();
|
||||
resolve(result);
|
||||
};
|
||||
|
||||
const handleEscape = function (e) {
|
||||
if (e.key === 'Escape' || e.key === 'Esc') {
|
||||
closeModal(false);
|
||||
}
|
||||
};
|
||||
$(document).on('keydown', handleEscape);
|
||||
|
||||
$input.on('input', function () {
|
||||
const matches = $(this).val().trim() === trimmedString;
|
||||
$submitBtn.toggleClass('notValidBtn', !matches);
|
||||
$(this).toggleClass('ok', matches).toggleClass('notOk', !matches);
|
||||
});
|
||||
|
||||
setTimeout(() => $div.addClass('show'), 50);
|
||||
$submitBtn.on('click', function () {
|
||||
if ($input.val().trim() !== trimmedString || $submitBtn.hasClass('notValidBtn')) return;
|
||||
closeModal(true);
|
||||
});
|
||||
|
||||
$cancelBtn.on('click', function () {
|
||||
closeModal(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -19,6 +19,7 @@ $(document).on('click', '.selectOptions li', function (e) {
|
||||
|
||||
$input.val(val);
|
||||
$label.text(text);
|
||||
$input.trigger('change');
|
||||
$label.removeClass('placeholder');
|
||||
$container.removeClass('open');
|
||||
});
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
# QRCode.js
|
||||
QRCode.js is javascript library for making QRCode. QRCode.js supports Cross-browser with HTML5 Canvas and table tag in DOM.
|
||||
QRCode.js has no dependencies.
|
||||
|
||||
## Basic Usages
|
||||
```
|
||||
<div id="qrcode"></div>
|
||||
<script type="text/javascript">
|
||||
new QRCode(document.getElementById("qrcode"), "http://jindo.dev.naver.com/collie");
|
||||
</script>
|
||||
```
|
||||
|
||||
or with some options
|
||||
|
||||
```
|
||||
<div id="qrcode"></div>
|
||||
<script type="text/javascript">
|
||||
var qrcode = new QRCode(document.getElementById("qrcode"), {
|
||||
text: "http://jindo.dev.naver.com/collie",
|
||||
width: 128,
|
||||
height: 128,
|
||||
colorDark : "#000000",
|
||||
colorLight : "#ffffff",
|
||||
correctLevel : QRCode.CorrectLevel.H
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
and you can use some methods
|
||||
|
||||
```
|
||||
qrcode.clear(); // clear the code.
|
||||
qrcode.makeCode("http://naver.com"); // make another code.
|
||||
```
|
||||
|
||||
## Browser Compatibility
|
||||
IE6~10, Chrome, Firefox, Safari, Opera, Mobile Safari, Android, Windows Mobile, ETC.
|
||||
|
||||
## License
|
||||
MIT License
|
||||
|
||||
## Contact
|
||||
twitter @davidshimjs
|
||||
|
||||
[](https://bitdeli.com/free "Bitdeli Badge")
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ko" lang="ko">
|
||||
<head>
|
||||
<title>Cross-Browser QRCode generator for Javascript</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no" />
|
||||
<script type="text/javascript" src="jquery.min.js"></script>
|
||||
<script type="text/javascript" src="qrcode.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<input id="text" type="text" value="http://jindo.dev.naver.com/collie" style="width:80%" />
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="qrcode"/>
|
||||
</svg>
|
||||
<script type="text/javascript">
|
||||
var qrcode = new QRCode(document.getElementById("qrcode"), {
|
||||
width : 100,
|
||||
height : 100,
|
||||
useSVG: true
|
||||
});
|
||||
|
||||
function makeCode () {
|
||||
var elText = document.getElementById("text");
|
||||
|
||||
if (!elText.value) {
|
||||
alert("Input a text");
|
||||
elText.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
qrcode.makeCode(elText.value);
|
||||
}
|
||||
|
||||
makeCode();
|
||||
|
||||
$("#text").
|
||||
on("blur", function () {
|
||||
makeCode();
|
||||
}).
|
||||
on("keydown", function (e) {
|
||||
if (e.keyCode == 13) {
|
||||
makeCode();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,44 +0,0 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ko" lang="ko">
|
||||
<head>
|
||||
<title>Cross-Browser QRCode generator for Javascript</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no" />
|
||||
<script type="text/javascript" src="jquery.min.js"></script>
|
||||
<script type="text/javascript" src="qrcode.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<input id="text" type="text" value="http://jindo.dev.naver.com/collie" style="width:80%" /><br />
|
||||
<div id="qrcode" style="width:100px; height:100px; margin-top:15px;"></div>
|
||||
|
||||
<script type="text/javascript">
|
||||
var qrcode = new QRCode(document.getElementById("qrcode"), {
|
||||
width : 100,
|
||||
height : 100
|
||||
});
|
||||
|
||||
function makeCode () {
|
||||
var elText = document.getElementById("text");
|
||||
|
||||
if (!elText.value) {
|
||||
alert("Input a text");
|
||||
elText.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
qrcode.makeCode(elText.value);
|
||||
}
|
||||
|
||||
makeCode();
|
||||
|
||||
$("#text").
|
||||
on("blur", function () {
|
||||
makeCode();
|
||||
}).
|
||||
on("keydown", function (e) {
|
||||
if (e.keyCode == 13) {
|
||||
makeCode();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
@@ -1,37 +0,0 @@
|
||||
<?xml version="1.0" standalone="yes"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-50 0 200 100">
|
||||
<g id="qrcode"/>
|
||||
<foreignObject x="-50" y="0" width="100" height="100">
|
||||
<body xmlns="http://www.w3.org/1999/xhtml" style="padding:0; margin:0">
|
||||
<div style="padding:inherit; margin:inherit; height:100%">
|
||||
<textarea id="text" style="height:100%; width:100%; position:absolute; margin:inherit; padding:inherit">james</textarea>
|
||||
</div>
|
||||
<script type="application/ecmascript" src="qrcode.js"></script>
|
||||
<script type="application/ecmascript">
|
||||
var elem = document.getElementById("qrcode");
|
||||
var qrcode = new QRCode(elem, {
|
||||
width : 100,
|
||||
height : 100
|
||||
});
|
||||
|
||||
function makeCode () {
|
||||
var elText = document.getElementById("text");
|
||||
|
||||
if (elText.value === "") {
|
||||
//alert("Input a text");
|
||||
//elText.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
qrcode.makeCode(elText.value);
|
||||
}
|
||||
|
||||
makeCode();
|
||||
|
||||
document.getElementById("text").onkeyup = function (e) {
|
||||
makeCode();
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</foreignObject>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -75,12 +75,12 @@ function changeFreigabe(freigabe) {
|
||||
});
|
||||
}
|
||||
|
||||
$(document).on('click', '.sidebar-nav .selectTrigger', function (e) {
|
||||
$(document).on('click', '.sidebar-nav .selectTriggerSidebar', function (e) {
|
||||
e.stopPropagation();
|
||||
$(this).closest('.customSelect').toggleClass('open');
|
||||
});
|
||||
|
||||
$(document).on('click', '.sidebar-nav .selectOptions li', function (e) {
|
||||
$(document).on('click', '.sidebar-nav .selectOptionsSidebar li', function (e) {
|
||||
e.stopPropagation();
|
||||
const $item = $(this);
|
||||
const $container = $item.closest('.customSelect');
|
||||
|
||||
@@ -1,8 +1,21 @@
|
||||
<?php
|
||||
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
require_once __DIR__ . '/../../scripts/rate-limiter/rate-limiter.php';
|
||||
|
||||
$limiter = new TokenBucket(
|
||||
redis: $redis,
|
||||
capacity: 3,
|
||||
refillRate: 1,
|
||||
refillInterval: 5.0
|
||||
);
|
||||
|
||||
$result = $limiter->allow('RATE_LIMITER:TYPE:otlogin:IP:'. $_SERVER['REMOTE_ADDR']);
|
||||
|
||||
if (!$result['allowed']) {
|
||||
http_response_code(429);
|
||||
include __DIR__ . "/../error-pages/429.html";
|
||||
exit;
|
||||
}
|
||||
|
||||
use Dotenv\Dotenv;
|
||||
|
||||
@@ -10,6 +23,8 @@ if (!isset($baseDir)) {
|
||||
$baseDir = $_SERVER['DOCUMENT_ROOT'];
|
||||
}
|
||||
|
||||
date_default_timezone_set('Europe/Zurich');
|
||||
|
||||
require_once $baseDir . '/../scripts/session_functions.php';
|
||||
|
||||
ini_wkvs_session(true);
|
||||
@@ -295,29 +310,23 @@ class otl {
|
||||
|
||||
$pwClass = New otl();
|
||||
|
||||
/* ============================================================
|
||||
PASSWORD SET ON POST
|
||||
============================================================ */
|
||||
|
||||
|
||||
/* ============================================================
|
||||
ONE-TIME-LOGIN VALIDATION (GET)
|
||||
============================================================ */
|
||||
|
||||
require $baseDir .'/../scripts/db/db-verbindung-script-guest.php';
|
||||
|
||||
// fetch one-time login record
|
||||
|
||||
$now = date("Y-m-d H:i:s");
|
||||
|
||||
$result = db_select(
|
||||
$guest,
|
||||
$tableOTL,
|
||||
'id, user_id, `type`',
|
||||
'url = ? AND timestamp >= NOW() - INTERVAL 24 HOUR',
|
||||
[$oturl]
|
||||
'url = ? AND expires_at >= ?',
|
||||
[$oturl, $now]
|
||||
);
|
||||
|
||||
if (!$result || count($result) !== 1) {
|
||||
echo 'forbidden';
|
||||
http_response_code(403);
|
||||
http_response_code(400);
|
||||
include $baseDir . "/error-pages/400.html";
|
||||
exit;
|
||||
}
|
||||
|
||||
@@ -389,9 +398,10 @@ $guest->close();
|
||||
<link href="/files/fonts/fonts.css" rel="stylesheet">
|
||||
<link href="/intern/css/otl.css" rel="stylesheet">
|
||||
<script src="/intern/js/custom-msg-display.js"></script>
|
||||
<link rel="stylesheet" href="/intern/css/user.css">
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<body class="otlogin">
|
||||
<section class="page-secure-login">
|
||||
<div class="bg-picture-secure-login">
|
||||
<img src="/intern/img/login/bgOtl.webp">
|
||||
|
||||
@@ -10,19 +10,28 @@ ini_wkvs_session();
|
||||
|
||||
verify_csrf();
|
||||
|
||||
check_multiple_allowed_permissions(['kampfrichter', 'wk_leitung']);
|
||||
$accessPermission = trim($_POST['accessPermission'] ?? '');
|
||||
|
||||
$access = preg_replace("/[\W]/", "", trim($_POST['access'] ?? ''));
|
||||
$accesstype = preg_replace("/[\W]/", "", trim($_POST['accesstype'] ?? ''));
|
||||
|
||||
if (!isset($_POST['access'])) {
|
||||
if ($accessPermission === '' || $access === '' || $accesstype === '') {
|
||||
echo json_encode(['success' => false, 'message' => 'Parameters not correctly set']);
|
||||
http_response_code(400);
|
||||
exit;
|
||||
}
|
||||
|
||||
$access = preg_replace("/[\W]/", "", trim($_POST['access']));
|
||||
|
||||
require $baseDir . "/../scripts/websocket/ws-create-token.php";
|
||||
|
||||
$token = generateWSToken($access);
|
||||
if ($accessPermission === "R") {
|
||||
$token = generateWSReadToken($accesstype, $access);
|
||||
} elseif ($accessPermission === "W") {
|
||||
check_multiple_allowed_permissions(['kampfrichter', 'wk_leitung']);
|
||||
$token = generateWSAdminToken($accesstype, $access);
|
||||
} else {
|
||||
http_response_code(403);
|
||||
exit;
|
||||
}
|
||||
|
||||
$responseBool = $token != null;
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ if (!$new_value) {
|
||||
}
|
||||
|
||||
if ($type === 'kampfrichter'){
|
||||
$_SESSION['selectedFreigabeKampfrichter'] = $new_value;
|
||||
$_SESSION['selectedFreigabeIdKampfrichter'] = $new_value;
|
||||
}
|
||||
|
||||
if ($type === 'trainer'){
|
||||
|
||||
@@ -10,6 +10,9 @@ check_multiple_allowed_permissions(['trainer', 'wk_leitung']);
|
||||
|
||||
verify_csrf();
|
||||
|
||||
$isTrainer = isset($_SESSION['access_granted_trainer']) && $_SESSION['access_granted_trainer'];
|
||||
$userId = $isTrainer ? intval($_SESSION['user_id_trainer'] ?? 0) : intval($_SESSION['user_id_wk_leitung'] ?? 0);
|
||||
|
||||
// Allow large uploads and enough memory for GD processing
|
||||
ini_set('memory_limit', '256M');
|
||||
ini_set('max_execution_time', '120');
|
||||
@@ -41,7 +44,23 @@ $normalDir = $saveDir;
|
||||
|
||||
$uploadDir = $baseDir . $saveDir;
|
||||
|
||||
$maxLengthMusic = db_get_var($mysqli, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['maxLengthMusic']);
|
||||
$maxLengthMusic = 0;
|
||||
|
||||
if ($isTrainer) {
|
||||
$geraet_id = (int) $_POST['geraetId'] ?? 0;
|
||||
|
||||
$geraet_exists = db_get_var($mysqli, "SELECT 1 FROM $tableGeraete WHERE `id` = ?", [$geraet_id]);
|
||||
|
||||
if ($geraet_exists === null) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'message' => 'Invalides Gerät angegeben.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$geraet_max_duration_audio_db = db_get_var($mysqli, "SELECT `audiofile_max_duration` FROM $tableGeraete WHERE `id` = ?", [$geraet_id]);
|
||||
|
||||
$maxLengthMusic = (int) $geraet_max_duration_audio_db;
|
||||
}
|
||||
|
||||
if (!is_dir($uploadDir)) {
|
||||
mkdir($uploadDir, 0755, true);
|
||||
@@ -109,10 +128,10 @@ if ($isTrainer && $maxLengthMusic !== null && intval($maxLengthMusic) !== 0) {
|
||||
}
|
||||
|
||||
|
||||
$sql = "INSERT INTO $tableAudiofiles (`file_name`,`file_path`) VALUES (?, ?)";
|
||||
$sql = "INSERT INTO $tableAudiofiles (`file_name`,`file_path`,`edited_by`) VALUES (?, ?, ?)";
|
||||
|
||||
$stmt = $mysqli->prepare($sql);
|
||||
$stmt->bind_param("ss", $originalName, $normalPath);
|
||||
$stmt->bind_param("ssi", $originalName, $normalPath, $userId);
|
||||
|
||||
if (!$stmt->execute()) {
|
||||
http_response_code(500);
|
||||
|
||||
@@ -26,7 +26,7 @@ if ($type === 'ctext'){
|
||||
$ctext = isset($_POST['ctext']) ? $_POST['ctext'] : '';
|
||||
}
|
||||
|
||||
$folder = realpath($baseDir.'/displays/json');
|
||||
$folder = realpath($baseDir.'/externe-geraete/json');
|
||||
if ($folder === false) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
<?php
|
||||
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!isset($baseDir)) {
|
||||
@@ -31,10 +27,14 @@ require $baseDir . '/../scripts/db/db-tables.php';
|
||||
|
||||
$allowedTypes = [
|
||||
'wkName',
|
||||
'displayCTextLogo',
|
||||
'displayCTextWKName',
|
||||
'displayColourLogo',
|
||||
'displayTextColourLogo',
|
||||
'displayColorScoringBg',
|
||||
'displayColorScoringBgSoft',
|
||||
'displayColorScoringShadowColor',
|
||||
'displayColorScoringBorderColor',
|
||||
'displayColorScoringPanel',
|
||||
'displayColorScoringPanelSoft',
|
||||
'displayColorScoringPanelText',
|
||||
@@ -53,8 +53,15 @@ $allowedTypes = [
|
||||
'maxLengthMusic',
|
||||
'linkWebseite',
|
||||
'rangNote',
|
||||
'orderBestRang'
|
||||
'orderBestRang',
|
||||
'rechnungenPostversand',
|
||||
'rechnungenPostversandKosten',
|
||||
'personEinzahl',
|
||||
'personMehrzahl',
|
||||
'rankLivePublic',
|
||||
'riegeneinteilungPublic'
|
||||
];
|
||||
|
||||
$type = $_POST['type'] ? trim($_POST['type']) : '';
|
||||
|
||||
if (!in_array($type, $allowedTypes)) {
|
||||
|
||||
@@ -16,7 +16,6 @@ verify_csrf();
|
||||
require $baseDir . '/../scripts/db/db-functions.php';
|
||||
require $baseDir . '/../scripts/db/db-tables.php';
|
||||
|
||||
|
||||
$type = 'wkl';
|
||||
$dbconnection = require $baseDir . '/../scripts/db/db-verbindung-script.php';
|
||||
|
||||
@@ -25,20 +24,24 @@ if ($dbconnection['success'] !== true){
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once $baseDir . '/../scripts/cache/cache-creators/regenerate-display-dependencies-cache.php';
|
||||
|
||||
$action = $_POST['action'] ?? '';
|
||||
|
||||
if ($action === 'add') {
|
||||
$name = trim($_POST['name'] ?? '');
|
||||
$start_index = intval($_POST['start_index'] ?? 0);
|
||||
$color = trim($_POST['color'] ?? '#424242');
|
||||
$audiofile = intval($_POST['audiofile'] ?? 0);
|
||||
$audiofile_max_duration = (int) $_POST['audiofileMaxDuration'] ?? 0;
|
||||
|
||||
if (!$name) {
|
||||
if ($name === '') {
|
||||
echo json_encode(['success' => false, 'message' => 'Name ist erforderlich.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$stmt = $mysqli->prepare("INSERT INTO $tableGeraete (name, start_index, color_kampfrichter) VALUES (?, ?, ?)");
|
||||
$stmt->bind_param("sis", $name, $start_index, $color);
|
||||
$stmt = $mysqli->prepare("INSERT INTO $tableGeraete (`name`, `start_index`, `color_kampfrichter`, `audiofile`, `audiofile_max_duration`) VALUES (?, ?, ?, ?, ?)");
|
||||
$stmt->bind_param("sisii", $name, $start_index, $color, $audiofile, $audiofile_max_duration);
|
||||
$success = $stmt->execute();
|
||||
$new_id = $mysqli->insert_id;
|
||||
$stmt->close();
|
||||
@@ -49,14 +52,17 @@ if ($action === 'add') {
|
||||
echo json_encode(['success' => false, 'message' => 'Fehler beim Hinzufügen.']);
|
||||
}
|
||||
|
||||
generateIntersectCache($mysqli, $baseDir);
|
||||
|
||||
} elseif ($action === 'update') {
|
||||
$id = intval($_POST['id'] ?? 0);
|
||||
$field = $_POST['field'] ?? '';
|
||||
$value = $_POST['value'] ?? '';
|
||||
$audiofile = intval($_POST['audiofile'] ?? 0);
|
||||
|
||||
$allowedFields = ['name', 'start_index', 'color_kampfrichter'];
|
||||
$allowedFields = ['name', 'start_index', 'color_kampfrichter', 'audiofile', 'audiofile_max_duration'];
|
||||
if ($id > 0 && in_array($field, $allowedFields)) {
|
||||
if ($field === 'start_index') {
|
||||
if ($field === 'start_index' || $field === 'audiofile' || $field === 'audiofile_max_duration') {
|
||||
$value = intval($value);
|
||||
}
|
||||
|
||||
@@ -80,6 +86,8 @@ if ($action === 'add') {
|
||||
echo json_encode(['success' => false, 'message' => 'Invalid ID.']);
|
||||
}
|
||||
|
||||
generateIntersectCache($mysqli, $baseDir);
|
||||
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Action not found.']);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
ini_set("display_errors",1);
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!isset($baseDir)) {
|
||||
@@ -16,6 +17,8 @@ verify_csrf();
|
||||
require $baseDir . '/../scripts/db/db-functions.php';
|
||||
require $baseDir . '/../scripts/db/db-tables.php';
|
||||
|
||||
require $baseDir . '/../scripts/string-calculator/string-calculator-functions.php';
|
||||
|
||||
|
||||
$type = 'wkl';
|
||||
$dbconnection = require $baseDir . '/../scripts/db/db-verbindung-script.php';
|
||||
@@ -25,6 +28,11 @@ if ($dbconnection['success'] !== true){
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once $baseDir . '/../scripts/cache/cache-creators/regenerate-display-dependencies-cache.php';
|
||||
require_once $baseDir . '/../scripts/cache/cache-creators/regenerate-noten-cache.php';
|
||||
|
||||
|
||||
|
||||
$recalculateJSONs = false;
|
||||
|
||||
$action = $_POST['action'] ?? '';
|
||||
@@ -77,6 +85,8 @@ if ($action === 'add') {
|
||||
$recalculateJSONs = true;
|
||||
|
||||
if ($success) {
|
||||
regenerate_noten_cache($mysqli, $tableNotenBezeichnungen, $baseDir);
|
||||
generateIntersectCache($mysqli, $baseDir);
|
||||
if (!$recalculateJSONs) { echo json_encode(['success' => true, 'id' => $new_id]); }
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Fehler beim Hinzufügen.']);
|
||||
@@ -88,7 +98,7 @@ if ($action === 'add') {
|
||||
$field = $_POST['field'] ?? '';
|
||||
$value = $_POST['value'] ?? '';
|
||||
|
||||
$allowedFields = ['name', 'type', 'berechnung', 'pro_geraet', 'geraete_json', 'anzahl_laeufe_json', 'default_value', 'min_value', 'max_value', 'zeige_in_tabelle', 'zeige_in_tabelle_mobile', 'zeige_in_tabelle_admin', 'zeige_auf_rangliste', 'nullstellen', 'prefix_display'];
|
||||
$allowedFields = ['name', 'type', 'berechnung', 'pro_geraet', 'geraete_json', 'anzahl_laeufe_json', 'default_value', 'min_value', 'max_value', 'zeige_auf_rangliste', 'nullstellen', 'display_string', 'groesse_auf_rangliste'];
|
||||
if ($id > 0 && in_array($field, $allowedFields)) {
|
||||
if ($field === 'pro_geraet') {
|
||||
$value = intval($value);
|
||||
@@ -100,6 +110,8 @@ if ($action === 'add') {
|
||||
|
||||
$updated = db_update($mysqli, $tableNotenBezeichnungen, [$field => $value], ['id' => $id]);
|
||||
if ($updated !== false) {
|
||||
regenerate_noten_cache($mysqli, $tableNotenBezeichnungen, $baseDir);
|
||||
generateIntersectCache($mysqli, $baseDir);
|
||||
if (!$recalculateJSONs) { echo json_encode(['success' => true]); }
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'DB Update failed.']);
|
||||
@@ -115,6 +127,8 @@ if ($action === 'add') {
|
||||
|
||||
if ($id > 0) {
|
||||
db_delete($mysqli, $tableNotenBezeichnungen, ['id' => $id]);
|
||||
regenerate_noten_cache($mysqli, $tableNotenBezeichnungen, $baseDir);
|
||||
generateIntersectCache($mysqli, $baseDir);
|
||||
echo json_encode(['success' => true]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Invalid ID.']);
|
||||
@@ -143,7 +157,6 @@ if ($recalculateJSONs) {
|
||||
exit;
|
||||
}
|
||||
|
||||
require $baseDir . "/../scripts/string-calculator/string-calculator-functions.php";
|
||||
$notenRechner = new NotenRechner();
|
||||
|
||||
// 1. Build the direct map
|
||||
@@ -151,15 +164,23 @@ if ($recalculateJSONs) {
|
||||
$dependencyMap = [];
|
||||
|
||||
foreach ($berechnungen as $calc) {
|
||||
$neededIdsArray = $notenRechner->getBenoetigteIdsComplex($calc['berechnung']);
|
||||
$neededIdsArrayWithRun = $notenRechner->getBenoetigteIdsComplexWithRun($calc['berechnung']);
|
||||
|
||||
if (empty($neededIdsArray)) {
|
||||
if (empty($neededIdsArrayWithRun) || count($neededIdsArrayWithRun) < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$calcId = (int)$calc['id'];
|
||||
|
||||
foreach ($neededIdsArray as $needed) {
|
||||
$neededIdsArray = $neededIdsArrayWithRun;
|
||||
|
||||
unset($neededIdsArray['targetRun']);
|
||||
unset($neededIdsArray['targetGeraet']);
|
||||
|
||||
$targetRunId = $neededIdsArrayWithRun['targetRun'];
|
||||
$target_geraet_id = $neededIdsArrayWithRun['targetGeraet'];
|
||||
|
||||
foreach ($neededIdsArray as $key => $needed) {
|
||||
|
||||
$nId = (int)$needed['noteId'];
|
||||
|
||||
@@ -167,14 +188,14 @@ if ($recalculateJSONs) {
|
||||
$gId = is_numeric($needed['geraetId']) ? (int)$needed['geraetId'] : $needed['geraetId'];
|
||||
|
||||
// Create a unique string key so we don't store exact duplicates
|
||||
$nodeKey = $calcId . '|' . $gId;
|
||||
$nodeKey = $calcId . '|' . $gId . '|' . $targetRunId . '|' . $target_geraet_id; // e.g., "10|S|R2" or "12|3|RA"
|
||||
|
||||
if (!isset($dependencyMap[$nId])) {
|
||||
$dependencyMap[$nId] = [];
|
||||
}
|
||||
|
||||
// Store it as the "little array" you requested: [DependentCalcId, GeraetId]
|
||||
$dependencyMap[$nId][$nodeKey] = [$calcId, $gId];
|
||||
$dependencyMap[$nId][$nodeKey] = [$calcId, $gId, [$targetRunId, $target_geraet_id]];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!isset($baseDir)) {
|
||||
$baseDir = $_SERVER['DOCUMENT_ROOT'];
|
||||
}
|
||||
|
||||
require_once $baseDir . '/../scripts/session_functions.php';
|
||||
|
||||
ini_wkvs_session();
|
||||
|
||||
check_user_permission('wk_leitung');
|
||||
|
||||
verify_csrf();
|
||||
|
||||
if (!isset($_POST['css_content'])) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'message' => 'Missing css_content parameter.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$css_content = $_POST['css_content'] ?? '';
|
||||
|
||||
if (strlen($css_content) > 200000) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'message' => 'File size too large.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (preg_match('/javascript:/i', $css_content) || preg_match('/<script/i', $css_content)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'message' => 'Invalid characters detected.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
file_put_contents($baseDir . '/intern/css/user.css', $css_content);
|
||||
|
||||
http_response_code(200);
|
||||
echo json_encode(['success' => true, 'message' => 'CSS saved successfully.']);
|
||||
exit;
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!isset($baseDir)) {
|
||||
$baseDir = $_SERVER['DOCUMENT_ROOT'];
|
||||
}
|
||||
|
||||
require_once $baseDir . '/../scripts/session_functions.php';
|
||||
|
||||
ini_wkvs_session();
|
||||
|
||||
check_user_permission('wk_leitung');
|
||||
|
||||
verify_csrf();
|
||||
|
||||
require $baseDir . '/../scripts/db/db-functions.php';
|
||||
require $baseDir . '/../scripts/db/db-tables.php';
|
||||
|
||||
|
||||
$type = 'wkl';
|
||||
$dbconnection = require $baseDir . '/../scripts/db/db-verbindung-script.php';
|
||||
|
||||
if ($dbconnection['success'] !== true){
|
||||
echo json_encode(['success' => false, 'message' => 'Critical DB Error.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
$action = $_POST['action'] ?? '';
|
||||
|
||||
if ($action === 'add') {
|
||||
$name = trim($_POST['name'] ?? '');
|
||||
$has_endtime = intval($_POST['has_endtime'] ?? 0);
|
||||
$index = intval($_POST['index'] ?? 0);
|
||||
|
||||
if (!$name) {
|
||||
echo json_encode(['success' => false, 'message' => 'Name ist erforderlich.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$stmt = $mysqli->prepare("INSERT INTO $tableZeitplanTypes (`name`, `has_endtime`, `display_index`) VALUES (?, ?, ?)");
|
||||
$stmt->bind_param("sii", $name, $has_endtime, $index);
|
||||
$success = $stmt->execute();
|
||||
$new_id = $mysqli->insert_id;
|
||||
$stmt->close();
|
||||
|
||||
if ($success) {
|
||||
echo json_encode(['success' => true, 'id' => $new_id]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Fehler beim Hinzufügen.']);
|
||||
}
|
||||
|
||||
} elseif ($action === 'update') {
|
||||
$id = intval($_POST['id'] ?? 0);
|
||||
$field = $_POST['field'] ?? '';
|
||||
$value = $_POST['value'] ?? '';
|
||||
$audiofile = intval($_POST['audiofile'] ?? 0);
|
||||
|
||||
$allowedFields = ['name', 'has_endtime', 'display_index'];
|
||||
if ($id > 0 && in_array($field, $allowedFields)) {
|
||||
if ($field === 'display_index') {
|
||||
$value = intval($value);
|
||||
}
|
||||
|
||||
$updated = db_update($mysqli, $tableZeitplanTypes, [$field => $value], ['id' => $id]);
|
||||
if ($updated !== false) {
|
||||
echo json_encode(['success' => true]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'DB Update failed.']);
|
||||
}
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Invalid parameters.']);
|
||||
}
|
||||
|
||||
} elseif ($action === 'delete') {
|
||||
$id = intval($_POST['id'] ?? 0);
|
||||
|
||||
if ($id > 0) {
|
||||
db_delete($mysqli, $tableZeitplanTypes, ['id' => $id]);
|
||||
echo json_encode(['success' => true]);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Invalid ID.']);
|
||||
}
|
||||
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Action not found.']);
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
if (!isset($baseDir)) $baseDir = $_SERVER['DOCUMENT_ROOT'];
|
||||
|
||||
require_once $baseDir . '/../scripts/session_functions.php';
|
||||
|
||||
ini_wkvs_session();
|
||||
|
||||
check_user_permission('wk_leitung');
|
||||
|
||||
verify_csrf();
|
||||
|
||||
$type = 'wkl';
|
||||
|
||||
$data = include $baseDir . '/../scripts/db/db-verbindung-script.php';
|
||||
|
||||
if ($data['success'] === false){
|
||||
echo json_encode(['success' => false, 'message' => $data['message']]);
|
||||
exit;
|
||||
}
|
||||
|
||||
require $baseDir . '/../scripts/db/db-tables.php';
|
||||
require $baseDir . '/../scripts/db/db-functions.php';
|
||||
|
||||
try {
|
||||
$mysqli->set_charset("utf8mb4");
|
||||
|
||||
// 4. Fetch all rows ordered by ID so the layout hierarchy is preserved
|
||||
$sql = "SELECT * FROM `$tableNotenBezeichnungen` ORDER BY `id` ASC";
|
||||
$result = $mysqli->query($sql);
|
||||
|
||||
$exportData = [];
|
||||
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
$presetItem = [];
|
||||
|
||||
foreach ($row as $columnName => $value) {
|
||||
if ($columnName === 'berechnung_json') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($value === null) {
|
||||
$presetItem[$columnName] = null;
|
||||
} elseif (is_numeric($value)) {
|
||||
$presetItem[$columnName] = (strpos($value, '.') !== false) ? (float)$value : (int)$value;
|
||||
} else {
|
||||
$presetItem[$columnName] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$exportDataDisc[] = $presetItem;
|
||||
}
|
||||
|
||||
$result->free();
|
||||
|
||||
$geraete = db_select($mysqli, $tableGeraete, '*', '', [], 'start_index ASC');
|
||||
$programme = db_select($mysqli, $tableProgramme, '*', '', [], 'id ASC');
|
||||
$zeitplanTypen = db_select($mysqli, $tableZeitplanTypes, '*', '', [], 'id ASC');
|
||||
$rangNote = db_get_var($mysqli, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['rangNote']);
|
||||
$wkName = db_get_var($mysqli, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['wkName']);
|
||||
$orderBestRang = db_get_var($mysqli, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['orderBestRang']);
|
||||
|
||||
$personEinzahl = db_get_variable($mysqli, $tableVar, ['personEinzahl']);
|
||||
if ($personEinzahl === null) $personEinzahl = 'Person';
|
||||
|
||||
$personMehrzahl = db_get_variable($mysqli, $tableVar, ['personMehrzahl']);
|
||||
if ($personMehrzahl === null) $personMehrzahl = 'Personen';
|
||||
|
||||
$mysqli->close();
|
||||
|
||||
$exportData = [
|
||||
'metadata' => [
|
||||
'erstellt_am' => date("d.m.Y H:i"),
|
||||
'ersteller' => $_SERVER['HTTP_HOST'],
|
||||
'erstellt_mit' => 'WKVS Auto-Export',
|
||||
'wkvs_json_version' => '1.0.0',
|
||||
'titel' => 'WKVS Preset ' . $wkName . ' (V 1.0.0)'
|
||||
],
|
||||
'noten' => $exportDataDisc,
|
||||
'disziplinen' => $geraete,
|
||||
'zeitplan_typen' => $zeitplanTypen,
|
||||
'rang_konfiguration' => [
|
||||
'rang_note_id' => $rangNote,
|
||||
'order_best_rang' => $orderBestRang
|
||||
],
|
||||
"leistungsklassen" => $programme,
|
||||
"bezeichnungen" =>[
|
||||
"singular" => $personEinzahl,
|
||||
"plural"=> $personMehrzahl
|
||||
]
|
||||
];
|
||||
|
||||
$jsonOutput = json_encode($exportData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
|
||||
// 6. Force Browser File Download
|
||||
$fileName = 'WKVS-preset-config-' . date('Y-m-d_H-i') . '.json';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Content-Disposition: attachment; filename="' . $fileName . '"');
|
||||
header('Expires: 0');
|
||||
header('Cache-Control: must-revalidate');
|
||||
header('Pragma: public');
|
||||
header('Content-Length: ' . strlen($jsonOutput));
|
||||
|
||||
echo $jsonOutput;
|
||||
exit;
|
||||
|
||||
} catch (mysqli_sql_exception $e) {
|
||||
http_response_code(500);
|
||||
echo "Database export failed";
|
||||
}
|
||||
@@ -0,0 +1,350 @@
|
||||
<?php
|
||||
|
||||
ini_set("display_errors",1);
|
||||
|
||||
if (!isset($baseDir)) $baseDir = $_SERVER['DOCUMENT_ROOT'];
|
||||
|
||||
require_once $baseDir . '/../scripts/session_functions.php';
|
||||
|
||||
ini_wkvs_session();
|
||||
|
||||
check_user_permission('wk_leitung');
|
||||
|
||||
verify_csrf();
|
||||
|
||||
if (!isset($_FILES['configFile']) || $_FILES['configFile']['error'] !== UPLOAD_ERR_OK) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'Keine Config-Datei ausgewählt'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$wkvsJSONVersion = '1.0.0';
|
||||
|
||||
$type = 'wkl';
|
||||
|
||||
$data = include $baseDir . '/../scripts/db/db-verbindung-script.php';
|
||||
|
||||
if ($data['success'] === false){
|
||||
echo json_encode(['success' => false, 'message' => $data['message']]);
|
||||
exit;
|
||||
}
|
||||
|
||||
require $baseDir . '/../scripts/db/db-tables.php';
|
||||
require $baseDir . '/../scripts/db/db-functions.php';
|
||||
require $baseDir . '/../scripts/string-calculator/string-calculator-functions.php';
|
||||
|
||||
|
||||
|
||||
$saveDir = '/../private-files/config-uploads/';
|
||||
|
||||
$uploadDir = $baseDir . $saveDir;
|
||||
|
||||
|
||||
if (!is_dir($uploadDir)) {
|
||||
mkdir($uploadDir, 0755, true);
|
||||
}
|
||||
|
||||
|
||||
$tmpPath = $_FILES['configFile']['tmp_name'];
|
||||
$originalName = $_FILES['configFile']['name'];
|
||||
$extension = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
|
||||
|
||||
$allowedExt = ['json'];
|
||||
if (!in_array($extension, $allowedExt, true)) {
|
||||
echo json_encode(['success' => false, 'message' => 'Falsches Format (Endung)']);
|
||||
http_response_code(422);
|
||||
exit;
|
||||
}
|
||||
|
||||
$finfo = new finfo(FILEINFO_MIME_TYPE);
|
||||
$mimeType = $finfo->file($tmpPath);
|
||||
$allowedMime = ['application/json'];
|
||||
|
||||
if (!in_array($mimeType, $allowedMime, true)) {
|
||||
echo json_encode(['success' => false, 'message' => 'Dateiinhalt ist kein gültiges JSON']);
|
||||
http_response_code(422);
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
$filename = date("Y_m_d_H_i_s") . '-wkvs-config.json';
|
||||
$destination = $uploadDir . $filename;
|
||||
|
||||
|
||||
if (!move_uploaded_file($tmpPath, $destination)) {
|
||||
http_response_code(500);
|
||||
exit;
|
||||
}
|
||||
|
||||
$JSONcontent = file_get_contents($destination);
|
||||
|
||||
$content = json_decode($JSONcontent, true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
die("Invalid JSON layout.");
|
||||
}
|
||||
|
||||
if (!isset($content['metadata']['wkvs_json_version']) || $content['metadata']['wkvs_json_version'] !== $wkvsJSONVersion) {
|
||||
echo json_encode(['success' => false, 'message' => 'Inkompatible Datei-Version. (V ' . $wkvsJSONVersion . ' benötigt'. (isset($content['metadata']['wkvs_json_version']) ? ", benutzte Datei-Version: V " . $content['metadata']['wkvs_json_version'] : '') . ')' ]);
|
||||
unlink($destination);
|
||||
http_response_code(422);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!isset($content['noten']) || !is_array($content['noten']) || !isset($content['disziplinen']) || !is_array($content['disziplinen'])) {
|
||||
echo json_encode(['success' => false, 'message' => 'Ungültige JSON-Struktur']);
|
||||
unlink($destination);
|
||||
http_response_code(422);
|
||||
exit;
|
||||
}
|
||||
|
||||
$content['added_timestamp'] = date("Y-m-d H:i:s");
|
||||
|
||||
file_put_contents($destination, json_encode($content));
|
||||
|
||||
$allowedColumns = [
|
||||
'noten' => [
|
||||
'id', 'name', 'default_value', 'type', 'berechnung', 'berechnung_json',
|
||||
'max_value', 'min_value', 'pro_geraet', 'anzahl_laeufe_json', 'geraete_json',
|
||||
'nullstellen', 'display_string'
|
||||
],
|
||||
'disziplinen' => ['id', 'name', 'start_index', 'color_kampfrichter', 'audiofile'],
|
||||
'zeitplan_typen' => ['id', 'name', 'has_endtime', 'display_index'],
|
||||
'leistungsklassen' => ['id', 'programm', 'order_index', 'preis', 'aktiv']
|
||||
];
|
||||
|
||||
$tableTable = [
|
||||
'noten' => $tableNotenBezeichnungen,
|
||||
'disziplinen' => $tableGeraete,
|
||||
'zeitplan_typen' => $tableZeitplanTypes,
|
||||
'leistungsklassen' => $tableProgramme
|
||||
];
|
||||
|
||||
$successCount = 0;
|
||||
|
||||
try {
|
||||
|
||||
$mysqli->begin_transaction();
|
||||
|
||||
foreach ($allowedColumns as $type => $columns) {
|
||||
|
||||
$tableName = "`" . $tableTable[$type] . "`";
|
||||
|
||||
$mysqli->query("DELETE FROM " . $tableName);
|
||||
|
||||
$columnsSql = implode(', ', array_map(fn($col) => "`$col`", $columns));
|
||||
$placeholdersSql = implode(', ', array_fill(0, count($columns), '?'));
|
||||
|
||||
$sql = "INSERT INTO " . $tableName . " ($columnsSql)
|
||||
VALUES ($placeholdersSql)";
|
||||
|
||||
$typesString = str_repeat('s', count($columns));
|
||||
|
||||
$stmt = $mysqli->prepare($sql);
|
||||
|
||||
foreach ($content[$type] as $index => $row) {
|
||||
$bindValues = [];
|
||||
|
||||
foreach ($columns as $column) {
|
||||
if (!array_key_exists($column, $row) || $row[$column] === null) {
|
||||
$bindValues[] = null;
|
||||
} else {
|
||||
$bindValues[] = $row[$column];
|
||||
}
|
||||
}
|
||||
|
||||
$stmt->bind_param($typesString, ...$bindValues);
|
||||
$stmt->execute();
|
||||
$successCount++;
|
||||
}
|
||||
|
||||
$stmt->close();
|
||||
}
|
||||
|
||||
$rangNote = intval($content['rang_konfiguration']['rang_note_id'] ?? 0);
|
||||
$orderBestRang = $content['rang_konfiguration']['order_best_rang'] ?? '';
|
||||
|
||||
$allowableOrderValues = ['ASC', 'DESC'];
|
||||
|
||||
if ($rangNote > 0) {
|
||||
$mysqli->query("INSERT INTO $tableVar (`name`, `value`) VALUES ('rangNote', '" . $rangNote . "') ON DUPLICATE KEY UPDATE `value` = VALUES(`value`)");
|
||||
}
|
||||
|
||||
if (in_array($orderBestRang, $allowableOrderValues, true)) {
|
||||
$mysqli->query("INSERT INTO $tableVar (`name`, `value`) VALUES ('orderBestRang', '" . $orderBestRang . "') ON DUPLICATE KEY UPDATE `value` = VALUES(`value`)");
|
||||
}
|
||||
|
||||
if (isset($content['bezeichnungen']['singular']) && $content['bezeichnungen']['singular'] !== null) {
|
||||
$stmt = $mysqli->prepare("INSERT INTO $tableVar (`name`, `value`) VALUES (?, ?) ON DUPLICATE KEY UPDATE `value` = VALUES(`value`)");
|
||||
$varName = 'personEinzahl';
|
||||
$stmt->bind_param("ss", $varName, $content['bezeichnungen']['singular']);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
}
|
||||
|
||||
if (isset($content['bezeichnungen']['plural']) && $content['bezeichnungen']['plural'] !== null) {
|
||||
$stmt = $mysqli->prepare("INSERT INTO $tableVar (`name`, `value`) VALUES (?, ?) ON DUPLICATE KEY UPDATE `value` = VALUES(`value`)");
|
||||
$varName = 'personMehrzahl';
|
||||
$stmt->bind_param("ss", $varName, $content['bezeichnungen']['plural']);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
}
|
||||
|
||||
|
||||
$noten = db_select($mysqli, $tableNotenBezeichnungen, "id, berechnung, type");
|
||||
|
||||
$notenById = array_column($noten, null, 'id');
|
||||
|
||||
$berechnungen = [];
|
||||
foreach ($notenById as $id => $sn) {
|
||||
if ($sn['type'] === 'berechnung') {
|
||||
$berechnungen[] = $sn;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($berechnungen)) {
|
||||
$mysqli->commit();
|
||||
echo json_encode(['success' => true, 'message' => "Successfully imported $successCount rows."]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$notenRechner = new NotenRechner();
|
||||
|
||||
$dependencyMap = [];
|
||||
|
||||
foreach ($berechnungen as $calc) {
|
||||
$neededIdsArrayWithRun = $notenRechner->getBenoetigteIdsComplexWithRun($calc['berechnung']);
|
||||
|
||||
if (empty($neededIdsArrayWithRun) || count($neededIdsArrayWithRun) < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$calcId = (int)$calc['id'];
|
||||
|
||||
$neededIdsArray = $neededIdsArrayWithRun;
|
||||
|
||||
unset($neededIdsArray['targetRun']);
|
||||
unset($neededIdsArray['targetGeraet']);
|
||||
|
||||
$targetRunId = $neededIdsArrayWithRun['targetRun'];
|
||||
|
||||
foreach ($neededIdsArray as $key => $needed) {
|
||||
|
||||
$nId = (int)$needed['noteId'];
|
||||
|
||||
$gId = is_numeric($needed['geraetId']) ? (int)$needed['geraetId'] : $needed['geraetId'];
|
||||
|
||||
$nodeKey = $calcId . '|' . $gId . '|' . $targetRunId;
|
||||
|
||||
if (!isset($dependencyMap[$nId])) {
|
||||
$dependencyMap[$nId] = [];
|
||||
}
|
||||
|
||||
$dependencyMap[$nId][$nodeKey] = [$calcId, $gId, [$targetRunId]];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function getCompleteDependencyChain($id, $directMap, $visited = [])
|
||||
{
|
||||
if (!isset($directMap[$id])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$allDependencies = [];
|
||||
|
||||
foreach ($directMap[$id] as $nodeKey => $complexNode) {
|
||||
if (isset($visited[$nodeKey])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$visited[$nodeKey] = true;
|
||||
|
||||
$allDependencies[$nodeKey] = $complexNode;
|
||||
|
||||
$childDependencies = getCompleteDependencyChain($complexNode[0], $directMap, $visited);
|
||||
|
||||
foreach ($childDependencies as $childKey => $childNode) {
|
||||
$allDependencies[$childKey] = $childNode;
|
||||
$visited[$childKey] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $allDependencies;
|
||||
}
|
||||
|
||||
$flatDependencyMap = [];
|
||||
|
||||
foreach (array_keys($notenById) as $id) {
|
||||
$chain = getCompleteDependencyChain($id, $dependencyMap);
|
||||
|
||||
if (!empty($chain)) {
|
||||
$flatDependencyMap[$id] = array_values($chain);
|
||||
}
|
||||
}
|
||||
|
||||
$resetSql = "UPDATE $tableNotenBezeichnungen SET `berechnung_json` = NULL";
|
||||
$mysqli->query($resetSql);
|
||||
|
||||
// Step 2: Prepare the statement
|
||||
$updateSql = "UPDATE $tableNotenBezeichnungen SET `berechnung_json` = ? WHERE id = ?";
|
||||
$stmt = $mysqli->prepare($updateSql);
|
||||
|
||||
foreach ($flatDependencyMap as $id => $completeDependencyArray) {
|
||||
if (empty($completeDependencyArray)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$jsonString = json_encode($completeDependencyArray);
|
||||
|
||||
$stmt->bind_param("si", $jsonString, $id);
|
||||
$stmt->execute();
|
||||
}
|
||||
|
||||
$stmt->close();
|
||||
|
||||
$mysqli->commit();
|
||||
echo json_encode(['success' => true, 'message' => "Successfully imported $successCount rows."]);
|
||||
|
||||
require_once $baseDir . '/../scripts/cache/cache-creators/regenerate-display-dependencies-cache.php';
|
||||
require_once $baseDir . '/../scripts/cache/cache-creators/regenerate-noten-cache.php';
|
||||
|
||||
generateIntersectCache($mysqli, $baseDir);
|
||||
regenerate_noten_cache($mysqli, $tableNotenBezeichnungen, $baseDir);
|
||||
|
||||
} catch (mysqli_sql_exception $e) {
|
||||
$mysqli->rollback();
|
||||
unlink($destination);
|
||||
|
||||
if ($e->getCode() === 1048) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'message' => "Import failed: A required data field is missing in your preset file."]);
|
||||
exit;
|
||||
} else {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'message' => "Database error during import." . " Error Code: " . $e->getMessage()]);
|
||||
exit;
|
||||
}
|
||||
} finally {
|
||||
$mysqli->close();
|
||||
$allFiles = glob($uploadDir . '*.json');
|
||||
|
||||
$timestamps = array_map(function($file) {
|
||||
$jsonContent = file_get_contents($file) ?? '';
|
||||
|
||||
$data = json_decode($jsonContent, true) ?? [];
|
||||
|
||||
return $data['added_timestamp'] ?? 0;
|
||||
}, $allFiles);
|
||||
|
||||
array_multisort($timestamps, SORT_DESC, $allFiles);
|
||||
|
||||
$filesToDelete = array_slice($allFiles, 3);
|
||||
|
||||
foreach ($filesToDelete as $file) {
|
||||
unlink($file);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!isset($baseDir)) {
|
||||
$baseDir = $_SERVER['DOCUMENT_ROOT'];
|
||||
}
|
||||
|
||||
ini_set('display_errors', 1);
|
||||
|
||||
require_once $baseDir . '/../scripts/session_functions.php';
|
||||
|
||||
ini_wkvs_session();
|
||||
|
||||
check_user_permission('wk_leitung');
|
||||
check_user_permission('kampfrichter');
|
||||
|
||||
require_once $baseDir . '/../scripts/delete_request_type_functions.php';
|
||||
|
||||
parse_input_to_delete();
|
||||
|
||||
verify_delete_csrf($_DELETE);
|
||||
|
||||
$programm_id = (int) ($_DELETE['programmId'] ?? 0);
|
||||
|
||||
if ($programm_id <= 0) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'message' => 'Keine valide Programm-ID']);
|
||||
exit;
|
||||
}
|
||||
|
||||
require $baseDir . '/../scripts/db/db-functions.php';
|
||||
require $baseDir . '/../scripts/db/db-tables.php';
|
||||
|
||||
|
||||
$type = 'wkl';
|
||||
|
||||
$dbconnection = require $baseDir . '/../scripts/db/db-verbindung-script.php';
|
||||
|
||||
if ($dbconnection['success'] !== true){
|
||||
echo 'Critical DB Error.';
|
||||
exit;
|
||||
}
|
||||
|
||||
$programm_name = db_get_var($mysqli, "SELECT `programm` FROM $tableProgramme WHERE id = ? LIMIT 1", [$programm_id]) ?: null;
|
||||
|
||||
if ($programm_name === null) {
|
||||
echo json_encode(['success' => true, 'message' => 'Dieses Programm existiert nicht.']);
|
||||
http_response_code(400);
|
||||
exit;
|
||||
}
|
||||
|
||||
$personen = db_select($mysqli, $tableTeilnehmende, "id", '`programm` = ?', [$programm_name]);
|
||||
|
||||
if (count($personen) < 1) {
|
||||
echo json_encode(['success' => true, 'message' => 'Es existieren keine Personen mit diesem Programm.']);
|
||||
http_response_code(400);
|
||||
exit;
|
||||
}
|
||||
|
||||
$personen_id_array = array_column($personen, 'id');
|
||||
|
||||
$parram_array = array_fill(0, count($personen_id_array), "?");
|
||||
$parram_string = implode(', ', $parram_array);
|
||||
|
||||
$year = date('n') > 6 ? intval(date('Y')) + 1 : intval(date('Y'));
|
||||
|
||||
$value_array = array_merge($personen_id_array, [$year]);
|
||||
|
||||
$stmt = $mysqli->prepare("DELETE FROM $tableNoten WHERE `person_id` IN ($parram_string) AND `jahr` = ?");
|
||||
|
||||
$types_string = str_repeat("i", count($personen_id_array)) . "i";
|
||||
$stmt->bind_param($types_string, ...$value_array);
|
||||
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
$stmt = $mysqli->prepare("DELETE FROM $tableNotenChanges WHERE `person_id` IN ($parram_string) AND `jahr` = ?");
|
||||
|
||||
$stmt->bind_param($types_string, ...$value_array);
|
||||
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
echo json_encode(['success' => true, 'message' => 'Noten gelöscht']);
|
||||
http_response_code(200);
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!isset($baseDir)) {
|
||||
$baseDir = $_SERVER['DOCUMENT_ROOT'];
|
||||
}
|
||||
|
||||
require_once $baseDir . '/../scripts/session_functions.php';
|
||||
|
||||
ini_wkvs_session();
|
||||
|
||||
check_user_permission('kampfrichter');
|
||||
|
||||
verify_csrf();
|
||||
|
||||
if (!isset($baseDir)) {
|
||||
$baseDir = $_SERVER['DOCUMENT_ROOT'];
|
||||
}
|
||||
|
||||
$type = 'kr';
|
||||
$data = require $baseDir . '/../scripts/db/db-verbindung-script.php';
|
||||
|
||||
if ($data['success'] === false){
|
||||
echo json_encode(['success' => false, 'message' => $data['message']]);
|
||||
exit;
|
||||
}
|
||||
|
||||
require $baseDir . '/../scripts/db/db-tables.php';
|
||||
require $baseDir . '/../scripts/db/db-functions.php';
|
||||
|
||||
$abt_id = intval($_POST['abteilungId'] ?? 1);
|
||||
|
||||
// Determine current year
|
||||
$current_year = date('Y');
|
||||
$monat = date('n');
|
||||
if ($monat > 6) $current_year++;
|
||||
|
||||
// Prepare SQL statement
|
||||
$stmt = $mysqli->prepare("
|
||||
SELECT
|
||||
t.id AS person_id
|
||||
FROM $tableTeilnehmendeAbt tab
|
||||
INNER JOIN $tableTeilnehmende t ON tab.turnerin_id = t.id
|
||||
INNER JOIN $tableAbt ab ON ab.id = tab.abteilung_id
|
||||
WHERE ab.id = ?
|
||||
ORDER BY t.id ASC
|
||||
");
|
||||
|
||||
$stmt->bind_param('i', $abt_id);
|
||||
$stmt->execute();
|
||||
|
||||
$result = $stmt->get_result();
|
||||
$personen = $result->fetch_all(MYSQLI_ASSOC);
|
||||
|
||||
$indexedPersonen = array_column($personen, null, 'person_id');
|
||||
|
||||
// Close statement
|
||||
$stmt->close();
|
||||
|
||||
// 1. Get the IDs from the first query results
|
||||
$personenIds = array_column($personen, 'person_id');
|
||||
|
||||
if (empty($personenIds)) {
|
||||
echo json_encode(["success" => false, "message" => "Keine Notenänderungen vorhanden"]);
|
||||
http_response_code(400);
|
||||
exit;
|
||||
}
|
||||
|
||||
$placeholders = implode(',', array_fill(0, count($personenIds), '?'));
|
||||
|
||||
$sqlNotenChanges = "DELETE FROM $tableNotenChanges WHERE person_id IN ($placeholders) AND `jahr` = ?";
|
||||
$stmtNotenChanges = $mysqli->prepare($sqlNotenChanges);
|
||||
|
||||
$paramsArray = array_merge($personenIds, [$current_year]);
|
||||
|
||||
$types = str_repeat('i', count($personenIds)) . 's';
|
||||
|
||||
$stmtNotenChanges->bind_param($types, ...$paramsArray);
|
||||
|
||||
$stmtNotenChanges->execute();
|
||||
$stmtNotenChanges->close();
|
||||
|
||||
echo json_encode(["success" => true, "message" => "Notenänderung für Abteilung " . $abt_id . " gelöscht"]);
|
||||
http_response_code(200);
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!isset($baseDir)) {
|
||||
$baseDir = $_SERVER['DOCUMENT_ROOT'];
|
||||
}
|
||||
|
||||
require_once $baseDir . '/../scripts/session_functions.php';
|
||||
|
||||
ini_wkvs_session();
|
||||
|
||||
check_user_permission('kampfrichter');
|
||||
|
||||
verify_csrf();
|
||||
|
||||
if (!isset($baseDir)) {
|
||||
$baseDir = $_SERVER['DOCUMENT_ROOT'];
|
||||
}
|
||||
|
||||
$type = 'kr';
|
||||
$data = require $baseDir . '/../scripts/db/db-verbindung-script.php';
|
||||
|
||||
if ($data['success'] === false){
|
||||
echo json_encode(['success' => false, 'message' => $data['message']]);
|
||||
exit;
|
||||
}
|
||||
|
||||
require $baseDir . '/../scripts/db/db-tables.php';
|
||||
require $baseDir . '/../scripts/db/db-functions.php';
|
||||
|
||||
$wkName = db_get_var($mysqli, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['wkName']);
|
||||
|
||||
$mysqli->close();
|
||||
|
||||
if ($wkName === null) {
|
||||
echo json_encode(["success" => false, "message" => "Keinen Wettkampfname angegeben. Setzen unter: /intern/wk-leitung/einstellungen"]);
|
||||
http_response_code(400);
|
||||
exit;
|
||||
}
|
||||
|
||||
$programm = trim($_POST['prog'] ?? '');
|
||||
|
||||
$current_year = date('Y');
|
||||
$monat = date('n');
|
||||
if ($monat > 6) $current_year++;
|
||||
|
||||
$dir = $baseDir . '/files/ranglisten/';
|
||||
$localPath = $dir . str_replace("/", "", str_replace(" ", "_", $wkName)) . "_Ergebnisse_" . str_replace("/", "", $programm) . "_" . $current_year . ".pdf";
|
||||
|
||||
if (!file_exists($localPath)) {
|
||||
echo json_encode(["success" => false, "message" => "Diese Rangliste existiert nicht."]);
|
||||
http_response_code(400);
|
||||
exit;
|
||||
}
|
||||
|
||||
unlink($localPath);
|
||||
|
||||
echo json_encode(["success" => true, "message" => "Rangliste gelöscht"]);
|
||||
http_response_code(200);
|
||||
@@ -14,13 +14,6 @@ check_user_permission('kampfrichter');
|
||||
|
||||
verify_csrf();
|
||||
|
||||
|
||||
|
||||
if (!verify_csrf()) {
|
||||
echo json_encode(['success' => false, 'message' => 'Forbidden']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Validate editId from POST
|
||||
if (isset($_POST['editId'])) {
|
||||
$editId = intval($_POST['editId']);
|
||||
@@ -37,8 +30,6 @@ if ($editId === false) {
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
|
||||
require $baseDir . '/../scripts/db/db-functions.php';
|
||||
require $baseDir . '/../scripts/db/db-tables.php';
|
||||
|
||||
@@ -51,21 +42,41 @@ if (!($data['success'] ?? false)) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$isAdmin = (($_SESSION['selectedFreigabeKampfrichter'] ?? '') === 'admin') ? true : false;
|
||||
$is_payed = db_get_var($mysqli, "SELECT 1 FROM $tableTeilnehmende WHERE id = ? AND ((bezahltoverride = ?) OR (bezahlt = ? AND bezahltoverride != ? AND bezahltoverride != ?))", [$editId, 5, 2, 3, 4]);
|
||||
|
||||
if ($is_payed != '1') {
|
||||
echo json_encode(['success'=> false, 'message'=> 'Startgebühr nicht bezahlt']);
|
||||
http_response_code(400);
|
||||
exit;
|
||||
}
|
||||
|
||||
$isAdmin = (($_SESSION['selectedFreigabeIdKampfrichter'] ?? '') === 'A') ? true : false;
|
||||
|
||||
$disciplines = db_select($mysqli, $tableGeraete, 'id', '', [], 'start_index ASC');
|
||||
|
||||
$disciplines = array_column($disciplines, "id");
|
||||
|
||||
if (!$isAdmin) {
|
||||
$requested_discipline = trim($_POST['geraet'] ?? '');
|
||||
|
||||
$discipline = intval($_POST['geraet']) ?? 0;
|
||||
$all_disciplines = false;
|
||||
|
||||
if (!in_array($discipline, $disciplines)) {
|
||||
if ($isAdmin && $requested_discipline === 'A') {
|
||||
$all_disciplines = true;
|
||||
} else {
|
||||
$requested_discipline = (int) $requested_discipline;
|
||||
|
||||
if (!in_array($requested_discipline, $disciplines)) {
|
||||
echo json_encode(['success' => false, 'message' => 'Falsche Geräte ID']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$discipline = $requested_discipline;
|
||||
}
|
||||
|
||||
if ($all_disciplines) {
|
||||
$stmt = $mysqli->prepare("SELECT t.`name`, t.`vorname`, t.`programm`, p.id as programm_id FROM $tableTeilnehmende t LEFT JOIN $tableProgramme p ON p.programm = t.programm WHERE t.id = ?");
|
||||
} else {
|
||||
|
||||
$disciplines = [$discipline];
|
||||
|
||||
|
||||
@@ -78,7 +89,7 @@ if (!$isAdmin) {
|
||||
agg.abteilung,
|
||||
agg.geraeteIndex,
|
||||
agg.startIndex
|
||||
FROM $tableTurnerinnen t
|
||||
FROM $tableTeilnehmende t
|
||||
LEFT JOIN $tableProgramme p ON p.programm = t.programm
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
@@ -86,7 +97,7 @@ if (!$isAdmin) {
|
||||
GROUP_CONCAT(DISTINCT a.name SEPARATOR ', ') AS abteilung,
|
||||
GROUP_CONCAT(DISTINCT g.start_index SEPARATOR ', ') AS geraeteIndex,
|
||||
ta.turnerin_index AS startIndex
|
||||
FROM $tableTurnerinnenAbt ta
|
||||
FROM $tableTeilnehmendeAbt ta
|
||||
INNER JOIN $tableAbt a
|
||||
ON a.id = ta.abteilung_id
|
||||
LEFT JOIN $tableGeraete g
|
||||
@@ -96,10 +107,7 @@ if (!$isAdmin) {
|
||||
WHERE t.id = ?
|
||||
");
|
||||
|
||||
} else {
|
||||
$stmt = $mysqli->prepare("SELECT t.`name`, t.`vorname`, t.`programm`, p.id as programm_id FROM $tableTurnerinnen t LEFT JOIN $tableProgramme p ON p.programm = t.programm WHERE t.id = ?");
|
||||
}
|
||||
|
||||
$stmt->bind_param('i', $editId);
|
||||
|
||||
$stmt->execute();
|
||||
@@ -118,7 +126,7 @@ $now = new DateTime();
|
||||
|
||||
$jahr = ($now->format('n') > 6) ? $now->modify('+1 year')->format('Y') : $now->format('Y');
|
||||
|
||||
if ($isAdmin) {
|
||||
if ($all_disciplines) {
|
||||
$stmt = $mysqli->prepare("SELECT `note_bezeichnung_id`, `value`, `geraet_id`, `run_number` FROM $tableNoten WHERE `person_id` = ? AND `jahr` = ?");
|
||||
|
||||
$stmt->bind_param('ss', $editId, $jahr);
|
||||
@@ -177,7 +185,7 @@ foreach ($disciplines as $d) {
|
||||
// Determine number of runs for this program
|
||||
$anzRunsConfig = !empty($snC['anzahl_laeufe_json']) ? json_decode($snC['anzahl_laeufe_json'], true) : [];
|
||||
|
||||
$runs = $anzRunsConfig[$d][$programm_id] ?? $anzRunsConfig['default'] ?? 1;
|
||||
$runs = $anzRunsConfig[$d][$programm_id] ?? $anzRunsConfig[$d]["all"] ?? $anzRunsConfig['default'] ?? 1;
|
||||
|
||||
if (isset($displayNoten) && array_key_exists($snC['id'], $displayNoten)) {
|
||||
$displayNoten[$snC['id']] = $runs;
|
||||
@@ -199,10 +207,9 @@ if (isset($displayNoten)) {
|
||||
|
||||
$titel = $row['vorname'].' '.$row['name'].', '.$row['programm'];
|
||||
|
||||
if (!$isAdmin) {
|
||||
|
||||
// $entries = db_select($mysqli, $tableTurnerinnen, 'name, vorname, programm, id', 'abteilung = ? AND startgeraet = ?', [$row['abteilung'], $row['startgeraet']]);
|
||||
if (!$all_disciplines) {
|
||||
|
||||
|
||||
$stmt = $mysqli->prepare("
|
||||
SELECT
|
||||
t.name,
|
||||
@@ -212,14 +219,14 @@ if (!$isAdmin) {
|
||||
agg.abteilung,
|
||||
agg.geraeteIndex,
|
||||
agg.startIndex
|
||||
FROM $tableTurnerinnen t
|
||||
FROM $tableTeilnehmende t
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
ta.turnerin_id,
|
||||
GROUP_CONCAT(DISTINCT a.name SEPARATOR ', ') AS abteilung,
|
||||
GROUP_CONCAT(DISTINCT g.start_index SEPARATOR ', ') AS geraeteIndex,
|
||||
ta.turnerin_index AS startIndex
|
||||
FROM $tableTurnerinnenAbt ta
|
||||
FROM $tableTeilnehmendeAbt ta
|
||||
INNER JOIN $tableAbt a
|
||||
ON a.id = ta.abteilung_id
|
||||
LEFT JOIN $tableGeraete g
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
<?php
|
||||
|
||||
/*ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
// Show all errors except deprecation notices (these come from vendor libraries
|
||||
// that aren't yet typed for newer PHP versions). Long-term fix: update
|
||||
// dependencies to versions compatible with your PHP runtime.
|
||||
error_reporting(E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED);*/
|
||||
use TCPDF;
|
||||
|
||||
// Start session if not already started
|
||||
if (!isset($baseDir)) {
|
||||
$baseDir = $_SERVER['DOCUMENT_ROOT'];
|
||||
}
|
||||
@@ -20,13 +14,6 @@ check_user_permission('kampfrichter');
|
||||
|
||||
verify_csrf();
|
||||
|
||||
|
||||
// Validate POST input
|
||||
if (!isset($_POST['abteilung'])) {
|
||||
http_response_code(400);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!isset($baseDir)) {
|
||||
$baseDir = $_SERVER['DOCUMENT_ROOT'];
|
||||
}
|
||||
@@ -34,296 +21,472 @@ if (!isset($baseDir)) {
|
||||
$type = 'kr';
|
||||
$data = require $baseDir . '/../scripts/db/db-verbindung-script.php';
|
||||
|
||||
if ($data['success'] === false){
|
||||
if ($data['success'] === false) {
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => false, 'message' => $data['message']]);
|
||||
exit;
|
||||
}
|
||||
|
||||
require $baseDir . '/../scripts/db/db-functions.php';
|
||||
require $baseDir . '/../scripts/db/db-tables.php';
|
||||
|
||||
|
||||
$abteilung = trim($_POST['abteilung']);
|
||||
require $baseDir . '/../scripts/db/db-functions.php';
|
||||
|
||||
$disciplines = db_select($mysqli, $tableGeraete, '*', '', [], 'start_index ASC');
|
||||
$wkName = db_get_var($mysqli, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['wkName']);
|
||||
|
||||
$wkName = ($wkName === null) ? 'Keinen Wettkampfname angegeben. Setzen unter: /intern/wk-leitung/einstellungen' : $wkName;
|
||||
|
||||
$normalFontSize = 11;
|
||||
$marginSide = 10;
|
||||
$marginTop = 10;
|
||||
$minMarginBottomTable = 20;
|
||||
$format = 'A4';
|
||||
$orientation = 'L';
|
||||
|
||||
// Load TCPDF
|
||||
require $baseDir . '/wp-content/uploads/TCPDF-main/tcpdf.php';
|
||||
$abt = intval($_POST['abteilungId'] ?? 1);
|
||||
|
||||
$current_year = (date('n') > 6) ? date('Y') + 1 : date('Y');
|
||||
$now = trim($_POST['date'] ?? date('Y-m-d H:i:s'));
|
||||
|
||||
class MYPDF extends TCPDF
|
||||
{
|
||||
public $current_year;
|
||||
public $abteilung;
|
||||
// Page header
|
||||
public function Header()
|
||||
{
|
||||
$image_file = 'https://kutu-tage-beider-basel.testseite-fh-ht.ch/wp-content/uploads/2025/06/ktbb-logo.png';
|
||||
$this->SetY(15);
|
||||
$this->SetFont('helvetica', 'B', 20);
|
||||
$this->Cell(0, 0, 'Protokoll Kutu-Tage beider Basel ' . $this->current_year . ' Abt. ' . $this->abteilung, 0, false, 'L', 0, '', 0, false, 'M', 'M');
|
||||
$this->Image($image_file, 272, 5, 15, '', 'PNG', '', 'T', false, 300, '', false, false, 0, false, false, false);
|
||||
}
|
||||
$geraete = db_select($mysqli, $tableGeraete, "*", '', [], "start_index ASC");
|
||||
|
||||
// Page footer
|
||||
public function Footer()
|
||||
{
|
||||
$this->SetY(-15);
|
||||
$this->SetFont('helvetica', 'I', 8);
|
||||
$this->Cell(0, 10, 'Seite ' . $this->getAliasNumPage() . ' von ' . $this->getAliasNbPages(), 0, false, 'C', 0, '', 0, false, 'T', 'M');
|
||||
}
|
||||
}
|
||||
$IndexedGeraete = array_column($geraete, 'name', 'id');
|
||||
|
||||
$pdf = new MYPDF('L', 'mm', 'A4', true, 'UTF-8', false);
|
||||
//$pdf->AddFont('outfit-bold', '', 'outfitb.php');
|
||||
$notenBezeichnungen = db_select($mysqli, $tableNotenBezeichnungen, "`name`, `id`, `nullstellen`, `anzahl_laeufe_json`", '', [], "");
|
||||
|
||||
$IndexedNotenBezeichnungen = array_column($notenBezeichnungen, 'name', 'id');
|
||||
|
||||
$pdf->current_year = $current_year;
|
||||
$pdf->abteilung = $abteilung;
|
||||
$IndexedNotenNullstellen = array_column($notenBezeichnungen, 'nullstellen', 'id');
|
||||
|
||||
$pdf->SetCreator(PDF_CREATOR);
|
||||
$pdf->SetAuthor('Turnerinnen System');
|
||||
$pdf->SetTitle("KTBB_Protokoll_Abt." . $abteilung . "_" . $current_year . ".pdf");
|
||||
$pdf->SetMargins(10, 20, 10);
|
||||
$pdf->SetAutoPageBreak(TRUE, 10);
|
||||
$pdf->SetFont('helvetica', '', 9);
|
||||
$IndexedNotenJsonGeraete = array_column($notenBezeichnungen, 'anzahl_laeufe_json', 'id');
|
||||
|
||||
$startindex = 0;
|
||||
$programme = db_select($mysqli, $tableProgramme, "`programm`, `id`", '', [], "");
|
||||
|
||||
foreach ($disciplines as $singledisciplin){
|
||||
|
||||
$startindex ++;
|
||||
$indexedProgramme = array_column($programme, 'id', 'programm');
|
||||
|
||||
$name_kampfrichterin1 = 'Nicht eingetragen';
|
||||
$name_kampfrichterin2 = 'Nicht eingetragen';
|
||||
|
||||
// Prepare SQL statement
|
||||
$stmt = $mysqli->prepare("
|
||||
SELECT * FROM $tableKrProtokoll
|
||||
WHERE abteilung = ?
|
||||
AND geraet = ?
|
||||
");
|
||||
$stmt->bind_param('ss', $abteilung, $singledisciplin['name']);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
$krresults = $result->fetch_all(MYSQLI_ASSOC);
|
||||
|
||||
foreach ($krresults as $krresult){
|
||||
if ($krresult['aufgabe'] == 1){
|
||||
$name_kampfrichterin1 = $krresult['name'];
|
||||
} elseif ($krresult['aufgabe'] == 2){
|
||||
$name_kampfrichterin2 = $krresult['name'];
|
||||
}
|
||||
}
|
||||
// Determine current year
|
||||
$current_year = date('Y');
|
||||
$monat = date('n');
|
||||
if ($monat > 6) $current_year++;
|
||||
|
||||
foreach ($disciplines as $shiftstartindex => $subdisciplin){
|
||||
// Prepare SQL statement
|
||||
$stmt = $mysqli->prepare("
|
||||
SELECT
|
||||
t.id AS person_id,
|
||||
t.name,
|
||||
t.vorname,
|
||||
t.programm,
|
||||
t.verein,
|
||||
ab.id AS abteilung_id,
|
||||
tab.geraet_id AS start_geraet_id,
|
||||
tab.turnerin_index
|
||||
FROM $tableTeilnehmendeAbt tab
|
||||
INNER JOIN $tableTeilnehmende t ON tab.turnerin_id = t.id
|
||||
INNER JOIN $tableAbt ab ON ab.id = tab.abteilung_id
|
||||
WHERE ab.id = ?
|
||||
ORDER BY t.id ASC
|
||||
");
|
||||
|
||||
$pdf->AddPage();
|
||||
$stmt->bind_param('i', $abt);
|
||||
$stmt->execute();
|
||||
|
||||
$pdf->Ln(10);
|
||||
$pdf->SetFont('', 'B', 14);
|
||||
$pdf->Cell(0, 0, ucfirst($singledisciplin['name']), 0, 1, 'L');
|
||||
$pdf->Ln(3);
|
||||
$pdf->SetFont('', 'I', 9);
|
||||
$newshiftindex = $shiftstartindex + 1;
|
||||
$pdf->Cell(0, 0, 'Gruppe '. $newshiftindex , 0, 1, 'L');
|
||||
$pdf->SetFont('', '');
|
||||
$result = $stmt->get_result();
|
||||
$personen = $result->fetch_all(MYSQLI_ASSOC);
|
||||
|
||||
$pdf->Ln(5);
|
||||
|
||||
$pdf->Cell(0, 0, '1. Kampfrichterin: ' . $name_kampfrichterin1, 0, 1, 'L');
|
||||
$pdf->Ln(2);
|
||||
$pdf->Cell(0, 0, '2. Kampfrichterin: ' . $name_kampfrichterin2, 0, 1, 'L');
|
||||
$pdf->Ln(8);
|
||||
|
||||
$startg = $shiftstartindex - $startindex;
|
||||
|
||||
if ($startg < 1) {
|
||||
$startg += 4; // shift into positive range
|
||||
}
|
||||
|
||||
// Prepare SQL statement
|
||||
|
||||
$stmt = $mysqli->prepare("
|
||||
SELECT
|
||||
t.*,
|
||||
agg.abteilung,
|
||||
agg.geraete_index,
|
||||
agg.start_index
|
||||
FROM $tableTurnerinnen t
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
ta.turnerin_id,
|
||||
GROUP_CONCAT(DISTINCT a.name SEPARATOR ', ') AS abteilung,
|
||||
GROUP_CONCAT(DISTINCT g.start_index SEPARATOR ', ') AS geraete_index,
|
||||
ta.turnerin_index AS start_index
|
||||
FROM $tableTurnerinnenAbt ta
|
||||
INNER JOIN $tableAbt a
|
||||
ON a.id = ta.abteilung_id
|
||||
LEFT JOIN $tableGeraete g
|
||||
ON g.id = ta.geraet_id
|
||||
GROUP BY ta.turnerin_id
|
||||
) agg ON agg.turnerin_id = t.id
|
||||
WHERE (t.bezahlt = ? OR t.bezahltoverride = ?) AND agg.abteilung = ? AND agg.geraete_index = ?
|
||||
ORDER BY agg.start_index DESC
|
||||
");
|
||||
|
||||
$bezahlt1 = '2';
|
||||
$bezahlt2 = '5';
|
||||
$stmt->bind_param('ssss', $bezahlt1, $bezahlt2, $abteilung, $startg);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
$entries = $result->fetch_all(MYSQLI_ASSOC);
|
||||
|
||||
|
||||
if (!empty($entries)) {
|
||||
$maxindex = $entries[0]['start_index'];
|
||||
|
||||
// Recalculate
|
||||
foreach ($entries as &$singleorderentry) {
|
||||
$singleorderentry['start_index'] -= $shiftstartindex;
|
||||
if ($singleorderentry['start_index'] < 1) {
|
||||
$singleorderentry['start_index'] += $maxindex;
|
||||
}
|
||||
}
|
||||
unset($singleorderentry); // break the reference
|
||||
|
||||
// Reorder by recalculated startindex (ascending)
|
||||
usort($entries, function($a, $b) {
|
||||
return $a['start_index'] <=> $b['start_index'];
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Define columns (including discipline sub-columns)
|
||||
$columns = [
|
||||
'start_index' => ['header' => 'Str.'],
|
||||
'name' => ['header' => 'Name'],
|
||||
'vorname' => ['header' => 'Vorname'],
|
||||
'programm' => ['header' => 'Programm'],
|
||||
'geburtsdatum' => ['header' => 'Jg.'],
|
||||
'verein' => ['header' => 'Verein'],
|
||||
'e1 note '.strtolower($subdisciplin['name']) => ['header' => 'E1 Note '.ucfirst($subdisciplin['name'])],
|
||||
'e2 note '.strtolower($subdisciplin['name']) => ['header' => 'E2 Note '.ucfirst($subdisciplin['name'])],
|
||||
'e note '.strtolower($subdisciplin['name']) => ['header' => 'E Note '.ucfirst($subdisciplin['name'])],
|
||||
'd-note '.strtolower($subdisciplin['name']) => ['header' => 'D Note '.ucfirst($subdisciplin['name'])],
|
||||
'neutrale abzuege '.strtolower($subdisciplin['name']) => ['header' => 'N. Abzüge '.ucfirst($subdisciplin['name'])],
|
||||
'note '.strtolower($subdisciplin['name']) => ['header' => 'Note '.ucfirst($subdisciplin['name'])],
|
||||
];
|
||||
|
||||
// Initialize max widths with header widths (+4 mm padding)
|
||||
foreach ($columns as $key => &$col) {
|
||||
$col['max_width'] = $pdf->GetStringWidth($col['header']) + 4;
|
||||
}
|
||||
|
||||
// Calculate max width of each column based on all data
|
||||
foreach ($entries as $r) {
|
||||
foreach ($columns as $key => &$col) {
|
||||
isset($r[$key]) ? $r[$key] : '';
|
||||
$text = '';
|
||||
|
||||
// Handle different column types
|
||||
if ($key === 'start_index' && !empty($r['start_index'])) {
|
||||
$text = (int)$r[$key];
|
||||
} elseif ($key === 'geburtsdatum' && !empty($r['geburtsdatum'])) {
|
||||
$text = (new DateTime($r['geburtsdatum']))->format('Y');
|
||||
} elseif (strpos($key, 'd-note') === 0 && isset($r[$key]) && is_numeric($r[$key])) {
|
||||
$text = number_format((float)$r[$key], 2, '.', ''); // 2 decimals
|
||||
} elseif (isset($r[$key]) && is_numeric($r[$key])) {
|
||||
$text = number_format((float)$r[$key], 3, '.', ''); // 3 decimals
|
||||
} elseif (isset($r[$key])) {
|
||||
$text = (string)$r[$key];
|
||||
} else {
|
||||
$text = '';
|
||||
}
|
||||
|
||||
// Calculate width with padding
|
||||
$w = $pdf->GetStringWidth($text) + 4;
|
||||
|
||||
// Update max width
|
||||
if ($w > $col['max_width']) {
|
||||
$col['max_width'] = $w;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unset($col);
|
||||
$columns['geburtsdatum']['max_width'] = $pdf->GetStringWidth("0000") + 4;
|
||||
|
||||
// Print first header row
|
||||
$pdf->SetFont('', 'B');
|
||||
foreach ($columns as $key => $col) {
|
||||
if (
|
||||
$key === 'd-note' || // only column with dash
|
||||
strpos($key, 'note') === 0 || // matches 'note...' at start
|
||||
strpos($key, 'e note') === 0 || // matches 'e-note...'
|
||||
strpos($key, 'e1 note') === 0 || // matches 'e1 note ...'
|
||||
strpos($key, 'e2 note') === 0 || // matches 'e2 note ...'
|
||||
strpos($key, 'neutrale abzuege') === 0 ||
|
||||
$key === 'geburtsdatum' ||
|
||||
$key === 'start_index'
|
||||
) {
|
||||
$align = 'C';
|
||||
} else {
|
||||
$align = 'L';
|
||||
}
|
||||
|
||||
$pdf->Cell($col['max_width'], 7, $col['header'], 0, 0, $align);
|
||||
}
|
||||
|
||||
|
||||
// Move to next line after headers
|
||||
$pdf->Ln();
|
||||
|
||||
|
||||
$pdf->SetFont('', '');
|
||||
foreach ($entries as $r) {
|
||||
foreach ($columns as $key => $col) {
|
||||
// Determine alignment
|
||||
if (strpos($key, 'note') === 0 || strpos($key, 'e note') === 0 || strpos($key, 'd-note') === 0 || strpos($key, 'neutrale abzuege') === 0 || $key === 'start_index' || $key === 'geburtsdatum' || strpos($key, 'e1 note') === 0 || strpos($key, 'e2 note') === 0){
|
||||
$align = 'C';
|
||||
} else {
|
||||
$align = 'L';
|
||||
}
|
||||
|
||||
// Format the value based on type
|
||||
if ($key === 'geburtsdatum' && !empty($r['geburtsdatum'])) {
|
||||
$text = (new DateTime($r['geburtsdatum']))->format('Y');
|
||||
} elseif (strpos($key, 'note') === 0 || strpos($key, 'e note') === 0 || strpos($key, 'd-note') === 0 || strpos($key, 'neutrale abzuege') === 0 || $key === 'gesamt' || strpos($key, 'e1 note') === 0 || strpos($key, 'e2 note') === 0){
|
||||
$text = isset($r[$key]) ? number_format((float)$r[$key], 3) : '';
|
||||
} else {
|
||||
$text = isset($r[$key]) ? $r[$key] : '';
|
||||
}
|
||||
|
||||
$pdf->Cell($col['max_width'], 6, $text, 1, 0, $align);
|
||||
}
|
||||
|
||||
$pdf->Ln(); // Move to next row
|
||||
}
|
||||
|
||||
$pdf->SetY(-50);
|
||||
if ($name_kampfrichterin1 !== 'Nicht eingetragen' || $name_kampfrichterin2 !== 'Nicht eingetragen'){
|
||||
$pdf->SetFont('', 'B');
|
||||
$pdf->Cell(0, 28, 'Unterschrift:', 0, 1, 'L');
|
||||
$pdf->SetFont('', '');
|
||||
if ($name_kampfrichterin1 !== 'Nicht eingetragen'){
|
||||
$pdf->Cell(120, 8, $name_kampfrichterin1.': ___________________________________________', 0, 0, 'L');
|
||||
}
|
||||
if ($name_kampfrichterin2 !== 'Nicht eingetragen'){
|
||||
$pdf->Cell(90, 8, $name_kampfrichterin2.': ___________________________________________', 0, 0, 'L');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$indexedPersonen = array_column($personen, null, 'person_id');
|
||||
|
||||
// Close statement
|
||||
$stmt->close();
|
||||
|
||||
$pdf->Output("Protokoll_Kutu-Tage_beider_Basel_{$current_year}_Abt_{$abteilung}.pdf", 'D');
|
||||
// 1. Get the IDs from the first query results
|
||||
$personenIds = array_column($personen, 'person_id');
|
||||
|
||||
if (!empty($personenIds)) {
|
||||
$placeholders = implode(',', array_fill(0, count($personenIds), '?'));
|
||||
|
||||
$sqlNotenChanges = "SELECT * FROM $tableNotenChanges WHERE person_id IN ($placeholders) AND `jahr` = ?";
|
||||
$stmtNotenChanges = $mysqli->prepare($sqlNotenChanges);
|
||||
|
||||
$paramsArray = array_merge($personenIds, [$current_year]);
|
||||
|
||||
$types = str_repeat('i', count($personenIds)) . 's';
|
||||
|
||||
$stmtNotenChanges->bind_param($types, ...$paramsArray);
|
||||
|
||||
$stmtNotenChanges->execute();
|
||||
$notenChangesResult = $stmtNotenChanges->get_result();
|
||||
$notenChangesEntries = $notenChangesResult->fetch_all(MYSQLI_ASSOC);
|
||||
|
||||
$stmtNotenChanges->close();
|
||||
} else {
|
||||
$notenChangesEntries = [];
|
||||
}
|
||||
|
||||
$kampfrichterIds = array_column($notenChangesEntries, 'edited_by');
|
||||
|
||||
if (!empty($kampfrichterIds)) {
|
||||
$placeholders = implode(',', array_fill(0, count($kampfrichterIds), '?'));
|
||||
|
||||
$sqlKampfrichter = "SELECT `id` AS `id_kampfrichter`, `name_person` AS `name_kampfrichter` FROM $tableInternUsers WHERE id IN ($placeholders)";
|
||||
$stmtKampfrichter = $mysqli->prepare($sqlKampfrichter);
|
||||
|
||||
$types = str_repeat('i', count($kampfrichterIds));
|
||||
|
||||
$stmtKampfrichter->bind_param($types, ...$kampfrichterIds);
|
||||
|
||||
$stmtKampfrichter->execute();
|
||||
$kampfrichterResult = $stmtKampfrichter->get_result();
|
||||
$kampfrichter = $kampfrichterResult->fetch_all(MYSQLI_ASSOC);
|
||||
|
||||
$indexedKampfrichter = array_column($kampfrichter, 'name_kampfrichter', 'id_kampfrichter');
|
||||
|
||||
$stmtKampfrichter->close();
|
||||
} else {
|
||||
$indexedKampfrichter = [];
|
||||
}
|
||||
|
||||
$entriesArray = [];
|
||||
|
||||
|
||||
|
||||
foreach ($notenChangesEntries as $snce) {
|
||||
|
||||
$programmId = $indexedProgramme[$indexedPersonen[$snce['person_id']]['programm']] ?? 0;
|
||||
|
||||
$runs = json_decode($IndexedNotenJsonGeraete[$snce['note_bezeichnung_id']], true) ?? [];
|
||||
|
||||
$countRuns = (int) $runs[$snce['geraet_id']][$programmId] ?? (int) $runs['default'] ?? 1;
|
||||
|
||||
$notenTextRaw = $IndexedNotenBezeichnungen[$snce['note_bezeichnung_id']] ?? 'Unbekannter Notenname';
|
||||
|
||||
$notenText = ($countRuns > 1) ? $notenTextRaw . ' (R' . ($snce['run_number'] ?? 1 ). ')' : $notenTextRaw;
|
||||
|
||||
$nullstellen = (int) $IndexedNotenNullstellen[$snce['note_bezeichnung_id']] ?? 2;
|
||||
$value = ($snce['old_value'] !== null) ? number_format((float) $snce['old_value'], $nullstellen) . ' ' . json_decode('"\u2192"') . ' ' . number_format((float) $snce['new_value'], $nullstellen) : number_format((float) $snce['new_value'], $nullstellen);
|
||||
|
||||
$entriesArray[$snce['geraet_id']][$indexedPersonen[$snce['person_id']]['start_geraet_id']][] = [
|
||||
'name' => ($indexedPersonen[$snce['person_id']]['name'] ?? 'Unbekannter Nachname') . ', ' . ($indexedPersonen[$snce['person_id']]['vorname'] ?? 'Unbekannter Vorname'),
|
||||
'programm' => $indexedPersonen[$snce['person_id']]['programm'] ?? 'Unbekanntes Programm',
|
||||
'verein' => $indexedPersonen[$snce['person_id']]['verein'] ?? 'Unbekannter Verein',
|
||||
'note' => $notenText,
|
||||
'value' => $value,
|
||||
'kampfrichter' => $indexedKampfrichter[$snce['edited_by']] ?? 'Unbekannter Kampfrichter',
|
||||
'timestamp' => new DateTime($snce['timestamp'])->format('d.m.Y H:i:s') ?? '---'
|
||||
];
|
||||
}
|
||||
|
||||
// Load TCPDF
|
||||
require $baseDir . '/../composer/vendor/autoload.php';
|
||||
/*
|
||||
// Optional: load custom font
|
||||
$fontfile = $baseDir . '/wp-content/uploads/fonts/Inter-Regular.ttf'; // adjust path
|
||||
if (file_exists($fontfile)) {
|
||||
$fontname = TCPDF_FONTS::addTTFfont($fontfile, 'TrueTypeUnicode', '', 32);
|
||||
}
|
||||
*/
|
||||
class ProtokollPDF extends TCPDF
|
||||
{
|
||||
public $current_year;
|
||||
public $subAbt;
|
||||
public $abt;
|
||||
public $columns;
|
||||
public $discipline;
|
||||
public $wkName;
|
||||
public $marginSide;
|
||||
public $marginTop;
|
||||
public $pdfHeight;
|
||||
public $pdfWidth;
|
||||
public $headerType;
|
||||
public $now;
|
||||
// Page header
|
||||
|
||||
public $headerBottomY = 0;
|
||||
|
||||
|
||||
public function Header()
|
||||
{
|
||||
$this->SetFillColor(255, 255, 255);
|
||||
|
||||
$this->Rect(0, 0, $this->pdfWidth, $this->pdfHeight, 'F');
|
||||
|
||||
$arrayTitles = [];
|
||||
$image_file = $_SERVER['DOCUMENT_ROOT'] . '/intern/img/logo-normal.png';
|
||||
$this->SetY($this->marginTop);
|
||||
$this->SetX(($this->marginSide ?? 10));
|
||||
$this->SetFont('freesans', '', 20);
|
||||
|
||||
if ($this->headerType === 'bigHeader') {
|
||||
|
||||
$this->Cell(0, 0, 'Protokoll ' . $this->wkName . ' ' . $this->current_year, 0, 1, 'L', 0, '', 0, false, 'T', 'M');
|
||||
$preimageX = $this->GetX();
|
||||
$preimageY = $this->GetY();
|
||||
$this->Image($image_file, $this->pdfWidth - 10 - $this->marginSide, $this->marginTop, '', 10, 'png', '', 'T');
|
||||
|
||||
$this->SetY($preimageY + 5);
|
||||
$this->SetX($this->marginSide ?? 10); // Force back to left margin
|
||||
$this->SetFont('freesans', '', 11);
|
||||
$this->Cell(0, 6, 'Gereat: ' . $this->discipline, 0, 1, 'L');
|
||||
$this->Cell(0, 6, 'Abteilung: ' . $this->abt, 0, 1, 'L');
|
||||
$this->Cell(0, 6, 'Gruppe: ' . $this->subAbt, 0, 1, 'L');
|
||||
|
||||
$this->Ln(5);
|
||||
|
||||
} else {
|
||||
|
||||
$this->Cell(10, 0, 'Protokoll ' . $this->wkName . ' ' . $this->current_year, 0, 1, 'L', 0, '', 0, false, 'T', 'M');
|
||||
$this->SetFont('freesans', '', 11);
|
||||
$info = "Gerät: {$this->discipline} | Abt: {$this->abt} | Gruppe: {$this->subAbt}";
|
||||
$this->Cell(10, 6, $info, 0, 1, 'L'); // Align all info to the right while title is on the left
|
||||
|
||||
$this->Image($image_file, $this->pdfWidth - 10 - $this->marginSide, $this->marginTop, '', 10, 'png', '', 'T');
|
||||
|
||||
$this->Ln(18);
|
||||
}
|
||||
|
||||
$columns = $this->columns;
|
||||
$startY = $this->GetY();
|
||||
$this->SetFont('', 'B');
|
||||
|
||||
$this->SetX($this->marginSide ?? 10);
|
||||
|
||||
foreach ($columns as $col) {
|
||||
|
||||
$title = $col['header'];
|
||||
|
||||
if (($col['rotate'] ?? false)) {
|
||||
$x = $this->GetX();
|
||||
$y = $this->GetY() + 7;
|
||||
|
||||
$this->StartTransform();
|
||||
$this->Rotate(45, $x, $y);
|
||||
|
||||
$lineY = $startY + 7;
|
||||
if (($col['id'] ?? '') === 'gesamt') {
|
||||
$this->SetLineWidth(0.6);
|
||||
} else {
|
||||
$this->SetLineWidth(0.2);
|
||||
}
|
||||
|
||||
$this->Line($x, $lineY, $x + ($col['width_header'] ?? 0), $lineY);
|
||||
$this->SetLineWidth(0.2);
|
||||
|
||||
$this->Cell($col['width_header'] ?? 0, 7, $title ?? '', 0, 0, 'C');
|
||||
$this->StopTransform();
|
||||
$this->SetX($x + ($col['max_width'] ?? 0));
|
||||
} else {
|
||||
$this->Cell($col['max_width'] ?? 0, 7, $title ?? '', 0, 0, $col['align'] ?? 'L');
|
||||
}
|
||||
}
|
||||
|
||||
$this->Ln();
|
||||
$this->headerBottomY = $this->GetY();
|
||||
}
|
||||
|
||||
|
||||
// Page footer
|
||||
public function Footer()
|
||||
{
|
||||
$this->SetLineWidth(0.6);
|
||||
$this->Line($this->marginSide ?? 10, $this->headerBottomY, $this->pdfWidth - ($this->marginSide ?? 10), $this->headerBottomY);
|
||||
|
||||
$this->SetLineWidth(0.2);
|
||||
|
||||
$this->SetY(0);
|
||||
$this->SetX(0);
|
||||
|
||||
$this->SetFillColor(255, 255, 255); // white
|
||||
|
||||
$this->Cell(($this->marginSide ?? 10), $this->pdfHeight, '', 0, 0, 'L', true);
|
||||
|
||||
$this->SetY(0);
|
||||
$this->SetX($this->pdfWidth - ($this->marginSide ?? 10));
|
||||
|
||||
$this->Cell(($this->marginSide ?? 10), $this->pdfHeight, '', 0, 0, 'L', true);
|
||||
|
||||
$this->SetY(-15);
|
||||
$this->SetFont('freesans', 'I', 8);
|
||||
|
||||
// 1. Page Number - Centered
|
||||
$this->Cell(0, 10, 'Seite ' . $this->getAliasNumPage() . ' von ' . $this->getAliasNbPages(), 0, false, 'C');
|
||||
|
||||
// 2. Timestamp - Force to Left
|
||||
$this->SetX($this->getMargins()['left']); // Reset to left margin
|
||||
$this->Cell(0, 10, 'Zeitstempel: ' . $this->now, 0, false, 'L');
|
||||
|
||||
// 3. Host - Force to Right (Cell with width 0 and align 'R' handles this)
|
||||
$this->SetX($this->getMargins()['left']); // Reset again so 'R' alignment works from the start
|
||||
$this->Cell(0, 10, $_SERVER['HTTP_HOST'], 0, 0, 'R', false, 'https://'.$_SERVER['HTTP_HOST']);
|
||||
}
|
||||
}
|
||||
|
||||
$pdf = new ProtokollPDF($orientation, 'mm', $format, true, 'UTF-8', false);
|
||||
|
||||
$pdfHeight = $pdf->getPageHeight();
|
||||
$pdfWidth = $pdf->getPageWidth();
|
||||
|
||||
$pdf->current_year = $current_year;
|
||||
$pdf->wkName = $wkName;
|
||||
$pdf->abt = $abt;
|
||||
$pdf->marginSide = $marginSide;
|
||||
$pdf->marginTop = $marginTop;
|
||||
$pdf->pdfHeight = $pdfHeight;
|
||||
$pdf->pdfWidth = $pdfWidth;
|
||||
$pdf->now = $now;
|
||||
|
||||
$pdf->SetCreator(PDF_CREATOR);
|
||||
$pdf->SetAuthor($wkName);
|
||||
$pdf->SetTitle($wkName . "_Protokoll_" . $abt . "_" . $current_year . ".pdf");
|
||||
|
||||
$pdf->SetAutoPageBreak(FALSE);
|
||||
|
||||
|
||||
$pdf->SetFont('freesans', '', 11);
|
||||
|
||||
// Define columns dynamically
|
||||
$columns = [
|
||||
['id' => 'name', 'header' => 'Name Person', 'align' => 'L', 'flex' => true, 'type' => 'field', 'field' => 'name'],
|
||||
['id' => 'programm', 'header' => 'Programm', 'align' => 'L', 'flex' => true, 'type' => 'field', 'field' => 'programm'],
|
||||
['id' => 'verein', 'header' => 'Verein', 'align' => 'L', 'flex' => true, 'type' => 'field', 'field' => 'verein'],
|
||||
['id' => 'note', 'header' => 'Note', 'align' => 'C', 'flex' => false, 'type' => 'field', 'field' => 'note'],
|
||||
['id' => 'value', 'header' => 'Wert', 'align' => 'C', 'flex' => false, 'type' => 'field', 'field' => 'value'],
|
||||
['id' => 'kr', 'header' => 'Kampfrichter/in', 'align' => 'C', 'flex' => true, 'type' => 'field', 'field' => 'kampfrichter'],
|
||||
['id' => 'timestamp', 'header' => 'Zeitstempel', 'align' => 'C', 'flex' => false, 'type' => 'field', 'field' => 'timestamp'],
|
||||
];
|
||||
|
||||
|
||||
$padding = 4;
|
||||
|
||||
unset($col);
|
||||
|
||||
// 1. Calculate initial max widths based on headers
|
||||
foreach ($columns as &$col) {
|
||||
$col['width_header'] = $pdf->GetStringWidth($col['header']) + ($padding * 2);
|
||||
if ($col['type'] !== 'note') {
|
||||
$col['max_width'] = $col['width_header'];
|
||||
} else {
|
||||
$col['max_width'] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($entriesArray as $geratId => $singleGeraet) {
|
||||
|
||||
$pdf->discipline = $IndexedGeraete[$geratId] ?? 'Unbekanntes Gerät';
|
||||
|
||||
foreach ($singleGeraet as $geratIdStart => $singleStartGeraet) {
|
||||
|
||||
$pdf->subAbt = $geratIdStart;
|
||||
|
||||
$tcolumns = $columns;
|
||||
|
||||
foreach ($singleStartGeraet as $se) {
|
||||
foreach ($tcolumns as &$col) {
|
||||
$text = '';
|
||||
if (isset($se[$col['field']])) {
|
||||
$text = $se[$col['field']];
|
||||
}
|
||||
$width = $pdf->GetStringWidth($text) + ($padding * 2);
|
||||
if ($width > $col['max_width']) {
|
||||
$col['max_width'] = $width;
|
||||
}
|
||||
}
|
||||
unset($col);
|
||||
}
|
||||
|
||||
// 4. Distribute Flexible Widths
|
||||
$tablew = $pdfWidth - (2 * $marginSide); // total table width (A4 landscape in mm minus margins)
|
||||
$fixedWidth = 0;
|
||||
$flexCols = [];
|
||||
|
||||
foreach ($tcolumns as $col) {
|
||||
if ($col['flex'] ?? false) {
|
||||
$flexCols[] = $col['id'];
|
||||
$fixedWidth += $col['max_width'];
|
||||
} else {
|
||||
$fixedWidth += $col['max_width'];
|
||||
}
|
||||
}
|
||||
|
||||
$effTableWidth = $tablew - $fixedWidth;
|
||||
|
||||
if (!empty($flexCols) && $effTableWidth > 0) {
|
||||
$flexTotalInitial = 0;
|
||||
foreach ($tcolumns as $col) {
|
||||
if ($col['flex'] ?? false) $flexTotalInitial += $col['max_width'];
|
||||
}
|
||||
|
||||
foreach ($tcolumns as &$col) {
|
||||
if ($col['flex'] ?? false) {
|
||||
$col['max_width'] += ($col['max_width'] / $flexTotalInitial) * $effTableWidth;
|
||||
}
|
||||
}
|
||||
unset($col);
|
||||
}
|
||||
|
||||
$pdf->columns = $tcolumns;
|
||||
|
||||
$pdf->headerType = 'bigHeader';
|
||||
$pdf->AddPage();
|
||||
$pdf->headerType = '';
|
||||
|
||||
// After AddPage(), the header has been drawn
|
||||
$margin_top = $pdf->headerBottomY;
|
||||
|
||||
// Now adjust your margins if needed
|
||||
$pdf->SetMargins($marginSide, $margin_top, $marginSide); // +5 mm padding below header
|
||||
$pdf->SetY($margin_top); // Move cursor below header manually
|
||||
|
||||
$pdf->SetFont('', '');
|
||||
$rIndex = 0;
|
||||
|
||||
foreach ($singleStartGeraet as $r) {
|
||||
|
||||
$pdf->SetTextColor(0, 0, 0);
|
||||
|
||||
if ($rIndex % 2 == 0) {
|
||||
$pdf->SetFillColor(255, 255, 255);
|
||||
} else {
|
||||
$pdf->SetFillColor(240, 240, 240);
|
||||
}
|
||||
|
||||
$rowHeight = 10;
|
||||
|
||||
foreach ($tcolumns as $col) {
|
||||
|
||||
if ($pdf->getY() + $rowHeight > $pdfHeight - $minMarginBottomTable) {
|
||||
$pdf->addPage();
|
||||
$margin_top = $pdf->headerBottomY;
|
||||
$pdf->setY($margin_top);
|
||||
}
|
||||
|
||||
$customFontSize = (isset($col['font_size']) && $col['font_size'] != 0);
|
||||
|
||||
if ($customFontSize) {
|
||||
$pdf->SetFont('', '', $col['font_size']);
|
||||
}
|
||||
|
||||
$startX = $pdf->GetX();
|
||||
$startY = $pdf->GetY();
|
||||
|
||||
$text = strip_tags($r[$col['field']]);
|
||||
|
||||
if ($col['type'] === 'field') {
|
||||
$pdf->MultiCell($col['max_width'], $rowHeight, $text, 'B', $col['align'], true, 0, $startX, $startY, true, 0, false, true, $rowHeight, 'M');
|
||||
}
|
||||
|
||||
if ($customFontSize) {
|
||||
$pdf->SetFont('', '', $normalFontSize);
|
||||
}
|
||||
}
|
||||
|
||||
$pdf->SetY($startY + $rowHeight); // Move Y manually instead of Ln() to account for rowHeight
|
||||
$rIndex++;
|
||||
}
|
||||
$textanzEintaege = (count($singleStartGeraet) > 1) ? 'Einträge': 'Eintrag';
|
||||
|
||||
$pdf->SetFont('', '', 7);
|
||||
|
||||
$pdf->Cell(20, 6, count($singleStartGeraet).' '.$textanzEintaege, 0, 0, 'L');
|
||||
|
||||
$pdf->SetFont('', '', $normalFontSize);
|
||||
}
|
||||
}
|
||||
|
||||
$pdf->Output($wkName . "_Protokoll_" . $abt . "_" . $current_year . ".pdf", 'I');
|
||||
exit;
|
||||
@@ -14,13 +14,6 @@ check_user_permission('kampfrichter');
|
||||
|
||||
verify_csrf();
|
||||
|
||||
// Validate POST input
|
||||
/*
|
||||
if (!isset($_POST['prog']) && !isset($_POST['type'])) {
|
||||
http_response_code(400);
|
||||
exit;
|
||||
}*/
|
||||
|
||||
if (!isset($baseDir)) {
|
||||
$baseDir = $_SERVER['DOCUMENT_ROOT'];
|
||||
}
|
||||
@@ -28,7 +21,8 @@ if (!isset($baseDir)) {
|
||||
$type = 'kr';
|
||||
$data = require $baseDir . '/../scripts/db/db-verbindung-script.php';
|
||||
|
||||
if ($data['success'] === false){
|
||||
if ($data['success'] === false) {
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['success' => false, 'message' => $data['message']]);
|
||||
exit;
|
||||
}
|
||||
@@ -36,9 +30,28 @@ if ($data['success'] === false){
|
||||
require $baseDir . '/../scripts/db/db-tables.php';
|
||||
require $baseDir . '/../scripts/db/db-functions.php';
|
||||
|
||||
$wkName = db_get_var($mysqli, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['wkName']);
|
||||
|
||||
$wkName = ($wkName === null) ? 'Keinen Wettkampfname angegeben. Setzen unter: /intern/wk-leitung/einstellungen' : $wkName;
|
||||
|
||||
$normalFontSize = 11;
|
||||
$marginSide = 10;
|
||||
$marginTop = 10;
|
||||
$minMarginBottomTable = 20;
|
||||
$format = 'A4';
|
||||
$orientation = 'L';
|
||||
|
||||
$programm = trim($_POST['prog'] ?? 'P6A');
|
||||
$buttontype = trim($_POST['type'] ?? 'downloadRangliste');
|
||||
|
||||
$allowedOperations = ['downloadRangliste', 'updateServerRangliste'];
|
||||
|
||||
if (!in_array($buttontype, $allowedOperations)) {
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(["success" => false, "message" => "Invalide Operation"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$programm = trim($_POST['prog'] ?? 'p6a');
|
||||
$buttontype = trim($_POST['type'] ?? 'export_programm');
|
||||
$upperprogramm = strtoupper($programm);
|
||||
|
||||
$stmt = $mysqli->prepare("SELECT `id`, `name` FROM $tableGeraete ORDER BY start_index ASC");
|
||||
@@ -64,26 +77,40 @@ if ($monat > 6) $current_year++;
|
||||
|
||||
// Prepare SQL statement
|
||||
$stmt = $mysqli->prepare("
|
||||
SELECT * FROM $tableTurnerinnen
|
||||
SELECT * FROM $tableTeilnehmende
|
||||
WHERE LOWER(programm) = LOWER(?)
|
||||
AND (bezahlt = ? OR bezahltoverride = ?)
|
||||
AND ((bezahlt = ? OR bezahltoverride = ?) AND bezahltoverride != ?)
|
||||
ORDER BY rang ASC
|
||||
");
|
||||
$bezahlt1 = '2';
|
||||
$bezahlt2 = '5';
|
||||
$stmt->bind_param('sss', $programm, $bezahlt1, $bezahlt2);
|
||||
$bezahlt3 = '3';
|
||||
$stmt->bind_param('ssss', $programm, $bezahlt1, $bezahlt2, $bezahlt3);
|
||||
$stmt->execute();
|
||||
|
||||
|
||||
$result = $stmt->get_result();
|
||||
$personen = $result->fetch_all(MYSQLI_ASSOC);
|
||||
|
||||
|
||||
// Close statement
|
||||
$stmt->close();
|
||||
|
||||
$programmId = db_get_var($mysqli, "SELECT `id` FROM $tableProgramme WHERE LOWER(`programm`) = LOWER(?)", [$programm]);
|
||||
$cache = require $baseDir . '/../scripts/cache/noten-config-calculating-cache.php';
|
||||
$ascArrayAnzahlLaeufeJSON = $cache['anzahl_laeufe_json'];
|
||||
|
||||
// 1. Get the IDs from the first query results
|
||||
$turnerinnenIds = array_column($personen, 'id');
|
||||
|
||||
$rangNote = intval(db_get_var($mysqli, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['rangNote']));
|
||||
|
||||
if ($rangNote === 0) {
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(["success" => false, "message" => "Keine Note für Rangvergabe ausgewählt"]);
|
||||
http_response_code(400);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!empty($turnerinnenIds)) {
|
||||
// 2. Create a string of placeholders: ?,?,?
|
||||
$placeholders = implode(',', array_fill(0, count($turnerinnenIds), '?'));
|
||||
@@ -115,26 +142,34 @@ if (!empty($turnerinnenIds)) {
|
||||
$notenEntries = [];
|
||||
}
|
||||
|
||||
$rangNote = intval(db_get_var($mysqli, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['rangNote']));
|
||||
|
||||
var_dump($rangNote);
|
||||
$indexedNotenEntries = [];
|
||||
foreach ($notenEntries as $sn) {
|
||||
if (!isset($indexedNotenEntries[$sn['person_id']][$sn['note_bezeichnung_id']][$sn['geraet_id']])) {
|
||||
$indexedNotenEntries[$sn['person_id']][$sn['note_bezeichnung_id']][$sn['geraet_id']] = [];
|
||||
}
|
||||
$indexedNotenEntries[$sn['person_id']][$sn['note_bezeichnung_id']][$sn['geraet_id']][$sn['run_number']] = $sn['value'];
|
||||
}
|
||||
|
||||
$orderBestRang = db_get_var($mysqli, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['orderBestRang']);
|
||||
|
||||
$okValuesOrderBestRang = ["ASC", "DESC"];
|
||||
|
||||
if (!in_array($orderBestRang, $okValuesOrderBestRang)) {
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(["success" => false, "message" => "Invalide Sortierung der Ränge"]);
|
||||
http_response_code(400);
|
||||
exit;
|
||||
}
|
||||
|
||||
$alleNoten = db_select($mysqli, $tableNotenBezeichnungen, "id, default_value, nullstellen, pro_geraet, geraete_json, zeige_auf_rangliste, name", "zeige_auf_rangliste = ? OR id = ?", ['1', $rangNote]);
|
||||
$alleNoten = db_select($mysqli, $tableNotenBezeichnungen, "id, default_value, nullstellen, pro_geraet, geraete_json, zeige_auf_rangliste, groesse_auf_rangliste, name", "zeige_auf_rangliste = ? OR id = ?", ['1', $rangNote]);
|
||||
|
||||
$displayedNoten = [];
|
||||
|
||||
foreach ($alleNoten as $sN) {
|
||||
$alleNotenIndexed = array_column($alleNoten, null, 'id');
|
||||
|
||||
foreach ($alleNotenIndexed as $key => $sN) {
|
||||
if (intval($sN['zeige_auf_rangliste']) === 1) {
|
||||
$displayedNoten[$sN['id']] = $sN;
|
||||
$displayedNoten[$key] = $sN;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,31 +182,38 @@ foreach ($ascArrayGeraeteJSON as $key => $saagj) {
|
||||
|
||||
// 1. Initialize the structure for every person
|
||||
$indexedNotenArray = [];
|
||||
$sortArray = [];
|
||||
|
||||
$notenGeraeteArray = array_merge($geraete, array(array("id" => 0)));
|
||||
|
||||
foreach ($personen as $sp) {
|
||||
$pId = $sp['id'];
|
||||
foreach ($geraete as $g) {
|
||||
$indexedNotenArray[$pId][$g['id']] = [];
|
||||
}
|
||||
$indexedNotenArray[$pId][0] = []; // Total/Overall device per person
|
||||
}
|
||||
foreach ($displayedNoten as $an) {
|
||||
$nId = $an['id'];
|
||||
$isProGeraet = (intval($an['pro_geraet']) === 1);
|
||||
$allowedGeraete = $ascArrayGeraeteJSON[$nId] ?? [];
|
||||
|
||||
// 2. Fill with existing database values
|
||||
$sortArray = [];
|
||||
foreach ($notenEntries as $sn) {
|
||||
$pId = $sn['person_id'];
|
||||
$gId = $sn['geraet_id'];
|
||||
$nId = $sn['note_bezeichnung_id'];
|
||||
$val = $sn['value'];
|
||||
foreach ($notenGeraeteArray as $g) {
|
||||
$gId = $g['id'];
|
||||
|
||||
if ($isProGeraet) {
|
||||
if ($gId === 0) continue;
|
||||
} else {
|
||||
if (!in_array($gId, $allowedGeraete)) continue;
|
||||
}
|
||||
|
||||
$indexedNotenArray[$pId][$gId][$nId] = $val;
|
||||
|
||||
// Check if this specific note is the one we rank by
|
||||
// Usually ranking happens on Device 0 (Total) for the $rangNote ID
|
||||
if (intval($nId) === $rangNote && intval($gId) === 0) {
|
||||
$sortArray[$pId] = $val; // FIXED: used = instead of ===
|
||||
$indexedNotenArray[$pId][$gId][$nId] = $indexedNotenEntries[$pId][$nId][$gId] ?? [$an['default_value']];
|
||||
}
|
||||
|
||||
if (intval($nId) === $rangNote) {
|
||||
$val = $indexedNotenArray[$pId][0][$nId] ?? [$an['default_value']];
|
||||
$sortArray[$pId] = is_array($val) ? end($val) : $val; // fallback to the last run for sorting if needed, or total should just be one run
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$wkWebpageDomain = db_get_var($mysqli, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['linkWebseite']);
|
||||
|
||||
// Load TCPDF
|
||||
require $baseDir . '/../composer/vendor/autoload.php';
|
||||
|
||||
@@ -181,227 +223,318 @@ if (file_exists($fontfile)) {
|
||||
$fontname = TCPDF_FONTS::addTTFfont($fontfile, 'TrueTypeUnicode', '', 32);
|
||||
}
|
||||
|
||||
class MYPDF extends TCPDF
|
||||
class RanglistePDF extends TCPDF
|
||||
{
|
||||
public $current_year;
|
||||
public $programm;
|
||||
public $upperprogramm;
|
||||
public $columns;
|
||||
public $disciplines;
|
||||
public $wkName;
|
||||
public $marginSide;
|
||||
public $marginTop;
|
||||
public $pdfHeight;
|
||||
public $pdfWidth;
|
||||
public $wkWebpageDomain;
|
||||
// Page header
|
||||
|
||||
public $headerBottomY = 0;
|
||||
|
||||
public function Header()
|
||||
{
|
||||
$image_file = 'https://kutu-tage-beider-basel.testseite-fh-ht.chhttps://kutu-tage-beider-basel.testseite-fh-ht.ch/wp-content/uploads/2025/06/ktbb-logo.png';
|
||||
$this->SetY(15);
|
||||
$this->SetFont('helvetica', 'B', 20);
|
||||
$this->Cell(0, 0, 'Rangliste Kutu-Tage beider Basel ' . $this->current_year, 0, 1, 'L', 0, '', 0, false, 'M', 'M');
|
||||
$this->SetFillColor(255, 255, 255);
|
||||
|
||||
$this->Rect(0, 0, $this->pdfWidth, $this->pdfHeight, 'F');
|
||||
|
||||
$arrayTitles = [];
|
||||
$image_file = $_SERVER['DOCUMENT_ROOT'] . '/intern/img/logo-normal.png';
|
||||
$this->SetY($this->marginTop);
|
||||
$this->SetX(($this->marginSide ?? 10));
|
||||
$this->SetFont('freesans', '', 20);
|
||||
$this->Cell(0, 0, 'Rangliste ' . $this->wkName . ' ' . $this->current_year, 0, 1, 'L', 0, '', 0, false, 'T', 'M');
|
||||
$preimageX = $this->GetX();
|
||||
$preimageY = $this->GetY();
|
||||
$this->Image($image_file, 272, 5, 15, '', 'JPG', '', 'T', false, 300, '', false, false, 0, false, false, false);
|
||||
$this->Image($image_file, $this->pdfWidth - 10 - $this->marginSide, $this->marginTop, '', 10, 'png', '', 'T');
|
||||
|
||||
$this->SetX($preimageX);
|
||||
$this->SetY($preimageY);
|
||||
$this->SetY($preimageY);
|
||||
$this->SetX($this->marginSide ?? 10); // Force back to left margin
|
||||
$this->SetFont('freesans', '', 11);
|
||||
$this->Cell(0, 10, 'Programm: ' . $this->upperprogramm, 0, 1, 'L');
|
||||
|
||||
$this->SetFont('helvetica', '', 11);
|
||||
$this->Cell(0, 10, 'Programm: ' . $this->upperprogramm, 0, 1, 'L');
|
||||
$this->Ln(5);
|
||||
|
||||
$columns = $this->columns;
|
||||
$disciplines = $this->disciplines;
|
||||
|
||||
// Print first header row
|
||||
$preheaderX = $this->GetX();
|
||||
$preheaderY = $this->GetY();
|
||||
$this->SetFont('', 'B');
|
||||
$this->Cell($columns['rang']['max_width'], 7, $columns['rang']['header'], 0, 0, 'C');
|
||||
$this->Cell($columns['name']['max_width'], 7, $columns['name']['header'], 0, 0, 'L');
|
||||
$this->Cell($columns['vorname']['max_width'], 7, $columns['vorname']['header'], 0, 0, 'L');
|
||||
$this->Cell($columns['geburtsdatum']['max_width'], 7, $columns['geburtsdatum']['header'], 0, 0, 'C');
|
||||
$this->Cell($columns['verein']['max_width'], 7, $columns['verein']['header'], 0, 0, 'L');
|
||||
$startX = $this->GetX();
|
||||
$startY = $this->GetY();
|
||||
$this->SetLineWidth(0.2);
|
||||
$this->SetFont('', 'B');
|
||||
|
||||
// Loop through disciplines for rotated headers
|
||||
foreach ($disciplines as $d) {
|
||||
$w = $columns["d-note $d"]['max_width'] + $columns["note $d"]['max_width'];
|
||||
$ws = $columns["note $d"]['max_width'];
|
||||
$this->SetX($this->marginSide ?? 10);
|
||||
|
||||
$x = $this->GetX();
|
||||
$y = $this->GetY() + 7;
|
||||
foreach ($columns as $col) {
|
||||
$isDuplicate = in_array($col['header'], $arrayTitles);
|
||||
|
||||
// Start transformation for rotation
|
||||
$this->StartTransform();
|
||||
$title = $isDuplicate ? '' : $col['header'];
|
||||
|
||||
// Rotate around top-left of cell
|
||||
$this->Rotate(45, $x, $y);
|
||||
if (!$isDuplicate) {
|
||||
$arrayTitles[] = $col['header'];
|
||||
}
|
||||
|
||||
// Draw a line **above the first data row**
|
||||
$lineY = $startY + 7; // adjust 7 depending on cell height
|
||||
$this->Line($x, $lineY, $x + $ws, $lineY);
|
||||
if (($col['rotate'] ?? false) && !$isDuplicate) {
|
||||
$x = $this->GetX();
|
||||
$y = $this->GetY() + 7;
|
||||
|
||||
// Print the rotated header text
|
||||
$this->Cell($w, 7, ucfirst($d), 0, 0, 'L');
|
||||
$this->StartTransform();
|
||||
$this->Rotate(45, $x, $y);
|
||||
|
||||
$this->StopTransform();
|
||||
$lineY = $startY + 7;
|
||||
if (($col['id'] ?? '') === 'gesamt') {
|
||||
$this->SetLineWidth(0.6);
|
||||
} else {
|
||||
$this->SetLineWidth(0.2);
|
||||
}
|
||||
|
||||
$this->Line($x, $lineY, $x + ($col['width_header'] ?? 0), $lineY);
|
||||
$this->SetLineWidth(0.2);
|
||||
|
||||
$this->SetX($x + $w);
|
||||
$this->Cell($col['width_header'] ?? 0, 7, $title ?? '', 0, 0, 'C');
|
||||
$this->StopTransform();
|
||||
$this->SetX($x + ($col['max_width'] ?? 0));
|
||||
} else {
|
||||
$this->Cell($col['max_width'] ?? 0, 7, $title ?? '', 0, 0, $col['align'] ?? 'L');
|
||||
}
|
||||
}
|
||||
|
||||
$w = $columns["gesamt"]['max_width'];
|
||||
$ws = $w;
|
||||
|
||||
$x = $this->GetX();
|
||||
$y = $this->GetY() + 7;
|
||||
|
||||
// Start transformation for rotation
|
||||
$this->StartTransform();
|
||||
|
||||
// Rotate around top-left of cell
|
||||
$this->Rotate(45, $x, $y);
|
||||
|
||||
// Draw a line **above the first data row**
|
||||
$lineY = $startY + 7; // adjust 7 depending on cell height
|
||||
|
||||
$this->SetLineWidth(0.6);
|
||||
|
||||
$this->Line($x, $lineY, $x + $ws, $lineY);
|
||||
|
||||
$this->SetLineWidth(0.2);
|
||||
|
||||
|
||||
// Print the rotated header text
|
||||
$this->Cell($w, 7, $columns['gesamt']['header'], 0, 0, 'L');
|
||||
|
||||
$this->StopTransform();
|
||||
|
||||
$this->SetX($x + $w);
|
||||
|
||||
$afterheaderX = $this->GetX();
|
||||
|
||||
$this->Ln();
|
||||
|
||||
$xheaderline = $preheaderX;
|
||||
$yheaderline = $this->GetY();
|
||||
|
||||
$this->headerBottomY = $this->GetY();
|
||||
}
|
||||
|
||||
|
||||
// Page footer
|
||||
public function Footer()
|
||||
{
|
||||
$this->SetY(-15);
|
||||
$this->SetFont('helvetica', 'I', 8);
|
||||
$this->Cell(0, 10, 'Seite ' . $this->getAliasNumPage() . ' von ' . $this->getAliasNbPages(), 0, false, 'C', 0, '', 0, false, 'T', 'M');
|
||||
$this->SetFont('', '');
|
||||
$this->Cell(0, 10, $_SERVER['HTTP_HOST'], 0, 0, 'R', false, 'https://'.$_SERVER['HTTP_HOST'].'/ergebnis/'.$this->current_year, 0, false, 'T', 'M');
|
||||
|
||||
$this->SetLineWidth(0.6);
|
||||
$this->Line(10, $this->headerBottomY, 297 - 10, $this->headerBottomY);
|
||||
$this->Line($this->marginSide ?? 10, $this->headerBottomY, $this->pdfWidth - ($this->marginSide ?? 10), $this->headerBottomY);
|
||||
|
||||
$this->SetLineWidth(0.2);
|
||||
|
||||
$this->SetY(0);
|
||||
$this->SetX(5);
|
||||
$this->SetX(0);
|
||||
|
||||
$this->SetFillColor(255, 255, 255); // white
|
||||
|
||||
$this->Cell(5, 190, '', 0, 0, 'L', true);
|
||||
$this->Cell(($this->marginSide ?? 10), $this->pdfHeight, '', 0, 0, 'L', true);
|
||||
|
||||
$this->SetY(0);
|
||||
$this->SetX(297 - 10);
|
||||
$this->SetX($this->pdfWidth - ($this->marginSide ?? 10));
|
||||
|
||||
$this->Cell(5, 190, '', 0, 0, 'L', true);
|
||||
$this->Cell(($this->marginSide ?? 10), $this->pdfHeight, '', 0, 0, 'L', true);
|
||||
|
||||
$this->SetY(-15);
|
||||
$this->SetFont('freesans', 'I', 8);
|
||||
$this->Cell(0, 10, 'Seite ' . $this->getAliasNumPage() . ' von ' . $this->getAliasNbPages(), 0, false, 'C', 0, '', 0, false, 'T', 'M');
|
||||
$this->SetFont('', '');
|
||||
$this->Cell(0, 10, ($this->wkWebpageDomain ?? $_SERVER['HTTP_HOST']), 0, 0, 'R', false, 'https://' . ($this->wkWebpageDomain ?? $_SERVER['HTTP_HOST']), 0, false, 'T', 'M');
|
||||
}
|
||||
}
|
||||
|
||||
$pdf = new MYPDF('L', 'mm', 'A4', true, 'UTF-8', false);
|
||||
$pdf = new RanglistePDF($orientation, 'mm', $format, true, 'UTF-8', false);
|
||||
|
||||
$pdfHeight = $pdf->getPageHeight();
|
||||
$pdfWidth = $pdf->getPageWidth();
|
||||
|
||||
$pdf->current_year = $current_year;
|
||||
$pdf->wkName = $wkName;
|
||||
$pdf->wkWebpageDomain = $wkWebpageDomain;
|
||||
$pdf->programm = $programm;
|
||||
$pdf->upperprogramm = $upperprogramm;
|
||||
$pdf->marginSide = $marginSide;
|
||||
$pdf->marginTop = $marginTop;
|
||||
$pdf->pdfHeight = $pdfHeight;
|
||||
$pdf->pdfWidth = $pdfWidth;
|
||||
|
||||
$pdf->SetCreator(PDF_CREATOR);
|
||||
$pdf->SetAuthor('WKVS-FH');
|
||||
$pdf->SetTitle("KTBB_Ergebnisse_" . $programm . "_" . $current_year . ".pdf");
|
||||
$pdf->SetAuthor($wkName);
|
||||
$pdf->SetTitle($wkName . "_Ergebnisse_" . $programm . "_" . $current_year . ".pdf");
|
||||
|
||||
$pdf->SetAutoPageBreak(TRUE, 10);
|
||||
$pdf->SetFont('helvetica', '', 11);
|
||||
$pdf->SetAutoPageBreak(FALSE);
|
||||
|
||||
|
||||
// Define columns (including discipline sub-columns)
|
||||
$pdf->SetFont('freesans', '', 11);
|
||||
|
||||
// Define columns dynamically
|
||||
$columns = [
|
||||
'rang' => [ 0 => ['header' => 'Rang']],
|
||||
'name' => [ 0 => ['header' => 'Name']],
|
||||
'vorname' => [ 0 => ['header' => 'Vorname']],
|
||||
'geburtsdatum' => [ 0 => ['header' => 'Jg.']],
|
||||
'verein' => [ 0 => ['header' => 'Verein']],
|
||||
['id' => 'rang', 'header' => 'Rang', 'align' => 'C', 'flex' => false, 'type' => 'rank'],
|
||||
['id' => 'name', 'header' => 'Name', 'align' => 'L', 'flex' => true, 'type' => 'field', 'field' => 'name'],
|
||||
['id' => 'vorname', 'header' => 'Vorname', 'align' => 'L', 'flex' => true, 'type' => 'field', 'field' => 'vorname'],
|
||||
['id' => 'geburtsdatum', 'header' => 'Jg.', 'align' => 'C', 'flex' => false, 'type' => 'year', 'field' => 'geburtsdatum'],
|
||||
['id' => 'verein', 'header' => 'Verein', 'align' => 'L', 'flex' => true, 'type' => 'field', 'field' => 'verein'],
|
||||
];
|
||||
|
||||
foreach ($displayedNoten as $sdN) {
|
||||
if (intval($sdN['pro_geraet']) === 1) {
|
||||
foreach ($geraete as $g) {
|
||||
$columns[$sdN['name']][$g['id']] = ['header' => $sdN['name'], 'nullstellen' => $sdN['nullstellen'] ?? 2];
|
||||
}
|
||||
} else {
|
||||
$allowed = $ascArrayGeraeteJSON[$sdN['id']] ?? [];
|
||||
foreach ($allowed as $gId) {
|
||||
$columns[$sdN['name']][$gId] = ['header' => $sdN['name'], 'nullstellen' => $sdN['nullstellen'] ?? 2];
|
||||
// Add notes dynamically per device
|
||||
foreach ($geraete as $g) {
|
||||
foreach ($displayedNoten as $noteDef) {
|
||||
$neni = $noteDef['id'];
|
||||
$isProGeraet = (intval($noteDef['pro_geraet']) === 1);
|
||||
$allowedGeraete = $ascArrayGeraeteJSON[$neni] ?? [];
|
||||
|
||||
if ($isProGeraet || in_array($g['id'], $allowedGeraete)) {
|
||||
$columns[] = [
|
||||
'id' => 'note_' . $g['id'] . '_' . $neni,
|
||||
'header' => $g['name'],
|
||||
'align' => 'C',
|
||||
'flex' => false,
|
||||
'rotate' => true,
|
||||
'type' => 'note',
|
||||
'geraet_id' => $g['id'],
|
||||
'note_bezeichnung_id' => $neni,
|
||||
'nullstellen' => $noteDef['nullstellen'] ?? 2,
|
||||
'font_size' => intval($noteDef['groesse_auf_rangliste']) ?? 0
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize max widths with header widths (+4 mm padding)
|
||||
foreach ($columns as $key => $col) {
|
||||
foreach ($col as $skey => $scol) {
|
||||
$columns[$key][$skey]['max_width'] = $pdf->GetStringWidth($scol['header']) + 8;
|
||||
}
|
||||
// Add Gesamt (Total) column
|
||||
$columns[] = [
|
||||
'id' => 'gesamt',
|
||||
'header' => $alleNotenIndexed[$rangNote]['name'] ?? 'Total',
|
||||
'align' => 'C',
|
||||
'flex' => false,
|
||||
'rotate' => true,
|
||||
'type' => 'note',
|
||||
'geraet_id' => 0,
|
||||
'note_bezeichnung_id' => $rangNote,
|
||||
'nullstellen' => $alleNotenIndexed[$rangNote]['nullstellen'] ?? 2,
|
||||
'font_size' => intval($alleNotenIndexed[$rangNote]['groesse_auf_rangliste']) ?? 0
|
||||
];
|
||||
|
||||
|
||||
|
||||
$padding = 4;
|
||||
|
||||
unset($col);
|
||||
|
||||
// 1. Calculate initial max widths based on headers
|
||||
foreach ($columns as &$col) {
|
||||
$col['width_header'] = $pdf->GetStringWidth($col['header']) + ($padding * 2);
|
||||
if ($col['type'] !== 'note') {
|
||||
$col['max_width'] = $col['width_header'];
|
||||
} else {
|
||||
$col['max_width'] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Fill missing defaults and finalize the sortArray
|
||||
// 2. Fill missing defaults and update max widths based on data
|
||||
foreach ($personen as $sp) {
|
||||
$pId = $sp['id'];
|
||||
|
||||
// We check all possible devices (including 0)
|
||||
foreach ($indexedNotenArray[$pId] as $sG => $currentNoten) {
|
||||
|
||||
foreach ($alleNoten as $noteDef) {
|
||||
foreach ($displayedNoten as $noteDef) {
|
||||
$neni = $noteDef['id'];
|
||||
|
||||
// Skip if note already exists for this person/device
|
||||
if (isset($currentNoten[$neni])) continue;
|
||||
|
||||
// Logic for "Pro Gerät" vs "Specific/Total"
|
||||
$isProGeraet = (intval($noteDef['pro_geraet']) === 1);
|
||||
$allowedGeraete = $ascArrayGeraeteJSON[$neni] ?? [];
|
||||
|
||||
if ($isProGeraet) {
|
||||
if ($sG === 0) continue;
|
||||
if (isset($currentNoten[$neni])) {
|
||||
$runs = $currentNoten[$neni];
|
||||
if (is_array($runs)) {
|
||||
$runsConfig = $ascArrayAnzahlLaeufeJSON[$neni] ?? [];
|
||||
$runsCount = $runsConfig[$sG][$programmId] ?? $runsConfig[$sG]['all'] ?? $runsConfig["default"] ?? 1;
|
||||
$defaultValue = $ascArrayDefaultValues[$neni] ?? 0;
|
||||
|
||||
for ($r = 1; $r <= $runsCount - 1; $r++) {
|
||||
if (!isset($runs[$r])) {
|
||||
$runs[$r] = $defaultValue;
|
||||
}
|
||||
}
|
||||
$indexedNotenArray[$pId][$sG][$neni] = $runs;
|
||||
|
||||
ksort($runs);
|
||||
$maxLenStr = "";
|
||||
foreach ($runs as $rVal) {
|
||||
$str = number_format((float)$rVal, $noteDef['nullstellen']);
|
||||
// Approximate max width by string length, GetStringWidth will do the real check later
|
||||
if (strlen($str) > strlen($maxLenStr)) {
|
||||
$maxLenStr = $str;
|
||||
}
|
||||
}
|
||||
$val = $maxLenStr;
|
||||
} else {
|
||||
$val = number_format((float)$runs, $noteDef['nullstellen']);
|
||||
}
|
||||
} else {
|
||||
if (!in_array($sG, $allowedGeraete)) continue;
|
||||
// Logic for "Pro Gerät" vs "Specific/Total"
|
||||
$isProGeraet = (intval($noteDef['pro_geraet']) === 1);
|
||||
$allowedGeraete = $ascArrayGeraeteJSON[$neni] ?? [];
|
||||
|
||||
if ($isProGeraet) {
|
||||
if ($sG === 0) continue;
|
||||
} else {
|
||||
if (!in_array($sG, $allowedGeraete)) continue;
|
||||
}
|
||||
|
||||
$defaultValue = $ascArrayDefaultValues[$neni] ?? 0;
|
||||
|
||||
$runsConfig = $ascArrayAnzahlLaeufeJSON[$neni] ?? [];
|
||||
$runsCount = $runsConfig[$sG][$programmId] ?? $runsConfig[$sG]['all'] ?? $runsConfig["default"] ?? 1;
|
||||
|
||||
$valArray = [];
|
||||
$maxLenStr = "";
|
||||
for ($r = 1; $r <= $runsCount - 1; $r++) {
|
||||
$valArray[$r] = $defaultValue;
|
||||
$str = number_format((float)$defaultValue, $noteDef['nullstellen']);
|
||||
if (strlen($str) > strlen($maxLenStr)) {
|
||||
$maxLenStr = $str;
|
||||
}
|
||||
}
|
||||
|
||||
$val = $maxLenStr;
|
||||
$indexedNotenArray[$pId][$sG][$neni] = $valArray;
|
||||
|
||||
if ($neni === $rangNote && $sG === 0) {
|
||||
$sortArray[$pId] = $defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Assign default value
|
||||
$defaultValue = $ascArrayDefaultValues[$neni] ?? 0;
|
||||
|
||||
$string = nuber_format($defaultValue, $noteDef['nullstellen']);
|
||||
// Update column width if this note is displayed
|
||||
foreach ($columns as &$col) {
|
||||
if ($col['type'] === 'note' && $col['geraet_id'] == $sG && $col['note_bezeichnung_id'] == $neni) {
|
||||
|
||||
$indexedNotenArray[$pId][$sG][$neni] = $string;
|
||||
$customFontSize = (isset($col['font_size']) && $col['font_size'] != 0);
|
||||
|
||||
$width = $pdf->GetStringWidth($string) + 4;
|
||||
$cpadding = $padding;
|
||||
|
||||
if ($w > $columns[$noteDef['name']][$sG]['max_width']) {
|
||||
$columns[$noteDef['name']][$sG] = $w;
|
||||
}
|
||||
if ($customFontSize) {
|
||||
$pdf->SetFont('', '', $col['font_size']);
|
||||
$cpadding = ($col['font_size'] / $normalFontSize) * $padding;
|
||||
}
|
||||
|
||||
// If this missing note was our ranking note, add default to sortArray
|
||||
if ($neni === $rangNote && $sG === 0) {
|
||||
$sortArray[$pId] = $defaultValue;
|
||||
$width = $pdf->GetStringWidth($val) + ($cpadding * 2);
|
||||
if ($width > $col['max_width']) {
|
||||
$col['max_width'] = $width;
|
||||
}
|
||||
|
||||
if ($customFontSize) {
|
||||
$pdf->SetFont('', '', $normalFontSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
unset($col);
|
||||
}
|
||||
}
|
||||
|
||||
// Also update widths for static fields
|
||||
foreach ($columns as &$col) {
|
||||
if ($col['type'] === 'field' || $col['type'] === 'year' || $col['type'] === 'rank') {
|
||||
$text = '';
|
||||
if ($col['type'] === 'rank') {
|
||||
$text = '999'; // Placeholder for rank width
|
||||
} elseif ($col['type'] === 'year') {
|
||||
$text = '0000';
|
||||
} elseif (isset($sp[$col['field']])) {
|
||||
$text = $sp[$col['field']];
|
||||
}
|
||||
$width = $pdf->GetStringWidth($text) + ($padding * 2);
|
||||
if ($width > $col['max_width']) {
|
||||
$col['max_width'] = $width;
|
||||
}
|
||||
}
|
||||
}
|
||||
unset($col);
|
||||
}
|
||||
|
||||
function calculateRanks(array $scoreArray, $direction = 'DESC') {
|
||||
@@ -431,66 +564,40 @@ function calculateRanks(array $scoreArray, $direction = 'DESC') {
|
||||
return $ranks;
|
||||
}
|
||||
|
||||
// 4. Calculate Ranks
|
||||
// FIXED: Passing $orderBestRang (the string "ASC"/"DESC") instead of the array
|
||||
|
||||
// 3. Finalize Ranks
|
||||
$ranks = calculateRanks($sortArray, $orderBestRang);
|
||||
|
||||
// Calculate max width of each column based on all data
|
||||
foreach ($personen as $p) {
|
||||
foreach ($columns as $key => &$col) {
|
||||
if (isset($p[$key])) {
|
||||
$text = $p[$key];
|
||||
} else {
|
||||
$text = '';
|
||||
}
|
||||
$w = $pdf->GetStringWidth($text) + 4;
|
||||
// Add some padding
|
||||
if ($w > $col['max_width']) {
|
||||
$col['max_width'] = $w;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unset($col);
|
||||
$columns['geburtsdatum'][0]['max_width'] = $pdf->GetStringWidth("0000") + 8;
|
||||
|
||||
$tablew = 297 - 20; // total table width (A4 landscape in mm)
|
||||
|
||||
// Step 1: Calculate total width of fixed columns
|
||||
// 4. Distribute Flexible Widths
|
||||
$tablew = $pdfWidth - (2 * $marginSide); // total table width (A4 landscape in mm minus margins)
|
||||
$fixedWidth = 0;
|
||||
$flexCols = [];
|
||||
|
||||
/*
|
||||
// Discipline columns (each discipline has two columns: "d-note" and "note")
|
||||
foreach ($disciplines as $d) {
|
||||
$fixedWidth += $columns["d-note $d"]['max_width'] + $columns["note $d"]['max_width'];
|
||||
foreach ($columns as $col) {
|
||||
if ($col['flex'] ?? false) {
|
||||
$flexCols[] = $col['id'];
|
||||
$fixedWidth += $col['max_width'];
|
||||
} else {
|
||||
$fixedWidth += $col['max_width'];
|
||||
}
|
||||
}
|
||||
|
||||
// Other fixed columns
|
||||
$fixedColumns = ['geburtsdatum','rang','gesamt'];
|
||||
foreach ($fixedColumns as $col) {
|
||||
$fixedWidth += $columns[$col]['max_width'];
|
||||
}
|
||||
|
||||
// Step 2: Compute extra space for flexible columns
|
||||
$flexCols = ['name','vorname','verein']; // columns that can grow
|
||||
$flexTotal = 0;
|
||||
|
||||
// Compute current total width of flexible columns
|
||||
foreach ($flexCols as $col) {
|
||||
$flexTotal += $columns[$col]['max_width'];
|
||||
}
|
||||
|
||||
// Available width for flexible columns
|
||||
$effTableWidth = $tablew - $fixedWidth;
|
||||
$extraWidth = $effTableWidth - $flexTotal;
|
||||
|
||||
// Step 3: Distribute extra width proportionally among flexible columns
|
||||
if ($extraWidth > 0) {
|
||||
foreach ($flexCols as $col) {
|
||||
$columns[$col]['max_width'] += ($columns[$col]['max_width'] / $flexTotal) * $extraWidth;
|
||||
}
|
||||
|
||||
if (!empty($flexCols) && $effTableWidth > 0) {
|
||||
$flexTotalInitial = 0;
|
||||
foreach ($columns as $col) {
|
||||
if ($col['flex'] ?? false) $flexTotalInitial += $col['max_width'];
|
||||
}
|
||||
|
||||
foreach ($columns as &$col) {
|
||||
if ($col['flex'] ?? false) {
|
||||
$col['max_width'] += ($col['max_width'] / $flexTotalInitial) * $effTableWidth;
|
||||
}
|
||||
}
|
||||
unset($col);
|
||||
}
|
||||
*/
|
||||
|
||||
$pdf->columns = $columns;
|
||||
$pdf->disciplines = $disciplines;
|
||||
@@ -501,14 +608,19 @@ $pdf->AddPage();
|
||||
$margin_top = $pdf->headerBottomY;
|
||||
|
||||
// Now adjust your margins if needed
|
||||
$pdf->SetMargins(10, $margin_top, 10); // +5 mm padding below header
|
||||
$pdf->SetMargins($marginSide, $margin_top, $marginSide); // +5 mm padding below header
|
||||
$pdf->SetY($margin_top); // Move cursor below header manually
|
||||
|
||||
|
||||
$mbs = false;
|
||||
$mbl = false;
|
||||
$pdf->SetFont('', '');
|
||||
$index = 0;
|
||||
$rIndex = 0;
|
||||
|
||||
usort($personen, function($a, $b) use ($ranks) {
|
||||
$rankA = $ranks[$a['id']];
|
||||
$rankB = $ranks[$b['id']];
|
||||
|
||||
return $rankA <=> $rankB;
|
||||
});
|
||||
|
||||
foreach ($personen as $r) {
|
||||
|
||||
$pdf->SetTextColor(0, 0, 0);
|
||||
@@ -519,81 +631,118 @@ foreach ($personen as $r) {
|
||||
$pdf->SetFillColor(255, 235, 128);
|
||||
} elseif($rang == 2) {
|
||||
$pdf->SetFillColor(224, 224, 224);
|
||||
} elseif($$rang == 3) {
|
||||
} elseif($rang == 3) {
|
||||
$pdf->SetFillColor(230, 191, 153);
|
||||
} elseif ($index % 2 == 0) {
|
||||
} elseif ($rIndex % 2 == 0) {
|
||||
$pdf->SetFillColor(255, 255, 255); // white
|
||||
} else {
|
||||
$pdf->SetFillColor(240, 240, 240); // light gray
|
||||
}
|
||||
|
||||
|
||||
$pdf->SetFont('', 'B');
|
||||
/*
|
||||
if ($buttontype === 'export_programm_bm') {
|
||||
if (($r['verein'] === 'BTV Basel' || $r['verein'] === 'TV Basel') && $mbs === false){
|
||||
$pdf->SetFillColor(0, 0, 0);
|
||||
$pdf->SetTextColor(255, 255, 255);
|
||||
$mbs = true;
|
||||
} elseif (($r['verein'] === 'Kutu Regio Basel') && $mbl === false) {
|
||||
$pdf->SetFillColor(255, 0, 0);
|
||||
$pdf->SetTextColor(255, 255, 255);
|
||||
$mbl = true;
|
||||
}
|
||||
}*/
|
||||
|
||||
$pdf->Cell($columns['rang'][0]['max_width'], 8, $rang, 'B', 0, 'C', true);
|
||||
|
||||
$pdf->SetFont('', '');
|
||||
$pdf->Cell($columns['name'][0]['max_width'], 8, $r['name'], 'B', 0, 'L', true);
|
||||
$pdf->Cell($columns['vorname'][0]['max_width'], 8, $r['vorname'], 'B', 0, 'L', true);
|
||||
$pdf->Cell($columns['geburtsdatum'][0]['max_width'], 8, (new DateTime($r['geburtsdatum']))->format("Y"), 'B', 0, 'C', true);
|
||||
$pdf->Cell($columns['verein'][0]['max_width'], 8, $r['verein'], 'B', 0, 'L', true);
|
||||
|
||||
|
||||
foreach ($disciplines as $d) {
|
||||
$d_note = isset($r["d-note $d"]) ?
|
||||
number_format((float)$r["d-note $d"], 2) : '';
|
||||
$note = isset($r["note $d"]) ? number_format((float)$r["note $d"], 3) : '';
|
||||
$pdf->SetFont('', '', 9);
|
||||
$pdf->Cell($columns["d-note $d"]['max_width'], 8, $d_note, 'LB', 0, 'C', true);
|
||||
$pdf->SetFont('', '', 11);
|
||||
$pdf->Cell($columns["note $d"]['max_width'], 8, $note, 'B', 0, 'C', true);
|
||||
// Determine row height based on max runs
|
||||
$maxRuns = 1;
|
||||
foreach ($columns as $col) {
|
||||
if ($col['type'] === 'note') {
|
||||
$runs = $indexedNotenArray[$r['id']][$col['geraet_id']][$col['note_bezeichnung_id']] ?? [];
|
||||
if (is_array($runs) && count($runs) > $maxRuns) {
|
||||
$maxRuns = count($runs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$gesamt = isset($r["gesamtpunktzahl"]) ? number_format((float)$r["gesamtpunktzahl"], 3) : '';
|
||||
// Base height is 8, add extra height for additional runs. Let's say 4mm per additional line.
|
||||
// But to keep it centered properly, we will use MultiCell with valign='M' (Middle).
|
||||
$rowHeight = max(8, $maxRuns * 5 + 3); // 5mm per line
|
||||
|
||||
$pdf->SetFont('', 'B');
|
||||
$pdf->Cell($columns['gesamt'][0]['max_width'], 8, $gesamt, 'B', 0, 'C', true);
|
||||
$pdf->SetFont('', '');
|
||||
if ($pdf->getY() + $rowHeight >= $pdfHeight - $minMarginBottomTable) {
|
||||
$pdf->addPage();
|
||||
$pdf->setX($marginSide);
|
||||
}
|
||||
|
||||
$x = $pdf->GetX() - $columns['gesamt'][0]['max_width'];
|
||||
$y = $pdf->GetY();
|
||||
foreach ($columns as $col) {
|
||||
|
||||
$pdf->SetLineWidth(0.6);
|
||||
$pdf->Line($x, $y, $x, $y + 7.7);
|
||||
$customFontSize = (isset($col['font_size']) && $col['font_size'] != 0);
|
||||
|
||||
$pdf->SetLineWidth(0.2);
|
||||
$pdf->Ln();
|
||||
if ($customFontSize) {
|
||||
$pdf->SetFont('', '', $col['font_size']);
|
||||
}
|
||||
|
||||
$index++;
|
||||
$startX = $pdf->GetX();
|
||||
$startY = $pdf->GetY();
|
||||
|
||||
if ($col['id'] === 'rang') {
|
||||
$pdf->SetFont('', 'B');
|
||||
$pdf->MultiCell($col['max_width'], $rowHeight, $rang, 'B', 'C', true, 0, $startX, $startY, true, 0, false, true, $rowHeight, 'M');
|
||||
$pdf->SetFont('', '');
|
||||
} elseif ($col['type'] === 'field') {
|
||||
$pdf->MultiCell($col['max_width'], $rowHeight, $r[$col['field']], 'B', $col['align'], true, 0, $startX, $startY, true, 0, false, true, $rowHeight, 'M');
|
||||
} elseif ($col['type'] === 'year') {
|
||||
$pdf->MultiCell($col['max_width'], $rowHeight, (new DateTime($r[$col['field']]))->format("Y"), 'B', $col['align'], true, 0, $startX, $startY, true, 0, false, true, $rowHeight, 'M');
|
||||
} elseif ($col['type'] === 'note') {
|
||||
$runs = $indexedNotenArray[$r['id']][$col['geraet_id']][$col['note_bezeichnung_id']] ?? [];
|
||||
if (!is_array($runs)) {
|
||||
$runs = [$runs];
|
||||
} else {
|
||||
ksort($runs);
|
||||
}
|
||||
|
||||
$numRuns = max(count($runs), 1);
|
||||
$lineHeight = $rowHeight / $numRuns;
|
||||
|
||||
$pdf->SetXY($startX, $startY);
|
||||
$pdf->Cell($col['max_width'], $rowHeight, '', 0, 0, '', true);
|
||||
|
||||
$runIndex = 0;
|
||||
foreach (array_values($runs) as $rnVal) {
|
||||
$formatted = number_format((float)$rnVal, $col['nullstellen'] ?? 0);
|
||||
$currentY = $startY + ($runIndex * $lineHeight);
|
||||
|
||||
$pdf->SetXY($startX, $currentY);
|
||||
|
||||
$pdf->Cell($col['max_width'], $lineHeight, $formatted, 0, 0, $col['align'], false);
|
||||
$runIndex++;
|
||||
}
|
||||
|
||||
// Draw the bottom border for the entire row height
|
||||
$pdf->Line($startX, $startY + $rowHeight, $startX + $col['max_width'], $startY + $rowHeight);
|
||||
|
||||
// Reset pointer for the next column
|
||||
$pdf->SetXY($startX + $col['max_width'], $startY);
|
||||
|
||||
if ($col['id'] === 'gesamt') {
|
||||
// Draw the heavy separator line on the left side of the "Gesamt" column
|
||||
$pdf->SetLineWidth(0.6);
|
||||
$pdf->Line($startX, $startY, $startX, $startY + $rowHeight);
|
||||
$pdf->SetLineWidth(0.2);
|
||||
}
|
||||
}
|
||||
|
||||
if ($customFontSize) {
|
||||
$pdf->SetFont('', '', $normalFontSize);
|
||||
}
|
||||
}
|
||||
|
||||
$pdf->SetY($startY + $rowHeight); // Move Y manually instead of Ln() to account for rowHeight
|
||||
$rIndex++;
|
||||
}
|
||||
|
||||
|
||||
|
||||
$pdf->SetFont('', '', 7);
|
||||
$textanzturnerinnen = (count($entries) > 1) ? 'Turnerinnen': 'Turnerin';
|
||||
$pdf->Cell(20, 6, count($entries).' '.$textanzturnerinnen, 0, 0, 'L');
|
||||
$textanzturnerinnen = (count($personen) > 1) ? 'Turnerinnen': 'Turnerin';
|
||||
$pdf->Cell(20, 6, count($personen).' '.$textanzturnerinnen, 0, 0, 'L');
|
||||
|
||||
if ($buttontype === 'export_programm' || $buttontype === 'export_programm_bm') {
|
||||
$pdf->Output("KTBB_Ergebnisse_" . $programm . "_" . $current_year . ".pdf", 'D');
|
||||
if ($buttontype === 'downloadRangliste') {
|
||||
$pdf->Output($wkName . "_Ergebnisse_" . $programm . "_" . $current_year . ".pdf", 'I');
|
||||
exit;
|
||||
} elseif ($buttontype === 'upload_programm') {
|
||||
$dir = $baseDir . '/wp-content/ergebnisse/';
|
||||
} elseif ($buttontype === 'updateServerRangliste') {
|
||||
$dir = $baseDir . '/files/ranglisten/';
|
||||
if (!file_exists($dir)) mkdir($dir, 0755, true);
|
||||
$localPath = $dir . "KTBB_Ergebnisse_" . $programm . "_" . $current_year . ".pdf";
|
||||
$localPath = $dir . str_replace("/", "", str_replace(" ", "_", $wkName)) . "_Ergebnisse_" . str_replace("/", "", $programm) . "_" . $current_year . ".pdf";
|
||||
if (file_exists($localPath)) unlink($localPath);
|
||||
$pdf->Output($localPath, 'F');
|
||||
$_SESSION['form_message'] = '<div class="success">PDF wurde hochgeladen</div>';
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(["success" => true, "message" => "PDF wurde aktualisiert"]);
|
||||
exit;
|
||||
}
|
||||
@@ -24,34 +24,54 @@ if ($data['success'] === false){
|
||||
require $baseDir . '/../scripts/db/db-tables.php';
|
||||
|
||||
// ---------- Get and sanitize input ----------
|
||||
$id = isset($_POST['id']) ? intval($_POST['id']) : 0;
|
||||
$discipline = isset($_POST['discipline']) ? preg_replace('/[^a-zA-Z0-9 _-]/', '', $_POST['discipline']) : '';
|
||||
$personId = intval($_POST['personId'] ?? 0);
|
||||
$gereatId = intval($_POST['geraetId'] ?? 0);
|
||||
|
||||
if ($discipline !== 'boden') {
|
||||
echo json_encode(['success' => false, 'message' => 'Invalid discipline']);
|
||||
if ($gereatId < 1) {
|
||||
echo json_encode(['success' => false, 'message' => 'Kein Gerät angegeben']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($id <= 0) {
|
||||
if ($personId < 1) {
|
||||
echo json_encode(['success' => false, 'message' => 'Invalid ID']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ---------- Step 2: Get values from DB ----------
|
||||
|
||||
$result = $mysqli->query("SELECT t.bodenmusik, agg.filePath FROM `$tableTurnerinnen` t LEFT JOIN (SELECT a.id, a.file_path AS filePath FROM $tableAudiofiles a) agg ON agg.id = t.bodenmusik WHERE t.id = $id");
|
||||
$row = $result->fetch_assoc();
|
||||
if (!$row || !isset($row['bodenmusik'])) {
|
||||
echo json_encode(['success' => false, 'message' => 'Row fetch failed '.$id]);
|
||||
exit;
|
||||
}
|
||||
$sqlQuery = "SELECT
|
||||
paf.`audiofile_id`,
|
||||
af.`file_path`,
|
||||
af.`file_name`,
|
||||
tu.`name`,
|
||||
tu.`vorname`
|
||||
FROM $tableTeilnehmendeAudiofiles paf
|
||||
INNER JOIN $tableAudiofiles af
|
||||
ON paf.`audiofile_id` = af.`id`
|
||||
LEFT JOIN $tableTeilnehmende tu
|
||||
ON paf.`person_id` = tu.`id`
|
||||
WHERE paf.`person_id` = ?
|
||||
AND paf.`geraet_id` = ?
|
||||
";
|
||||
|
||||
if ($row['bodenmusik'] === "0" || $row['filePath'] == null) {
|
||||
$stmt = $mysqli->prepare($sqlQuery);
|
||||
|
||||
$stmt->bind_param('ii', $personId, $gereatId);
|
||||
|
||||
$stmt->execute();
|
||||
|
||||
$result = $stmt->get_result();
|
||||
|
||||
$rows = $result->fetch_all(MYSQLI_ASSOC);
|
||||
|
||||
if (count($rows) !== 1 || !isset($rows[0]['file_path']) || $rows[0]['file_path'] === null) {
|
||||
echo json_encode(['success' => false, 'message' => 'Keine Musik']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$folder = realpath($baseDir . '/displays/json');
|
||||
$row = $rows[0];
|
||||
|
||||
$folder = realpath($baseDir . '/externe-geraete/json');
|
||||
if ($folder === false) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
@@ -60,11 +80,11 @@ if ($folder === false) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$filename = 'audio.json';
|
||||
$filename = 'audio-id-'. $gereatId .'.json';
|
||||
$filepath = $folder . '/' . $filename;
|
||||
|
||||
if (!is_writable($folder)) {
|
||||
echo json_encode(['success' => false, 'message' => 'Folder not writable: ']);
|
||||
echo json_encode(['success' => false, 'message' => 'Folder not writable.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
@@ -74,22 +94,23 @@ if (!is_file($filepath)){
|
||||
}
|
||||
|
||||
|
||||
$jsonString = file_get_contents($filepath);
|
||||
$json = [
|
||||
"audio_path" => $row['file_path'],
|
||||
"audio_name" => $row['file_name'],
|
||||
"start" => true,
|
||||
"person" => [
|
||||
"name" => $row['name'],
|
||||
"vorname" => $row['vorname']
|
||||
]
|
||||
];
|
||||
|
||||
// decode JSON, fallback to empty array if invalid
|
||||
$oldjson = json_decode($jsonString, true) ?? [];
|
||||
|
||||
|
||||
$oldjson["musik"] = $row['filePath'];
|
||||
$oldjson["start"] = true;
|
||||
|
||||
$jsonData = json_encode($oldjson);
|
||||
$jsonData = json_encode($json);
|
||||
|
||||
// Write file
|
||||
if (file_put_contents($filepath, $jsonData) === false) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'Failed to write JSON file: ' . $filepath
|
||||
'message' => 'Failed to write JSON file'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
@@ -99,7 +120,6 @@ if (file_put_contents($filepath, $jsonData) === false) {
|
||||
// ---------- Return JSON ----------
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'JSON updated successfully for '.$discipline,
|
||||
'disable_musik_button' => true
|
||||
'message' => 'Audio JSON updated successfully'
|
||||
]);
|
||||
exit;
|
||||
@@ -13,9 +13,14 @@ check_user_permission('kampfrichter');
|
||||
|
||||
verify_csrf();
|
||||
|
||||
$discipline = 'boden';
|
||||
$gereatId = intval($_POST['geraetId'] ?? 0);
|
||||
|
||||
$folder = realpath($baseDir . '/displays/json');
|
||||
if ($gereatId < 1) {
|
||||
echo json_encode(['success' => false, 'message' => 'Kein Gerät angegeben']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$folder = realpath($baseDir . '/externe-geraete/json');
|
||||
if ($folder === false) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
@@ -24,7 +29,7 @@ if ($folder === false) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$filename = 'audio.json';
|
||||
$filename = 'audio-id-'. $gereatId .'.json';
|
||||
$filepath = $folder . '/' . $filename;
|
||||
|
||||
if (!is_writable($folder)) {
|
||||
@@ -36,14 +41,11 @@ if (!is_file($filepath)){
|
||||
file_put_contents($filepath, []);
|
||||
}
|
||||
|
||||
$jsonString = file_get_contents($filepath);
|
||||
$json = [
|
||||
"start" => false
|
||||
];
|
||||
|
||||
// decode JSON, fallback to empty array if invalid
|
||||
$oldjson = json_decode($jsonString, true) ?? [];
|
||||
|
||||
$oldjson["start"] = false;
|
||||
|
||||
$jsonData = json_encode($oldjson);
|
||||
$jsonData = json_encode($json);
|
||||
|
||||
// Write file
|
||||
if (file_put_contents($filepath, $jsonData) === false) {
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
<?php
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!isset($baseDir)) {
|
||||
$baseDir = $_SERVER['DOCUMENT_ROOT'];
|
||||
}
|
||||
|
||||
require_once $baseDir . '/../scripts/session_functions.php';
|
||||
|
||||
ini_wkvs_session();
|
||||
|
||||
check_user_permission('kampfrichter');
|
||||
|
||||
verify_csrf();
|
||||
|
||||
$type = 'kr';
|
||||
|
||||
$data = require $baseDir . '/../scripts/db/db-verbindung-script.php';
|
||||
|
||||
if ($data['success'] === false){
|
||||
echo json_encode(['success' => false, 'message' => $data['message']]);
|
||||
exit;
|
||||
}
|
||||
|
||||
require $baseDir . '/../scripts/db/db-tables.php';
|
||||
|
||||
$abteilung = isset($_POST['abteilung']) ? strval($_POST['abteilung']) : '1';
|
||||
$aufgabe = isset($_POST['aufgabe']) ? strval($_POST['aufgabe']) : '2';
|
||||
$name = isset($_POST['name']) ? strval($_POST['name']) : 'RK';
|
||||
$geraet = isset($_POST['geraet']) ? strtolower($_POST['geraet']) : 'boden';
|
||||
|
||||
if ($abteilung === ''){
|
||||
echo json_encode(['success' => false, 'message' => 'Keine Abteilung angegeben.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($aufgabe === ''){
|
||||
echo json_encode(['success' => false, 'message' => 'Keine Aufgabe angegeben.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!is_numeric($aufgabe)){
|
||||
echo json_encode(['success' => false, 'message' => 'Keine valide Aufgabe. (is not numeric)']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($name === ''){
|
||||
echo json_encode(['success' => false, 'message' => 'Kein Namen angegeben']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$stmt = $mysqli->prepare("SELECT `name` FROM $tableGeraete ORDER BY start_index ASC");
|
||||
|
||||
if (!$stmt->execute()) {
|
||||
http_response_code(500);
|
||||
exit;
|
||||
}
|
||||
|
||||
$result = $stmt->get_result();
|
||||
$disciplines = array_map(
|
||||
'strtolower',
|
||||
array_column($result->fetch_all(MYSQLI_ASSOC), 'name')
|
||||
);
|
||||
|
||||
$stmt->close();
|
||||
|
||||
if (!in_array($geraet, $disciplines)){
|
||||
echo json_encode(['success' => false, 'message' => 'Invalides Gerät: '.$geraet]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$stmt = $mysqli->prepare("SELECT * FROM $tableKrProtokoll WHERE abteilung = ? AND geraet = ? AND aufgabe = ? LIMIT 1");
|
||||
$stmt->bind_param("sss", $abteilung, $geraet, $aufgabe);
|
||||
|
||||
$stmt->execute();
|
||||
|
||||
$result = $stmt->get_result();
|
||||
|
||||
$row = $result->fetch_assoc();
|
||||
|
||||
if ($row){
|
||||
$updatestmt = $mysqli->prepare("UPDATE $tableKrProtokoll SET name = ? WHERE id = ?");
|
||||
$updatestmt->bind_param("si", $name, $row['id']);
|
||||
|
||||
if (!$updatestmt->execute()) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'Insert ERROR: ' . $updatestmt->error
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
echo json_encode(['success' => true, 'message' => 'Updated: updated']);
|
||||
exit;
|
||||
} else {
|
||||
$insertstmt = $mysqli->prepare("INSERT INTO $tableKrProtokoll (abteilung, geraet, name, aufgabe) VALUES (?, ?, ?, ?)");
|
||||
$insertstmt->bind_param("ssss", $abteilung, $geraet, $name, $aufgabe);
|
||||
|
||||
if (!$insertstmt->execute()) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'Insert ERROR: ' . $insertstmt->error
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
echo json_encode(['success' => true, 'message' => 'Updated: inserted']);
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
@@ -24,7 +24,6 @@ if ($data['success'] === false){
|
||||
require $baseDir . '/../scripts/db/db-functions.php';
|
||||
require $baseDir . '/../scripts/db/db-tables.php';
|
||||
|
||||
|
||||
$person_id = isset($_POST['personId']) ? intval($_POST['personId']) : 0;
|
||||
$field_type_id = intval($_POST['fieldTypeId'] ?? 0);
|
||||
$gereat_id = intval($_POST['gereatId'] ?? 0);
|
||||
@@ -84,6 +83,8 @@ if ($singleNoteConfig['min_value'] !== null && $valueNoteUpdate < $singleNoteCon
|
||||
exit;
|
||||
}
|
||||
|
||||
$oldNote = db_get_var($mysqli, "SELECT `value` FROM $tableNoten WHERE `person_id` = ? AND `note_bezeichnung_id` = ? AND `geraet_id` = ? AND `jahr` = ? AND `run_number` = ?", [$person_id, $field_type_id, $gereat_id, $jahr, $run_number]);
|
||||
|
||||
$sql = "INSERT INTO $tableNoten (`value`, `person_id`, `note_bezeichnung_id`, `geraet_id`, `jahr`, `run_number`)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE `value` = VALUES(`value`)";
|
||||
@@ -96,6 +97,19 @@ $stmt->execute();
|
||||
|
||||
$stmt->close();
|
||||
|
||||
$userId = (int) $_SESSION['user_id_kampfrichter'];
|
||||
|
||||
$sql = "INSERT INTO $tableNotenChanges (`new_value`, `old_value`, `person_id`, `note_bezeichnung_id`, `geraet_id`, `jahr`, `run_number`, `edited_by`)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
|
||||
$stmt = $mysqli->prepare($sql);
|
||||
|
||||
$stmt->bind_param("ssiiiiii", $valueNoteUpdate, $oldNote, $person_id, $field_type_id, $gereat_id, $jahr, $run_number, $userId);
|
||||
|
||||
$stmt->execute();
|
||||
|
||||
$stmt->close();
|
||||
|
||||
if ($singleNoteConfig['berechnung_json'] === null) {
|
||||
echo json_encode(['success' => true, 'message' => "Wert aktualisiert"]);
|
||||
exit;
|
||||
@@ -116,23 +130,21 @@ try {
|
||||
|
||||
$geraete = db_select($mysqli, $tableGeraete, "id");
|
||||
|
||||
$programmName = db_get_var($mysqli, "SELECT `programm` FROM $tableTurnerinnen WHERE `id` = ?", [$person_id]);
|
||||
$programmName = db_get_var($mysqli, "SELECT `programm` FROM $tableTeilnehmende WHERE `id` = ?", [$person_id]);
|
||||
|
||||
$programmId = db_get_var($mysqli, "SELECT `id` FROM $tableProgramme WHERE `programm` = ?", [$programmName]);
|
||||
|
||||
// Alle Werte werden von der Datenbank geholt und werden, wenn nicht vorhanden, durch den Standartwert ersetzt.
|
||||
// We load the configuration native cached array
|
||||
$cache = require $baseDir . '/../scripts/cache/noten-config-calculating-cache.php';
|
||||
|
||||
$alleNoten = db_select($mysqli, $tableNotenBezeichnungen, "id, berechnung, default_value, nullstellen, pro_geraet, geraete_json, anzahl_laeufe_json");
|
||||
$noten = db_select($mysqli, $tableNoten, "`value`, `note_bezeichnung_id`, `geraet_id`, `run_number`", "`person_id` = ? AND `jahr` = ? AND `value` IS NOT NULL", [$person_id, $jahr]);
|
||||
|
||||
$noten = db_select($mysqli, $tableNoten, "`value`, `note_bezeichnung_id`, `geraet_id`, `run_number`", "`person_id` = ? AND `jahr` = ?", [$person_id, $jahr]);
|
||||
|
||||
$ascArrayDefaultValues = array_column($alleNoten, 'default_value', 'id');
|
||||
$ascArrayProGeraet = array_column($alleNoten, 'pro_geraet', 'id');
|
||||
$ascArrayGeraeteJSON = array_column($alleNoten, 'geraete_json', 'id');
|
||||
$ascArrayAnzahlLaeufeJSON = array_column($alleNoten, 'anzahl_laeufe_json', 'id');
|
||||
$ascArrayRechnungen = array_column($alleNoten, 'berechnung', 'id');
|
||||
|
||||
// $proGeraet = intval($calc['pro_geraet']) !== 1;
|
||||
$ascArrayDefaultValues = $cache['default_values'];
|
||||
$ascArrayProGeraet = $cache['pro_geraet'];
|
||||
$ascArrayGeraeteJSON = $cache['geraete_json'];
|
||||
$ascArrayAnzahlLaeufeJSON = $cache['anzahl_laeufe_json'];
|
||||
$ascArrayRechnungen = $cache['rechnungen'];
|
||||
$indexedNullstellen = $cache['nullstellen'];
|
||||
|
||||
$mRunFunctions = [];
|
||||
|
||||
@@ -143,20 +155,11 @@ foreach ($abhaenigeRechnungen as $saRechnung) {
|
||||
|
||||
if ($mRunCalc) {
|
||||
$mRunFunctions[] = $saRechnung[0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$indexedNotenArray = [];
|
||||
|
||||
foreach ($ascArrayGeraeteJSON as $key => $saagj) {
|
||||
$ascArrayGeraeteJSON[$key] = json_decode($saagj, true);
|
||||
}
|
||||
|
||||
foreach ($ascArrayAnzahlLaeufeJSON as $key => $saalj) {
|
||||
$ascArrayAnzahlLaeufeJSON[$key] = json_decode($saalj, true) ?? [];
|
||||
}
|
||||
|
||||
foreach ($geraete as $g) {
|
||||
$indexedNotenArray[$g['id']] = [];
|
||||
}
|
||||
@@ -164,76 +167,61 @@ foreach ($geraete as $g) {
|
||||
$indexedNotenArray[0] = [];
|
||||
|
||||
foreach ($noten as $sn) {
|
||||
$indexedNotenArray[$sn['geraet_id']][$sn['note_bezeichnung_id']][$sn['run_number']] = $sn['value'];
|
||||
$indexedNotenArray[$sn['geraet_id']][$sn['note_bezeichnung_id']][$sn['run_number']] = ["value" => $sn['value'], "type" => "note"];
|
||||
}
|
||||
|
||||
$alleNotenIds = array_column($alleNoten, 'id') ?? [];
|
||||
$requiredNoteIds = [];
|
||||
$requiredNoteIds[$field_type_id] = true;
|
||||
|
||||
foreach ($abhaenigeRechnungen as $saRechnung) {
|
||||
// Only load dependencies for the specifically connected calculations
|
||||
$sRechnungStr = $ascArrayRechnungen[$saRechnung[0]] ?? '';
|
||||
if ($sRechnungStr !== '') {
|
||||
$ids = $notenRechner->getBenoetigteIdsComplex($sRechnungStr);
|
||||
foreach ($ids as $idConfig) {
|
||||
$requiredNoteIds[(int)$idConfig['noteId']] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
$alleNotenIds = array_keys($requiredNoteIds);
|
||||
|
||||
if (count($mRunFunctions) > 0) {
|
||||
foreach ($indexedNotenArray as $sG => $siNA) {
|
||||
|
||||
foreach ($alleNotenIds as $neni) { // Use $neni as the ID
|
||||
|
||||
// 1. Skip if no default value is defined
|
||||
if (!isset($ascArrayDefaultValues[$neni])) {
|
||||
continue;
|
||||
}
|
||||
foreach ($alleNotenIds as $neni) {
|
||||
if (!isset($ascArrayDefaultValues[$neni])) continue;
|
||||
|
||||
// 2. Logic Check: Is this note assigned to this device?
|
||||
$isProGeraet = (int)($ascArrayProGeraet[$neni] ?? 0);
|
||||
|
||||
if ($isProGeraet === 1 && (int)$sG === 0) {
|
||||
continue;
|
||||
}
|
||||
if ($isProGeraet === 1 && (int)$sG === 0) continue;
|
||||
|
||||
if ($isProGeraet !== 1) {
|
||||
$allowedGeraete = $ascArrayGeraeteJSON[$neni] ?? [];
|
||||
if (!is_array($allowedGeraete) || !in_array($sG, $allowedGeraete)) {
|
||||
continue;
|
||||
}
|
||||
if (!is_array($allowedGeraete) || !in_array($sG, $allowedGeraete)) continue;
|
||||
}
|
||||
|
||||
$runs = $ascArrayAnzahlLaeufeJSON[$neni][$sG][$programmId] ?? $ascArrayAnzahlLaeufeJSON[$neni]["default"] ?? 1;
|
||||
$runs = $ascArrayAnzahlLaeufeJSON[$neni][$sG][$programmId] ?? $ascArrayAnzahlLaeufeJSON[$neni][$sG]['all'] ?? $ascArrayAnzahlLaeufeJSON[$neni]["default"] ?? 1;
|
||||
|
||||
for ($r = 1; $r <= $runs; $r++) {
|
||||
if (isset($indexedNotenArray[$sG][$neni][$r])) {
|
||||
continue;
|
||||
if (!isset($indexedNotenArray[$sG][$neni][$r])) {
|
||||
$indexedNotenArray[$sG][$neni][$r] = ["value" => $ascArrayDefaultValues[$neni], "type" => "default_value"];
|
||||
}
|
||||
$indexedNotenArray[$sG][$neni][$r] = $ascArrayDefaultValues[$neni];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foreach ($indexedNotenArray as $sG => $siNA) {
|
||||
|
||||
foreach ($alleNotenIds as $neni) { // Use $neni as the ID
|
||||
|
||||
// 1. Skip if value already exists for this specific run
|
||||
if (isset($indexedNotenArray[$sG][$neni][$run_number])) {
|
||||
continue;
|
||||
}
|
||||
foreach ($alleNotenIds as $neni) {
|
||||
if (isset($indexedNotenArray[$sG][$neni][$run_number])) continue;
|
||||
if (!isset($ascArrayDefaultValues[$neni])) continue;
|
||||
|
||||
// 2. Skip if no default value is defined
|
||||
if (!isset($ascArrayDefaultValues[$neni])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 3. Logic Check: Is this note assigned to this device?
|
||||
$isProGeraet = (int)($ascArrayProGeraet[$neni] ?? 0);
|
||||
|
||||
if ($isProGeraet === 1 && (int)$sG === 0) {
|
||||
continue;
|
||||
}
|
||||
if ($isProGeraet === 1 && (int)$sG === 0) continue;
|
||||
|
||||
if ($isProGeraet !== 1) {
|
||||
$allowedGeraete = $ascArrayGeraeteJSON[$neni] ?? [];
|
||||
if (!is_array($allowedGeraete) || !in_array($sG, $allowedGeraete)) {
|
||||
continue;
|
||||
}
|
||||
if (!is_array($allowedGeraete) || !in_array($sG, $allowedGeraete)) continue;
|
||||
}
|
||||
|
||||
// 4. Assign the default value
|
||||
$indexedNotenArray[$sG][$neni][$run_number] = $ascArrayDefaultValues[$neni];
|
||||
$indexedNotenArray[$sG][$neni][$run_number] = ["value" => $ascArrayDefaultValues[$neni], "type" => "default_value"];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -244,6 +232,7 @@ $idsToSave = [];
|
||||
foreach ($abhaenigeRechnungen as $sRechnung) {
|
||||
$targetNoteId = $sRechnung[0];
|
||||
$rechnungType = $sRechnung[1];
|
||||
$targetRun = $sRechnung[2][0] ?? 'A';
|
||||
|
||||
// 1. Initial Filter
|
||||
if ($rechnungType !== "A" && intval($rechnungType) !== $gereat_id) continue;
|
||||
@@ -266,14 +255,14 @@ foreach ($abhaenigeRechnungen as $sRechnung) {
|
||||
|
||||
// 3. Calculation Logic
|
||||
$runsConfig = $ascArrayAnzahlLaeufeJSON[$targetNoteId] ?? [];
|
||||
$runs = $runsConfig[$gereat_id][$programmId] ?? $runsConfig["default"] ?? 1;
|
||||
$runs = $runsConfig[$targetGeraetKey][$programmId] ?? $runsConfig[$targetGeraetKey]["all"] ?? $runsConfig["default"] ?? 1;
|
||||
|
||||
$acrun = min($runs, $run_number);
|
||||
|
||||
if (in_array($targetNoteId, $mRunFunctions)) {
|
||||
$calcResult = $notenRechner->berechneStringComplexRun($rechnung, $indexedNotenArray, $gereat_id, $programmId, $ascArrayAnzahlLaeufeJSON);
|
||||
$calcResult = $notenRechner->berechneStringComplexRun($rechnung, $indexedNotenArray, $targetGeraetKey, $programmId, $ascArrayAnzahlLaeufeJSON);
|
||||
} else {
|
||||
$calcResult = $notenRechner->berechneStringComplex($rechnung, $indexedNotenArray, $gereat_id, $acrun);
|
||||
$calcResult = $notenRechner->berechneStringComplex($rechnung, $indexedNotenArray, $targetGeraetKey, $acrun);
|
||||
}
|
||||
|
||||
if (!($calcResult['success'] ?? false)) {
|
||||
@@ -281,10 +270,13 @@ foreach ($abhaenigeRechnungen as $sRechnung) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$acTargetRun = ($targetRun === "A") ? $acrun : intval($targetRun);
|
||||
|
||||
|
||||
// 4. Update State
|
||||
$val = $calcResult['value'];
|
||||
$indexedNotenArray[$targetGeraetKey][$targetNoteId][$acrun] = $val;
|
||||
$updatedValues[$targetGeraetKey][$targetNoteId][$acrun] = $val;
|
||||
$indexedNotenArray[$targetGeraetKey][$targetNoteId][$acTargetRun] = ["value" => $val, "type" => "berechnet"];
|
||||
$updatedValues[$targetGeraetKey][$targetNoteId][$acTargetRun] = $val;
|
||||
}
|
||||
|
||||
// Prepare the statement once
|
||||
@@ -293,11 +285,10 @@ $sql = "INSERT INTO $tableNoten (`value`, `person_id`, `note_bezeichnung_id`, `g
|
||||
ON DUPLICATE KEY UPDATE `value` = VALUES(`value`)";
|
||||
|
||||
$stmt = $mysqli->prepare($sql);
|
||||
|
||||
$indexedNullstellen = array_column($alleNoten, 'nullstellen', 'id');
|
||||
|
||||
$formatedNoten = [];
|
||||
|
||||
$mysqli->begin_transaction();
|
||||
|
||||
foreach ($updatedValues as $gereat => $notenArray) {
|
||||
foreach ($notenArray as $note => $runArray) {
|
||||
foreach ($runArray as $run => $value) {
|
||||
@@ -308,6 +299,8 @@ foreach ($updatedValues as $gereat => $notenArray) {
|
||||
}
|
||||
}
|
||||
|
||||
$mysqli->commit();
|
||||
|
||||
$formatedNoten[$gereat_id][$field_type_id][$run_number] = number_format($valueNoteUpdate ,$indexedNullstellen[$field_type_id] ?? 2);
|
||||
|
||||
$stmt->close();
|
||||
|
||||
@@ -14,10 +14,10 @@ check_user_permission('kampfrichter');
|
||||
verify_csrf();
|
||||
|
||||
// ---------- Get and sanitize input ----------
|
||||
$id = intval($_POST['personId']) ?? 0;
|
||||
$run = intval($_POST['run']) ?? 0;
|
||||
$geraetId = intval($_POST['geraetId']) ?? 0;
|
||||
$dataType = intval($_POST['dataType']) ?? 0;
|
||||
$id = intval($_POST['personId'] ?? 0);
|
||||
$initNoteRun = intval($_POST['run'] ?? 0);
|
||||
$geraetId = intval($_POST['geraetId'] ?? 0);
|
||||
$dataType = intval($_POST['dataType'] ?? 0);
|
||||
$jahr = isset($_POST['jahr']) ? preg_replace('/[^0-9]/', '', $_POST['jahr']) : '';
|
||||
$anfrageType = $_POST['type'] ?? '';
|
||||
|
||||
@@ -70,7 +70,7 @@ $geraetName = $geraetData['name'];
|
||||
|
||||
$stmt->close();
|
||||
|
||||
$folder = realpath($baseDir . '/displays/json');
|
||||
$folder = realpath($baseDir . '/externe-geraete/json');
|
||||
|
||||
if ($folder === false) {
|
||||
echo json_encode([
|
||||
@@ -80,7 +80,7 @@ if ($folder === false) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$filename = 'display_' . strtolower($geraetName) . '.json';
|
||||
$filename = 'display-id-' . $geraetId . '.json';
|
||||
$filepath = $folder . '/' . $filename;
|
||||
|
||||
if (!is_writable($folder)) {
|
||||
@@ -95,7 +95,7 @@ $oldjson = json_decode($jsonString, true) ?? [];
|
||||
|
||||
switch ($anfrageType) {
|
||||
case "neu":
|
||||
$stmt = $mysqli->prepare("SELECT * FROM `$tableTurnerinnen` WHERE id = ? LIMIT 1");
|
||||
$stmt = $mysqli->prepare("SELECT `id`, `name`, `vorname`, `programm`, `verein` FROM `$tableTeilnehmende` WHERE id = ? LIMIT 1");
|
||||
$stmt->bind_param("i", $id);
|
||||
$stmt->execute();
|
||||
|
||||
@@ -109,9 +109,6 @@ switch ($anfrageType) {
|
||||
|
||||
$row = $rows[0];
|
||||
|
||||
// safely get value, default 0 if missing
|
||||
$olduniqueid = $oldjson['uniqueid'] ?? 0;
|
||||
$uniqueid = $olduniqueid + 1;
|
||||
|
||||
$data = ["noteLinks" => '',
|
||||
"noteRechts" => '',
|
||||
@@ -120,10 +117,10 @@ switch ($anfrageType) {
|
||||
"vorname" => $row['vorname'],
|
||||
"programm" => $row['programm'],
|
||||
"verein" => $row['verein'],
|
||||
"start" => false,
|
||||
"musik" => 'nan',
|
||||
"uniqueid" => $uniqueid];
|
||||
$jsonData = json_encode($data);
|
||||
"start" => false
|
||||
];
|
||||
|
||||
$arrayData = $data;
|
||||
break;
|
||||
case "start":
|
||||
if (!array_key_exists("id", $oldjson) || intval($oldjson["id"]) !== $id || !array_key_exists("start", $oldjson)) {
|
||||
@@ -133,21 +130,68 @@ switch ($anfrageType) {
|
||||
|
||||
$oldjson["start"] = (bool) $dataType;
|
||||
|
||||
$jsonData = json_encode($oldjson);
|
||||
$arrayData = $oldjson;
|
||||
break;
|
||||
case "result":
|
||||
// 1. Get IDs and filter out empty values
|
||||
$noteLinksId = db_get_var($mysqli, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['displayIdNoteL']);
|
||||
$noteRechtsId = db_get_var($mysqli, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['displayIdNoteR']);
|
||||
$noteLinksId = db_get_variable($mysqli, $tableVar, ['displayIdNoteL']);
|
||||
$noteRechtsId = db_get_variable($mysqli, $tableVar, ['displayIdNoteR']);
|
||||
|
||||
$stmt = $mysqli->prepare("UPDATE $tableNoten SET `is_public` = 1, `public_value` = `value` WHERE `person_id` = ? AND `jahr` = ? AND `geraet_id` = ? AND `run_number` = ?");
|
||||
$programm_id = db_get_var($mysqli, "SELECT p.`id` FROM $tableTeilnehmende t INNER JOIN $tableProgramme p ON p.`programm` = t.`programm` WHERE t.`id` = ?", [$id]) ?? null;
|
||||
|
||||
$stmt->bind_param("ssss", $id, $jahr, $geraetId, $run);
|
||||
$display_cache = require $baseDir . '/../scripts/cache/display-dependencies.php';
|
||||
|
||||
$stmt->execute();
|
||||
$this_display_cache = $display_cache[$programm_id][$geraetId][$initNoteRun];
|
||||
|
||||
$stmt->close();
|
||||
$sql_where_values = $this_display_cache['values'];
|
||||
|
||||
$sql_where_string = $this_display_cache['SQL'];
|
||||
|
||||
$intersect_noten = $this_display_cache['intersect_noten'];
|
||||
|
||||
$rankLiveArray = [];
|
||||
|
||||
if (!empty($sql_where_values) && $sql_where_string !== '') {
|
||||
|
||||
$notenDB = db_select($mysqli, $tableNoten, '`value`, `note_bezeichnung_id`, `geraet_id`, `run_number`', "`person_id` = ? AND `jahr` = ? AND ($sql_where_string)", array_merge([$id, $jahr], $sql_where_values));
|
||||
|
||||
$indexedNotenDB = [];
|
||||
|
||||
foreach ($notenDB as $sn) {
|
||||
$indexedNotenDB[$sn['geraet_id']][$sn['note_bezeichnung_id']][$sn['run_number']] = $sn;
|
||||
}
|
||||
}
|
||||
|
||||
$mysqli->begin_transaction();
|
||||
|
||||
|
||||
try {
|
||||
$stmt = $mysqli->prepare("INSERT INTO $tableNoten (`person_id`, `note_bezeichnung_id`, `jahr`, `geraet_id`, `run_number`, `is_public`, `public_value`) VALUES (?, ?, ?, ?, ?, 1, ?) ON DUPLICATE KEY UPDATE `public_value` = VALUES(`public_value`), `is_public` = 1");
|
||||
|
||||
foreach ($intersect_noten as $s_note) {
|
||||
$n_id = $s_note['n_id'];
|
||||
$g_id = $s_note['g_id'];
|
||||
$run = $s_note['r'];
|
||||
|
||||
$value = $indexedNotenDB[$g_id][$n_id][$run]['value'] ?? $notenConfig['default_values'][$n_id] ?? 0;
|
||||
$rankLiveArray[$g_id][$run][$n_id] = number_format($value, $notenConfig['nullstellen'][$n_id] ?? 2);
|
||||
|
||||
$stmt->bind_param("iiiiid", $id, $n_id, $jahr, $g_id, $run, $value);
|
||||
|
||||
$stmt->execute();
|
||||
}
|
||||
|
||||
$mysqli->commit();
|
||||
$stmt->close();
|
||||
} catch (Exception $e) {
|
||||
$mysqli->rollback();
|
||||
echo json_encode(['success' => false, 'message' => 'DB Transaction failed Error: ' . $e->getMessage()]);
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Create an array of IDs that actually exist
|
||||
$validIds = array_filter([$noteLinksId, $noteRechtsId]);
|
||||
|
||||
@@ -164,7 +208,7 @@ switch ($anfrageType) {
|
||||
|
||||
$stmt = $mysqli->prepare($sqlNoten);
|
||||
// Combine standard params with our dynamic ID list
|
||||
$params = array_merge([$id, $jahr, $geraetId, $run], $validIds);
|
||||
$params = array_merge([$id, $jahr, $geraetId, $initNoteRun], $validIds);
|
||||
$types = str_repeat('s', count($params));
|
||||
$stmt->bind_param($types, ...$params);
|
||||
$stmt->execute();
|
||||
@@ -173,7 +217,7 @@ switch ($anfrageType) {
|
||||
$stmt->close();
|
||||
|
||||
// 3. Fetch Config
|
||||
$sqlConfig = "SELECT `id`, `default_value`, `nullstellen`, `prefix_display`
|
||||
$sqlConfig = "SELECT `id`, `default_value`, `nullstellen`, `display_string`
|
||||
FROM $tableNotenBezeichnungen WHERE `id` IN ($placeholders)";
|
||||
|
||||
$stmt = $mysqli->prepare($sqlConfig);
|
||||
@@ -194,20 +238,34 @@ switch ($anfrageType) {
|
||||
$conf = $notenConfig[$id];
|
||||
$val = $noten[$id] ?? $conf['default_value'] ?? 0;
|
||||
$prec = $conf['nullstellen'] ?? 2;
|
||||
$pre = $conf['prefix_display'] ?? '';
|
||||
|
||||
return $pre . number_format((float)$val, (int)$prec, '.', '');
|
||||
|
||||
$display_schema = $conf['display_string'] ?? '${note}';
|
||||
|
||||
$formatted_note = number_format((float)$val, (int)$prec, '.', '');
|
||||
|
||||
$result = str_ireplace('${note}', $formatted_note, $display_schema);
|
||||
|
||||
if ($result === $display_schema) {
|
||||
return $formatted_note;
|
||||
}
|
||||
|
||||
return $result;
|
||||
};
|
||||
|
||||
// 5. Assign to JSON
|
||||
$oldjson["noteLinks"] = $formatNote($noteLinksId);
|
||||
$oldjson["noteRechts"] = $formatNote($noteRechtsId);
|
||||
|
||||
$jsonData = json_encode($oldjson);
|
||||
$arrayData = $oldjson;
|
||||
|
||||
break;
|
||||
default:
|
||||
http_response_code(400);
|
||||
exit;
|
||||
}
|
||||
|
||||
$jsonData = json_encode($arrayData);
|
||||
|
||||
// Write file
|
||||
if (file_put_contents($filepath, $jsonData) === false) {
|
||||
echo json_encode([
|
||||
@@ -217,11 +275,21 @@ if (file_put_contents($filepath, $jsonData) === false) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// ---------- Return JSON ----------
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'JSON updated successfully for ' . $geraetName,
|
||||
'data' => json_decode($jsonData, true),
|
||||
'nameGeraet' => strtolower($geraetName)
|
||||
]);
|
||||
if ($anfrageType === "result") {
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'JSON updated successfully for ' . $geraetName,
|
||||
'data' => $arrayData,
|
||||
'rankLive' => $rankLiveArray,
|
||||
'nameGeraet' => strtolower($geraetName)
|
||||
]);
|
||||
} else {
|
||||
// ---------- Return JSON ----------
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'JSON updated successfully for ' . $geraetName,
|
||||
'data' => $arrayData,
|
||||
'nameGeraet' => strtolower($geraetName)
|
||||
]);
|
||||
}
|
||||
exit;
|
||||
@@ -1,201 +0,0 @@
|
||||
<?php
|
||||
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
|
||||
session_start();
|
||||
|
||||
if (empty($_SESSION['access_granted_kampfrichter']) || $_SESSION['access_granted_kampfrichter'] !== true || empty($_SESSION['user_id_kampfrichter']) || $_SESSION['user_id_kampfrichter'] < 1) {
|
||||
http_response_code(403);
|
||||
exit;
|
||||
}
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!isset($baseDir)) {
|
||||
$baseDir = $_SERVER['DOCUMENT_ROOT'];
|
||||
}
|
||||
|
||||
$type = 'kr';
|
||||
|
||||
$data = require $baseDir . '/../scripts/db/db-verbindung-script.php';
|
||||
|
||||
if ($data['success'] === false){
|
||||
echo json_encode(['success' => false, 'message' => $data['message']]);
|
||||
exit;
|
||||
}
|
||||
|
||||
require $baseDir . '/../scripts/db/db-functions.php';
|
||||
require $baseDir . '/../scripts/db/db-tables.php';
|
||||
|
||||
|
||||
$person_id = isset($_POST['personId']) ? intval($_POST['personId']) : 0;
|
||||
$field_type_id = intval($_POST['fieldTypeId'] ?? 0);
|
||||
$gereat_id = intval($_POST['gereatId'] ?? 0);
|
||||
$jahr = isset($_POST['jahr']) ? intval($_POST['jahr']) : 0;
|
||||
|
||||
if (!isset($_POST['value'])) {
|
||||
echo json_encode(['success' => false, 'message' => 'Kein Value angegeben']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$value = floatval($_POST['value']);
|
||||
|
||||
if ($person_id < 1) {
|
||||
echo json_encode(['success' => false, 'message' => 'Invalide Personen-ID']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($jahr < 1) {
|
||||
echo json_encode(['success' => false, 'message' => 'Invalides Jahr']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($gereat_id < 1) {
|
||||
echo json_encode(['success' => false, 'message' => 'Invalide Geraet-ID']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$geratExistiert = db_get_var($mysqli, "SELECT 1 FROM $tableNotenBezeichnungen WHERE id = ? LIMIT 1", [$gereat_id]);
|
||||
|
||||
if (!$geratExistiert) {
|
||||
echo json_encode(['success' => false, 'message' => 'Invalide Geraet-ID']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($field_type_id < 1) {
|
||||
echo json_encode(['success' => false, 'message' => 'Invalide Notentyp-ID']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$noteConfig = db_select($mysqli, $tableNotenBezeichnungen, "*", "id = ?", [$field_type_id]);
|
||||
|
||||
if (count($noteConfig) !== 1) {
|
||||
echo json_encode(['success' => false, 'message' => 'Invalide Notentyp-ID']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$singleNoteConfig = $noteConfig[0];
|
||||
|
||||
if ($singleNoteConfig['max_value'] !== null && $value > $singleNoteConfig['max_value']) {
|
||||
echo json_encode(['success' => false, 'message' => "Wert zu hoch (max " . $singleNoteConfig['max_value'].")"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($singleNoteConfig['min_value'] !== null && $value < $singleNoteConfig['min_value']){
|
||||
echo json_encode(['success' => false, 'message' => "Wert zu niedrig (min " . $singleNoteConfig['min_value'].")"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$sql = "INSERT INTO $tableNoten (`value`, `person_id`, `note_bezeichnung_id`, `geraet_id`, `jahr`)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE `value` = VALUES(`value`)";
|
||||
|
||||
$stmt = $mysqli->prepare($sql);
|
||||
|
||||
$stmt->bind_param("siiii", $value, $person_id, $field_type_id, $gereat_id, $jahr);
|
||||
|
||||
$stmt->execute();
|
||||
|
||||
$stmt->close();
|
||||
|
||||
if ($singleNoteConfig['berechnung_json'] === null) {
|
||||
echo json_encode(['success' => true, 'message' => "Wert aktualisiert"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
require $baseDir . "/../scripts/string-calculator/string-calculator-functions.php";
|
||||
|
||||
$updateNoten = [];
|
||||
|
||||
$notenRechner = new NotenRechner();
|
||||
|
||||
try {
|
||||
$abhaenigeRechnungen = json_decode($singleNoteConfig['berechnung_json']);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['success' => true, 'message' => "Wert aktualisiert, fehler bei der Berechnung der weiteren Werte"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Alle Werte werden von der Datenbank geholt und werden, wenn nicht vorhanden, durch den Standartwert ersetzt.
|
||||
|
||||
$alleNoten = db_select($mysqli, $tableNotenBezeichnungen, "id, berechnung, default_value, nullstellen");
|
||||
|
||||
$noten = db_select($mysqli, $tableNoten, "`value`, `note_bezeichnung_id`", "`person_id` = ? AND `geraet_id` = ? AND `jahr` = ?", [$person_id, $gereat_id, $jahr]);
|
||||
|
||||
$ascArrayDefaultValues = array_column($alleNoten, 'default_value', 'id');
|
||||
$ascArrayRechnungen = array_column($alleNoten, 'berechnung', 'id');
|
||||
|
||||
$existierendeNotenIds = array_column($noten, 'note_bezeichnung_id') ?? [];
|
||||
|
||||
$alleNotenIds = array_column($alleNoten, 'id') ?? [];
|
||||
|
||||
$nichtExistierendeNotenIds = array_diff($alleNotenIds, $existierendeNotenIds) ?? [];
|
||||
|
||||
$noten = array_column($noten, 'value', 'note_bezeichnung_id');
|
||||
|
||||
foreach ($nichtExistierendeNotenIds as $neni) {
|
||||
if (!isset($ascArrayDefaultValues[$neni])) { continue; }
|
||||
$noten[$neni] = $ascArrayDefaultValues[$neni];
|
||||
}
|
||||
|
||||
// We only want to save the IDs that were actually recalculated
|
||||
$idsToSave = [];
|
||||
|
||||
foreach ($abhaenigeRechnungen as $sRechnung) {
|
||||
$rechnung = $ascArrayRechnungen[$sRechnung] ?? null;
|
||||
|
||||
if ($rechnung === null) {
|
||||
echo json_encode(['success' => true, 'message' => "Wert aktualisiert, Fehler: Rechnung $sRechnung nicht gefunden"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$calcResult = $notenRechner->berechneString($rechnung, $noten);
|
||||
if ($calcResult['success'] !== true) {
|
||||
echo json_encode(['success' => true, 'message' => "Wert aktualisiert, Rechenfehler: " . ($calcResult['value'] ?? '')]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Update the local array so the NEXT calculation in the loop can use this new value
|
||||
$noten[$sRechnung] = $calcResult['value'];
|
||||
|
||||
// Track that this ID needs to be written to the database
|
||||
$idsToSave[] = $sRechnung;
|
||||
}
|
||||
|
||||
// Prepare the statement once
|
||||
$sql = "INSERT INTO $tableNoten (`value`, `person_id`, `note_bezeichnung_id`, `geraet_id`, `jahr`)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE `value` = VALUES(`value`)";
|
||||
|
||||
$stmt = $mysqli->prepare($sql);
|
||||
|
||||
foreach ($idsToSave as $notenId) {
|
||||
$currentValue = $noten[$notenId]; // Get the calculated value from our array
|
||||
|
||||
// i = integer, s = string (use 's' for decimals/floats to prevent rounding issues in PHP)
|
||||
$stmt->bind_param("siiii", $currentValue, $person_id, $notenId, $gereat_id, $jahr);
|
||||
$stmt->execute();
|
||||
}
|
||||
|
||||
$stmt->close();
|
||||
$mysqli->close();
|
||||
|
||||
// 1. Combine the ID the user sent with the IDs we recalculated
|
||||
$changedIds = array_merge([$field_type_id], $idsToSave);
|
||||
|
||||
// 2. Filter the $noten array to only include these keys
|
||||
// array_flip turns [10, 20] into [10 => 0, 20 => 1] so we can intersect by keys
|
||||
$changedNoten = array_intersect_key($noten, array_flip($changedIds));
|
||||
|
||||
$indexedNullstellen = array_column($alleNoten, 'nullstellen', 'id');
|
||||
|
||||
foreach ($changedNoten as $key => $scN) {
|
||||
$changedNoten[$key] = number_format($scN ,$indexedNullstellen[$key] ?? 2);
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => "Wert aktualisiert, alle Berechnungen durchgeführt",
|
||||
"noten" => $changedNoten
|
||||
]);
|
||||
@@ -1,11 +1,20 @@
|
||||
jQuery(document).ready(function($) {
|
||||
const csrf_token = window.CSDR_TOKEN;
|
||||
|
||||
const allowedRanglistenTypes = ['downloadRangliste', 'updateServerRangliste'];
|
||||
|
||||
$('.ranglisteExport').on('click', function() {
|
||||
const $button = $(this);
|
||||
const progId = $button.data('id');
|
||||
const fieldType = $button.data('field_type');
|
||||
|
||||
if (!allowedRanglistenTypes.includes(fieldType)) {
|
||||
displayMsg(0, "Dieser Button besitzt eine fehlerhafte Konfiguration");
|
||||
return;
|
||||
}
|
||||
|
||||
// Visual feedback (optional but recommended)
|
||||
$button.prop('disabled', true).text('Generating...');
|
||||
$button.addClass('opacity50');
|
||||
|
||||
const url = '/intern/scripts/kampfrichter/ajax/ajax-neu_rangliste.php';
|
||||
|
||||
@@ -13,81 +22,193 @@ jQuery(document).ready(function($) {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: new URLSearchParams({
|
||||
csrf_token,
|
||||
prog: progId,
|
||||
type: fieldType
|
||||
})
|
||||
})
|
||||
.then(response => {
|
||||
// CRITICAL: Check if the server actually returned a success code
|
||||
if (!response.ok) {
|
||||
throw new Error('Server returned an error (Status ' + response.status + ')');
|
||||
.then(res => {
|
||||
if (!res.ok) throw new Error('Network response was not ok');
|
||||
|
||||
// Return both the response and the blob/json promise
|
||||
// to keep the chain clean, or handle them based on fieldType here.
|
||||
if (fieldType === 'downloadRangliste') {
|
||||
return res.blob().then(blob => ({ type: 'blob', data: blob }));
|
||||
} else {
|
||||
return res.json().then(json => ({ type: 'json', data: json }));
|
||||
}
|
||||
return response.blob();
|
||||
})
|
||||
.then(blob => {
|
||||
if (fieldType !== 'upload_programm') {
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
.then(result => {
|
||||
if (result.type === 'blob') {
|
||||
const url = window.URL.createObjectURL(result.data);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `Ergebnisse_${progId}.pdf`;
|
||||
a.download = "Rangliste.pdf";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
a.remove();
|
||||
window.URL.revokeObjectURL(url);
|
||||
} else {
|
||||
alert('PDF auf Webseite geladen!');
|
||||
displayMsg(1, "Rangliste wurde erfolgreich heuntergeladen.");
|
||||
} else if (result.type === 'json') {
|
||||
if (result.data.success) {
|
||||
const $container = $button.closest(".singleAbtDiv");
|
||||
$container.find(".uploadRanglisteText").addClass("hidden");
|
||||
$container.find(".updateRanglisteText").removeClass("hidden");
|
||||
$container.find(".rangliseOnlineSpan").removeClass("hidden");
|
||||
$container.find(".ranglisteDelete").removeClass("hidden");
|
||||
displayMsg(1, "Die Rangliste wurde auf dem Server aktualisiert.");
|
||||
} else {
|
||||
displayMsg(0, "Fehler beim Aktualiseren der Rangliste.");
|
||||
}
|
||||
}
|
||||
$button.removeClass('opacity50');
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Export Error:', error);
|
||||
alert('Fehler beim Exportieren der Rangliste.');
|
||||
})
|
||||
.finally(() => {
|
||||
// Restore button state
|
||||
$button.prop('disabled', false).text('Export PDF');
|
||||
.catch(err => {
|
||||
displayMsg(0, "Error processing request:", err);
|
||||
console.error("Error processing request:", err);
|
||||
$button.removeClass('opacity50');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
$('.protokollExport').on('click', function() {
|
||||
console.log('ok');
|
||||
const $input = $(this);
|
||||
|
||||
// Build the data to send
|
||||
const data = new URLSearchParams();
|
||||
data.append('abteilung', $input.data('abteilung'));
|
||||
|
||||
// Record start time
|
||||
const start = performance.now();
|
||||
$('.ranglisteDelete').on('click', function() {
|
||||
const $button = $(this);
|
||||
const progId = $button.data('id');
|
||||
|
||||
const url = '/intern/scripts/kampfrichter/ajax/ajax-neu_protokoll.php';
|
||||
// Visual feedback (optional but recommended)
|
||||
$button.addClass('opacity50');
|
||||
|
||||
const url = '/intern/scripts/kampfrichter/ajax/ajax-delete_rangliste.php';
|
||||
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: new URLSearchParams({
|
||||
abteilung: $input.data('abteilung')
|
||||
csrf_token,
|
||||
prog: progId
|
||||
})
|
||||
})
|
||||
.then(res => res.blob())
|
||||
.then(blob => {
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = "KTBB_Protokoll.pdf"; // optional
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
window.URL.revokeObjectURL(url);
|
||||
.then(res => {
|
||||
if (!res.ok) throw new Error('Network response was not ok');
|
||||
|
||||
|
||||
return res.json().then(json => ({ type: 'json', data: json }));
|
||||
|
||||
})
|
||||
.then(result => {
|
||||
if (result.data.success) {
|
||||
const $container = $button.closest(".singleAbtDiv");
|
||||
$container.find(".uploadRanglisteText").removeClass("hidden");
|
||||
$container.find(".updateRanglisteText").addClass("hidden");
|
||||
$container.find(".rangliseOnlineSpan").addClass("hidden");
|
||||
$container.find(".ranglisteDelete").addClass("hidden");
|
||||
displayMsg(1, "Die Rangliste wurde erfolgreich entfernt.");
|
||||
} else {
|
||||
displayMsg(0, "Fehler beim Löschen der Rangliste.");
|
||||
}
|
||||
$button.removeClass('opacity50');
|
||||
})
|
||||
.catch(err => {
|
||||
displayMsg(0, "Error processing request:", err);
|
||||
console.error("Error processing request:", err);
|
||||
$button.removeClass('opacity50');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
$('.protokollExport').on('click', function() {
|
||||
const $button = $(this);
|
||||
const abteilungId = $button.data('abteilung-id');
|
||||
|
||||
|
||||
const now = new Date();
|
||||
|
||||
const formattedDate = new Intl.DateTimeFormat('en-CA', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false
|
||||
}).format(now).replace(/,/g, '');
|
||||
|
||||
const url = '/intern/scripts/kampfrichter/ajax/ajax-neu_protokoll.php';
|
||||
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: new URLSearchParams({
|
||||
csrf_token,
|
||||
abteilungId: abteilungId,
|
||||
date: formattedDate
|
||||
})
|
||||
})
|
||||
.then(res => {
|
||||
if (!res.ok) throw new Error('Network response was not ok');
|
||||
|
||||
return res.blob().then(blob => ({ type: 'blob', data: blob }));
|
||||
})
|
||||
.then(result => {
|
||||
if (result.type === 'blob') {
|
||||
const url = window.URL.createObjectURL(result.data);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = "Protokoll.pdf";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
window.URL.revokeObjectURL(url);
|
||||
displayMsg(1, "Protokoll wurde erfolgreich heuntergeladen.");
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
displayMsg(0, "Error processing request:", err);
|
||||
console.error("Error processing request:", err);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
$('.protokollDeleteEntries').on('click', async function() {
|
||||
const $button = $(this);
|
||||
const abt = $button.data('abteilung-id');
|
||||
|
||||
if (!await displayConfirmImportant("Möchten Sie wirklich alle Protokolleinträge für diese Abteilung löschen? Diese Aktion kann nicht rückgängig gemacht werden.")) { return; }
|
||||
|
||||
const url = '/intern/scripts/kampfrichter/ajax/ajax-delete_protokoll_entries.php';
|
||||
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: new URLSearchParams({
|
||||
csrf_token,
|
||||
abteilungId: abt
|
||||
})
|
||||
})
|
||||
.then(res => {
|
||||
if (!res.ok) throw new Error('Network response was not ok');
|
||||
|
||||
|
||||
return res.json().then(json => ({ type: 'json', data: json }));
|
||||
|
||||
})
|
||||
.then(result => {
|
||||
if (result.data.success) {
|
||||
displayMsg(1, "Die Einträge wurden erfolgreich entfernt.");
|
||||
} else {
|
||||
displayMsg(0, "Fehler beim Löschen der Einträge.");
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
displayMsg(0, "Error processing request:", err);
|
||||
console.error("Error processing request:", err);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
let activeEdit = null;
|
||||
|
||||
console.log('pre');
|
||||
|
||||
$(document).on('click', '.editableValue', function () {
|
||||
console.log('ok');
|
||||
const input = $(this);
|
||||
|
||||
// Already editing
|
||||
@@ -189,4 +310,248 @@ jQuery(document).ready(function($) {
|
||||
activeEdit = null;
|
||||
}
|
||||
|
||||
let rangNotenArray = JSON.parse(window.RANG_NOTEN_ARRAY) ?? [];
|
||||
|
||||
const rangNotenId = window.RANG_NOTE_ID ?? 0;
|
||||
|
||||
const rangSort = window.RANG_SORT ?? '';
|
||||
|
||||
const selecteddiscipline = window.FREIGABE;
|
||||
|
||||
ws.addEventListener("message", event => { // Use 'event' as it's more standard than 'blob'
|
||||
let msgOBJ;
|
||||
|
||||
try {
|
||||
msgOBJ = JSON.parse(event.data);
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure it's an UPDATE type (matches your sendToGroup logic)
|
||||
if (msgOBJ?.type === "UPDATE") {
|
||||
|
||||
const data = msgOBJ.payload;
|
||||
|
||||
// Check access rights
|
||||
if (selecteddiscipline === 'A') {
|
||||
const noten = data.noten;
|
||||
|
||||
|
||||
if (noten[0][rangNotenId][1] !== undefined && rangNotenArray[data.programmId][data.personId] !== undefined) {
|
||||
rangNotenArray[data.programmId][data.personId] = noten[0][rangNotenId][1];
|
||||
applyRanks(rankProgramm(rangNotenArray, data.programmId, rangSort), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function rankProgramm(dataObj, programmId, order = 'DESC') {
|
||||
if (!dataObj[programmId]) {
|
||||
console.error(`ID ${programmId} not found in data.`);
|
||||
return [];
|
||||
}
|
||||
|
||||
const values = dataObj[programmId];
|
||||
|
||||
// 1. Transform and Sort
|
||||
const sorted = Object.entries(values)
|
||||
.map(([id, val]) => ({ id, val }))
|
||||
.sort((a, b) => {
|
||||
// 1. Handle null values first (Always push them to the bottom)
|
||||
if (a.val === null && b.val === null) return 0;
|
||||
if (a.val === null) return 1; // Move 'a' to a higher index (down)
|
||||
if (b.val === null) return -1; // Move 'b' to a higher index (down)
|
||||
|
||||
// 2. Normal sorting logic for valid numbers
|
||||
return order.toUpperCase() === 'ASC'
|
||||
? a.val - b.val
|
||||
: b.val - a.val;
|
||||
});
|
||||
|
||||
let lastVal = null;
|
||||
let lastRank = 0;
|
||||
|
||||
let sortedMap = [];
|
||||
|
||||
sortedMap[programmId] = sorted.map((item, index) => {
|
||||
// If current value is different from the last, update rank to current position
|
||||
if (item.val !== lastVal) {
|
||||
lastRank = index + 1;
|
||||
}
|
||||
|
||||
lastVal = item.val;
|
||||
|
||||
return {
|
||||
rang: lastRank,
|
||||
...item
|
||||
};
|
||||
});
|
||||
|
||||
return sortedMap;
|
||||
}
|
||||
|
||||
|
||||
function rankAll(dataObj, order = 'DESC') {
|
||||
return Object.fromEntries(
|
||||
Object.entries(dataObj).map(([groupKey, values]) => {
|
||||
const sorted = Object.entries(values)
|
||||
.map(([id, val]) => ({ id, val }))
|
||||
.sort((a, b) => {
|
||||
// 1. Handle null values first (Always push them to the bottom)
|
||||
if (a.val === null && b.val === null) return 0;
|
||||
if (a.val === null) return 1; // Move 'a' to a higher index (down)
|
||||
if (b.val === null) return -1; // Move 'b' to a higher index (down)
|
||||
|
||||
// 2. Normal sorting logic for valid numbers
|
||||
return order.toUpperCase() === 'ASC'
|
||||
? a.val - b.val
|
||||
: b.val - a.val;
|
||||
});
|
||||
|
||||
let lastVal = null;
|
||||
let lastRank = 0;
|
||||
|
||||
const ranked = sorted.map((item, index) => {
|
||||
if (item.val !== lastVal) {
|
||||
lastRank = index + 1;
|
||||
}
|
||||
lastVal = item.val;
|
||||
|
||||
return {
|
||||
rang: lastRank,
|
||||
...item
|
||||
};
|
||||
});
|
||||
|
||||
return [groupKey, ranked];
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function applyRanks(array, sort = false) {
|
||||
for (const [key, entries] of Object.entries(array)) {
|
||||
const $tbody = $(`.customDisplayEditorTable tbody[data-programm-id="${key}"]`);
|
||||
|
||||
if ($tbody.length === 0) continue;
|
||||
|
||||
for (const [skey, entry] of Object.entries(entries)) {
|
||||
const $row = $tbody.find(`tr[data-person-id="${entry.id}"]:not(.notpaid)`);
|
||||
|
||||
if ($row.length === 0) continue;
|
||||
|
||||
$row.attr('data-sort-rank', entry.rang);
|
||||
|
||||
const $cell = $row.find('.rangField');
|
||||
|
||||
const points = parseFloat(rangNotenArray[key][entry.id]);
|
||||
|
||||
const text = !isNaN(points) ? entry.rang : '';
|
||||
|
||||
$cell.text(text);
|
||||
}
|
||||
|
||||
if (sort) {
|
||||
const rows = $tbody.find('tr').get();
|
||||
|
||||
rows.sort((a, b) => {
|
||||
const rankA = parseFloat($(a).attr('data-sort-rank')) || 999;
|
||||
const rankB = parseFloat($(b).attr('data-sort-rank')) || 999;
|
||||
return rankA - rankB;
|
||||
});
|
||||
|
||||
$.each(rows, (index, row) => {
|
||||
$tbody.append(row);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
applyRanks(rankAll(rangNotenArray, rangSort), true);
|
||||
|
||||
window.addEventListener('valueChanged', (e) => {
|
||||
// Use e.detail to get your object
|
||||
const { noten, programmId, personId } = e.detail;
|
||||
|
||||
if (noten[0][rangNotenId][1] !== undefined && rangNotenArray[programmId][personId] !== undefined) {
|
||||
rangNotenArray[programmId][personId] = noten[0][rangNotenId][1];
|
||||
applyRanks(rankProgramm(rangNotenArray, programmId, rangSort), true);
|
||||
}
|
||||
});
|
||||
|
||||
const $parent = $('.allAbtContainer');
|
||||
const $children = $parent.children('.shiftedGeraetTable');
|
||||
|
||||
$children.sort((a, b) => {
|
||||
const idA = parseFloat($(a).data('geraet-index'));
|
||||
const idB = parseFloat($(b).data('geraet-index'));
|
||||
|
||||
return idA - idB;
|
||||
});
|
||||
|
||||
$parent.append($children);
|
||||
|
||||
const wsMessage = window.WS_MESSAGE || '';
|
||||
|
||||
if (wsMessage !== '') {
|
||||
ws.addEventListener('open', () => {
|
||||
setTimeout(() => {
|
||||
ws.send(wsMessage);
|
||||
}, 300);
|
||||
});
|
||||
}
|
||||
|
||||
$('.deleteNotenAbt').on('click', async function () {
|
||||
if (!(await displayConfirmImportant('Alle Noten dieses Programmes wirklich löschen? Diese Aktion ist unumkehrbar!'))) return;
|
||||
|
||||
const $btn = $(this);
|
||||
|
||||
const programmId = parseInt($btn.data('programm-id') || 0);
|
||||
|
||||
$.ajax({
|
||||
url: '/intern/scripts/kampfrichter/ajax/ajax-delete-noten-turnerinnen.php',
|
||||
type: 'DELETE',
|
||||
data: {
|
||||
csrf_token,
|
||||
programmId
|
||||
},
|
||||
success: function (response) {
|
||||
if (response.success) {
|
||||
const $tbody = $btn.closest('.singleAbtDiv').find('tbody');
|
||||
|
||||
if ($tbody.length !== 1 || $tbody.data('programm-id') !== programmId) {
|
||||
location.reload();
|
||||
return;
|
||||
}
|
||||
|
||||
const $allValues = $tbody.find('.changebleValue:not(.emtyPlaceholder)');
|
||||
|
||||
$allValues.each((ind, el) => {
|
||||
$(el).text('-').addClass('emtyPlaceholder');
|
||||
updateDependentVisibility($(el));
|
||||
});
|
||||
|
||||
const $allRanks = $tbody.find('.rangField');
|
||||
|
||||
$allRanks.each((ind, el) => {
|
||||
$(el).text('');
|
||||
});
|
||||
|
||||
const $allRows = $tbody.find('tr');
|
||||
|
||||
$allRows.each((ind, el) => {
|
||||
$(el).attr('data-sort-rank', "0");
|
||||
rangNotenArray[programmId][$(el).attr('data-person-id')] = null;
|
||||
});
|
||||
|
||||
displayMsg(1, 'Noten gelöscht');
|
||||
|
||||
} else {
|
||||
displayMsg(0, response.message || 'Fehler beim Löschen');
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
displayMsg(0, 'Fehler beim Löschen');
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
||||
@@ -5,6 +5,50 @@ document.addEventListener('keydown', function (e) {
|
||||
if (isOptionOrAlt && e.shiftKey && e.key.toLowerCase() === 'f') {
|
||||
toggleFullscreen();
|
||||
}
|
||||
|
||||
if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
const menuburger = document.querySelector('.kampfrichterBurgerMenuDiv');
|
||||
const menudiv = document.querySelector('.internMenuDiv');
|
||||
const closeMenuMobile = document.querySelector('.closeInternMenuMobileDiv');
|
||||
|
||||
const storageKey = "kampfrichterInternMenuOpen";
|
||||
|
||||
const isOpen = localStorage.getItem(storageKey) === "true";
|
||||
|
||||
if (isOpen) {
|
||||
menuburger.classList.add("open");
|
||||
menudiv.classList.add("open");
|
||||
}
|
||||
|
||||
menuburger.addEventListener("click", function () {
|
||||
menuburger.classList.add("menuTransition");
|
||||
menudiv.classList.add("menuTransition");
|
||||
|
||||
menuburger.classList.toggle("open");
|
||||
menudiv.classList.toggle("open");
|
||||
|
||||
const isOpenEl =
|
||||
menudiv.classList.contains("open") &&
|
||||
menuburger.classList.contains("open");
|
||||
|
||||
localStorage.setItem(storageKey, isOpenEl);
|
||||
});
|
||||
|
||||
closeMenuMobile.addEventListener("click", function () {
|
||||
const isOpenEl =
|
||||
menudiv.classList.contains("open") &&
|
||||
menuburger.classList.contains("open");
|
||||
|
||||
if (isOpenEl) {
|
||||
menuburger.classList.remove("open");
|
||||
menudiv.classList.remove("open");
|
||||
|
||||
localStorage.setItem(storageKey, false);
|
||||
}
|
||||
});
|
||||
|
||||
function toggleFullscreen() {
|
||||
@@ -22,61 +66,33 @@ function toggleFullscreen() {
|
||||
let messagePosArray = [];
|
||||
const csrf_token = window.CSDR_TOKEN;
|
||||
|
||||
function displayMsg(type, msg) {
|
||||
const colors = ["#900000ff", "#00b200ff"];
|
||||
if (type !== 0 && type !== 1) return;
|
||||
|
||||
|
||||
// idx is ALWAYS a valid non-negative index now
|
||||
const $div = $('<div class="msgBox"></div>')
|
||||
.css({'border-color': colors[type]})
|
||||
.text(msg);
|
||||
|
||||
$('.msgDiv').append($div);
|
||||
|
||||
// trigger entry animation
|
||||
setTimeout(() => {
|
||||
$div.addClass('show');
|
||||
}, 200);
|
||||
|
||||
setTimeout(() => {
|
||||
// First: set the transition properties
|
||||
$div.css({
|
||||
'transition': 'all 0.3s ease'
|
||||
});
|
||||
|
||||
// Next frame: apply the transform so the transition animates
|
||||
requestAnimationFrame(() => {
|
||||
$div.removeClass('show');
|
||||
$div.css({
|
||||
'transform': 'scaleY(0) translateX(calc(100% + 40px))'
|
||||
});
|
||||
});
|
||||
}, 3000);
|
||||
|
||||
// auto-remove
|
||||
setTimeout(() => {
|
||||
$div.remove();
|
||||
}, 3500);
|
||||
}
|
||||
|
||||
const text = document.getElementById('wsInfo');
|
||||
const rect = document.getElementById('wsInfoRectangle');
|
||||
|
||||
let ws;
|
||||
let firstConnect = true;
|
||||
let wsOpen = false;
|
||||
const RETRY_DELAY = 2000; // ms
|
||||
|
||||
const WSaccesstype = "kampfrichter";
|
||||
const WSaccess = window.FREIGABE;
|
||||
const WSaccessPermission = "W";
|
||||
|
||||
const urlAjaxNewWSToken = '/intern/scripts/ajax-create-ws-token.php';
|
||||
|
||||
async function fetchNewWSToken(freigabe) {
|
||||
async function fetchNewWSToken(accesstype, access, accessPermission) {
|
||||
try {
|
||||
const response = await fetch(urlAjaxNewWSToken, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: new URLSearchParams({ access: freigabe, csrf_token })
|
||||
body: new URLSearchParams({
|
||||
csrf_token,
|
||||
accesstype,
|
||||
access,
|
||||
accessPermission
|
||||
})
|
||||
});
|
||||
|
||||
if (response.status === 403) {
|
||||
@@ -102,7 +118,7 @@ async function startWebSocket() {
|
||||
if (firstConnect) {
|
||||
token = window.WS_ACCESS_TOKEN;
|
||||
} else {
|
||||
token = await fetchNewWSToken('kampfrichter');
|
||||
token = await fetchNewWSToken(WSaccesstype, WSaccess, WSaccessPermission);
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
@@ -125,6 +141,8 @@ async function startWebSocket() {
|
||||
displayMsg(1, "Live Syncronisation wieder verfügbar");
|
||||
}
|
||||
firstConnect = true;
|
||||
wsOpen = true;
|
||||
|
||||
rect.innerHTML =
|
||||
'<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="10" fill="#28a745"></circle><path d="M7 12l3 3 7-7" stroke="white" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"></path></svg>';
|
||||
|
||||
@@ -138,8 +156,13 @@ async function startWebSocket() {
|
||||
};
|
||||
|
||||
ws.onclose = (event) => {
|
||||
displayMsg(0, "Live Syncronisation verloren");
|
||||
|
||||
if (firstConnect) {
|
||||
displayMsg(0, "Live Syncronisation verloren");
|
||||
console.log(event);
|
||||
}
|
||||
firstConnect = false;
|
||||
wsOpen = false;
|
||||
rect.innerHTML =
|
||||
'<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="10" fill="#dc3545"/><path d="M8 8l8 8M16 8l-8 8" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
|
||||
|
||||
@@ -167,6 +190,8 @@ function updateRunButtons(targetCount, personId, $container) {
|
||||
|
||||
const currentCount = $container.find('.submit-display-result').length;
|
||||
|
||||
$container.find('.submit-display-result').removeClass('hidden');
|
||||
|
||||
if (targetCount > currentCount) {
|
||||
for (let i = currentCount + 1; i <= targetCount; i++) {
|
||||
const buttonHtml = `
|
||||
@@ -214,6 +239,11 @@ $.fn.updateCurrentEdit = function() {
|
||||
name: 'next_subabt_submit',
|
||||
value: '>'
|
||||
}))
|
||||
.append($('<input>', {
|
||||
type: 'hidden',
|
||||
name: 'csrf_token',
|
||||
value: csrf_token
|
||||
}))
|
||||
.appendTo('body')
|
||||
.submit();
|
||||
}
|
||||
@@ -256,6 +286,9 @@ $.fn.updateCurrentEdit = function() {
|
||||
const $editAllDiv = $('.div_edit_values_all_gereate');
|
||||
const noten = response.noten;
|
||||
const personId = response.id;
|
||||
const programmId = response.programm_id;
|
||||
|
||||
$editAllDiv.find(`.submit-display-turnerin`).closest('.all_vaules_div').addClass('hidden');
|
||||
|
||||
// 1. Loop directly through the 'noten' object
|
||||
for (const [geraetId, disciplineData] of Object.entries(noten)) {
|
||||
@@ -266,6 +299,8 @@ $.fn.updateCurrentEdit = function() {
|
||||
|
||||
if ($disciplineWrapper.length === 0) continue;
|
||||
|
||||
$disciplineWrapper.removeClass('hidden');
|
||||
|
||||
// --- UPDATE GENERAL BUTTONS FOR THIS GERAET ---
|
||||
$disciplineWrapper.find(".submit-display-turnerin, .submit-display-start").attr({
|
||||
'data-person-id': personId,
|
||||
@@ -280,17 +315,18 @@ $.fn.updateCurrentEdit = function() {
|
||||
// 2. Identify master containers for this specific discipline
|
||||
const $masterContainer = $disciplineWrapper.find('.singleNotentable').first();
|
||||
const $displayresultDiv = $disciplineWrapper.find('.div-submit-display-result');
|
||||
|
||||
$masterContainer.find('.note-container').addClass('hidden');
|
||||
|
||||
// 3. CLEANUP: Remove previously generated runs and buttons
|
||||
$disciplineWrapper.find('.singleNotentable').not(':first').remove();
|
||||
$displayresultDiv.find('.submit-display-result').not(':first').remove();
|
||||
|
||||
const $originalResultBtn = $displayresultDiv.find('.submit-display-result').first();
|
||||
$displayresultDiv.find('.submit-display-result').addClass('hidden');
|
||||
const runKeys = Object.keys(disciplineData).sort((a, b) => a - b);
|
||||
const totalRuns = runKeys.length;
|
||||
|
||||
console.log(totalRuns);
|
||||
|
||||
// 4. Process each Run in the data
|
||||
runKeys.forEach(runNum => {
|
||||
const runInt = parseInt(runNum);
|
||||
@@ -310,6 +346,7 @@ $.fn.updateCurrentEdit = function() {
|
||||
// 5. Update all Tables and Inputs inside this Run Container
|
||||
for (const [noteId, value] of Object.entries(disciplineData[runNum])) {
|
||||
const $table = $currentRunContainer.find(`.note-container[data-note-id="${noteId}"]`);
|
||||
$table.removeClass('hidden');
|
||||
|
||||
// Update Header to show Run Number
|
||||
if (runInt > 1) {
|
||||
@@ -324,7 +361,8 @@ $.fn.updateCurrentEdit = function() {
|
||||
$input.attr({
|
||||
'data-run': runNum,
|
||||
'data-person-id': personId,
|
||||
'data-geraet-id': geraetId
|
||||
'data-geraet-id': geraetId,
|
||||
'data-programm-id': programmId
|
||||
}).val(value ?? '');
|
||||
}
|
||||
|
||||
@@ -362,6 +400,10 @@ $.fn.updateCurrentEdit = function() {
|
||||
});
|
||||
};
|
||||
|
||||
function getRandomInt(max) {
|
||||
return Math.floor(Math.random() * max);
|
||||
}
|
||||
|
||||
jQuery(document).ready(function($) {
|
||||
|
||||
|
||||
@@ -370,16 +412,17 @@ jQuery(document).ready(function($) {
|
||||
});
|
||||
|
||||
const $ajaxInputDiv = $('.div_edit_values_all_gereate');
|
||||
|
||||
|
||||
$ajaxInputDiv.on('change', '.ajax-input', function(e) {
|
||||
const start = performance.now();
|
||||
const $input = $(this);
|
||||
|
||||
const url = `/intern/scripts/kampfrichter/ajax/ajax-update_value_kampfrichter.php`;
|
||||
|
||||
personId = $input.data('person-id');
|
||||
fieldTypeId = $input.data('field-type-id');
|
||||
gereatId = $input.data('geraet-id');
|
||||
personId = $input.attr('data-person-id');
|
||||
fieldTypeId = $input.attr('data-field-type-id');
|
||||
programmId = $input.attr('data-programm-id');
|
||||
gereatId = $input.attr('data-geraet-id');
|
||||
runNum = $input.attr('data-run') || 1;
|
||||
jahr = window.AKTUELLES_JAHR;
|
||||
value = $input.val();
|
||||
@@ -399,8 +442,6 @@ jQuery(document).ready(function($) {
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(response => {
|
||||
const end = performance.now();
|
||||
console.log(`Total AJAX time: ${(end - start).toFixed(3)} ms`);
|
||||
|
||||
if (response.success) {
|
||||
|
||||
@@ -415,32 +456,63 @@ jQuery(document).ready(function($) {
|
||||
|
||||
const noten = response.noten;
|
||||
|
||||
for (const [keyN, noteGroup] of Object.entries(noten)) {
|
||||
for (const [keyG, noteGroup] of Object.entries(noten)) {
|
||||
for (const [key, runGroup] of Object.entries(noteGroup)) {
|
||||
for (const [run, value] of Object.entries(runGroup)) {
|
||||
const selectorBase = `[data-field-type-id="${key}"][data-geraet-id="${keyN}"][data-person-id="${personId}"][data-run="${run}"]`;
|
||||
// Handle Inputs (excluding current one)
|
||||
$(`input.changebleValue${selectorBase}`)
|
||||
.not(this)
|
||||
.val(value ?? '');
|
||||
|
||||
// Handle Display elements (Spans/Divs)
|
||||
$(`.changebleValue:not(input)${selectorBase}`)
|
||||
.text(value ?? '');
|
||||
const $elements = $(`.changebleValue[data-field-type-id="${key}"][data-geraet-id="${keyG}"][data-person-id="${personId}"][data-run="${run}"]`);
|
||||
|
||||
$elements.each((ind, el) => {
|
||||
const $el = $(el);
|
||||
|
||||
if ($el.is('input')) {
|
||||
$el.val(value ?? '');
|
||||
} else {
|
||||
const isNullable = ($el.attr('data-noten-nullable') ?? 'false') === 'true';
|
||||
const floatValZero = parseFloat(value.replace(",", ".").replace("'", "")) === 0.0;
|
||||
|
||||
let hasValue = false;
|
||||
|
||||
if (isNullable && floatValZero || value === '') {
|
||||
$el.text('-').addClass('emtyPlaceholder');
|
||||
} else {
|
||||
$el.text(value).removeClass('emtyPlaceholder');
|
||||
hasValue = true;
|
||||
}
|
||||
|
||||
updateDependentVisibility($el, hasValue);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ws.send(JSON.stringify({
|
||||
type: "KAMPFRICHTER_UPDATE",
|
||||
payload: {
|
||||
discipline: window.FREIGABE,
|
||||
gereatId: gereatId,
|
||||
personId: personId,
|
||||
jahr: jahr,
|
||||
noten: noten
|
||||
}
|
||||
}));
|
||||
if (selecteddiscipline === 'A') {
|
||||
const event = new CustomEvent('valueChanged', {
|
||||
detail: {
|
||||
noten: noten,
|
||||
programmId: programmId,
|
||||
personId: personId
|
||||
}
|
||||
});
|
||||
|
||||
window.dispatchEvent(event);
|
||||
}
|
||||
|
||||
if (wsOpen) {
|
||||
ws.send(JSON.stringify({
|
||||
type: "KAMPFRICHTER_UPDATE",
|
||||
payload: {
|
||||
discipline: window.FREIGABE,
|
||||
gereatId: gereatId,
|
||||
personId: personId,
|
||||
programmId: programmId,
|
||||
jahr: jahr,
|
||||
noten: noten
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
} else {
|
||||
// Flash red on error
|
||||
@@ -455,59 +527,9 @@ jQuery(document).ready(function($) {
|
||||
});
|
||||
});
|
||||
|
||||
$('.inputnamekr').on('change', function() {
|
||||
const $input = $(this);
|
||||
|
||||
|
||||
const url = '/intern/scripts/kampfrichter/ajax/ajax-update_name_kampfrichter_protokoll.php';
|
||||
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: new URLSearchParams({
|
||||
csrf_token,
|
||||
aufgabe: $input.data('id'),
|
||||
abteilung: $input.data('abt'),
|
||||
geraet: $input.data('user'),
|
||||
name: $input.val()
|
||||
})
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(response => {
|
||||
if (response.success) {
|
||||
console.log(response.message);
|
||||
$input.css({
|
||||
'background-color': '#a4bf4a',
|
||||
'color': '#fff',
|
||||
'transition': 'all 0.3s ease-out'
|
||||
});
|
||||
setTimeout(() => $input.css({
|
||||
'background-color': '',
|
||||
'color': ''
|
||||
}), 2000);
|
||||
} else {
|
||||
console.error(response.message);
|
||||
$input.css({
|
||||
'background-color': '#f8d7da',
|
||||
'color': '#fff',
|
||||
'transition': 'all 0.3s ease-out'
|
||||
});
|
||||
setTimeout(() => $input.css({
|
||||
'background-color': '',
|
||||
'color': ''
|
||||
}), 2000);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
$input.css('background-color', '#f8d7da');
|
||||
console.error('AJAX fetch error:', err);
|
||||
displayMsg(0, 'AJAX fetch error:' + err);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
$('.submit-display-turnerin').on('click', function() {
|
||||
const $input = $(this);
|
||||
const geraetId = $input.attr('data-geraet-id');
|
||||
|
||||
// Build the URL with GET parameters safely
|
||||
const url = '/intern/scripts/kampfrichter/ajax/displays/ajax-display-functions.php';
|
||||
@@ -518,7 +540,7 @@ jQuery(document).ready(function($) {
|
||||
body: new URLSearchParams({
|
||||
csrf_token,
|
||||
personId: $input.attr('data-person-id'),
|
||||
geraetId: $input.attr('data-geraet-id'),
|
||||
geraetId,
|
||||
jahr: window.AKTUELLES_JAHR,
|
||||
type: "neu"
|
||||
})
|
||||
@@ -527,14 +549,19 @@ jQuery(document).ready(function($) {
|
||||
.then(response => {
|
||||
|
||||
if (response.success) {
|
||||
ws.send(JSON.stringify({
|
||||
type: "UPDATE_SCORE",
|
||||
payload: {
|
||||
geraet: response.nameGeraet,
|
||||
data: response.data
|
||||
}
|
||||
}));
|
||||
displayMsg(1, 'Neue Turnerin wird angezigt');
|
||||
if (wsOpen) {
|
||||
ws.send(JSON.stringify({
|
||||
type: "UPDATE_SCORE",
|
||||
payload: {
|
||||
geraet: response.nameGeraet,
|
||||
geraetId,
|
||||
data: response.data
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
displayMsg(2, 'Keine live Syncrosation, Verzögerungen');
|
||||
}
|
||||
displayMsg(1, 'Neue Person wird angezigt');
|
||||
|
||||
$input.css('opacity', 0.5);
|
||||
} else {
|
||||
@@ -550,6 +577,8 @@ jQuery(document).ready(function($) {
|
||||
|
||||
$('.submit-display-start').on('click', function() {
|
||||
const $input = $(this);
|
||||
const geraetId = $input.attr('data-geraet-id');
|
||||
const personId = $input.attr('data-person-id')
|
||||
|
||||
const url = '/intern/scripts/kampfrichter/ajax/displays/ajax-display-functions.php';
|
||||
const dataType = $input.attr('data-type');
|
||||
@@ -559,8 +588,8 @@ jQuery(document).ready(function($) {
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: new URLSearchParams({
|
||||
csrf_token,
|
||||
geraetId: $input.attr('data-geraet-id'),
|
||||
personId: $input.attr('data-person-id'),
|
||||
geraetId,
|
||||
personId,
|
||||
dataType: dataType,
|
||||
type: "start"
|
||||
})
|
||||
@@ -569,13 +598,18 @@ jQuery(document).ready(function($) {
|
||||
.then(response => {
|
||||
|
||||
if (response.success) {
|
||||
ws.send(JSON.stringify({
|
||||
type: "UPDATE_SCORE",
|
||||
payload: {
|
||||
geraet: response.nameGeraet,
|
||||
data: response.data
|
||||
}
|
||||
}));
|
||||
if (wsOpen) {
|
||||
ws.send(JSON.stringify({
|
||||
type: "UPDATE_SCORE",
|
||||
payload: {
|
||||
geraet: response.nameGeraet,
|
||||
geraetId,
|
||||
data: response.data
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
displayMsg(2, 'Keine live Syncrosation, Verzögerungen');
|
||||
}
|
||||
|
||||
if (dataType == 1) {
|
||||
displayMsg(1, 'Start freigegeben');
|
||||
@@ -598,6 +632,7 @@ jQuery(document).ready(function($) {
|
||||
|
||||
$('.submit-musik-start').on('click', function() {
|
||||
const $input = $(this);
|
||||
const geraetId = $input.attr('data-geraet-id');
|
||||
|
||||
// Build the URL with GET parameters safely
|
||||
const url = `/intern/scripts/kampfrichter/ajax/ajax-update_kampfrichter_start_musik.php`;
|
||||
@@ -607,17 +642,22 @@ jQuery(document).ready(function($) {
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: new URLSearchParams({
|
||||
csrf_token,
|
||||
id: $input.attr('data-id'),
|
||||
discipline: $input.data('geraet')
|
||||
personId: $input.attr('data-id'),
|
||||
geraetId
|
||||
})
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(response => {
|
||||
const end = performance.now();
|
||||
ws.send(JSON.stringify({
|
||||
type: "AUDIO",
|
||||
payload: {}
|
||||
}));
|
||||
if (wsOpen) {
|
||||
ws.send(JSON.stringify({
|
||||
type: "AUDIO",
|
||||
payload: {
|
||||
audioDiscipline: geraetId
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
displayMsg(2, 'Keine live Syncrosation, Verzögerungen');
|
||||
}
|
||||
|
||||
if (response.success) {
|
||||
displayMsg(1, 'Musik wird abgespielt werden');
|
||||
@@ -634,6 +674,7 @@ jQuery(document).ready(function($) {
|
||||
|
||||
$('.submit-musik-stopp').on('click', function() {
|
||||
const $input = $(this);
|
||||
const geraetId = $input.attr('data-geraet-id');
|
||||
|
||||
// Build the URL with GET parameters safely
|
||||
const url = `/intern/scripts/kampfrichter/ajax/ajax-update_kampfrichter_stopp_musik.php`;
|
||||
@@ -642,16 +683,23 @@ jQuery(document).ready(function($) {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: new URLSearchParams({
|
||||
csrf_token
|
||||
csrf_token,
|
||||
geraetId
|
||||
})
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(response => {
|
||||
if (response.success) {
|
||||
ws.send(JSON.stringify({
|
||||
type: "AUDIO",
|
||||
payload: {}
|
||||
}));
|
||||
if (wsOpen) {
|
||||
ws.send(JSON.stringify({
|
||||
type: "AUDIO",
|
||||
payload: {
|
||||
audioDiscipline: geraetId
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
displayMsg(2, 'Keine live Syncrosation, Verzögerungen');
|
||||
}
|
||||
displayMsg(1, 'Musik wird gestoppt werden');
|
||||
} else {
|
||||
alert('Error: ' + response.message);
|
||||
@@ -666,6 +714,8 @@ jQuery(document).ready(function($) {
|
||||
|
||||
$('.div-submit-display-result').on('click', '.submit-display-result', function() {
|
||||
$input = $(this);
|
||||
const geraetId = $input.attr('data-geraet-id');
|
||||
const personId = $input.attr('data-person-id');
|
||||
|
||||
// Build the URL with GET parameters safely
|
||||
const url = '/intern/scripts/kampfrichter/ajax/displays/ajax-display-functions.php';
|
||||
@@ -675,8 +725,8 @@ jQuery(document).ready(function($) {
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: new URLSearchParams({
|
||||
csrf_token,
|
||||
personId: $input.attr('data-person-id'),
|
||||
geraetId: $input.attr('data-geraet-id'),
|
||||
personId,
|
||||
geraetId,
|
||||
run: $input.attr("data-run"),
|
||||
jahr: window.AKTUELLES_JAHR,
|
||||
type: "result"
|
||||
@@ -686,13 +736,27 @@ jQuery(document).ready(function($) {
|
||||
.then(response => {
|
||||
|
||||
if (response.success) {
|
||||
ws.send(JSON.stringify({
|
||||
type: "UPDATE_SCORE",
|
||||
payload: {
|
||||
geraet: response.nameGeraet,
|
||||
data: response.data
|
||||
}
|
||||
}));
|
||||
if (wsOpen) {
|
||||
ws.send(JSON.stringify({
|
||||
type: "UPDATE_SCORE",
|
||||
payload: {
|
||||
geraetId,
|
||||
geraet: response.nameGeraet,
|
||||
data: response.data
|
||||
}
|
||||
}));
|
||||
ws.send(JSON.stringify({
|
||||
type: "UPDATE_RANKLIVE_SCORE",
|
||||
payload: {
|
||||
geraetId,
|
||||
personId,
|
||||
jahr: window.AKTUELLES_JAHR,
|
||||
rankLive: response.rankLive
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
displayMsg(2, 'Keine live Syncrosation, Verzögerungen');
|
||||
}
|
||||
$input.css('opacity', 0.5);
|
||||
displayMsg(1, 'Resultat wird angezeigt');
|
||||
} else {
|
||||
@@ -707,6 +771,58 @@ jQuery(document).ready(function($) {
|
||||
});
|
||||
});
|
||||
|
||||
function updateDependentVisibility($changedEl, hasValue = false) {
|
||||
const uid = $changedEl.attr('data-uid');
|
||||
if (uid === undefined || uid === null) return;
|
||||
|
||||
const $row = $changedEl.closest('tr');
|
||||
|
||||
if ($row.length !== 1) return;
|
||||
|
||||
const isVisible = $changedEl.hasClass('emtyPlaceholder');
|
||||
const doesExist = $changedEl.hasClass('nonExistentEl');
|
||||
|
||||
const $linkedEls = $row.find(`.singleValueSpan[data-linked-el-uid="${uid}"]`);
|
||||
|
||||
$linkedEls.each((ind, el) => {
|
||||
const $linkedEl = $(el);
|
||||
if (!hasValue) {
|
||||
hasValue = $linkedEl.hasClass('changebleValue') && $linkedEl.text().trim() !== '-';
|
||||
}
|
||||
|
||||
$linkedEl.toggleClass('emtyPlaceholder', (isVisible && !hasValue)).toggleClass('nonExistentEl', doesExist);
|
||||
|
||||
const linkedElUid = $linkedEl.data('linked-el-uid');
|
||||
|
||||
const $subLinkedEls = $row.find(`.singleValueSpan[data-linked-el-uid="${linkedElUid}"]`);
|
||||
|
||||
if ($subLinkedEls.length > 0) updateDependentVisibility($linkedEl);
|
||||
});
|
||||
}
|
||||
|
||||
function initConditionalVisibility() {
|
||||
const $table = $('table.customDisplayEditorTable');
|
||||
|
||||
const $rows = $table.find('tr');
|
||||
|
||||
$rows.each((ind, el) => {
|
||||
const $row = $(el);
|
||||
$row.find('.singleValueSpan[data-linked-el-uid]').each(function() {
|
||||
|
||||
const linkedUid = $(this).data('linked-el-uid');
|
||||
|
||||
const $linkedEl = $row.find(`.singleValueSpan[data-uid="${linkedUid}"]`);
|
||||
|
||||
if ($linkedEl.length === 1) {
|
||||
updateDependentVisibility($linkedEl);
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
initConditionalVisibility();
|
||||
initConditionalVisibility();
|
||||
|
||||
const selecteddiscipline = window.FREIGABE;
|
||||
|
||||
@@ -724,39 +840,53 @@ ws.addEventListener("message", event => { // Use 'event' as it's more standard t
|
||||
const data = msgOBJ.payload;
|
||||
|
||||
// Check access rights
|
||||
if (data.discipline === selecteddiscipline.toLowerCase() || selecteddiscipline.toLowerCase() === 'admin') {
|
||||
if (data.discipline === selecteddiscipline.toLowerCase() || selecteddiscipline === 'A') {
|
||||
const noten = data.noten;
|
||||
|
||||
for (const [keyG, noteGroup] of Object.entries(noten)) {
|
||||
for (const [key, runGroup] of Object.entries(noteGroup)) {
|
||||
|
||||
for (const [run, value] of Object.entries(runGroup)) {
|
||||
// OPTIONAL: Skip if the current user is currently focused on this specific input
|
||||
if (document.activeElement.dataset.fieldTypeId === key &&
|
||||
document.activeElement.dataset.geraetId === keyG &&
|
||||
document.activeElement.dataset.run == run) continue;
|
||||
|
||||
// Select all matching elements (inputs and spans)
|
||||
const $elements = $(`.changebleValue[data-field-type-id="${key}"][data-geraet-id="${keyG}"][data-person-id="${data.personId}"][data-run="${run}"]`);
|
||||
|
||||
$elements.each(function() {
|
||||
const $el = $(this);
|
||||
$elements.each((ind, el) => {
|
||||
const $el = $(el);
|
||||
|
||||
// Update value or text
|
||||
if ($el.is('input')) {
|
||||
$el.val(value ?? '');
|
||||
} else {
|
||||
$el.text(value ?? '');
|
||||
}
|
||||
|
||||
// Visual feedback: Flash color
|
||||
const nativeEl = $el[0];
|
||||
nativeEl.style.transition = 'color 0.3s';
|
||||
nativeEl.style.color = '#008e85';
|
||||
|
||||
setTimeout(() => {
|
||||
nativeEl.style.color = '';
|
||||
}, 1000);
|
||||
// Visual feedback: Flash color
|
||||
const nativeEl = $el[0];
|
||||
nativeEl.style.transition = 'color 0.3s';
|
||||
nativeEl.style.color = '#008e85';
|
||||
|
||||
setTimeout(() => {
|
||||
nativeEl.style.color = '';
|
||||
}, 1000);
|
||||
} else {
|
||||
const isNullable = ($el.attr('data-noten-nullable') ?? 'false') === 'true';
|
||||
const floatValZero = parseFloat(value.replace(",", ".").replace("'", "")) === 0.0;
|
||||
|
||||
let hasValue = false;
|
||||
|
||||
if (isNullable && floatValZero || value === '') {
|
||||
$el.text('-').addClass('emtyPlaceholder');
|
||||
} else {
|
||||
$el.text(value).removeClass('emtyPlaceholder');
|
||||
hasValue = true;
|
||||
}
|
||||
|
||||
updateDependentVisibility($el, hasValue);
|
||||
|
||||
$el.removeClass('updated');
|
||||
$el.addClass('updated');
|
||||
|
||||
setTimeout(() => {
|
||||
$el.removeClass('updated');
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
function allNameKrValid() {
|
||||
var isValid = true;
|
||||
|
||||
$('.ajax-input-namekr').each(function () {
|
||||
if ($(this).val() === '---') {
|
||||
isValid = false;
|
||||
return false; // break loop
|
||||
}
|
||||
});
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
function updateSelectKrDiv() {
|
||||
const krDiv = $('.noKampfrichterDiv');
|
||||
|
||||
if (krDiv.length !== 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (allNameKrValid()) {
|
||||
krDiv.css('display', 'none');
|
||||
} else {
|
||||
krDiv.css('display', 'flex');
|
||||
}
|
||||
}
|
||||
|
||||
// initial check
|
||||
updateSelectKrDiv();
|
||||
|
||||
// react to changes
|
||||
$('.ajax-input-namekr').on('change', function () {
|
||||
updateSelectKrDiv();
|
||||
});
|
||||
@@ -4,6 +4,7 @@ use Dotenv\Dotenv;
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
date_default_timezone_set('Europe/Zurich');
|
||||
|
||||
if (!isset($baseDir)) {
|
||||
$baseDir = $_SERVER['DOCUMENT_ROOT'];
|
||||
@@ -32,6 +33,26 @@ if ($dbconnection['success'] !== true){
|
||||
exit;
|
||||
}
|
||||
|
||||
$link_expires_at = $_POST['linkExpiresAt'] ?? '';
|
||||
|
||||
if ($link_expires_at === '' || strtotime($link_expires_at) === false) {
|
||||
echo json_encode(['success' => false, 'message' => 'Ungültiges Ablaufdatum']);
|
||||
http_response_code(400);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (strtotime($link_expires_at) > strtotime('+8 days')) {
|
||||
echo json_encode(['success' => false, 'message' => 'Datum liegt zu weit in der Zukunft']);
|
||||
http_response_code(400);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (strtotime($link_expires_at) < time()) {
|
||||
echo json_encode(['success' => false, 'message' => 'Datum liegt in der Vergangenheit']);
|
||||
http_response_code(400);
|
||||
exit;
|
||||
}
|
||||
|
||||
$editor_id = $_SESSION['user_id_wk_leitung'];
|
||||
|
||||
$plain = trim($_POST['password'] ?? null);
|
||||
@@ -136,15 +157,12 @@ if (!$updated) {
|
||||
|
||||
$new_id = $mysqli->insert_id;
|
||||
|
||||
// Delete old OTL links for this user (recommended)
|
||||
db_delete($mysqli, $tableOTL, ['user_id' => $new_id]);
|
||||
|
||||
// Insert the row — url + timestamp are auto-generated by MySQL
|
||||
|
||||
$typeOp = "create_profile";
|
||||
|
||||
$stmt = $mysqli->prepare("INSERT INTO {$tableOTL} (user_id, `type`) VALUES (?, ?)");
|
||||
$stmt->bind_param("is", $new_id, $typeOp);
|
||||
$stmt = $mysqli->prepare("INSERT INTO $tableOTL (user_id, `type`, `expires_at`) VALUES (?, ?, ?)");
|
||||
$stmt->bind_param("iss", $new_id, $typeOp, $link_expires_at);
|
||||
|
||||
|
||||
if (!$stmt->execute()) {
|
||||
@@ -164,4 +182,4 @@ if (!$url) {
|
||||
exit;
|
||||
}
|
||||
|
||||
echo json_encode(['success' => true, 'url' => $url]);
|
||||
echo json_encode(['success' => true, 'url' => $url, 'expiresAt' => $link_expires_at]);
|
||||
@@ -16,7 +16,7 @@ verify_csrf();
|
||||
require $baseDir . '/../scripts/db/db-functions.php';
|
||||
require $baseDir . '/../scripts/db/db-tables.php';
|
||||
|
||||
|
||||
date_default_timezone_set('Europe/Zurich');
|
||||
|
||||
$type = 'wkl';
|
||||
|
||||
@@ -45,10 +45,12 @@ if ($id <= 0) {
|
||||
// Delete old OTL links for this user (recommended)
|
||||
db_delete($mysqli, $tableOTL, ['user_id' => $id]);
|
||||
|
||||
// Insert the row — url + timestamp are auto-generated by MySQL
|
||||
$timestamp_one_day = strtotime('+1 days');
|
||||
|
||||
$stmt = $mysqli->prepare("INSERT INTO {$tableOTL} (user_id, `type`) VALUES (?, ?)");
|
||||
$stmt->bind_param("is", $id, $typeOp);
|
||||
$link_expires_at = date("Y-m-d H:i:s", $timestamp_one_day);
|
||||
|
||||
$stmt = $mysqli->prepare("INSERT INTO $tableOTL (user_id, `type`, `expires_at`) VALUES (?, ?, ?)");
|
||||
$stmt->bind_param("iss", $id, $typeOp, $link_expires_at);
|
||||
|
||||
|
||||
if (!$stmt->execute()) {
|
||||
@@ -60,7 +62,6 @@ $row_id = $stmt->insert_id;
|
||||
|
||||
$stmt->close();
|
||||
|
||||
// Now fetch the auto-generated URL
|
||||
$url = db_get_var($mysqli, "SELECT url FROM $tableOTL WHERE id = ? LIMIT 1", [$row_id]);
|
||||
|
||||
if (!$url) {
|
||||
@@ -68,4 +69,4 @@ if (!$url) {
|
||||
exit;
|
||||
}
|
||||
|
||||
echo json_encode(['success' => true, 'url' => $url]);
|
||||
echo json_encode(['success' => true, 'url' => $url, 'expiresAt' => $link_expires_at]);
|
||||
|
||||
@@ -33,6 +33,7 @@ if ($id <= 0) {
|
||||
}
|
||||
|
||||
db_delete($mysqli, $tableInternUsers, ['id' => $id]);
|
||||
db_delete($mysqli, $tableOTL, ['user_id' => $id]);
|
||||
|
||||
echo json_encode(['success' => true, 'message' => "Benutzer $id erfolgreich gelöscht.", 'id' => $id]);
|
||||
exit;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!isset($baseDir)) {
|
||||
$baseDir = $_SERVER['DOCUMENT_ROOT'];
|
||||
@@ -10,9 +11,13 @@ ini_wkvs_session();
|
||||
|
||||
check_user_permission('wk_leitung');
|
||||
|
||||
verify_csrf();
|
||||
require_once $baseDir . '/../scripts/delete_request_type_functions.php';
|
||||
|
||||
$id = intval($_POST['field_id'] ?? 0);
|
||||
parse_input_to_delete();
|
||||
|
||||
verify_delete_csrf($_DELETE);
|
||||
|
||||
$id = (int) ($_DELETE['id'] ?? 0);
|
||||
|
||||
if ($id <= 0) {
|
||||
echo json_encode(['success' => false, 'message' => 'No valid ID']);
|
||||
@@ -31,6 +36,15 @@ if ($dbconnection['success'] !== true){
|
||||
exit;
|
||||
}
|
||||
|
||||
$entrys = db_select($mysqli, $tableVereine, "1", '`id` = ?', [$id]);
|
||||
|
||||
if (count($entrys) < 1) {
|
||||
echo json_encode(['success' => true, 'message' => 'Verein bereits gelöscht']);
|
||||
http_response_code(410);
|
||||
exit;
|
||||
}
|
||||
|
||||
db_delete($mysqli, $tableVereine, ['id' => $id]);
|
||||
|
||||
echo json_encode(['success' => true, 'message' => 'Verein gelöscht']);
|
||||
echo json_encode(['success' => true, 'message' => 'Verein gelöscht']);
|
||||
http_response_code(200);
|
||||
@@ -23,6 +23,7 @@ if ($data['success'] === false){
|
||||
}
|
||||
|
||||
require $baseDir . '/../scripts/db/db-tables.php';
|
||||
require $baseDir . '/../scripts/db/db-functions.php';
|
||||
|
||||
$value = isset($_POST['value']) ? preg_replace('/[^a-zA-Z0-9\s\-"]/u', '', $_POST['value']) : '';
|
||||
|
||||
@@ -65,10 +66,15 @@ while ($entry = $programme->fetch_assoc()) {
|
||||
'id' => $entry['id'],
|
||||
'programm' => $entry['programm'],
|
||||
'aktiv' => intval($entry['aktiv']),
|
||||
'preis' => floatval($entry['preis'])
|
||||
'preis' => floatval($entry['preis']),
|
||||
'orderIndex' => intval($entry['order_index'])
|
||||
];
|
||||
}
|
||||
|
||||
require_once $baseDir . '/../scripts/cache/cache-creators/regenerate-display-dependencies-cache.php';
|
||||
|
||||
generateIntersectCache($mysqli, $baseDir);
|
||||
|
||||
// Return JSON
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
if (!isset($baseDir)) {
|
||||
$baseDir = $_SERVER['DOCUMENT_ROOT'];
|
||||
}
|
||||
@@ -15,7 +11,11 @@ ini_wkvs_session();
|
||||
|
||||
check_user_permission('wk_leitung');
|
||||
|
||||
verify_csrf();
|
||||
require_once $baseDir . '/../scripts/delete_request_type_functions.php';
|
||||
|
||||
parse_input_to_delete();
|
||||
|
||||
verify_delete_csrf($_DELETE);
|
||||
|
||||
$type = 'wkl';
|
||||
|
||||
@@ -27,57 +27,34 @@ if ($data['success'] === false){
|
||||
}
|
||||
|
||||
require $baseDir . '/../scripts/db/db-tables.php';
|
||||
require $baseDir . '/../scripts/db/db-functions.php';
|
||||
|
||||
$id = isset($_POST['id']) ? intval($_POST['id']) : 0;
|
||||
$id = (int) ($_DELETE['id'] ?? 0);
|
||||
|
||||
if ($id <= 0) {
|
||||
if ($id < 1) {
|
||||
echo json_encode(['success' => false, 'message' => 'Invalid ID']);
|
||||
http_response_code(400);
|
||||
exit;
|
||||
}
|
||||
|
||||
$entrys = db_select($mysqli, $tableProgramme, "programm", '`id` = ?', [$id]);
|
||||
|
||||
// ---------- Step 2: Get values from DB ----------
|
||||
|
||||
|
||||
$stmt = $mysqli->prepare("DELETE FROM `$tableProgramme` WHERE id = ?");
|
||||
if (!$stmt) {
|
||||
echo json_encode(['success' => false, 'message' => 'Critical DB ERROR']);
|
||||
if (count($entrys) !== 1) {
|
||||
echo json_encode(['success' => true, 'message' => 'Alters-/ Leistungsklasse bereits gelöscht']);
|
||||
http_response_code(410);
|
||||
exit;
|
||||
}
|
||||
|
||||
$stmt->bind_param("i", $id);
|
||||
$bezeichnung = $entrys[0]['programm'];
|
||||
|
||||
$success = $stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
if (!$success) {
|
||||
echo json_encode(['success' => false, 'message' => 'Insert failed']);
|
||||
exit;
|
||||
}
|
||||
db_delete($mysqli, $tableProgramme, ['id' => $id]);
|
||||
|
||||
// Fetch all rows
|
||||
$query2 = "SELECT * FROM `$tableProgramme` ORDER BY programm ASC";
|
||||
$programme = $mysqli->query($query2);
|
||||
require_once $baseDir . '/../scripts/cache/cache-creators/regenerate-display-dependencies-cache.php';
|
||||
|
||||
if (!$programme) {
|
||||
echo json_encode(['success' => false, 'message' => 'Select failed']);
|
||||
exit;
|
||||
}
|
||||
generateIntersectCache($mysqli, $baseDir);
|
||||
|
||||
$output = [];
|
||||
while ($entry = $programme->fetch_assoc()) {
|
||||
$output[] = [
|
||||
'id' => $entry['id'],
|
||||
'programm' => $entry['programm'],
|
||||
'aktiv' => intval($entry['aktiv']),
|
||||
'preis' => floatval($entry['preis'])
|
||||
];
|
||||
}
|
||||
|
||||
// Return JSON
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'output' => $output,
|
||||
'message' => $id.' gelöscht'
|
||||
]);
|
||||
echo json_encode(['success' => true, 'message' => $bezeichnung.' gelöscht']);
|
||||
http_response_code(200);
|
||||
exit;
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
use Dotenv\Dotenv;
|
||||
|
||||
ini_set('display_errors', 1);
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!isset($baseDir)) {
|
||||
@@ -28,25 +30,73 @@ if ($dbconnection['success'] !== true){
|
||||
exit;
|
||||
}
|
||||
|
||||
if (isset($_POST['field_id'])){
|
||||
$id = intval($_POST['field_id']);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Invalid Input.']);
|
||||
$allowed_update_types = ['all', 'no_password', 'new_user'];
|
||||
|
||||
$update_type = $_POST['updateType'] ?? '';
|
||||
|
||||
if (!in_array($update_type, $allowed_update_types)) {
|
||||
echo json_encode(['success' => false, 'message' => 'Unbekannte Aktion']);
|
||||
http_response_code(400);
|
||||
exit;
|
||||
}
|
||||
|
||||
$editor_id = $_SESSION['user_id_wk_leitung'];
|
||||
$plain = trim($_POST['password'] ?? '');
|
||||
$editor_id = (int) $_SESSION['user_id_wk_leitung'];
|
||||
|
||||
if (!$plain) {
|
||||
echo json_encode(['success' => false, 'message' => 'Invalid Input.']);
|
||||
if ($update_type === 'new_user') {
|
||||
$stmt = $mysqli->prepare(
|
||||
"INSERT INTO {$tableInternUsers}
|
||||
(created_at, updated_at, edited_by, login_active)
|
||||
VALUES (?, ?, ?, ?)"
|
||||
);
|
||||
|
||||
$loginActive = 1;
|
||||
|
||||
$created_at = date('Y-m-d H:i:s');
|
||||
$updated_at = $created_at;
|
||||
|
||||
$stmt->bind_param(
|
||||
"ssis",
|
||||
$created_at,
|
||||
$updated_at,
|
||||
$editor_id,
|
||||
$loginActive
|
||||
);
|
||||
|
||||
$updated = $stmt->execute();
|
||||
|
||||
$new_id = $mysqli->insert_id;
|
||||
echo json_encode(['success' => true, 'message' => 'Neuer Benutzer wurde erfolgreich erstellt', 'id' => $new_id]);
|
||||
http_response_code(200);
|
||||
exit;
|
||||
}
|
||||
|
||||
$id = (int) ($_POST['personId'] ?? 0);
|
||||
|
||||
if ($id < 1 && $update_type !== 'new_user') {
|
||||
echo json_encode(['success' => false, 'message' => 'Die angegebene ID ist nicht valide']);
|
||||
http_response_code(400);
|
||||
exit;
|
||||
}
|
||||
|
||||
$plain_password = trim($_POST['password'] ?? '');
|
||||
|
||||
if ($plain_password === '' && $update_type !== 'no_password') {
|
||||
echo json_encode(['success' => false, 'message' => 'Kein Passwort angegeben']);
|
||||
http_response_code(422);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($update_type !== 'no_password' && strlen($plain_password) < 6) {
|
||||
echo json_encode(['success' => false, 'message' => 'Das Passwort muss mindestens 6 Zeichen enthalten']);
|
||||
http_response_code(400);
|
||||
exit;
|
||||
}
|
||||
|
||||
$username = htmlspecialchars(trim($_POST['username'] ?? ''));
|
||||
|
||||
if (!$username) {
|
||||
echo json_encode(['success' => false, 'message' => 'Invalid Input.']);
|
||||
if ($username === '') {
|
||||
echo json_encode(['success' => false, 'message' => 'Keinen Benutzernamen angegeben']);
|
||||
http_response_code(422);
|
||||
exit;
|
||||
}
|
||||
|
||||
@@ -75,45 +125,52 @@ $array = [
|
||||
// Store as proper JSON string
|
||||
$freigabe_store = json_encode($array);
|
||||
|
||||
// Hash for login
|
||||
$hash = password_hash($plain, PASSWORD_ARGON2ID);
|
||||
if ($update_type !== 'no_password') {
|
||||
$hash = password_hash($plain_password, PASSWORD_ARGON2ID);
|
||||
|
||||
|
||||
require $baseDir . '/../composer/vendor/autoload.php';
|
||||
require $baseDir . '/../composer/vendor/autoload.php';
|
||||
|
||||
$envFile = realpath($baseDir . '/../config/.env.pw-encryption-key');
|
||||
$envFile = realpath($baseDir . '/../config/.env.pw-encryption-key');
|
||||
|
||||
if ($envFile === false) {
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => "Environment file not found"
|
||||
]);
|
||||
exit;
|
||||
if ($envFile === false) {
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => "Environment file not found"
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$envDir = dirname($envFile);
|
||||
|
||||
$dotenv = Dotenv::createImmutable($envDir, '.env.pw-encryption-key');
|
||||
|
||||
$dotenv->load();
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => "Dotenv error"
|
||||
]);
|
||||
}
|
||||
|
||||
$iv_length = openssl_cipher_iv_length('aes-256-cbc');
|
||||
$iv = random_bytes($iv_length);
|
||||
$encrypted = openssl_encrypt($plain_password, 'aes-256-cbc', $_ENV['PW_ENCRYPTION_KEY'], 0, $iv);
|
||||
$cipher_store = base64_encode($iv . $encrypted);
|
||||
}
|
||||
|
||||
try {
|
||||
$envDir = dirname($envFile);
|
||||
|
||||
$dotenv = Dotenv::createImmutable($envDir, '.env.pw-encryption-key');
|
||||
|
||||
$dotenv->load();
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => "Dotenv error"
|
||||
]);
|
||||
}
|
||||
|
||||
// Encrypt for display
|
||||
$iv_length = openssl_cipher_iv_length('aes-256-cbc');
|
||||
$iv = random_bytes($iv_length);
|
||||
$encrypted = openssl_encrypt($plain, 'aes-256-cbc', $_ENV['PW_ENCRYPTION_KEY'], 0, $iv);
|
||||
$cipher_store = base64_encode($iv . $encrypted);
|
||||
|
||||
|
||||
if ($id > 0) {
|
||||
if ($update_type === 'no_password') {
|
||||
$updated = db_update($mysqli, $tableInternUsers, [
|
||||
'username' => $username,
|
||||
'name_person' => $namePerson,
|
||||
'freigabe' => $freigabe_store,
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
'edited_by' => $editor_id
|
||||
], ['id' => $id]);
|
||||
} else {
|
||||
$updated = db_update($mysqli, $tableInternUsers, [
|
||||
'password_hash' => $hash,
|
||||
'password_cipher' => $cipher_store,
|
||||
@@ -123,42 +180,14 @@ if ($id > 0) {
|
||||
'updated_at' => date('Y-m-d H:i:s'),
|
||||
'edited_by' => $editor_id
|
||||
], ['id' => $id]);
|
||||
} else {
|
||||
$stmt = $mysqli->prepare(
|
||||
"INSERT INTO {$tableInternUsers}
|
||||
(username, name_person, password_hash, password_cipher, freigabe, created_at, updated_at, edited_by, login_active)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
||||
);
|
||||
|
||||
$loginActive = 1;
|
||||
|
||||
$stmt->bind_param(
|
||||
"sssssssis",
|
||||
$username,
|
||||
$namePerson,
|
||||
$hash,
|
||||
$cipher_store,
|
||||
$freigabe_store,
|
||||
$created_at,
|
||||
$updated_at,
|
||||
$editor_id,
|
||||
$loginActive
|
||||
);
|
||||
|
||||
$created_at = date('Y-m-d H:i:s');
|
||||
$updated_at = $created_at;
|
||||
|
||||
$updated = $stmt->execute();
|
||||
}
|
||||
|
||||
if ($updated !== false) {
|
||||
if ($id == 0) { // new user
|
||||
$new_id = $mysqli->insert_id;
|
||||
|
||||
echo json_encode(['success' => true, 'message' => $username.' wurde erfolgreich erstellt.', 'id' => $new_id]);
|
||||
} else {
|
||||
echo json_encode(['success' => true, 'message' => $username.' wurde erfolgreich aktualisiert.']);
|
||||
}
|
||||
} else {
|
||||
if ($updated === false) {
|
||||
echo json_encode(['success' => false, 'message' => 'DB Error']);
|
||||
}
|
||||
http_response_code(500);
|
||||
exit;
|
||||
}
|
||||
|
||||
echo json_encode(['success' => true, 'message' => $username.' wurde erfolgreich aktualisiert.']);
|
||||
http_response_code(200);
|
||||
exit;
|
||||
@@ -1,6 +1,8 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
|
||||
ini_set('display_errors', 1);
|
||||
|
||||
if (!isset($baseDir)) {
|
||||
$baseDir = $_SERVER['DOCUMENT_ROOT'];
|
||||
}
|
||||
@@ -23,11 +25,19 @@ if ($data['success'] === false){
|
||||
}
|
||||
|
||||
require $baseDir . '/../scripts/db/db-tables.php';
|
||||
require $baseDir . '/../scripts/db/db-functions.php';
|
||||
|
||||
// ---------- Get and sanitize input ----------
|
||||
$id = isset($_POST['id']) ? intval($_POST['id']) : 0;
|
||||
$value = isset($_POST['value']) ? floatval($_POST['value']) : 0;
|
||||
$type = isset($_POST['type']) ? $_POST['type'] : '';
|
||||
|
||||
$allowedTypes = ['preis', 'order_index', 'aktiv'];
|
||||
if (!in_array($type, $allowedTypes, true)) {
|
||||
echo json_encode(['success' => false, 'message' => 'Invalid type']);
|
||||
http_response_code(400);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($id < 0) {
|
||||
echo json_encode(['success' => false, 'message' => 'Invalid ID']);
|
||||
@@ -37,7 +47,7 @@ if ($id < 0) {
|
||||
// ---------- Step 2: Get values from DB ----------
|
||||
|
||||
|
||||
$query = "UPDATE `$tableProgramme` SET aktiv = '$value' WHERE id = $id";
|
||||
$query = "UPDATE `$tableProgramme` SET $type = '$value' WHERE id = $id";
|
||||
$result = $mysqli->query($query);
|
||||
|
||||
if (!$result) {
|
||||
@@ -45,6 +55,12 @@ if (!$result) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($type === 'aktiv') {
|
||||
require_once $baseDir . '/../scripts/cache/cache-creators/regenerate-display-dependencies-cache.php';
|
||||
|
||||
generateIntersectCache($mysqli, $baseDir);
|
||||
}
|
||||
|
||||
// ---------- Return JSON ----------
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
@@ -1,6 +1,8 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
|
||||
ini_set('display_errors', 1);
|
||||
|
||||
if (!isset($baseDir)) {
|
||||
$baseDir = $_SERVER['DOCUMENT_ROOT'];
|
||||
}
|
||||
@@ -23,31 +25,27 @@ if ($data['success'] === false){
|
||||
}
|
||||
|
||||
require $baseDir . '/../scripts/db/db-tables.php';
|
||||
require $baseDir . '/../scripts/db/db-functions.php';
|
||||
|
||||
// ---------- Get and sanitize input ----------
|
||||
$id = isset($_POST['id']) ? intval($_POST['id']) : 0;
|
||||
$value = isset($_POST['value']) ? round(floatval($_POST['value']), 2) : 0;
|
||||
$slug = $_POST['slug'] ?? '';
|
||||
|
||||
$allowed_slugs = ['rankLive-overview', 'rankLive-geraet', 'kampfrichter-admin', 'kampfrichter-geraet', 'rangliste'];
|
||||
|
||||
if ($id < 0) {
|
||||
echo json_encode(['success' => false, 'message' => 'Invalid ID']);
|
||||
if (!in_array($slug, $allowed_slugs)){
|
||||
echo json_encode(['success' => false, 'message' => 'Invalid Type']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ---------- Step 2: Get values from DB ----------
|
||||
$_SESSION['current_ranklive_slug'] = $slug;
|
||||
|
||||
$db_res = db_select($mysqli, $tableRankLiveConfigs, '`json`', '`type_slug` = ?', [$slug], '', 1);
|
||||
|
||||
$query = "UPDATE `$tableProgramme` SET preis = '$value' WHERE id = $id";
|
||||
$result = $mysqli->query($query);
|
||||
$res = $db_res[0]['json'] ?? '';
|
||||
|
||||
if (!$result) {
|
||||
echo json_encode(['success' => false, 'message' => 'Update failed']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ---------- Return JSON ----------
|
||||
// Return JSON
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'Startgebüren set to '.$value.' Fr.'
|
||||
'data' => $res,
|
||||
'message' => 'gespeichert'
|
||||
]);
|
||||
exit;
|
||||
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
|
||||
ini_set('display_errors', 1);
|
||||
|
||||
if (!isset($baseDir)) {
|
||||
$baseDir = $_SERVER['DOCUMENT_ROOT'];
|
||||
}
|
||||
|
||||
require_once $baseDir . '/../scripts/session_functions.php';
|
||||
|
||||
ini_wkvs_session();
|
||||
|
||||
check_user_permission('wk_leitung');
|
||||
|
||||
verify_csrf();
|
||||
|
||||
$type = 'wkl';
|
||||
|
||||
$data = include $baseDir . '/../scripts/db/db-verbindung-script.php';
|
||||
|
||||
if ($data['success'] === false){
|
||||
echo json_encode(['success' => false, 'message' => $data['message']]);
|
||||
exit;
|
||||
}
|
||||
|
||||
require $baseDir . '/../scripts/db/db-tables.php';
|
||||
require $baseDir . '/../scripts/db/db-functions.php';
|
||||
|
||||
$json = $_POST['json'] ?? '';
|
||||
|
||||
$slug = $_POST['slug'] ?? '';
|
||||
|
||||
$allowed_slugs = ['rankLive-overview' => 'all', 'rankLive-geraet' => 'geraet', 'kampfrichter-admin' => 'all', 'kampfrichter-geraet' => 'geraet', 'rangliste' => 'all'];
|
||||
|
||||
if (!array_key_exists($slug, $allowed_slugs)){
|
||||
echo json_encode(['success' => false, 'message' => 'Invalid Type']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!$json || $json === ''){
|
||||
echo json_encode(['success' => false, 'message' => 'No input']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$array = json_decode($json, true) ?? [];
|
||||
|
||||
$body = $array['body'];
|
||||
|
||||
$all_noten = [];
|
||||
|
||||
function extractNotenData(array $array): array {
|
||||
$result = [];
|
||||
|
||||
// Check if the current array is a 'notenData' object itself
|
||||
if (isset($array['type']) && $array['type'] === 'notenData') {
|
||||
return [$array];
|
||||
}
|
||||
|
||||
// Otherwise, loop through the elements to go deeper
|
||||
foreach ($array as $value) {
|
||||
if (is_array($value)) {
|
||||
// Recursively extract from the sub-array and merge into our result list
|
||||
$result = array_merge($result, extractNotenData($value));
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
function extractNotenGeraetData(array $array): array {
|
||||
$result = [];
|
||||
|
||||
// Check if the current array is a 'notenData' object itself
|
||||
if (isset($array['type']) && $array['type'] === 'notenGeraetData') {
|
||||
return [$array];
|
||||
}
|
||||
|
||||
// Otherwise, loop through the elements to go deeper
|
||||
foreach ($array as $value) {
|
||||
if (is_array($value)) {
|
||||
// Recursively extract from the sub-array and merge into our result list
|
||||
$result = array_merge($result, extractNotenGeraetData($value));
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
$all_noten = ($allowed_slugs[$slug] === 'all') ? extractNotenData($body) : extractNotenGeraetData($body);
|
||||
|
||||
$all_noten_export = [];
|
||||
|
||||
foreach ($all_noten as $single_note) {
|
||||
$all_noten_export[] = [
|
||||
'g_id' => $single_note['geraet-id'] ?? 'A',
|
||||
'n_id' => $single_note['field-type-id'],
|
||||
'r' => $single_note['run']
|
||||
];
|
||||
}
|
||||
|
||||
function unique_matrix($matrix) {
|
||||
$matrixAux = $matrix;
|
||||
|
||||
foreach($matrix as $key => $subMatrix) {
|
||||
unset($matrixAux[$key]);
|
||||
|
||||
foreach($matrixAux as $subMatrixAux) {
|
||||
if($subMatrix === $subMatrixAux) {
|
||||
unset($matrix[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $matrix;
|
||||
}
|
||||
|
||||
$all_noten_json = json_encode(unique_matrix($all_noten_export) ?? []) ?? '';
|
||||
|
||||
$user_id = (int) $_SESSION['user_id_wk_leitung'] ?? 0;
|
||||
|
||||
$stmt = $mysqli->prepare("INSERT INTO `$tableRankLiveConfigs` (`json`, `all_noten_json`, `type_slug`, `created_by`, `updated_by`) VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE `json` = VALUES(`json`), `all_noten_json` = VAlUES(`all_noten_json`), `updated_by` = VALUES(`updated_by`)");
|
||||
|
||||
if (!$stmt) {
|
||||
echo json_encode(['success' => false, 'message' => 'Critical DB ERROR']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$stmt->bind_param("sssii", $json, $all_noten_json, $slug, $user_id, $user_id);
|
||||
$success = $stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
if (!$success) {
|
||||
echo json_encode(['success' => false, 'message' => 'Insert failed']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (str_contains($slug, 'rankLive')) {
|
||||
require_once $baseDir . '/../scripts/cache/cache-creators/regenerate-display-dependencies-cache.php';
|
||||
generateIntersectCache($mysqli, $baseDir);
|
||||
}
|
||||
|
||||
// Return JSON
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => 'gespeichert'
|
||||
]);
|
||||
exit;
|
||||