First version, for githup; UNSTABLE, DO NOT USE!
This commit is contained in:
9
www/intern/wk-leitung/.htaccess
Normal file
9
www/intern/wk-leitung/.htaccess
Normal file
@@ -0,0 +1,9 @@
|
||||
RewriteEngine On
|
||||
|
||||
# Do not interfere with real files or directories
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
|
||||
# If a matching .php file exists, serve it
|
||||
RewriteCond %{REQUEST_FILENAME}.php -f
|
||||
RewriteRule ^(.+)$ $1.php [L]
|
||||
244
www/intern/wk-leitung/displaycontrol.php
Normal file
244
www/intern/wk-leitung/displaycontrol.php
Normal file
@@ -0,0 +1,244 @@
|
||||
<?php
|
||||
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
|
||||
if (session_status() !== PHP_SESSION_ACTIVE) session_start();
|
||||
|
||||
$baseDir = $_SERVER['DOCUMENT_ROOT'];
|
||||
|
||||
$access_granted_wkl = $_SESSION['access_granted_wk_leitung'] ?? false;
|
||||
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<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">
|
||||
<title>Intern - Displaycontrol</title>
|
||||
|
||||
<link rel="stylesheet" href="/intern/css/displaycontrol.css">
|
||||
<link rel="stylesheet" href="/intern/css/custom-msg-display.css">
|
||||
<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="/files/fonts/fonts.css">
|
||||
<link rel="stylesheet" href="/intern/css/sidebar.css">
|
||||
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<?php
|
||||
|
||||
if ( ! $access_granted_wkl ) :
|
||||
|
||||
$logintype = 'wk_leitung';
|
||||
|
||||
require $baseDir . '/../scripts/login/login.php';
|
||||
|
||||
$logintype = '';
|
||||
|
||||
else :
|
||||
|
||||
$type = 'wkl';
|
||||
|
||||
$dbconnection = require $baseDir . '/../scripts/db/db-verbindung-script.php';
|
||||
|
||||
if ($dbconnection['success'] !== true){
|
||||
echo 'Critical DB Error.';
|
||||
exit;
|
||||
}
|
||||
|
||||
require $baseDir . '/../scripts/websocket/ws-create-token.php';
|
||||
require $baseDir . '/../scripts/db/db-tables.php';
|
||||
|
||||
$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 = $result->fetch_all(MYSQLI_ASSOC);
|
||||
|
||||
$stmt->close();
|
||||
|
||||
$currentPage = 'displaycontrol';
|
||||
require $baseDir . '/intern/scripts/sidebar/sidebar.php';
|
||||
?>
|
||||
|
||||
<script>
|
||||
let ws;
|
||||
let firstConnect = true;
|
||||
const RETRY_DELAY = 2000;
|
||||
|
||||
const urlAjaxNewWSToken = '/intern/scripts/ajax-create-ws-token.php';
|
||||
|
||||
async function fetchNewWSToken(freigabe) {
|
||||
try {
|
||||
const response = await fetch(urlAjaxNewWSToken, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: new URLSearchParams({ access: freigabe })
|
||||
});
|
||||
|
||||
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 = '<?= generateWSToken('displaycontrol') ?>';
|
||||
} else {
|
||||
token = await fetchNewWSToken('displaycontrol');
|
||||
}
|
||||
|
||||
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();
|
||||
</script>
|
||||
|
||||
<div class="headerDivTrainer">
|
||||
<div class="headingPanelDiv">
|
||||
<h2 class="headingPanel">Anzeigesteuerung</h2>
|
||||
</div>
|
||||
<?php sidebarRender('button'); ?>
|
||||
</div>
|
||||
|
||||
<?php sidebarRender('modal'); ?>
|
||||
|
||||
<section class="bgSection">
|
||||
|
||||
<div class="controls-wrapper">
|
||||
<span>Anzeigeoptionen</span>
|
||||
<div>
|
||||
<button class="change-type" value="logo">Logo + WK-Name</button>
|
||||
<button class="change-type" value="scoring">WK-Betrieb</button>
|
||||
</div>
|
||||
<form class="change-type-form" data-type="ctext">
|
||||
<button type="submit">Individueller Text</button>
|
||||
<input placeholder="Individuellen Text eingeben...">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="iframe-grid">
|
||||
<?php
|
||||
foreach ($disciplines as $dis){
|
||||
echo '<div class="iframeWithTitle"><h1>Display '.ucfirst($dis['name']).'</h1>';
|
||||
echo '<iframe
|
||||
id="inlineFrame-'.$dis['name'].'"
|
||||
title="Display '.$dis['name'].'"
|
||||
src="/displays/display.php/?geraet='.$dis['name'].'">
|
||||
</iframe></div>';
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
|
||||
jQuery(document).ready(function($) {
|
||||
|
||||
const changeTypes = document.querySelectorAll('.change-type');
|
||||
changeTypes.forEach(btn => {
|
||||
btn.addEventListener("click", function() {
|
||||
const $input = $(this);
|
||||
const typeValue = $input.val();
|
||||
sendType(typeValue, $input, '');
|
||||
});
|
||||
});
|
||||
|
||||
$('.change-type-form').on('submit', function(event) {
|
||||
event.preventDefault();
|
||||
const $form = $(this);
|
||||
const typeValue = $form.data('type');
|
||||
const ctext = $form.find('input').val() || '';
|
||||
sendType(typeValue, $form.find('input'), ctext);
|
||||
});
|
||||
|
||||
function sendType(typeValue, $element, ctext) {
|
||||
const url = `/intern/scripts/displaycontrol/ajax-update_display_config_json.php?type=${encodeURIComponent(typeValue)}&ctext=${encodeURIComponent(ctext)}`;
|
||||
|
||||
fetch(url)
|
||||
.then(res => res.json())
|
||||
.then(response => {
|
||||
|
||||
if (response.success) {
|
||||
ws.send(JSON.stringify({
|
||||
type: "DISPLAY_CONTROL",
|
||||
payload: {
|
||||
type: typeValue,
|
||||
ctext: ctext
|
||||
}
|
||||
}));
|
||||
displayMsg(1, "Erfolgreich aktualisiert");
|
||||
} else {
|
||||
alert('Error: ' + response.message);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
$element.css('background-color', '#f8d7da');
|
||||
console.error('AJAX fetch error:', err);
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
<?php $mysqli->close();
|
||||
endif;
|
||||
696
www/intern/wk-leitung/einstellungen.php
Normal file
696
www/intern/wk-leitung/einstellungen.php
Normal file
@@ -0,0 +1,696 @@
|
||||
<?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);
|
||||
|
||||
if (!isset($baseDir)) {
|
||||
$baseDir = $_SERVER['DOCUMENT_ROOT'];
|
||||
}
|
||||
|
||||
if (session_status() !== PHP_SESSION_ACTIVE) {
|
||||
session_start();
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<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">
|
||||
<title>WK-leitung - Einstellungen</title>
|
||||
|
||||
|
||||
<link rel="stylesheet" href="/intern/css/einstellungen.css">
|
||||
<link rel="stylesheet" href="/intern/css/sidebar.css">
|
||||
<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">
|
||||
<script src="/intern/js/custom-select.js"></script>
|
||||
<link rel="stylesheet" href="/intern/css/custom-select.css">
|
||||
<link rel="stylesheet" href="/files/fonts/fonts.css">
|
||||
</head>
|
||||
<body>
|
||||
<?php
|
||||
$access_granted_wkl = $_SESSION['access_granted_wk_leitung'] ?? false;
|
||||
|
||||
if ( ! $access_granted_wkl ) :
|
||||
|
||||
$logintype = 'wk_leitung';
|
||||
|
||||
require $baseDir . '/../scripts/login/login.php';
|
||||
|
||||
$logintype = '';
|
||||
|
||||
else :
|
||||
|
||||
|
||||
require $baseDir . '/../scripts/db/db-functions.php';
|
||||
require $baseDir . '/../scripts/db/db-tables.php';
|
||||
require $baseDir . '/../scripts/csrf_functions.php';
|
||||
require $baseDir . '/../scripts/websocket/ws-create-token.php';
|
||||
|
||||
|
||||
$type = 'wkl';
|
||||
|
||||
$dbconnection = require $baseDir . '/../scripts/db/db-verbindung-script.php';
|
||||
|
||||
if ($dbconnection['success'] !== true){
|
||||
echo 'Critical DB Error.';
|
||||
exit;
|
||||
}
|
||||
|
||||
$currentPage = 'einstellungen';
|
||||
|
||||
require $baseDir . '/intern/scripts/sidebar/sidebar.php';
|
||||
|
||||
$wkName = db_get_var($mysqli, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['wkName']);
|
||||
|
||||
$displayColor = db_get_var($mysqli, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['displayColourLogo']);
|
||||
$textColor = db_get_var($mysqli, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['displayTextColourLogo']);
|
||||
$displayColorScoringBg = db_get_var($mysqli, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['displayColorScoringBg']);
|
||||
$displayColorScoringBgSoft = db_get_var($mysqli, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['displayColorScoringBgSoft']);
|
||||
$displayColorScoringPanel = db_get_var($mysqli, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['displayColorScoringPanel']);
|
||||
$displayColorScoringPanelSoft = db_get_var($mysqli, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['displayColorScoringPanelSoft']);
|
||||
|
||||
$displayColorScoringPanelText = db_get_var($mysqli, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['displayColorScoringPanelText']);
|
||||
$displayColorScoringPanelTextSoft = db_get_var($mysqli, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['displayColorScoringPanelTextSoft']);
|
||||
|
||||
$displayColorScoringPanelTextNoteL = db_get_var($mysqli, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['displayColorScoringPanelTextNoteL']);
|
||||
$displayColorScoringPanelTextNoteR = db_get_var($mysqli, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['displayColorScoringPanelTextNoteR']);
|
||||
|
||||
$displayIdNoteL = db_get_var($mysqli, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['displayIdNoteL']);
|
||||
$displayIdNoteR = db_get_var($mysqli, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['displayIdNoteR']);
|
||||
$rechnungenName = db_get_var($mysqli, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['rechnungenName']);
|
||||
$rechnungenVorname = db_get_var($mysqli, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['rechnungenVorname']);
|
||||
$rechnungenStrasse = db_get_var($mysqli, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['rechnungenStrasse']);
|
||||
$rechnungenHausnummer = db_get_var($mysqli, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['rechnungenHausnummer']);
|
||||
$rechnungenPostleitzahl = db_get_var($mysqli, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['rechnungenPostleitzahl']);
|
||||
$rechnungenOrt = db_get_var($mysqli, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['rechnungenOrt']);
|
||||
$rechnungenIBAN = db_get_var($mysqli, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['rechnungenIBAN']);
|
||||
$linkWebseite = db_get_var($mysqli, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['linkWebseite']);
|
||||
|
||||
|
||||
$maxLengthMusic = db_get_var($mysqli, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['maxLengthMusic']);
|
||||
|
||||
$rangNote = db_get_var($mysqli, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['rangNote']);
|
||||
$orderBestRang = db_get_var($mysqli, "SELECT `value` FROM $tableVar WHERE `name` = ?", ['orderBestRang']);
|
||||
|
||||
$geraete = db_select($mysqli, $tableGeraete, '*', '', [], 'start_index ASC');
|
||||
$noten = db_select($mysqli, $tableNotenBezeichnungen, '*', '', [], '`id` ASC');
|
||||
|
||||
setlocale(LC_TIME, 'de_DE.UTF-8');
|
||||
|
||||
?>
|
||||
<section class="bgSection">
|
||||
<?php sidebarRender('modal'); ?>
|
||||
<div class="headerDivTrainer">
|
||||
<div class="headingPanelDiv">
|
||||
<h2 class="headingPanel">Einstellungen</h2>
|
||||
</div>
|
||||
<div class="menuWrapper">
|
||||
<?php sidebarRender('button'); ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="containerDiv">
|
||||
<h3 class="containerHeading">Wettkampf:</h3>
|
||||
<div class="settingsGrid">
|
||||
<div class="settingsRow"><span>Wettkampfname: </span><input type="text" class="ajaxInput" data-key="wkName" placeholder="Test WKVS" value="<?= $wkName ?>"></div>
|
||||
<div class="settingsRow"><span>Anzahl Tage: </span><input type="number" id="anzahlTageWK" placeholder="2"></div>
|
||||
<div id="divTageInputs" style="display: contents;"></div>
|
||||
<div class="settingsRow"><span>Hintergrundfarbe Displays (bei Logo): </span><input type="color" class="ajaxInput" data-key="displayColourLogo" value="<?= $displayColor ?>"></div>
|
||||
<div class="settingsRow"><span>Textfarbe Displays (bei Logo): </span><input type="color" class="ajaxInput" data-key="displayTextColourLogo" value="<?= $textColor ?>"></div>
|
||||
<div class="settingsRow"><span>Hintergrundfarbe Displays primär (bei Notenanzeige): </span><input type="color" class="ajaxInput" data-key="displayColorScoringBg" value="<?= $displayColorScoringBg ?>"></div>
|
||||
<div class="settingsRow"><span>Hintergrundfarbe Displays sekundär (bei Notenanzeige): </span><input type="color" class="ajaxInput" data-key="displayColorScoringBgSoft" value="<?= $displayColorScoringBgSoft ?>"></div>
|
||||
<div class="settingsRow"><span>Hintergrundfarbe Kacheln Displays primär (bei Notenanzeige): </span><input type="color" class="ajaxInput" data-key="displayColorScoringPanel" value="<?= $displayColorScoringPanel ?>"></div>
|
||||
<div class="settingsRow"><span>Hintergrundfarbe Kacheln Displays sekundär (bei Notenanzeige): </span><input type="color" class="ajaxInput" data-key="displayColorScoringPanelSoft" value="<?= $displayColorScoringPanelSoft ?>"></div>
|
||||
<div class="settingsRow"><span>Textfarbe Displays (bei Notenanzeige): </span><input type="color" class="ajaxInput" data-key="displayColorScoringPanelText" value="<?= $displayColorScoringPanelText ?>"></div>
|
||||
<div class="settingsRow"><span>Sekundäre Textfarbe Displays (bei Notenanzeige): </span><input type="color" class="ajaxInput" data-key="displayColorScoringPanelTextSoft" value="<?= $displayColorScoringPanelTextSoft ?>"></div>
|
||||
<div class="settingsRow"><span>Textfarbe Note Links Displays (bei Notenanzeige): </span><input type="color" class="ajaxInput" data-key="displayColorScoringPanelTextNoteL" value="<?= $displayColorScoringPanelTextNoteL ?>"></div>
|
||||
<div class="settingsRow"><span>Textfarbe Note Rechts Displays (bei Notenanzeige): </span><input type="color" class="ajaxInput" data-key="displayColorScoringPanelTextNoteR" value="<?= $displayColorScoringPanelTextNoteR ?>"></div>
|
||||
<div class="settingsRow"><span>Id Note links auf Display: </span><input type="number" class="ajaxInput" data-key="displayIdNoteL" placeholder="10" value="<?= $displayIdNoteL ?>"></div>
|
||||
<div class="settingsRow"><span>Id Note rechts auf Display: </span><input type="number" class="ajaxInput" data-key="displayIdNoteR" placeholder="10" value="<?= $displayIdNoteR ?>"></div>
|
||||
<div class="settingsRow"><span>Link Webseite: </span><input type="text" class="ajaxInput" data-key="linkWebseite" placeholder="www.wkvs.ch" value="<?= $linkWebseite ?>"></div>
|
||||
<div class="settingsRow"><span>Maximale Länge Musik (in Sekunden) (0 = keine Beschränkung): </span><input type="number" class="ajaxInput" data-key="maxLengthMusic" placeholder="0" value="<?= $maxLengthMusic ?>"></div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="containerHeading">Rechnungen:</h3>
|
||||
<div class="settingsGrid">
|
||||
<div class="settingsRow"><span>Name: </span><input type="text" class="ajaxInput" data-key="rechnungenName" placeholder="Mustermann" value="<?= $rechnungenName ?>"></div>
|
||||
<div class="settingsRow"><span>Vorname: </span><input type="text" class="ajaxInput" data-key="rechnungenVorname" placeholder="Max" value="<?= $rechnungenVorname ?>"></div>
|
||||
<div class="settingsRow"><span>Strasse: </span><input type="text" class="ajaxInput" data-key="rechnungenStrasse" placeholder="Musterstrasse" value="<?= $rechnungenStrasse ?>"></div>
|
||||
<div class="settingsRow"><span>Hausnummer: </span><input type="text" class="ajaxInput" data-key="rechnungenHausnummer" placeholder="123" value="<?= $rechnungenHausnummer ?>"></div>
|
||||
<div class="settingsRow"><span>Postleitzahl: </span><input type="text" class="ajaxInput" data-key="rechnungenPostleitzahl" placeholder="4000" value="<?= $rechnungenPostleitzahl ?>"></div>
|
||||
<div class="settingsRow"><span>Ort: </span><input type="text" class="ajaxInput" data-key="rechnungenOrt" placeholder="Basel" value="<?= $rechnungenOrt ?>"></div>
|
||||
<div class="settingsRow"><span>IBAN: </span><input type="text" class="ajaxInput" data-key="rechnungenIBAN" placeholder="CH44 3199 9123 0008 8901 2" value="<?= $rechnungenIBAN ?>"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="containerDiv">
|
||||
<h3 class="containerHeading">Geräte management:</h3>
|
||||
<table class="wkvsTabelle trainer" id="geraeteTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Name</th>
|
||||
<th>Start Index</th>
|
||||
<th>Farbe Kampfrichter</th>
|
||||
<th>Löschen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($geraete as $g): ?>
|
||||
<tr data-id="<?= $g['id'] ?>">
|
||||
<td><?= $g['id'] ?></td>
|
||||
<td><input type="text" class="ajaxGeraete" data-field="name" value="<?= htmlspecialchars($g['name'] ?? '') ?>" style="width: 100%; padding: 6px; border: 1px dashed #777; border-radius: 4px;"></td>
|
||||
<td><input type="number" class="ajaxGeraete" data-field="start_index" value="<?= $g['start_index'] ?>" style="width: 80px; padding: 6px; border: 1px dashed #777; border-radius: 4px;"></td>
|
||||
<td><input type="color" class="ajaxGeraete" data-field="color_kampfrichter" value="<?= $g['color_kampfrichter'] ?>" style="width: 50px; height: 35px; padding: 2px; border: 1px dashed #777; border-radius: 4px;"></td>
|
||||
<td>
|
||||
<button class="deleteGeraete" data-id="<?= $g['id'] ?>" style="border:none; background:none; cursor:pointer;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#b91c1c" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<tr class="addRow">
|
||||
<td></td>
|
||||
<td><input type="text" id="newGeraeteName" placeholder="Neues Gerät..." style="width: 100%; padding: 6px; border: 1px solid #777; border-radius: 4px;"></td>
|
||||
<td><input type="number" id="newGeraeteIndex" value="0" style="width: 80px; padding: 6px; border: 1px solid #777; border-radius: 4px;"></td>
|
||||
<td><input type="color" id="newGeraeteColor" value="#424242" style="width: 50px; height: 35px; padding: 2px; border: 1px solid #777; border-radius: 4px;"></td>
|
||||
<td><button id="addGeraeteBtn" style="padding: 6px 12px; background: var(--bg-top); color: #fff; border: none; border-radius: 4px; cursor: pointer;">Hinzufügen</button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="containerDiv">
|
||||
<h3 class="containerHeading">Noten Bezeichnungen:</h3>
|
||||
<div class="tableWraperOverflowY">
|
||||
<table class="wkvsTabelle trainer" id="notenTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Name</th>
|
||||
<th>Typ</th>
|
||||
<th>Standardwert</th>
|
||||
<th>Berechnung</th>
|
||||
<th>Min Wert</th>
|
||||
<th>Max Wert</th>
|
||||
<th>Pro Gerät</th>
|
||||
<th>Gerät</th>
|
||||
<th>Läufe (JSON)</th>
|
||||
<th>In Tabelle</th>
|
||||
<th>In Tabelle (mobile)</th>
|
||||
<th>In Tabelle (admin)</th>
|
||||
<th>Auf Rangliste</th>
|
||||
<th>Nullstellen</th>
|
||||
<th>Prefix Display</th>
|
||||
<th>Löschen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($noten as $n): ?>
|
||||
<tr data-id="<?= $n['id'] ?>">
|
||||
<td><?= htmlspecialchars($n['id'] ?? '') ?></td>
|
||||
<td><input type="text" class="ajaxNoten" data-field="name" value="<?= htmlspecialchars($n['name'] ?? '') ?>" style="width: 100%; padding: 6px; border: 1px dashed #777; border-radius: 4px;"></td>
|
||||
<td>
|
||||
<select class="ajaxNoten" data-field="type" style="padding: 6px; border: 1px dashed #777; border-radius: 4px;">
|
||||
<option value="input" <?= $n['type'] === 'input' ? 'selected' : '' ?>>Input</option>
|
||||
<option value="berechnung" <?= $n['type'] === 'berechnung' ? 'selected' : '' ?>>Berechnung</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><input type="number" class="ajaxNoten" data-field="default_value" value="<?= htmlspecialchars($n['default_value'] ?? '') ?>" placeholder="<?= htmlspecialchars(($n['default_value'] === null) ? 'Kein Standartwert' : '') ?>" style="width: 100%; padding: 6px; border: 1px dashed #777; border-radius: 4px;"></td>
|
||||
<td><input type="text" class="ajaxNoten" data-field="berechnung" value="<?= htmlspecialchars($n['berechnung'] ?? '') ?>" style="min-width: 300px; padding: 6px; border: 1px dashed #777; border-radius: 4px;"></td>
|
||||
<td><input type="number" class="ajaxNoten" data-field="min_value" value="<?= htmlspecialchars($n['min_value'] ?? '') ?>" placeholder="<?= htmlspecialchars(($n['min_value'] === null) ? 'Kein Minimalwert' : '') ?>" style="width: 100%; padding: 6px; border: 1px dashed #777; border-radius: 4px;"></td>
|
||||
<td><input type="number" class="ajaxNoten" data-field="max_value" value="<?= htmlspecialchars($n['max_value'] ?? '') ?>" placeholder="<?= htmlspecialchars(($n['max_value'] === null) ? 'Kein Maximalwert' : '') ?>" style="width: 100%; padding: 6px; border: 1px dashed #777; border-radius: 4px;"></td>
|
||||
<td>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" class="ajaxNoten" data-field="pro_geraet" <?= $n['pro_geraet'] ? 'checked' : '' ?>>
|
||||
<span class="checkbox-ui"></span>
|
||||
</label>
|
||||
</td>
|
||||
<td>
|
||||
<?php
|
||||
// 1. JSON sicher decodieren.
|
||||
// json_decode gibt null zurück, wenn null übergeben wird, daher der erste Teil.
|
||||
$arrayGerateJson = !empty($n['geraete_json']) ? json_decode($n['geraete_json'], true) : [];
|
||||
|
||||
// Sicherheit: Stelle sicher, dass es ein Array ist
|
||||
if (!is_array($arrayGerateJson)) {
|
||||
$arrayGerateJson = [];
|
||||
}
|
||||
|
||||
$arrayGerateJsonNamen = [];
|
||||
|
||||
$geraeteIdName = array_column($geraete, 'name', 'id');
|
||||
$geraeteIdName[0] = 'Keine Gerätezuweisung';
|
||||
|
||||
foreach ($arrayGerateJson as $id) {
|
||||
$arrayGerateJsonNamen[$id] = $geraeteIdName[$id] ?? '';
|
||||
}
|
||||
?>
|
||||
<div class="customSelect" id="selectedOption" data-value="[<?= implode(",", $arrayGerateJsonNamen)?>]">
|
||||
<button type="button" id="selectTriggergeraete_json" class="selectTriggerBulk" aria-expanded="false">
|
||||
<span class="selectLabel placeholder"><?= ($arrayGerateJsonNamen !== []) ? implode(", ", $arrayGerateJsonNamen) : 'Bitte auswählen…' ?></span>
|
||||
<svg class="selectArrow" xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' height="14" width="14">
|
||||
<path d='M6 9L12 15L18 9' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/>
|
||||
</svg>
|
||||
</button>
|
||||
<ul class="selectOptionsBulk">
|
||||
<?php foreach ($geraete as $g): ?>
|
||||
<?php
|
||||
// 2. ID als String umwandeln für den sicheren Vergleich
|
||||
$currentId = (int) $g['id'];
|
||||
|
||||
// 3. Prüfen, ob die aktuelle ID im Array der zugewiesenen Geräte enthalten ist
|
||||
$isSelected = in_array($currentId, $arrayGerateJson);
|
||||
?>
|
||||
|
||||
<li data-value="<?= $g['id'] ?>" class="<?= $isSelected ? 'selected' : '' ?>">
|
||||
<?= $g['name'] ?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<?php $isSelected = in_array(0, $arrayGerateJson); ?>
|
||||
|
||||
<li data-value="0" class="<?= $isSelected ? 'selected' : '' ?>">Keine Gerätezuweisung</li>
|
||||
</ul>
|
||||
<input type="hidden" name="geraete_json" class="selectValue ajaxNoten" data-field="geraete_json" value="[<?= implode(",", $arrayGerateJson)?>]">
|
||||
</div>
|
||||
</td>
|
||||
<td><input type="text" class="ajaxNoten" data-field="anzahl_laeufe_json" value="<?= htmlspecialchars($n['anzahl_laeufe_json'] ?? '') ?>" placeholder='{"default": 1, "ID": 2}' style="min-width: 200px; padding: 6px; border: 1px dashed #777; border-radius: 4px;"></td>
|
||||
<td>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" class="ajaxNoten" data-field="zeige_in_tabelle" <?= $n['zeige_in_tabelle'] ? 'checked' : '' ?>>
|
||||
<span class="checkbox-ui"></span>
|
||||
</label>
|
||||
</td>
|
||||
<td>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" class="ajaxNoten" data-field="zeige_in_tabelle_mobile" <?= $n['zeige_in_tabelle_mobile'] ? 'checked' : '' ?>>
|
||||
<span class="checkbox-ui"></span>
|
||||
</label>
|
||||
</td>
|
||||
<td>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" class="ajaxNoten" data-field="zeige_in_tabelle_admin" <?= $n['zeige_in_tabelle_admin'] ? 'checked' : '' ?>>
|
||||
<span class="checkbox-ui"></span>
|
||||
</label>
|
||||
</td>
|
||||
<td>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" class="ajaxNoten" data-field="zeige_auf_rangliste" <?= $n['zeige_auf_rangliste'] ? 'checked' : '' ?>>
|
||||
<span class="checkbox-ui"></span>
|
||||
</label>
|
||||
</td>
|
||||
<td><input type="number" class="ajaxNoten" data-field="nullstellen" value="<?= htmlspecialchars($n['nullstellen'] ?? '') ?>" style="width: 100%; padding: 6px; border: 1px dashed #777; border-radius: 4px;"></td>
|
||||
<td><input type="text" class="ajaxNoten" data-field="prefix_display" value="<?= htmlspecialchars($n['prefix_display'] ?? '') ?>" style="width: 100%; padding: 6px; border: 1px dashed #777; border-radius: 4px;"></td>
|
||||
<td>
|
||||
<button class="deleteNoten" data-id="<?= $n['id'] ?>" style="border:none; background:none; cursor:pointer;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#b91c1c" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<tr class="addRow">
|
||||
<td><input type="number" id="newNotenId" placeholder="Neue einzigartige ID..." style="width: 100%; padding: 6px; border: 1px solid #777; border-radius: 4px;"></td>
|
||||
<td><input type="text" id="newNotenName" placeholder="Neue Note..." style="width: 100%; padding: 6px; border: 1px solid #777; border-radius: 4px;"></td>
|
||||
|
||||
<td><button id="addNotenBtn" style="padding: 6px 12px; background: var(--bg-top); color: #fff; border: none; border-radius: 4px; cursor: pointer;">Hinzufügen</button></td>
|
||||
<td colspan="13"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="settingsRow"><span>ID der Note, welche für die Berechnung der Ränge verwendet wird: </span><input type="number" class="ajaxInput" data-key="rangNote" placeholder="Bitte Eingeben…" value="<?= $rangNote ?>"></div>
|
||||
<div class="settingsRow">
|
||||
<span>Wert, welcher Rang 1 erhält: </span>
|
||||
<select class="ajaxInput" data-key="orderBestRang">
|
||||
<option disabled <?= ($orderBestRang !== "DESC" && $orderBestRang !== "ASC") ? "selected" : "" ?>>Bitte Auswählen…</option>
|
||||
<option value="DESC" <?= ($orderBestRang === "DESC") ? "selected" : "" ?>>Höchster Wert</option>
|
||||
<option value="ASC" <?= ($orderBestRang === "ASC") ? "selected" : "" ?>>Niedrigster Wert</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="containerDiv">
|
||||
<h3 class="containerHeading">Bilder:</h3>
|
||||
<div class="containerImages">
|
||||
<div><span>Kampfrichter: </span><label for="imgKampfrichter">Bild auswählen…</label><input type="file" data-key="Kampfrichter" accept="image/png, image/jpeg" id="imgKampfrichter"><img src="/intern/img/login/bgKampfrichter.webp"></div>
|
||||
<div><span>Trainer: </span><label for="imgTrainer">Bild auswählen…</label><input type="file" data-key="Trainer" accept="image/png, image/jpeg" id="imgTrainer"><img src="/intern/img/login/bgTrainer.webp"></div>
|
||||
<div><span>WK-Leitung: </span><label for="imgWk_leitung">Bild auswählen…</label><input type="file" data-key="Wk_leitung" accept="image/png, image/jpeg" id="imgWk_leitung"><img src="/intern/img/login/bgWk_leitung.webp"></div>
|
||||
<div><span>Einmal Login: </span><label for="imgOtl">Bild auswählen…</label><input type="file" data-key="Otl" accept="image/png, image/jpeg" id="imgOtl"><img src="/intern/img/login/bgOtl.webp"></div>
|
||||
<div><span>Icon Webseite: </span><label for="imgIcon">Bild auswählen…</label><input type="file" data-key="icon" accept="image/png, image/jpeg" id="imgIcon"><img src="/intern/img/icon.png"></div>
|
||||
<div><span>Logo: </span><label for="imgLogo_normal">Bild auswählen…</label><input type="file" data-key="logo-normal" accept="image/png, image/jpeg" id="imgLogo_normal"><img src="/intern/img/logo-normal.png"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="msgDiv"></div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
|
||||
let ws;
|
||||
|
||||
const $inputAnzahlTage = $('#anzahlTageWK');
|
||||
const $divTageInputs = $('#divTageInputs');
|
||||
|
||||
function displayNDayFields() {
|
||||
console.log('ja');
|
||||
const anzTageRoh = parseInt($inputAnzahlTage.val(), 10);
|
||||
const anzTage = anzTageRoh + 1;
|
||||
const $children = $divTageInputs.children();
|
||||
const anzChildren = $children.length + 1;
|
||||
|
||||
if (anzChildren < anzTage) {
|
||||
for(let i = anzChildren; i < anzTage; i++) {
|
||||
$divTageInputs.append($('<div class="settingsRow"><span for="' + i + '-wk-date">' + i + '. Wettkampftag: </span><input type="date" id="' + i + '-wk-date"></div>'));
|
||||
}
|
||||
} else if (anzChildren > anzTage) {
|
||||
for(let i = anzChildren; i > anzTage; i--) {
|
||||
$divTageInputs.find('div:last-child').remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
displayNDayFields();
|
||||
|
||||
$inputAnzahlTage.on('change', displayNDayFields);
|
||||
|
||||
const arrayWSSend = [
|
||||
'wkName',
|
||||
'displayColourLogo',
|
||||
'displayTextColourLogo',
|
||||
'displayColorScoringBg',
|
||||
'displayColorScoringBgSoft',
|
||||
'displayColorScoringPanel',
|
||||
'displayColorScoringPanelSoft',
|
||||
'displayColorScoringPanelText',
|
||||
'displayColorScoringPanelTextSoft',
|
||||
'displayColorScoringPanelTextNoteL',
|
||||
'displayColorScoringPanelTextNoteR'
|
||||
];
|
||||
|
||||
$('input[type="file"][data-key]').on('change', function() {
|
||||
const $input = $(this);
|
||||
const file = this.files[0];
|
||||
const type = $input.data('key');
|
||||
|
||||
if (!file) return;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('image', file);
|
||||
formData.append('type', type);
|
||||
|
||||
$('.msgDiv').html('<p style="color: #007bff; font-weight: 500;">Wird hochgeladen...</p>');
|
||||
$input.closest('div').css('opacity', '0.5');
|
||||
|
||||
$.ajax({
|
||||
url: '/intern/scripts/einstellungen/ajax-upload-image.php',
|
||||
type: 'POST',
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
success: function(response) {
|
||||
$input.closest('div').css('opacity', '1');
|
||||
if (response.success) {
|
||||
$('.msgDiv').html('<p style="color: #28a745; font-weight: 500;">Bild "' + type + '" erfolgreich aktualisiert!</p>');
|
||||
setTimeout(() => { $('.msgDiv').fadeOut(500, function() { $(this).empty().show(); }); }, 3000);
|
||||
const timestamp = new Date().getTime();
|
||||
|
||||
if (type === 'icon') {
|
||||
$input.parent().find('img').attr('src', '/intern/img/icon.png?t=' + timestamp);
|
||||
} else if (type === 'logo-normal') {
|
||||
$input.parent().find('img').attr('src', '/intern/img/logo-normal.png?t=' + timestamp);
|
||||
ws.send(JSON.stringify({type: 'EINSTELLUNGEN_DISPLAY_UPDATE', payload: {key: type, value: null}}));
|
||||
} else {
|
||||
$input.parent().find('img').attr('src', '/intern/img/login/bg' + type + '.webp?t=' + timestamp);
|
||||
}
|
||||
} else {
|
||||
$('.msgDiv').html('<p style="color: #dc3545; font-weight: 500;">Fehler: ' + response.message + '</p>');
|
||||
}
|
||||
},
|
||||
error: function(xhr) {
|
||||
$input.closest('div').css('opacity', '1');
|
||||
$('.msgDiv').html('<p style="color: #dc3545; font-weight: 500;">Kritischer Fehler beim Hochladen.</p>');
|
||||
console.error(xhr);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('.ajaxInput').on('change', function() {
|
||||
const $input = $(this);
|
||||
const val = $input.val();
|
||||
const type = $input.data('key');
|
||||
|
||||
if (!val) return;
|
||||
|
||||
$.ajax({
|
||||
url: '/intern/scripts/einstellungen/ajax-change-value.php',
|
||||
type: 'POST',
|
||||
data: {
|
||||
type: type,
|
||||
value: val
|
||||
},
|
||||
success: function(response) {
|
||||
|
||||
if (response.success) {
|
||||
if (arrayWSSend.includes(type)) {
|
||||
ws.send(JSON.stringify({type: 'EINSTELLUNGEN_DISPLAY_UPDATE', payload: {key: type, value: val}}));
|
||||
}
|
||||
} else {
|
||||
|
||||
}
|
||||
},
|
||||
error: function(xhr) {
|
||||
$input.closest('div').css('opacity', '1');
|
||||
$('.msgDiv').html('<p style="color: #dc3545; font-weight: 500;">Kritischer Fehler beim Hochladen.</p>');
|
||||
console.error(xhr);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Gerate management
|
||||
$(document).on('change', '.ajaxGeraete', function() {
|
||||
const $input = $(this);
|
||||
const id = $input.closest('tr').data('id');
|
||||
const field = $input.data('field');
|
||||
const value = $input.val();
|
||||
|
||||
$.ajax({
|
||||
url: '/intern/scripts/einstellungen/ajax-gereate-management.php',
|
||||
type: 'POST',
|
||||
data: { action: 'update', id: id, field: field, value: value },
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
displayMsg(1, "Gerät aktualisiert");
|
||||
} else {
|
||||
displayMsg(0, "Fehler: " + response.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '#addGeraeteBtn', function() {
|
||||
const name = $('#newGeraeteName').val();
|
||||
const index = $('#newGeraeteIndex').val();
|
||||
const color = $('#newGeraeteColor').val();
|
||||
|
||||
if (!name) {
|
||||
displayMsg(0, "Name erforderlich");
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: '/intern/scripts/einstellungen/ajax-gereate-management.php',
|
||||
type: 'POST',
|
||||
data: { action: 'add', name: name, start_index: index, color: color },
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
displayMsg(0, "Fehler: " + response.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '.deleteGeraete', function() {
|
||||
if (!confirm("Gerät wirklich löschen?")) return;
|
||||
const id = $(this).data('id');
|
||||
const $tr = $(this).closest('tr');
|
||||
|
||||
$.ajax({
|
||||
url: '/intern/scripts/einstellungen/ajax-gereate-management.php',
|
||||
type: 'POST',
|
||||
data: { action: 'delete', id: id },
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
$tr.remove();
|
||||
displayMsg(1, "Gerät gelöscht");
|
||||
} else {
|
||||
displayMsg(0, "Fehler: " + response.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Noten management
|
||||
$(document).on('change', '.ajaxNoten', function() {
|
||||
const $input = $(this);
|
||||
const id = $input.closest('tr').data('id');
|
||||
const field = $input.data('field');
|
||||
let value = $input.val();
|
||||
|
||||
if ($input.attr('type') === 'checkbox') {
|
||||
value = $input.is(':checked') ? 1 : 0;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: '/intern/scripts/einstellungen/ajax-noten-management.php',
|
||||
type: 'POST',
|
||||
data: { action: 'update', id: id, field: field, value: value },
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
displayMsg(1, "Note aktualisiert");
|
||||
} else {
|
||||
displayMsg(0, "Fehler: " + response.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '#addNotenBtn', function() {
|
||||
const id = $('#newNotenId').val();
|
||||
const name = $('#newNotenName').val();
|
||||
const type_val = $('#newNotenType').val();
|
||||
const berechnung = $('#newNotenBerechnung').val();
|
||||
const pro_geraet = $('#newNotenProGeraet').is(':checked') ? 1 : 0;
|
||||
|
||||
if (!name) {
|
||||
displayMsg(0, "Name erforderlich");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!id) {
|
||||
displayMsg(0, "Id erforderlich");
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: '/intern/scripts/einstellungen/ajax-noten-management.php',
|
||||
type: 'POST',
|
||||
data: { action: 'add', id: id, name: name, type_val: type_val, berechnung: berechnung, pro_geraet: pro_geraet },
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
displayMsg(0, "Fehler: " + response.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '.deleteNoten', function() {
|
||||
if (!confirm("Note wirklich löschen?")) return;
|
||||
const id = $(this).data('id');
|
||||
const $tr = $(this).closest('tr');
|
||||
|
||||
$.ajax({
|
||||
url: '/intern/scripts/einstellungen/ajax-noten-management.php',
|
||||
type: 'POST',
|
||||
data: { action: 'delete', id: id },
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
$tr.remove();
|
||||
displayMsg(1, "Note gelöscht");
|
||||
} else {
|
||||
displayMsg(0, "Fehler: " + response.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
let firstConnect = true;
|
||||
const RETRY_DELAY = 2000;
|
||||
|
||||
const urlAjaxNewWSToken = '/intern/scripts/ajax-create-ws-token.php';
|
||||
|
||||
async function fetchNewWSToken(freigabe) {
|
||||
try {
|
||||
const response = await fetch(urlAjaxNewWSToken, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: new URLSearchParams({ access: freigabe })
|
||||
});
|
||||
|
||||
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 = '<?= generateWSToken('einstellungen') ?>';
|
||||
} else {
|
||||
token = await fetchNewWSToken('einstellungen');
|
||||
}
|
||||
|
||||
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();
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
22
www/intern/wk-leitung/index.php
Normal file
22
www/intern/wk-leitung/index.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
if (session_status() !== PHP_SESSION_ACTIVE) session_start();
|
||||
|
||||
$baseDir = $_SERVER['DOCUMENT_ROOT'];
|
||||
|
||||
$access_granted_wkl = $_SESSION['access_granted_wk_leitung'] ?? false;
|
||||
|
||||
if ( ! $access_granted_wkl ) :
|
||||
|
||||
$logintype = 'wk_leitung';
|
||||
|
||||
require $baseDir . '/../scripts/login/login.php';
|
||||
|
||||
$logintype = '';
|
||||
|
||||
else :
|
||||
|
||||
header("Location: /intern/wk-leitung/rechnungen");
|
||||
exit;
|
||||
|
||||
endif;
|
||||
529
www/intern/wk-leitung/kalender.php
Normal file
529
www/intern/wk-leitung/kalender.php
Normal file
@@ -0,0 +1,529 @@
|
||||
<?php
|
||||
$days = 2;
|
||||
$firstDay = "2026-04-18";
|
||||
|
||||
$dates = [];
|
||||
|
||||
$startDate = new DateTime($firstDay);
|
||||
|
||||
for ($i = 0; $i < $days; $i++) {
|
||||
$current = clone $startDate;
|
||||
$current->modify("+{$i} day");
|
||||
$dates[] = $current->format("Y-m-d");
|
||||
}
|
||||
|
||||
setlocale(LC_TIME, 'de_DE.UTF-8'); // set German locale
|
||||
|
||||
?>
|
||||
|
||||
<div class="calendar">
|
||||
|
||||
<!-- empty top-left corner -->
|
||||
<div class="corner fBorderRight fBorderBottom"></div>
|
||||
|
||||
<!-- headers -->
|
||||
|
||||
<?php foreach ($dates as $ind => $d): ?>
|
||||
<div class="day-header fBorderBottom"><h3><?= strftime("%a", strtotime($d)) ?> Halle</h3></div>
|
||||
<div class="day-header fBorderBottom"><h3><?= strftime("%a", strtotime($d)) ?> Aula</h3></div>
|
||||
<?php $notLast = ($ind !== count($dates) - 1) ? 'fBorderRight' : '';?>
|
||||
<div class="day-header <?= $notLast ?> fBorderBottom"><h3><?= strftime("%a", strtotime($d)) ?> Sonstige</h3></div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<!-- time column -->
|
||||
<div class="time-column fBorderRight">
|
||||
<?php for ($i = 0; $i <= 23; $i++) {
|
||||
$si = str_pad($i, 2, '0', STR_PAD_LEFT);
|
||||
echo '<div class="time">'.$si.':00</div>';
|
||||
} ?>
|
||||
</div>
|
||||
|
||||
<!-- day columns -->
|
||||
|
||||
<?php foreach ($dates as $ind => $d): ?>
|
||||
<div class="day-column" data-day="<?= $d ?>" data-type="halle"></div>
|
||||
<div class="day-column" data-day="<?= $d ?>" data-type="aula"></div>
|
||||
<?php $notLast = ($ind !== count($dates) - 1) ? 'fBorderRight' : '';?>
|
||||
<div class="day-column <?= $notLast ?>" data-day="<?= $d ?>" data-type="sonstige"></div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
</div>
|
||||
|
||||
<button id="exportCal">Export Calendar</button>
|
||||
|
||||
<style>
|
||||
.calendar {
|
||||
display: grid;
|
||||
grid-template-columns: 80px <?php foreach ($dates as $d) { echo '1fr 1fr 1fr ';}?>;
|
||||
grid-template-rows: 40px auto;
|
||||
border: 1px solid #ccc;
|
||||
font-family: Arial, sans-serif;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.corner {
|
||||
border-right: 1px solid #ccc;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.day-header {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
border-bottom: 1px solid #ccc;
|
||||
line-height: 40px;
|
||||
}
|
||||
|
||||
.day-header h3 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.time-column {
|
||||
border-right: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.time {
|
||||
height: 90px;
|
||||
padding: 5px;
|
||||
font-size: 12px;
|
||||
box-sizing: border-box;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.day-column {
|
||||
position: relative;
|
||||
border-right: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.event {
|
||||
position: absolute;
|
||||
border-radius: 4px;
|
||||
padding: 4px;
|
||||
font-size: 12px;
|
||||
cursor: grab;
|
||||
transition: left 0.2s, width 0.2s;
|
||||
}
|
||||
|
||||
.event.dragging {
|
||||
opacity: 0.8;
|
||||
z-index: 1000;
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.event div span {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
.fBorderRight { border-right: 3px solid #000; }
|
||||
.fBorderBottom { border-bottom: 3px solid #000; }
|
||||
|
||||
.day-column.highlight {
|
||||
background-color: rgba(255, 255, 0, 0.1);
|
||||
}
|
||||
|
||||
.day-column.highlight-active {
|
||||
background-color: rgba(255, 255, 0, 0.25);
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const events = [
|
||||
|
||||
{ day: "2026-04-18", start: 12.0, end: 13.0, title: "Kampfrichteressen", type: "sonstige", abt: "0", elem: "" },
|
||||
// Day 1 - 2026-04-18
|
||||
{ day: "2026-04-18", start: 8.0, end: 8.50, title: "Körperliches Aufwärmen", type: "aula", abt: "1", elem: "15" },
|
||||
{ day: "2026-04-18", start: 8.50, end: 9.0, title: "Geführtes Einturnen", type: "halle", abt: "1", elem: "15" },
|
||||
{ day: "2026-04-18", start: 9.0, end: 10.0, title: "Wettkampf", type: "halle", abt: "1", elem: "15" },
|
||||
{ day: "2026-04-18", start: 10.0, end: 10.50, title: "Rangverkündigung", type: "aula", abt: "1", elem: "15" },
|
||||
|
||||
{ day: "2026-04-18", start: 10.50, end: 11.0, title: "Körperliches Aufwärmen", type: "aula", abt: "2", elem: "13" },
|
||||
{ day: "2026-04-18", start: 11.0, end: 11.50, title: "Geführtes Einturnen", type: "halle", abt: "2", elem: "13" },
|
||||
{ day: "2026-04-18", start: 11.50, end: 12.50, title: "Wettkampf", type: "halle", abt: "2", elem: "13" },
|
||||
{ day: "2026-04-18", start: 12.50, end: 13.0, title: "Rangverkündigung", type: "aula", abt: "2", elem: "13" },
|
||||
|
||||
{ day: "2026-04-18", start: 13.0, end: 13.50, title: "Körperliches Aufwärmen", type: "aula", abt: "3", elem: "10" },
|
||||
{ day: "2026-04-18", start: 13.50, end: 14.0, title: "Geführtes Einturnen", type: "halle", abt: "3", elem: "10" },
|
||||
{ day: "2026-04-18", start: 14.0, end: 15.0, title: "Wettkampf", type: "halle", abt: "3", elem: "10" },
|
||||
{ day: "2026-04-18", start: 15.0, end: 15.50, title: "Rangverkündigung", type: "aula", abt: "3", elem: "10" },
|
||||
|
||||
{ day: "2026-04-18", start: 15.50, end: 16.0, title: "Körperliches Aufwärmen", type: "aula", abt: "4", elem: "12" },
|
||||
{ day: "2026-04-18", start: 16.0, end: 16.50, title: "Geführtes Einturnen", type: "halle", abt: "4", elem: "12" },
|
||||
{ day: "2026-04-18", start: 16.50, end: 17.50, title: "Wettkampf", type: "halle", abt: "4", elem: "12" },
|
||||
{ day: "2026-04-18", start: 17.30, end: 18.0, title: "Rangverkündigung", type: "aula", abt: "4", elem: "12" },
|
||||
|
||||
{ day: "2026-04-18", start: 18.0, end: 18.50, title: "Körperliches Aufwärmen", type: "aula", abt: "5", elem: "11" },
|
||||
{ day: "2026-04-18", start: 18.50, end: 19.0, title: "Geführtes Einturnen", type: "halle", abt: "5", elem: "11" },
|
||||
{ day: "2026-04-18", start: 19.0, end: 20.0, title: "Wettkampf", type: "halle", abt: "5", elem: "11" },
|
||||
{ day: "2026-04-18", start: 20.0, end: 20.50, title: "Rangverkündigung", type: "aula", abt: "5", elem: "11" },
|
||||
|
||||
{ day: "2026-04-18", start: 20.50, end: 21.0, title: "Körperliches Aufwärmen", type: "aula", abt: "6", elem: "14" },
|
||||
{ day: "2026-04-18", start: 21.0, end: 21.50, title: "Geführtes Einturnen", type: "halle", abt: "6", elem: "14" },
|
||||
{ day: "2026-04-18", start: 21.50, end: 22.50, title: "Wettkampf", type: "halle", abt: "6", elem: "14" },
|
||||
{ day: "2026-04-18", start: 22.50, end: 23.0, title: "Rangverkündigung", type: "aula", abt: "6", elem: "14" },
|
||||
|
||||
// Day 2 - 2026-04-19 (same pattern with shifted times)
|
||||
{ day: "2026-04-19", start: 8.0, end: 8.30, title: "Körperliches Aufwärmen", type: "aula", abt: "1", elem: "15" },
|
||||
{ day: "2026-04-19", start: 8.30, end: 9.0, title: "Geführtes Einturnen", type: "halle", abt: "1", elem: "15" },
|
||||
{ day: "2026-04-19", start: 9.0, end: 10.0, title: "Wettkampf", type: "halle", abt: "1", elem: "15" },
|
||||
{ day: "2026-04-19", start: 10.0, end: 10.30, title: "Rangverkündigung", type: "aula", abt: "1", elem: "15" },
|
||||
|
||||
{ day: "2026-04-19", start: 10.30, end: 11.0, title: "Körperliches Aufwärmen", type: "aula", abt: "2", elem: "13" },
|
||||
{ day: "2026-04-19", start: 11.0, end: 11.30, title: "Geführtes Einturnen", type: "halle", abt: "2", elem: "13" },
|
||||
{ day: "2026-04-19", start: 11.30, end: 12.30, title: "Wettkampf", type: "halle", abt: "2", elem: "13" },
|
||||
{ day: "2026-04-19", start: 12.30, end: 13.0, title: "Rangverkündigung", type: "aula", abt: "2", elem: "13" },
|
||||
|
||||
// ...repeat for abt 3–6 with times staggered
|
||||
];
|
||||
|
||||
const pixelsPerHour = 90;
|
||||
|
||||
// Convert hour float to Date
|
||||
function hourFloatToDate(day, hourFloat) {
|
||||
const d = new Date(day);
|
||||
const hours = Math.floor(hourFloat);
|
||||
const minutes = Math.round((hourFloat % 1) * 60);
|
||||
d.setHours(hours, minutes, 0, 0);
|
||||
return d;
|
||||
}
|
||||
|
||||
function hoursToHHMM(hours) {
|
||||
const h = Math.floor(hours);
|
||||
const m = Math.round((hours - h) * 60);
|
||||
return `${h.toString().padStart(2,'0')}:${m.toString().padStart(2,'0')}`;
|
||||
}
|
||||
|
||||
// Convert HH:MM string to hours float
|
||||
function HHMMToHours(str) {
|
||||
const [h, m] = str.split(':').map(Number);
|
||||
return h + m/60;
|
||||
}
|
||||
|
||||
// Format Date to hh:mm
|
||||
function formatTime(date) {
|
||||
return new Intl.DateTimeFormat(undefined, { hour: '2-digit', minute: '2-digit' }).format(date);
|
||||
}
|
||||
|
||||
// Collision detection
|
||||
function eventsCollide(a, b) {
|
||||
return a.start < b.end && a.end > b.start && a.day === b.day && a.type === b.type;
|
||||
}
|
||||
|
||||
// Assign columns with proper overlap tracking
|
||||
function assignColumns(events) {
|
||||
events.sort((a,b) => a.start - b.start);
|
||||
const active = [];
|
||||
|
||||
events.forEach(event => {
|
||||
// Remove finished events
|
||||
for (let i = active.length-1; i>=0; i--) {
|
||||
if (active[i].end <= event.start) active.splice(i,1);
|
||||
}
|
||||
|
||||
// Find first free column index
|
||||
let colIndex = 0;
|
||||
while(active.some(e => e.columnIndex === colIndex)) colIndex++;
|
||||
event.columnIndex = colIndex;
|
||||
active.push(event);
|
||||
|
||||
// Update totalColumns for current overlap group
|
||||
const maxColumns = Math.max(...active.map(e => e.columnIndex + 1));
|
||||
active.forEach(e => e.totalColumns = maxColumns);
|
||||
});
|
||||
}
|
||||
|
||||
// Render events into the calendar
|
||||
function renderEvents(events, colors) {
|
||||
events.forEach(event => {
|
||||
const dayColumn = document.querySelector(`.day-column[data-day="${event.day}"][data-type="${event.type}"]`);
|
||||
const el = document.createElement("div");
|
||||
el.className = "event";
|
||||
|
||||
const startDate = hourFloatToDate(event.day, event.start);
|
||||
const endDate = hourFloatToDate(event.day, event.end);
|
||||
const duration = event.end - event.start; // in hours
|
||||
const lineBreak = duration >= 1 ? "<br>" : " "; // use space if < 1h
|
||||
|
||||
if (event.abt != 0){
|
||||
el.innerHTML = `<div><span><b>${event.title} - Abt. ${event.abt}</b> (${event.elem})${lineBreak}${formatTime(startDate)} - ${formatTime(endDate)}</span></div>`;
|
||||
} else {
|
||||
el.innerHTML = `<div><span><b>${event.title}</b>${lineBreak}${formatTime(startDate)} - ${formatTime(endDate)}</span></div>`;
|
||||
}
|
||||
|
||||
|
||||
el.style.background = colors[event.abt].background;
|
||||
el.style.color = colors[event.abt].foreground;
|
||||
el.style.borderTop = `3px solid ${colors[event.abt].borderTop}`;
|
||||
|
||||
const widthPercent = 100 / event.totalColumns;
|
||||
el.style.top = (event.start * pixelsPerHour) + "px";
|
||||
el.style.height = ((event.end - event.start) * pixelsPerHour - 11) + "px";
|
||||
el.style.width = `calc(${widthPercent}% - 10px)`;
|
||||
el.style.left = `${event.columnIndex * widthPercent}%`;
|
||||
|
||||
// Store reference for updates
|
||||
event.el = el;
|
||||
|
||||
addDragBehavior(el, event, colors);
|
||||
|
||||
dayColumn.appendChild(el);
|
||||
});
|
||||
}
|
||||
|
||||
// Drag & drop behavior
|
||||
function addDragBehavior(el, event, colors) {
|
||||
el.addEventListener("pointerdown", e => {
|
||||
e.preventDefault();
|
||||
el.setPointerCapture(e.pointerId);
|
||||
el.classList.add("dragging");
|
||||
|
||||
const startY = e.clientY;
|
||||
const origTop = parseFloat(el.style.top);
|
||||
const type = event.type;
|
||||
|
||||
const validColumns = Array.from(document.querySelectorAll(`.day-column[data-type="${type}"]`));
|
||||
validColumns.forEach(c => c.classList.add("highlight"));
|
||||
|
||||
function onPointerMove(eMove) {
|
||||
const deltaY = eMove.clientY - startY;
|
||||
|
||||
// Vertical movement (snapped to 15 min)
|
||||
let newTop = origTop + deltaY;
|
||||
newTop = Math.max(0, newTop);
|
||||
newTop = Math.round(newTop / (pixelsPerHour/12)) * (pixelsPerHour/12);
|
||||
el.style.top = newTop + "px";
|
||||
|
||||
// Horizontal movement locked to same type columns
|
||||
const pointerX = eMove.clientX;
|
||||
const targetColumn = validColumns.find(c => {
|
||||
const rect = c.getBoundingClientRect();
|
||||
return pointerX >= rect.left && pointerX <= rect.right;
|
||||
});
|
||||
|
||||
if (targetColumn && targetColumn !== el.parentElement) {
|
||||
targetColumn.appendChild(el);
|
||||
el.style.left = 0;
|
||||
}
|
||||
|
||||
// Update live time
|
||||
const newStart = newTop / pixelsPerHour;
|
||||
const newEnd = newStart + (event.end - event.start);
|
||||
const startDate = hourFloatToDate(event.day, newStart);
|
||||
const endDate = hourFloatToDate(event.day, newEnd);
|
||||
const duration = event.end - event.start; // in hours
|
||||
const lineBreak = duration >= 1 ? "<br>" : " "; // use space if < 1h
|
||||
|
||||
if (event.abt != 0){
|
||||
el.querySelector("span").innerHTML = `<b>${event.title} - Abt. ${event.abt}</b> (${event.elem})${lineBreak}${formatTime(startDate)} - ${formatTime(endDate)}`;
|
||||
} else {
|
||||
el.querySelector("span").innerHTML = `<b>${event.title}</b>${lineBreak}${formatTime(startDate)} - ${formatTime(endDate)}`;
|
||||
}
|
||||
}
|
||||
|
||||
function onPointerUp(eUp) {
|
||||
el.classList.remove("dragging");
|
||||
el.releasePointerCapture(eUp.pointerId);
|
||||
document.removeEventListener("pointermove", onPointerMove);
|
||||
document.removeEventListener("pointerup", onPointerUp);
|
||||
validColumns.forEach(c => c.classList.remove("highlight"));
|
||||
|
||||
// Update event day based on column
|
||||
const newColumn = validColumns.find(c => el.parentElement === c);
|
||||
if(newColumn) event.day = newColumn.dataset.day;
|
||||
|
||||
// Update start/end
|
||||
const newStart = parseFloat(el.style.top) / pixelsPerHour;
|
||||
const duration = event.end - event.start;
|
||||
event.start = newStart;
|
||||
event.end = newStart + duration;
|
||||
|
||||
// Recalculate columns for current day/type
|
||||
const dayEvents = events.filter(ev => ev.day === event.day && ev.type === event.type);
|
||||
assignColumns(dayEvents);
|
||||
|
||||
// Update positions, widths, and displayed time
|
||||
dayEvents.forEach(ev => {
|
||||
const widthPercent = 100 / ev.totalColumns;
|
||||
ev.el.style.width = `calc(${widthPercent}% - 10px)`;
|
||||
ev.el.style.left = `${ev.columnIndex * widthPercent}%`;
|
||||
|
||||
const startDate = hourFloatToDate(ev.day, ev.start);
|
||||
const endDate = hourFloatToDate(ev.day, ev.end);
|
||||
const duration = ev.end - ev.start; // in hours
|
||||
const lineBreak = duration >= 1 ? "<br>" : " "; // use space if < 1h
|
||||
|
||||
if (ev.abt != 0){
|
||||
ev.el.querySelector("span").innerHTML = `<b>${ev.title} - Abt. ${ev.abt}</b> (${ev.elem})${lineBreak}${formatTime(startDate)} - ${formatTime(endDate)}`;
|
||||
} else {
|
||||
ev.el.querySelector("span").innerHTML = `<b>${ev.title}</b>${lineBreak}${formatTime(startDate)} - ${formatTime(endDate)}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener("pointermove", onPointerMove);
|
||||
document.addEventListener("pointerup", onPointerUp);
|
||||
});
|
||||
|
||||
el.addEventListener("dblclick", () => {
|
||||
const durationHours = prompt("Enter new duration in hours:", hoursToHHMM((event.end - event.start).toFixed(2)));
|
||||
if(durationHours !== null) {
|
||||
const duration = HHMMToHours(durationHours);
|
||||
if(!isNaN(duration) && duration > 0) {
|
||||
event.end = event.start + duration;
|
||||
|
||||
// Recalculate columns for the day/type
|
||||
const dayEvents = events.filter(ev => ev.day === event.day && ev.type === event.type);
|
||||
assignColumns(dayEvents);
|
||||
|
||||
// Update all event positions, widths, and displayed text
|
||||
dayEvents.forEach(ev => {
|
||||
const widthPercent = 100 / ev.totalColumns;
|
||||
ev.el.style.width = `calc(${widthPercent}% - 10px)`;
|
||||
ev.el.style.left = `${ev.columnIndex * widthPercent}%`;
|
||||
ev.el.style.top = (ev.start * pixelsPerHour) + "px";
|
||||
ev.el.style.height = ((ev.end - ev.start) * pixelsPerHour - 11) + "px";
|
||||
|
||||
// Update displayed time with conditional line break
|
||||
const startDate = hourFloatToDate(ev.day, ev.start);
|
||||
const endDate = hourFloatToDate(ev.day, ev.end);
|
||||
const dur = ev.end - ev.start;
|
||||
const lineBreak = dur >= 1 ? "<br>" : " ";
|
||||
if (ev.abt != 0){
|
||||
ev.el.querySelector("span").innerHTML = `<b>${ev.title} - Abt. ${ev.abt}</b> (${ev.elem})${lineBreak}${formatTime(startDate)} - ${formatTime(endDate)}`;
|
||||
} else {
|
||||
ev.el.querySelector("span").innerHTML = `<b>${ev.title}</b>${lineBreak}${formatTime(startDate)} - ${formatTime(endDate)}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ----------------- USAGE -----------------
|
||||
|
||||
// Example colors
|
||||
const colors = {
|
||||
"0": {
|
||||
"background": "rgba(172, 172, 172, 0.18)",
|
||||
"foreground": "#4b4b4bff",
|
||||
"borderTop": "#5a5a5aff"
|
||||
},
|
||||
"1": {
|
||||
"background": "rgba(59, 130, 246, 0.18)",
|
||||
"foreground": "#1e3a8a",
|
||||
"borderTop": "#1e3a8a"
|
||||
},
|
||||
"2": {
|
||||
"background": "rgba(20, 184, 166, 0.18)",
|
||||
"foreground": "#065f46",
|
||||
"borderTop": "#065f46"
|
||||
},
|
||||
"3": {
|
||||
"background": "rgba(34, 197, 94, 0.18)",
|
||||
"foreground": "#14532d",
|
||||
"borderTop": "#14532d"
|
||||
},
|
||||
"4": {
|
||||
"background": "rgba(163, 230, 53, 0.20)",
|
||||
"foreground": "#365314",
|
||||
"borderTop": "#365314"
|
||||
},
|
||||
"5": {
|
||||
"background": "rgba(245, 158, 11, 0.20)",
|
||||
"foreground": "#92400e",
|
||||
"borderTop": "#92400e"
|
||||
},
|
||||
"6": {
|
||||
"background": "rgba(249, 115, 22, 0.20)",
|
||||
"foreground": "#9a3412",
|
||||
"borderTop": "#9a3412"
|
||||
},
|
||||
"7": {
|
||||
"background": "rgba(244, 63, 94, 0.18)",
|
||||
"foreground": "#9f1239",
|
||||
"borderTop": "#9f1239"
|
||||
},
|
||||
"8": {
|
||||
"background": "rgba(236, 72, 153, 0.18)",
|
||||
"foreground": "#9d174d",
|
||||
"borderTop": "#9d174d"
|
||||
},
|
||||
"9": {
|
||||
"background": "rgba(168, 85, 247, 0.18)",
|
||||
"foreground": "#581c87",
|
||||
"borderTop": "#581c87"
|
||||
},
|
||||
"10": {
|
||||
"background": "rgba(99, 102, 241, 0.18)",
|
||||
"foreground": "#312e81",
|
||||
"borderTop": "#312e81"
|
||||
},
|
||||
"11": {
|
||||
"background": "rgba(100, 116, 139, 0.20)",
|
||||
"foreground": "#1e293b",
|
||||
"borderTop": "#1e293b"
|
||||
},
|
||||
"12": {
|
||||
"background": "rgba(6, 182, 212, 0.18)",
|
||||
"foreground": "#164e63",
|
||||
"borderTop": "#164e63"
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize
|
||||
const grouped = {};
|
||||
events.forEach(e => {
|
||||
const key = `${e.day}-${e.type}`;
|
||||
if(!grouped[key]) grouped[key] = [];
|
||||
grouped[key].push(e);
|
||||
});
|
||||
Object.values(grouped).forEach(arr => assignColumns(arr));
|
||||
renderEvents(events, colors);
|
||||
|
||||
function exportToICS(events) {
|
||||
let icsContent = `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
PRODID:-//YourApp//Calendar//EN
|
||||
CALSCALE:GREGORIAN
|
||||
METHOD:PUBLISH
|
||||
`;
|
||||
|
||||
events.forEach(event => {
|
||||
const startDate = hourFloatToDate(event.day, event.start);
|
||||
const endDate = hourFloatToDate(event.day, event.end);
|
||||
|
||||
// Format as YYYYMMDDTHHMMSS
|
||||
function formatICSDate(d) {
|
||||
return d.toISOString().replace(/[-:]/g,'').split('.')[0] + 'Z';
|
||||
}
|
||||
|
||||
icsContent += `BEGIN:VEVENT
|
||||
UID:${event.day}-${event.start}-${event.title}
|
||||
DTSTAMP:${formatICSDate(new Date())}
|
||||
DTSTART:${formatICSDate(startDate)}
|
||||
DTEND:${formatICSDate(endDate)}
|
||||
SUMMARY:${event.title} - Abt. ${event.abt}
|
||||
DESCRIPTION:Type: ${event.type}, Elem: ${event.elem}
|
||||
END:VEVENT
|
||||
`;
|
||||
});
|
||||
|
||||
icsContent += `END:VCALENDAR`;
|
||||
|
||||
// Create downloadable link
|
||||
const blob = new Blob([icsContent], { type: 'text/calendar;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'calendar.ics';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
// Attach to button
|
||||
document.getElementById('exportCal').addEventListener('click', () => exportToICS(events));
|
||||
</script>
|
||||
1012
www/intern/wk-leitung/logindata.php
Normal file
1012
www/intern/wk-leitung/logindata.php
Normal file
File diff suppressed because it is too large
Load Diff
639
www/intern/wk-leitung/rechnungen.php
Normal file
639
www/intern/wk-leitung/rechnungen.php
Normal file
@@ -0,0 +1,639 @@
|
||||
<?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);
|
||||
|
||||
if (!isset($baseDir)) {
|
||||
$baseDir = $_SERVER['DOCUMENT_ROOT'];
|
||||
}
|
||||
|
||||
if (session_status() !== PHP_SESSION_ACTIVE) {
|
||||
session_start();
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<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">
|
||||
<title>Intern - Rechnungen</title>
|
||||
|
||||
<link rel="stylesheet" href="/intern/css/rechnungen.css">
|
||||
<link rel="stylesheet" href="/intern/css/sidebar.css">
|
||||
<link rel="stylesheet" href="/files/fonts/fonts.css">
|
||||
<script src="/intern/js/jquery/jquery-3.7.1.min.js"></script>
|
||||
<script>
|
||||
(function () {
|
||||
let lastWrite = 0;
|
||||
const interval = 200;
|
||||
|
||||
window.addEventListener('scroll', function () {
|
||||
const now = Date.now();
|
||||
if (now - lastWrite >= interval) {
|
||||
sessionStorage.setItem('scrollY', window.scrollY);
|
||||
lastWrite = now;
|
||||
}
|
||||
}, { passive: true });
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<?php
|
||||
$access_granted_wkl = $_SESSION['access_granted_wk_leitung'] ?? false;
|
||||
|
||||
if ( ! $access_granted_wkl ) :
|
||||
|
||||
$logintype = 'wk_leitung';
|
||||
|
||||
require $baseDir . '/../scripts/login/login.php';
|
||||
|
||||
$logintype = '';
|
||||
|
||||
else :
|
||||
|
||||
|
||||
require $baseDir . '/../scripts/db/db-functions.php';
|
||||
require $baseDir . '/../scripts/db/db-tables.php';
|
||||
require $baseDir . '/../scripts/csrf_functions.php';
|
||||
|
||||
|
||||
$type = 'wkl';
|
||||
|
||||
$dbconnection = require $baseDir . '/../scripts/db/db-verbindung-script.php';
|
||||
|
||||
if ($dbconnection['success'] !== true){
|
||||
echo 'Critical DB Error.';
|
||||
exit;
|
||||
}
|
||||
|
||||
$currentPage = 'rechnungen';
|
||||
|
||||
require $baseDir . '/intern/scripts/sidebar/sidebar.php';
|
||||
|
||||
setlocale(LC_TIME, 'de_DE.UTF-8');
|
||||
|
||||
$svgbezahlt = '<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"/><path d="M7 12l3 3 7-7" stroke="white" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/></svg>';
|
||||
$svgpending = '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M6 2h12v2a6 6 0 0 1-6 6 6 6 0 0 1-6-6V2zm0 20h12v-2a6 6 0 0 0-6-6 6 6 0 0 0-6 6v2zm6-10c1.657 0 3-1.343 3-3H9c0 1.657 1.343 3 3 3z" fill="#0073aa"/></svg>';
|
||||
$svgnichtbezahlt = '<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>';
|
||||
|
||||
$currentYear = (date('n') > 6) ? date('Y') + 1 : date('Y');
|
||||
|
||||
$sql = "SELECT
|
||||
o.*,
|
||||
iu.username AS order_creator
|
||||
FROM
|
||||
$tableOrders o
|
||||
LEFT JOIN
|
||||
$tableInternUsers iu
|
||||
ON iu.id = o.user_id
|
||||
WHERE o.order_status <> 0
|
||||
ORDER BY
|
||||
o.order_status ASC,
|
||||
o.timestamp DESC;
|
||||
";
|
||||
|
||||
$sel = $mysqli->prepare($sql);
|
||||
|
||||
if (!$sel->execute()) {
|
||||
http_response_code(500);
|
||||
exit;
|
||||
}
|
||||
|
||||
$allRechnungenUnsorted = [];
|
||||
|
||||
$res = $sel->get_result();
|
||||
while ($row = $res->fetch_assoc()) {
|
||||
$allRechnungenUnsorted[] = $row;
|
||||
}
|
||||
|
||||
|
||||
foreach ($allRechnungenUnsorted as $r) {
|
||||
$allRechnungen[$r['order_status']][] = $r;
|
||||
}
|
||||
|
||||
function titleStatus(int $int) : string {
|
||||
switch ($int) {
|
||||
case 1:
|
||||
return "Zahlungseingang überprüfen";
|
||||
case 2:
|
||||
return "Bezahlte Rechnungen";
|
||||
default:
|
||||
return "FEHLER BEI NAMENSGENERRIERUNG";
|
||||
}
|
||||
}
|
||||
|
||||
function classStatus(int $int) : string {
|
||||
switch ($int) {
|
||||
case 1:
|
||||
return "inProcess";
|
||||
case 2:
|
||||
return "payed";
|
||||
default:
|
||||
return "notPayed";
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
<div class="internMenuDiv">
|
||||
<div class="closeInternMenuMobileDiv"></div>
|
||||
<div class="innerInternMenuDiv">
|
||||
|
||||
<div class="header-text-conatiner"></div>
|
||||
|
||||
<div class="referenzDiv">
|
||||
<h3 class="containerHeading">Rechnung als bezahlt eintagen</h3>
|
||||
<label for="scorNumber">Referenznummer<br><br>
|
||||
|
||||
<div class="referenzDivInputDiv">
|
||||
<span>RF</span>
|
||||
<input
|
||||
type="text"
|
||||
id="scorNumber"
|
||||
inputmode="numeric"
|
||||
autocomplete="off"
|
||||
placeholder="12 3456 7890"
|
||||
maxlength="19"
|
||||
>
|
||||
</div>
|
||||
</label>
|
||||
<!-- Hidden field for backend -->
|
||||
<input type="hidden" id="scorNumberRaw" name="scor_reference">
|
||||
|
||||
<button id="submitScorNumber">Bestätigen</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="bgSection">
|
||||
<?php sidebarRender('modal'); ?>
|
||||
<div class="headerDivTrainer">
|
||||
<div class="headingPanelDiv">
|
||||
<h2 class="headingPanel">Rechnungen</h2>
|
||||
<h3 class="headingPanelUser"><?= $currentYear ?></h3>
|
||||
</div>
|
||||
<div class="menuWrapper">
|
||||
<div class="trainerBurgerMenuDiv">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<?php sidebarRender('button'); ?>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
const menuburger = document.querySelector('.trainerBurgerMenuDiv');
|
||||
const menudiv = document.querySelector('.internMenuDiv');
|
||||
//const menubg = document.querySelector('.menuBg');
|
||||
const content = document.querySelector('.bgSection');
|
||||
const closeMenuMobile = document.querySelector('.closeInternMenuMobileDiv');
|
||||
|
||||
const storageKey = "trainerInternMenuOpen";
|
||||
|
||||
const isOpen = localStorage.getItem(storageKey) === "true";
|
||||
|
||||
document.addEventListener('keydown', function (e) {
|
||||
// Mac = Option, Windows/Linux = Alt
|
||||
const isOptionOrAlt = e.altKey || e.metaKey;
|
||||
|
||||
if (e.key.toLowerCase() === 'm') {
|
||||
menuburger.classList.add("menuTransition");
|
||||
menudiv.classList.add("menuTransition");
|
||||
content.classList.add("menuTransition");
|
||||
|
||||
menuburger.classList.toggle("open");
|
||||
menudiv.classList.toggle("open");
|
||||
content.classList.toggle("open");
|
||||
|
||||
const isOpenEl =
|
||||
menudiv.classList.contains("open") &&
|
||||
menuburger.classList.contains("open");
|
||||
|
||||
localStorage.setItem(storageKey, isOpenEl);
|
||||
}
|
||||
});
|
||||
|
||||
if (isOpen) {
|
||||
menuburger.classList.add("open");
|
||||
menudiv.classList.add("open");
|
||||
content.classList.add("open");
|
||||
}
|
||||
|
||||
menuburger.addEventListener("click", function () {
|
||||
menuburger.classList.add("menuTransition");
|
||||
menudiv.classList.add("menuTransition");
|
||||
content.classList.add("menuTransition");
|
||||
|
||||
menuburger.classList.toggle("open");
|
||||
menudiv.classList.toggle("open");
|
||||
content.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");
|
||||
content.classList.remove("open");
|
||||
|
||||
localStorage.setItem(storageKey, false);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
</div>
|
||||
|
||||
<div class="containerDiv">
|
||||
|
||||
<h3 class="containerHeading">Alle Rechnungen:</h3>
|
||||
|
||||
<?php if (!isset($allRechnungen) || !is_array($allRechnungen) || count($allRechnungen) < 1) : ?>
|
||||
<h3>Keine erstellten Rechungen vorhanden</h3>
|
||||
<?php else :?>
|
||||
|
||||
|
||||
|
||||
<div>
|
||||
<p class="labelBulkSelect">Bulk Select:</p>
|
||||
<div class="bulkSelectDiv">
|
||||
<div class="customSelect" id="bulkSelectedOption" data-value="">
|
||||
<button type="button" class="selectTrigger bulkSelect" aria-expanded="false">
|
||||
<span class="selectLabel">Aktion auswählen</span>
|
||||
|
||||
<svg class="selectArrow" xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' height="14" width="14">
|
||||
<path d='M6 9L12 15L18 9' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/>\
|
||||
</svg>
|
||||
|
||||
</button>
|
||||
|
||||
<ul class="selectOptions">
|
||||
<li data-value="deleteEntrys">Rechnungen stornieren</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<input type="submit" name="apply_bulk_action" class="bulkSelectSubmit" value="Anwenden">
|
||||
</div>
|
||||
|
||||
|
||||
<?php foreach ($allRechnungen as $status => $oneRechnungsStatus) : ?>
|
||||
<?php $totalOrderVolume = 0; $totalPayedVolume = 0; ?>
|
||||
<h2 class="titleSingleProg"><?= titleStatus(intval($status)) ?></h2>
|
||||
<table class="wkvsTabelle widefat striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:20px;">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" id="check_all_<?= titleStatus(intval($status)) ?>">
|
||||
<span class="checkbox-ui"></span>
|
||||
</label>
|
||||
</th>
|
||||
<th>Rechnungsnummer</th>
|
||||
<th>Status</th>
|
||||
<th>Typ</th>
|
||||
<th>Ersteller</th>
|
||||
<th>Datum</th>
|
||||
<th>Betrag</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($oneRechnungsStatus as $row) : ?>
|
||||
<tr>
|
||||
<td>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="bulk_ids[]" class="row_check_<?= titleStatus(intval($status)) ?>" value="<?= intval($row['order_id']) ?>">
|
||||
<span class="checkbox-ui"></span>
|
||||
</label>
|
||||
</td>
|
||||
<td>
|
||||
<?php if (file_exists($baseDir . "/../test-wkvs/rechnungen/" . intval($row['order_id']) . ".pdf")) : ?>
|
||||
<a href="/intern/wk-leitung/rechnungen_viewer?order_id=<?= intval($row['order_id']) ?>" target="_blank">
|
||||
<?= intval($row['order_id']) ?>
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<?= intval($row['order_id']) ?>
|
||||
<?php endif; ?>
|
||||
|
||||
</td>
|
||||
<td><?php
|
||||
switch (intval($status)) {
|
||||
case '2':
|
||||
echo $svgbezahlt;
|
||||
break;
|
||||
|
||||
case '1':
|
||||
echo $svgpending;
|
||||
break;
|
||||
|
||||
default:
|
||||
echo $svgnichtbezahlt;
|
||||
break;
|
||||
}?> </td>
|
||||
<td><?= htmlspecialchars($row['order_type']) ?></td>
|
||||
<td><?= htmlspecialchars($row['order_creator']) ?></td>
|
||||
|
||||
<td><?= htmlspecialchars(strftime('%d. %B %Y', (new DateTime($row['timestamp']))->getTimestamp())) ?></td>
|
||||
<td><?= htmlspecialchars(number_format(floatval($row['preis']), 2)) ?> CHF</td>
|
||||
<?php $totalOrderVolume += floatval($row['preis']); ?>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<tr class="totalTr">
|
||||
<td colspan="6" class="totalTd"></td>
|
||||
<td class="totalValue"><span class="<?= classStatus(intval($status)) ?>"><?= htmlspecialchars(number_format(floatval($totalOrderVolume), 2))?> CHF</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!--<div class="containerDiv"><h3 class="headingAlleTurnerinnen">Alle Turnerinnen:</h3>
|
||||
|
||||
|
||||
<?php //else : ?>
|
||||
<p>Noch keine Datensätze vorhanden.</p>
|
||||
<?php // endif; ?>
|
||||
-->
|
||||
<div class="msgDiv"></div>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<script>
|
||||
const $input = $('#scorNumber');
|
||||
const $rawInput = $('#scorNumberRaw');
|
||||
|
||||
$input.on('input', function() {
|
||||
let value = $(this).val().replace(/\D/g, ''); // remove non-digits
|
||||
|
||||
// Optional: limit total digits to 21 (SCOR standard)
|
||||
value = value.slice(0, 21);
|
||||
|
||||
// Update hidden raw value
|
||||
$rawInput.val(value);
|
||||
|
||||
// Format: first 2 digits, then groups of 4
|
||||
let formatted = '';
|
||||
if (value.length > 0) {
|
||||
// First 2 digits
|
||||
formatted += value.substr(0, 2);
|
||||
|
||||
// Remaining digits
|
||||
let remaining = value.substr(2);
|
||||
if (remaining.length > 0) {
|
||||
formatted += ' ' + remaining.match(/.{1,4}/g).join(' ');
|
||||
}
|
||||
}
|
||||
|
||||
$(this).val(formatted);
|
||||
});
|
||||
|
||||
$('#submitScorNumber').on('click', function(e) {
|
||||
e.preventDefault(); // prevent form submission if it's a button
|
||||
|
||||
const SCOR = $('#scorNumberRaw').val();
|
||||
|
||||
fetch('/intern/scripts/rechnungen/ajax_update_order_status.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
scor: SCOR
|
||||
})
|
||||
})
|
||||
.then(res => res.json().then(data => ({ status: res.status, ok: res.ok, data })))
|
||||
.then(({ status, ok, data }) => {
|
||||
if (!ok) {
|
||||
throw {
|
||||
status,
|
||||
message: data.message || 'Update failed'
|
||||
};
|
||||
}
|
||||
|
||||
window.location.reload();
|
||||
})
|
||||
.catch(err => {
|
||||
displayMsg(0, `${err.message}`);
|
||||
});
|
||||
});
|
||||
|
||||
$('.bulkSelectSubmit').on('click', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
const valBulkSelect = $('#bulkSelectedOption').data('value');
|
||||
console.log(valBulkSelect);
|
||||
|
||||
if (valBulkSelect === 'deleteEntrys') {
|
||||
let arrayIds = [];
|
||||
|
||||
$('[class^="row_check_"]').each(function () {
|
||||
if ($(this).prop('checked')) {
|
||||
arrayIds.push($(this).val());
|
||||
}
|
||||
});
|
||||
|
||||
if (Array.isArray(arrayIds) && arrayIds.length) {
|
||||
|
||||
const params = new URLSearchParams();
|
||||
|
||||
arrayIds.forEach(id => {
|
||||
params.append('ids[]', id);
|
||||
});
|
||||
|
||||
|
||||
fetch('/intern/scripts/rechnungen/ajax_delete_order.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
body: params
|
||||
})
|
||||
.then(res => res.json().then(data => ({ status: res.status, ok: res.ok, data })))
|
||||
.then(({ status, ok, data }) => {
|
||||
if (!ok) {
|
||||
throw {
|
||||
status,
|
||||
message: data.message || 'Update failed'
|
||||
};
|
||||
}
|
||||
|
||||
window.location.reload();
|
||||
})
|
||||
.catch(err => {
|
||||
displayMsg(0, `${err.message} (HTTP ${err.status ?? 'unknown'})`);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
document.querySelectorAll('[id^="check_all_"]').forEach(master => {
|
||||
const programm = master.id.replace("check_all_", "");
|
||||
const checkboxes = document.querySelectorAll(".row_check_" + programm);
|
||||
|
||||
// toggle all rows when master is clicked
|
||||
master.addEventListener("change", function() {
|
||||
checkboxes.forEach(cb => cb.checked = master.checked);
|
||||
});
|
||||
|
||||
// update master if all rows are manually checked/unchecked
|
||||
checkboxes.forEach(cb => {
|
||||
cb.addEventListener("change", function() {
|
||||
master.checked = [...checkboxes].every(c => c.checked);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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 1s ease'
|
||||
});
|
||||
|
||||
// Next frame: apply the transform so the transition animates
|
||||
requestAnimationFrame(() => {
|
||||
$div.removeClass('show');
|
||||
$div.css({
|
||||
'transform': 'translateX(calc(100% + 40px)) scaleY(0)' //scaleY(0.5)
|
||||
});
|
||||
});
|
||||
}, 5000);
|
||||
|
||||
// auto-remove
|
||||
setTimeout(() => {
|
||||
$div.remove();
|
||||
}, 6500);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<style>
|
||||
.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;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
$(function () {
|
||||
|
||||
$(".selectTrigger").on("click", function (e) {
|
||||
e.stopPropagation();
|
||||
|
||||
const $select = $(this).closest(".customSelect");
|
||||
const $options = $select.find(".selectOptions");
|
||||
|
||||
$(".selectOptions").not($options).hide();
|
||||
$(".selectTrigger").not(this).attr("aria-expanded", "false");
|
||||
|
||||
$options.toggle();
|
||||
$(this).attr("ariaExpanded", $options.is(":visible"));
|
||||
});
|
||||
|
||||
$(".selectOptions li").on("click", function () {
|
||||
const $item = $(this);
|
||||
const $select = $item.closest(".customSelect");
|
||||
|
||||
$select.find(".selectLabel").text($item.text());
|
||||
$select.attr("data-value", $item.data("value"));
|
||||
|
||||
$item
|
||||
.addClass("selected")
|
||||
.siblings()
|
||||
.removeClass("selected");
|
||||
|
||||
$select.find(".selectOptions").hide();
|
||||
$select.find(".selectTrigger").attr("aria-expanded", "false");
|
||||
});
|
||||
|
||||
$(document).on("click", function () {
|
||||
$(".selectOptions").hide();
|
||||
$(".selectTrigger").attr("aria-expanded", "false");
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
46
www/intern/wk-leitung/rechnungen_viewer.php
Normal file
46
www/intern/wk-leitung/rechnungen_viewer.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (session_status() !== PHP_SESSION_ACTIVE) session_start();
|
||||
|
||||
if (empty($_SESSION['access_granted_wk_leitung']) || $_SESSION['access_granted_wk_leitung'] !== true || empty($_SESSION['passcodewk_leitung_id']) || intval($_SESSION['passcodewk_leitung_id']) < 0 ) {
|
||||
http_response_code(403);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!isset($_GET['order_id']) || intval($_GET['order_id']) < 1) {
|
||||
echo json_encode(['success' => false, 'message' => 'Keine Id angegeben']);
|
||||
http_response_code(422);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!isset($baseDir)) {
|
||||
$baseDir = $_SERVER['DOCUMENT_ROOT'];
|
||||
}
|
||||
|
||||
$id = intval($_GET['order_id']);
|
||||
|
||||
$filename = basename($id . '.pdf');
|
||||
|
||||
$filePath = $baseDir . '/../private-files/rechnungen/' . $filename;
|
||||
|
||||
if (!file_exists($filePath)) {
|
||||
http_response_code(404);
|
||||
exit('File not found');
|
||||
}
|
||||
|
||||
// 5. Send headers
|
||||
header('Content-Description: File Transfer');
|
||||
header('Content-Type: application/pdf'); // adjust if needed
|
||||
header('Content-Disposition: inline; filename="' . $filename . '"');
|
||||
header('Content-Length: ' . filesize($filePath));
|
||||
header('Cache-Control: no-cache, must-revalidate');
|
||||
header('Pragma: public');
|
||||
|
||||
// 6. Clean output buffer
|
||||
ob_clean();
|
||||
flush();
|
||||
|
||||
// 7. Stream file
|
||||
readfile($filePath);
|
||||
exit;
|
||||
749
www/intern/wk-leitung/riegeneinteilung.php
Normal file
749
www/intern/wk-leitung/riegeneinteilung.php
Normal file
@@ -0,0 +1,749 @@
|
||||
<?php
|
||||
|
||||
if (session_status() !== PHP_SESSION_ACTIVE) session_start();
|
||||
|
||||
|
||||
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);
|
||||
|
||||
if (!isset($baseDir)) {
|
||||
$baseDir = $_SERVER['DOCUMENT_ROOT'];
|
||||
}
|
||||
|
||||
$logintype = 'wk_leitung';
|
||||
|
||||
if (empty($_SESSION['access_granted_wk_leitung']) || $_SESSION['access_granted_wk_leitung'] !== true || empty($_SESSION['passcodewk_leitung_id']) || intval($_SESSION['passcodewk_leitung_id']) < 0 ) {
|
||||
|
||||
require $baseDir . '/../scripts/login/login.php';
|
||||
|
||||
$logintype = '';
|
||||
} else {
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
$allGeraete = db_select($mysqli, $tableGeraete, "name, start_index", "", [], "start_index ASC");
|
||||
$allAbt = db_select($mysqli, $tableAbt, "name, id", "", [], "name ASC");
|
||||
|
||||
// $allGeraete[] = ['name' => 'null', 'id' => 'null'];
|
||||
// $allAbt[] = ['name' => 'null', 'id' => 'null'];
|
||||
|
||||
$stmt = $mysqli->prepare("SELECT
|
||||
t.id,
|
||||
t.name,
|
||||
t.vorname,
|
||||
t.programm,
|
||||
t.verein,
|
||||
GROUP_CONCAT(a.name SEPARATOR ', ') AS abteilung,
|
||||
GROUP_CONCAT(g.name SEPARATOR ', ') AS geraet
|
||||
FROM $tableTurnerinnen t
|
||||
LEFT JOIN $tableTurnerinnenAbt ta ON ta.turnerin_id = t.id
|
||||
LEFT JOIN $tableAbt a ON a.id = ta.abteilung_id
|
||||
LEFT JOIN $tableGeraete g ON g.id = ta.geraet_id
|
||||
GROUP BY t.id
|
||||
ORDER BY t.id DESC;
|
||||
");
|
||||
|
||||
$stmt->execute();
|
||||
|
||||
|
||||
$result = $stmt->get_result();
|
||||
$allTurnerinnen = $result->fetch_all(MYSQLI_ASSOC);
|
||||
|
||||
$grouped = [];
|
||||
|
||||
|
||||
foreach ($allTurnerinnen as $entry) {
|
||||
if ($entry['abteilung'] == '') {
|
||||
$grouped['null']['null'][] = $entry;
|
||||
} else {
|
||||
$grouped[$entry['abteilung']][$entry['geraet']][] = $entry;
|
||||
}
|
||||
}
|
||||
|
||||
$noAbt = $grouped;
|
||||
foreach ($allAbt as $abt) {
|
||||
$abtName = $abt['name'];
|
||||
foreach ($allGeraete as $geraet) {
|
||||
$geraetName = $geraet['name'];
|
||||
$noAbt[$abtName][$geraetName] = [];
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Riegeneinteilung</title>
|
||||
<link rel="icon" type="png" href="/intern/img/icon.png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="/intern/css/sidebar.css">
|
||||
<link rel="stylesheet" href="/intern/css/riegeneinteilung.css">
|
||||
<link rel="icon" type="png" href="/intern/img/icon.png">
|
||||
<link href="/files/fonts/fonts.css" rel="stylesheet">
|
||||
<script src="/intern/js/custom-msg-display.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<?php
|
||||
$currentPage = 'riegeneinteilung';
|
||||
require $baseDir . '/intern/scripts/sidebar/sidebar.php';
|
||||
?>
|
||||
|
||||
<?php sidebarRender('modal'); ?>
|
||||
|
||||
<div class="internMenuDiv">
|
||||
<div class="innerInternMenuDiv">
|
||||
|
||||
<div class="header-text-conatiner">
|
||||
<button id="autoEinteilung">
|
||||
Automatische Riegeneinteilung
|
||||
</button>
|
||||
<button id="groupByVereinProgramm">
|
||||
Group by Verein & Programm
|
||||
</button>
|
||||
<button id="ungroup">
|
||||
Reset grouping
|
||||
</button>
|
||||
<label>Anzahl Abteilungen</label>
|
||||
<input type="number" min="1" max="<?= count($allTurnerinnen) ?>" step="1" id="anzAbt" value="<?= count($allAbt) ?>">
|
||||
</div>
|
||||
<?php
|
||||
|
||||
$arrayRest = [];
|
||||
|
||||
foreach ($noAbt as $level1) {
|
||||
foreach ($level1 as $level2) {
|
||||
foreach ($level2 as $entry) {
|
||||
$arrayRest[] = $entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$count = count($arrayRest);
|
||||
|
||||
echo '<div class="invalidAbtDiv"><table class="geraet-table"
|
||||
data-abteilung="0">';
|
||||
|
||||
echo '<thead>
|
||||
<tr>
|
||||
<th>
|
||||
Keine Valide Abt
|
||||
<i><span>(' . $count . ')</span></i>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>';
|
||||
|
||||
echo '<tbody>';
|
||||
|
||||
foreach ($arrayRest as $entry) {
|
||||
echo '<tr class="turnerin-row"
|
||||
data-id="' . (int)$entry['id'] . '"
|
||||
data-verein="' . htmlspecialchars($entry['verein']) . '"
|
||||
data-programm="' . htmlspecialchars($entry['programm']) . '">
|
||||
<td>' .
|
||||
htmlspecialchars(
|
||||
$entry['vorname'] . ' ' .
|
||||
$entry['name'] . ' - ' .
|
||||
$entry['programm'] . ' - ' .
|
||||
$entry['verein']
|
||||
) .
|
||||
'</td>
|
||||
</tr>';
|
||||
|
||||
}
|
||||
|
||||
echo '</tbody>';
|
||||
echo '</table></div>';
|
||||
?>
|
||||
</div>
|
||||
</div>
|
||||
<section class="bgSection">
|
||||
<div class="headerDivKampfrichter">
|
||||
<h2 class="heading-pannel">Riegeneinteilungspanel</h2>
|
||||
<div class="menuWrapper">
|
||||
<div class="wk-leitungBurgerMenuDiv">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<?php sidebarRender('button'); ?>
|
||||
</div>
|
||||
<script>
|
||||
|
||||
const menuburger = document.querySelector('.wk-leitungBurgerMenuDiv');
|
||||
const menudiv = document.querySelector('.internMenuDiv');
|
||||
const content = document.querySelector('.bgSection');
|
||||
|
||||
const storageKey = "wk-leitungInternMenuOpen";
|
||||
|
||||
const isOpen = localStorage.getItem(storageKey) === "true";
|
||||
|
||||
document.addEventListener('keydown', function (e) {
|
||||
// Mac = Option, Windows/Linux = Alt
|
||||
const isOptionOrAlt = e.altKey || e.metaKey;
|
||||
|
||||
if (e.key.toLowerCase() === 'm') {
|
||||
menuburger.classList.add("menuTransition");
|
||||
menudiv.classList.add("menuTransition");
|
||||
content.classList.add("menuTransition");
|
||||
|
||||
menuburger.classList.toggle("open");
|
||||
menudiv.classList.toggle("open");
|
||||
content.classList.toggle("open");
|
||||
|
||||
const isOpenEl =
|
||||
menudiv.classList.contains("open") &&
|
||||
menuburger.classList.contains("open");
|
||||
|
||||
localStorage.setItem(storageKey, isOpenEl);
|
||||
}
|
||||
});
|
||||
|
||||
if (isOpen) {
|
||||
menuburger.classList.add("open");
|
||||
menudiv.classList.add("open");
|
||||
content.classList.add("open");
|
||||
}
|
||||
|
||||
/*menubg.addEventListener("click", function () {
|
||||
menuburger.classList.add("menuTransition");
|
||||
menudiv.classList.add("menuTransition");
|
||||
menubg.classList.add("menuTransition");
|
||||
content.classList.add("menuTransition");
|
||||
|
||||
menuburger.classList.remove("open");
|
||||
menudiv.classList.remove("open");
|
||||
menubg.classList.remove("open");
|
||||
content.classList.remove("open");
|
||||
|
||||
localStorage.setItem(storageKey, false);
|
||||
});*/
|
||||
|
||||
menuburger.addEventListener("click", function () {
|
||||
menuburger.classList.add("menuTransition");
|
||||
menudiv.classList.add("menuTransition");
|
||||
content.classList.add("menuTransition");
|
||||
|
||||
menuburger.classList.toggle("open");
|
||||
menudiv.classList.toggle("open");
|
||||
content.classList.toggle("open");
|
||||
|
||||
const isOpenEl =
|
||||
menudiv.classList.contains("open") &&
|
||||
menuburger.classList.contains("open");
|
||||
|
||||
localStorage.setItem(storageKey, isOpenEl);
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
<div class="allTurnerinenDiv">
|
||||
<?php
|
||||
|
||||
//print_r($grouped);
|
||||
|
||||
foreach ($allAbt as $abt) {
|
||||
|
||||
$abtName = $abt['name'];
|
||||
|
||||
echo '<div style="display:flex; flex-direction:column;">';
|
||||
echo '<div class="headerAbt"><h2>Abteilung ' . htmlspecialchars($abtName) . '</h2>
|
||||
<button class="deleteProgramm" data-abt="'.(int)$abtName.'">
|
||||
<!-- License: MIT. Made by xivapi: https://github.com/xivapi/classjob-icons -->
|
||||
<svg
|
||||
width="30px"
|
||||
height="30px"
|
||||
viewBox="0 0 1000 1000"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
transform="matrix(-1,0,0,1,0,0)"
|
||||
>
|
||||
<!-- BIN BODY -->
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="
|
||||
M767 336H233q-12 0-21 9t-9 21l38 505
|
||||
q1 13 12 21.5t30 8.5h434
|
||||
q18 0 29-8.5t13-21.5l38-505
|
||||
q0-12-9-21t-21-9z
|
||||
M344 841q-10 0-18-9t-8-21l-26-386
|
||||
q0-12 9-20.5t21-8.5 21 8.5 9 20.5
|
||||
l18 386q0 12-7.5 21t-18.5 9z
|
||||
M526 810q0 13-7.5 22t-18.5 9
|
||||
-18.5-9-7.5-22l-4-385
|
||||
q0-12 9-20.5t21-8.5 21 8.5 9 20.5z
|
||||
M682 811q0 12-8 21t-18 9
|
||||
q-11 0-18.5-9t-7.5-21l18-386
|
||||
q0-12 9-20.5t21-8.5 21 8.5 9 20.5z
|
||||
"
|
||||
/>
|
||||
|
||||
<!-- BIN LID -->
|
||||
<path
|
||||
id="bin-lid"
|
||||
fill="currentColor"
|
||||
d="
|
||||
M783 206l-179-30q-12-2-15-15l-8-33
|
||||
q-4-20-14-26-6-3-22-3h-90
|
||||
q-16 0-23 3-10 6-13 26l-8 33
|
||||
q-2 13-15 15l-179 30
|
||||
q-19 3-31.5 14.5T173 249v28
|
||||
q0 9 6.5 15t15.5 6h610
|
||||
q9 0 15.5-6t6.5-15v-28
|
||||
q0-17-12.5-28.5T783 206z
|
||||
"
|
||||
/>
|
||||
</svg>
|
||||
</button></div>';
|
||||
echo '<div style="display:flex; flex-direction:row; gap:16px; align-items:flex-start; margin-top:24px; margin-bottom:8px;">';
|
||||
|
||||
|
||||
|
||||
foreach ($allGeraete as $geraet) {
|
||||
|
||||
$geraetName = $geraet['name'];
|
||||
|
||||
// Safely resolve entries
|
||||
$entries = $grouped[$abtName][$geraetName] ?? [];
|
||||
$count = count($entries);
|
||||
|
||||
echo '<table class="geraet-table"
|
||||
data-abteilung="' . htmlspecialchars($abtName) . '"
|
||||
data-geraet="' . htmlspecialchars($geraetName) . '">';
|
||||
|
||||
echo '<thead>
|
||||
<tr>
|
||||
<th>
|
||||
Gerät: ' . htmlspecialchars($geraetName) . '
|
||||
<i><span>(' . $count . ')</span></i>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>';
|
||||
|
||||
echo '<tbody>';
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
echo '<tr class="turnerin-row"
|
||||
data-id="' . (int)$entry['id'] . '"
|
||||
data-verein="' . htmlspecialchars($entry['verein']) . '"
|
||||
data-programm="' . htmlspecialchars($entry['programm']) . '">
|
||||
<td>
|
||||
<b>' . htmlspecialchars($entry['verein']) . '</b>,<br>' .
|
||||
htmlspecialchars($entry['vorname']) . ' ' .
|
||||
htmlspecialchars($entry['name']) . ', ' .
|
||||
htmlspecialchars($entry['programm']) .
|
||||
'</td>
|
||||
</tr>';
|
||||
|
||||
}
|
||||
|
||||
$grouped[$abtName][$geraetName] = [];
|
||||
|
||||
echo '</tbody>';
|
||||
echo '</table>';
|
||||
}
|
||||
|
||||
echo '</div></div>';
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
|
||||
|
||||
<script src="/intern/js/jquery/jquery-3.7.1.min.js"></script>
|
||||
<script src="/intern/js/jquery/jquery-ui.min.js"></script>
|
||||
<script src="/intern/js/jquery/jquery.ui.touch-punch.min.js"></script>
|
||||
<link rel="stylesheet" href="/intern/js/jquery/jquery-ui.css">
|
||||
|
||||
|
||||
<script>
|
||||
$(function () {
|
||||
|
||||
function updateCounters() {
|
||||
$(".geraet-table").each(function () {
|
||||
const count = $(this).find("tr.turnerin-row").length;
|
||||
$(this).find("thead span").text(`(${count})`);
|
||||
});
|
||||
}
|
||||
|
||||
$(".turnerin-row").each(function () {
|
||||
$(this).data("original-table", $(this).closest(".geraet-table"));
|
||||
});
|
||||
|
||||
|
||||
|
||||
function updateCounters() {
|
||||
$(".geraet-table").each(function () {
|
||||
const count = $(this).find(".turnerin-row").length;
|
||||
$(this).find("thead span").text(`(${count})`);
|
||||
});
|
||||
}
|
||||
|
||||
const programmColorMap = {};
|
||||
let nextColorIndex = 1;
|
||||
|
||||
function getColorForProgramm(programm) {
|
||||
if (!programmColorMap[programm]) {
|
||||
programmColorMap[programm] = colors[nextColorIndex % Object.keys(colors).length];
|
||||
nextColorIndex++;
|
||||
}
|
||||
return programmColorMap[programm];
|
||||
}
|
||||
|
||||
|
||||
$(".geraet-table tbody").sortable({
|
||||
connectWith: ".geraet-table tbody",
|
||||
placeholder: "drop-placeholder",
|
||||
helper: "clone",
|
||||
|
||||
start: function (e, ui) {
|
||||
ui.item.addClass("dragging");
|
||||
ui.item.data("sourceTbody", ui.item.closest(".geraet-table tbody"));
|
||||
},
|
||||
|
||||
stop: function (e, ui) {
|
||||
const item = ui.item;
|
||||
|
||||
const oldTable = item.data("sourceTbody");
|
||||
const newTable = item.closest(".geraet-table");
|
||||
const newAbt = newTable.data("abteilung");
|
||||
const newGeraet = newTable.data("geraet");
|
||||
|
||||
// Remove placeholder from target table
|
||||
newTable.find(".empty-drop-row").remove();
|
||||
|
||||
// Add placeholder to source table if empty
|
||||
const sourceTbody = ui.sender || item.data("original-tbody");
|
||||
if (
|
||||
sourceTbody &&
|
||||
sourceTbody.find(".turnerin-row").length === 0 &&
|
||||
sourceTbody.find(".group-row").length === 0
|
||||
) {
|
||||
sourceTbody.append('<tr class="empty-drop-row"><td> </td></tr>');
|
||||
}
|
||||
|
||||
item.removeClass("dragging");
|
||||
|
||||
// === AJAX UPDATE LOGIC ===
|
||||
|
||||
// GROUPED DROP
|
||||
if (item.hasClass("group-row")) {
|
||||
|
||||
item.find(".turnerin-row").each(function () {
|
||||
const turnerinId = $(this).data("id");
|
||||
|
||||
$.ajax({
|
||||
url: "/intern/scripts/riegeneinteilung/ajax_update_turnerin.php",
|
||||
method: "POST",
|
||||
data: {
|
||||
turnerin_id: turnerinId,
|
||||
abteilung: newAbt,
|
||||
geraet: newGeraet
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
// SINGLE ROW DROP
|
||||
else {
|
||||
|
||||
const turnerinId = item.data("id");
|
||||
|
||||
$.ajax({
|
||||
url: "/intern/scripts/riegeneinteilung/ajax_update_turnerin.php",
|
||||
method: "POST",
|
||||
data: {
|
||||
turnerin_id: turnerinId,
|
||||
abteilung: newAbt,
|
||||
geraet: newGeraet
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
const payload = {
|
||||
new: newIndexes(newTable),
|
||||
old: newTable.is(oldTable) ? [] : newIndexes(oldTable)
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
url: "/intern/scripts/riegeneinteilung/ajax_update_start_indexes.php",
|
||||
method: "POST",
|
||||
contentType: "application/json",
|
||||
data: JSON.stringify(payload),
|
||||
success: function () {
|
||||
console.log("Order saved");
|
||||
},
|
||||
error: function () {
|
||||
alert("Error saving order");
|
||||
}
|
||||
});
|
||||
|
||||
// Recalculate counters AFTER DOM is settled
|
||||
updateCounters();
|
||||
},
|
||||
|
||||
receive: function (e, ui) {
|
||||
ui.item.data("original-tbody", $(this));
|
||||
}
|
||||
}).disableSelection();
|
||||
|
||||
function newIndexes(table) {
|
||||
const order = [];
|
||||
|
||||
table.find(".turnerin-row").each(function (index) {
|
||||
order.push({
|
||||
id: $(this).data("id"),
|
||||
order_index: index + 1
|
||||
});
|
||||
});
|
||||
|
||||
return order;
|
||||
}
|
||||
|
||||
|
||||
$(".turnerin-row").each(function () {
|
||||
let color = getColorForProgramm($(this).data("programm"));
|
||||
|
||||
$(this).css({
|
||||
background: color.background,
|
||||
color: color.foreground
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
// Initial calculation on page load
|
||||
updateCounters();
|
||||
|
||||
function groupByVereinProgramm() {
|
||||
|
||||
$(".geraet-table tbody").each(function () {
|
||||
const tbody = $(this);
|
||||
const groups = {};
|
||||
|
||||
tbody.find(".turnerin-row").each(function () {
|
||||
const row = $(this);
|
||||
const key = row.data("verein") + "||" + row.data("programm");
|
||||
|
||||
if (!groups[key]) {
|
||||
groups[key] = [];
|
||||
}
|
||||
groups[key].push(row);
|
||||
});
|
||||
|
||||
tbody.empty();
|
||||
|
||||
Object.keys(groups).forEach(function (key) {
|
||||
const rows = groups[key];
|
||||
const verein = rows[0].data("verein");
|
||||
const programm = rows[0].data("programm");
|
||||
|
||||
const color = getColorForProgramm(programm);
|
||||
|
||||
const groupRow = $(`
|
||||
<div class="group-row" data-verein="${verein}" data-programm="${programm}">
|
||||
<td>
|
||||
<div class="group-header"
|
||||
style="
|
||||
background:${color.background};
|
||||
color:${color.foreground};
|
||||
border: 1px solid ${color.borderTop};
|
||||
">
|
||||
<span><b>${verein}</b><br>${programm}</span><span>(${rows.length})</span>
|
||||
</div>
|
||||
<table class="group-inner">
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</td>
|
||||
</div>
|
||||
`);
|
||||
|
||||
rows.forEach(r => {
|
||||
groupRow.find(".group-inner tbody").append(r);
|
||||
});
|
||||
|
||||
tbody.append(groupRow);
|
||||
});
|
||||
});
|
||||
|
||||
updateCounters();
|
||||
}
|
||||
|
||||
groupByVereinProgramm();
|
||||
|
||||
$("#groupByVereinProgramm").on("click", groupByVereinProgramm);
|
||||
|
||||
|
||||
$("#ungroup").on("click", function () {
|
||||
|
||||
$(".geraet-table tbody").each(function () {
|
||||
const tbody = $(this);
|
||||
|
||||
tbody.find(".group-row").each(function () {
|
||||
const group = $(this);
|
||||
group.find(".turnerin-row").appendTo(tbody);
|
||||
group.remove();
|
||||
});
|
||||
});
|
||||
|
||||
updateCounters();
|
||||
});
|
||||
|
||||
|
||||
$(document).on("click", ".deleteProgramm", function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
|
||||
|
||||
if (window.confirm("Bitte bestätige die Löschung der Abteilung")) {
|
||||
|
||||
$.ajax({
|
||||
url: "/intern/scripts/riegeneinteilung/ajax_delete_specific_abt.php",
|
||||
method: "POST",
|
||||
data: {
|
||||
abt: $(this).data('abt')
|
||||
}
|
||||
})
|
||||
.done(function (data, textStatus, jqXHR) {
|
||||
if (jqXHR.status === 201) {
|
||||
window.location.reload();
|
||||
}
|
||||
})
|
||||
.fail(function (jqXHR) {
|
||||
console.error("Request failed:", jqXHR.status);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$("#anzAbt").on("change", function () {
|
||||
|
||||
$.ajax({
|
||||
url: "/intern/scripts/riegeneinteilung/ajax_change_number_of_abt.php",
|
||||
method: "POST",
|
||||
data: {
|
||||
anz_abt: $(this).val()
|
||||
}
|
||||
})
|
||||
.done(function (data, textStatus, jqXHR) {
|
||||
if (jqXHR.status === 201) {
|
||||
console.log("201 Created received");
|
||||
window.location.reload();
|
||||
}
|
||||
})
|
||||
.fail(function (jqXHR) {
|
||||
console.error("Request failed:", jqXHR.status);
|
||||
});
|
||||
});
|
||||
|
||||
$("#autoEinteilung").on("click", function () {
|
||||
|
||||
$.ajax({
|
||||
url: "/intern/scripts/riegeneinteilung/ajax_auto_riegeneinteilung.php",
|
||||
method: "POST"
|
||||
})
|
||||
.done(function (data, textStatus, jqXHR) {
|
||||
if (jqXHR.status === 201) {
|
||||
console.log("201 Created received");
|
||||
window.location.reload();;
|
||||
}
|
||||
})
|
||||
.fail(function (jqXHR) {
|
||||
console.error("Request failed:", jqXHR.status);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const colors = {
|
||||
"0": {
|
||||
"background": "rgba(172, 172, 172, 0.18)",
|
||||
"foreground": "#4b4b4bff",
|
||||
"borderTop": "#5a5a5aff"
|
||||
},
|
||||
"1": {
|
||||
"background": "rgba(59, 130, 246, 0.18)",
|
||||
"foreground": "#1e3a8a",
|
||||
"borderTop": "#1e3a8a"
|
||||
},
|
||||
"2": {
|
||||
"background": "rgba(20, 184, 166, 0.18)",
|
||||
"foreground": "#065f46",
|
||||
"borderTop": "#065f46"
|
||||
},
|
||||
"3": {
|
||||
"background": "rgba(34, 197, 94, 0.18)",
|
||||
"foreground": "#14532d",
|
||||
"borderTop": "#14532d"
|
||||
},
|
||||
"4": {
|
||||
"background": "rgba(163, 230, 53, 0.20)",
|
||||
"foreground": "#365314",
|
||||
"borderTop": "#365314"
|
||||
},
|
||||
"5": {
|
||||
"background": "rgba(245, 158, 11, 0.20)",
|
||||
"foreground": "#92400e",
|
||||
"borderTop": "#92400e"
|
||||
},
|
||||
"6": {
|
||||
"background": "rgba(249, 115, 22, 0.20)",
|
||||
"foreground": "#9a3412",
|
||||
"borderTop": "#9a3412"
|
||||
},
|
||||
"7": {
|
||||
"background": "rgba(244, 63, 94, 0.18)",
|
||||
"foreground": "#9f1239",
|
||||
"borderTop": "#9f1239"
|
||||
},
|
||||
"8": {
|
||||
"background": "rgba(236, 72, 153, 0.18)",
|
||||
"foreground": "#9d174d",
|
||||
"borderTop": "#9d174d"
|
||||
},
|
||||
"9": {
|
||||
"background": "rgba(168, 85, 247, 0.18)",
|
||||
"foreground": "#581c87",
|
||||
"borderTop": "#581c87"
|
||||
},
|
||||
"10": {
|
||||
"background": "rgba(99, 102, 241, 0.18)",
|
||||
"foreground": "#312e81",
|
||||
"borderTop": "#312e81"
|
||||
},
|
||||
"11": {
|
||||
"background": "rgba(100, 116, 139, 0.20)",
|
||||
"foreground": "#1e293b",
|
||||
"borderTop": "#1e293b"
|
||||
},
|
||||
"12": {
|
||||
"background": "rgba(6, 182, 212, 0.18)",
|
||||
"foreground": "#164e63",
|
||||
"borderTop": "#164e63"
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<!--<iframe src="/intern/test.php" width="800px" height="600"></iframe>-->
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<?php
|
||||
}
|
||||
Reference in New Issue
Block a user