New Filestructure for Docker
This commit is contained in:
@@ -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]
|
||||
@@ -0,0 +1,278 @@
|
||||
<?php
|
||||
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
|
||||
$baseDir = $_SERVER['DOCUMENT_ROOT'];
|
||||
|
||||
require_once $baseDir . '/../scripts/session_functions.php';
|
||||
|
||||
ini_wkvs_session(true);
|
||||
|
||||
$csrf_token = $_SESSION['csrf_token'] ?? '';
|
||||
|
||||
$access_granted_wkl = check_user_permission('wk_leitung', true) ?? false;
|
||||
|
||||
if ( ! $access_granted_wkl ) :
|
||||
|
||||
$logintype = 'wk_leitung';
|
||||
|
||||
require $baseDir . '/../scripts/login/login.php';
|
||||
|
||||
$logintype = '';
|
||||
|
||||
else :
|
||||
?>
|
||||
|
||||
<!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">
|
||||
<link rel="stylesheet" href="/intern/css/user.css">
|
||||
|
||||
</head>
|
||||
<body class="displaycontrol">
|
||||
|
||||
<?php
|
||||
|
||||
$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 $db_tabelle_disziplinen 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 . '/../scripts/sidebar/sidebar.php';
|
||||
|
||||
$WSaccesstype = "wk_leitung";
|
||||
$WSaccess = "displaycontrol";
|
||||
$WSaccessPermission = "W";
|
||||
|
||||
?>
|
||||
|
||||
<script>
|
||||
let ws;
|
||||
let firstConnect = true;
|
||||
const RETRY_DELAY = 2000;
|
||||
const csrf_token = "<?= $csrf_token ?>";
|
||||
|
||||
const WSaccesstype = "<?= $WSaccesstype ?>";
|
||||
const WSaccess = "<?= $WSaccess ?>";
|
||||
const WSaccessPermission = "<?= $WSaccessPermission ?>"
|
||||
|
||||
const urlAjaxNewWSToken = '/intern/scripts/ajax-create-ws-token.php';
|
||||
|
||||
async function fetchNewWSToken(accesstype, access, accessPermission) {
|
||||
try {
|
||||
const response = await fetch(urlAjaxNewWSToken, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
csrf_token,
|
||||
accesstype,
|
||||
access,
|
||||
accessPermission
|
||||
})
|
||||
});
|
||||
|
||||
if (response.status === 403) {
|
||||
console.warn("Please Re-Autenithicate. Reloading page...");
|
||||
location.reload();
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!response.ok) return null;
|
||||
|
||||
const data = await response.json();
|
||||
return data.success ? data.token : null;
|
||||
} catch (error) {
|
||||
console.error("Token fetch failed:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function startWebSocket() {
|
||||
console.log("Attempting WebSocket connection...");
|
||||
|
||||
let token;
|
||||
if (firstConnect) {
|
||||
token = '<?= generateWSAdminToken($WSaccesstype, $WSaccess) ?>';
|
||||
} else {
|
||||
token = await fetchNewWSToken(WSaccesstype, WSaccess, WSaccessPermission);
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
console.error("No valid token available. Retrying...");
|
||||
scheduleRetry();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
ws = new WebSocket(`wss://${window.location.hostname}/ws/?access=token&token=${token}`);
|
||||
} catch (err) {
|
||||
console.error("Malformed WebSocket URL", err);
|
||||
scheduleRetry();
|
||||
return;
|
||||
}
|
||||
|
||||
ws.onopen = () => {
|
||||
console.log("WebSocket connected!");
|
||||
if (!firstConnect) {
|
||||
displayMsg(1, "Live Syncronisation wieder verfügbar");
|
||||
}
|
||||
firstConnect = true;
|
||||
};
|
||||
|
||||
ws.onerror = (event) => {
|
||||
console.error("WebSocket error observed.");
|
||||
};
|
||||
|
||||
ws.onclose = (event) => {
|
||||
displayMsg(0, "Live Syncronisation verloren");
|
||||
firstConnect = false;
|
||||
|
||||
scheduleRetry();
|
||||
};
|
||||
}
|
||||
|
||||
function scheduleRetry() {
|
||||
console.log(`Retrying in ${RETRY_DELAY}ms...`);
|
||||
setTimeout(startWebSocket, RETRY_DELAY);
|
||||
}
|
||||
|
||||
startWebSocket();
|
||||
</script>
|
||||
|
||||
<div class="headerDivTrainer">
|
||||
<div class="headingPanelDiv">
|
||||
<h2 class="headingPanel">Anzeigesteuerung</h2>
|
||||
</div>
|
||||
<div class="menuWrapper">
|
||||
<?php sidebarRender('button'); ?>
|
||||
</div>
|
||||
</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>
|
||||
<textarea placeholder="Individuellen Text eingeben..."></textarea>
|
||||
</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="/externe-geraete/display/'. strtolower($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('textarea').val() || '';
|
||||
sendType(typeValue, $form.find('textarea'), ctext);
|
||||
});
|
||||
|
||||
function sendType(typeValue, $element, ctext) {
|
||||
const url = `/intern/scripts/displaycontrol/ajax-update_display_config_json.php`;
|
||||
|
||||
fetch(url,{
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: new URLSearchParams({
|
||||
csrf_token,
|
||||
type: typeValue,
|
||||
ctext
|
||||
})
|
||||
})
|
||||
.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;
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
|
||||
if (!isset($baseDir)) {
|
||||
$baseDir = $_SERVER['DOCUMENT_ROOT'];
|
||||
}
|
||||
|
||||
require_once $baseDir . '/../scripts/session_functions.php';
|
||||
|
||||
ini_wkvs_session();
|
||||
|
||||
check_user_permission('wk_leitung');
|
||||
|
||||
|
||||
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;
|
||||
@@ -0,0 +1,797 @@
|
||||
<?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'];
|
||||
}
|
||||
|
||||
require_once $baseDir . '/../scripts/session_functions.php';
|
||||
|
||||
ini_wkvs_session(true);
|
||||
|
||||
$csrf_token = $_SESSION['csrf_token'] ?? '';
|
||||
|
||||
$access_granted_wkl = check_user_permission('wk_leitung', true) ?? false;
|
||||
|
||||
if ( ! $access_granted_wkl ) :
|
||||
|
||||
$logintype = 'wk_leitung';
|
||||
|
||||
require $baseDir . '/../scripts/login/login.php';
|
||||
|
||||
$logintype = '';
|
||||
|
||||
else :
|
||||
?>
|
||||
<!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 src="/intern/js/chart/chart.js"></script>
|
||||
<link rel="stylesheet" href="/intern/css/user.css">
|
||||
|
||||
<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 class="rechnungen">
|
||||
<?php
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
$currentPage = 'rechnungen';
|
||||
|
||||
require $baseDir . '/../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
|
||||
$db_tabelle_verbuchte_startgebueren o
|
||||
LEFT JOIN
|
||||
$db_tabelle_intern_benutzende 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";
|
||||
case 3:
|
||||
return "Stornierte Rechnungen";
|
||||
default:
|
||||
return "FEHLER BEI NAMENSGENERRIERUNG";
|
||||
}
|
||||
}
|
||||
|
||||
function classStatus(int $int) : string {
|
||||
switch ($int) {
|
||||
case 1:
|
||||
return "inProcess";
|
||||
case 2:
|
||||
return "payed";
|
||||
case 3:
|
||||
return "storniert";
|
||||
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 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4 2a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v24a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1z" stroke="currentColor" stroke-width="1.5"/><path d="M7 5h10M7 8h10M7 11h5" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/>
|
||||
<path stroke="currentColor" stroke-dasharray="2 1" d="M4 16h16"/>
|
||||
<rect x="6" y="18" width="5" height="5" rx=".5" stroke="currentColor" stroke-width="1.2"/>
|
||||
<path fill="currentColor" d="M8 20h1v1H8z"/>
|
||||
<path d="M13 19h4m-4 2h4m-4 2h2" stroke="currentColor" stroke-linecap="round"/>
|
||||
</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 style="width: 200px;">
|
||||
<canvas id="rechnungenOverviewChart"></canvas>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<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>
|
||||
<li data-value="payEntrys">Rechnungen bezahlt</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<input type="submit" name="apply_bulk_action" class="bulkSelectSubmit" value="Anwenden">
|
||||
</div>
|
||||
|
||||
<?php $totalOrderVolumeArray = []; ?>
|
||||
|
||||
|
||||
<?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" class="masterCheckbox" data-type="<?= $status ?>">
|
||||
<span class="checkbox-ui"></span>
|
||||
</label>
|
||||
</th>
|
||||
<th>Rechnungsnummer</th>
|
||||
<th>Status</th>
|
||||
<th>Typ</th>
|
||||
<th>Ersteller</th>
|
||||
<th>Datum</th>
|
||||
<th>Änderung</th>
|
||||
<th>Betrag</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($oneRechnungsStatus as $row) : ?>
|
||||
<tr>
|
||||
<td>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="bulk_ids[]" class="rowCheckbox" data-type="<?= $status ?>" value="<?= intval($row['order_id']) ?>">
|
||||
<span class="checkbox-ui"></span>
|
||||
</label>
|
||||
</td>
|
||||
<td>
|
||||
<?php if (file_exists($baseDir . "/../private-files/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(strftime('%d. %B %Y', (new DateTime($row['edited_at']))->getTimestamp())) ?></td>
|
||||
<td>CHF <?= htmlspecialchars(number_format(floatval($row['preis']), 2)) ?></td>
|
||||
<?php $totalOrderVolume += floatval($row['preis']); ?>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<tr class="totalTr">
|
||||
<td colspan="7" class="totalTd"></td>
|
||||
<td class="totalValue"><span class="<?= classStatus(intval($status)) ?>">CHF <?= htmlspecialchars(number_format(floatval($totalOrderVolume), 2))?></span></td>
|
||||
</tr>
|
||||
<?php $totalOrderVolumeArray[$status] = number_format(floatval($totalOrderVolume), 2); ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endforeach; ?>
|
||||
<div>
|
||||
<button class="buttonDownload" id="downloadRechnungenProtokoll">Protokoll Rechnungsänderungen herunterladen</button>
|
||||
</div>
|
||||
<?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 csrf_token = "<?= $csrf_token ?>";
|
||||
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);
|
||||
});
|
||||
|
||||
$('#downloadRechnungenProtokoll').on('click', function() {
|
||||
const $button = $(this);
|
||||
|
||||
$button.addClass('opacity50');
|
||||
|
||||
const url = '/intern/scripts/rechnungen/ajax-neu_protokoll.php';
|
||||
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: new URLSearchParams({
|
||||
csrf_token
|
||||
})
|
||||
})
|
||||
.then(res => {
|
||||
if (!res.ok) throw new Error('Network response was not ok');
|
||||
|
||||
return res.blob().then(blob => ({ type: 'blob', data: blob }));
|
||||
})
|
||||
.then(result => {
|
||||
if (result.type === 'blob') {
|
||||
const url = window.URL.createObjectURL(result.data);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = "Protokoll_Rechnungen.pdf";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
window.URL.revokeObjectURL(url);
|
||||
displayMsg(1, "Protokoll wurde erfolgreich heuntergeladen.");
|
||||
}
|
||||
$button.removeClass('opacity50');
|
||||
})
|
||||
.catch(err => {
|
||||
displayMsg(0, "Error processing request:", err);
|
||||
console.error("Error processing request:", err);
|
||||
$button.removeClass('opacity50');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
$('#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_by_scor.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
csrf_token,
|
||||
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');
|
||||
|
||||
if (valBulkSelect === 'deleteEntrys' || valBulkSelect === 'payEntrys') {
|
||||
let arrayIds = [];
|
||||
|
||||
$('.rowCheckbox').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);
|
||||
});
|
||||
|
||||
params.append('csrf_token', csrf_token);
|
||||
params.append('action', valBulkSelect);
|
||||
|
||||
|
||||
fetch('/intern/scripts/rechnungen/ajax_update_order_status.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('.masterCheckbox').forEach(master => {
|
||||
const programm = master.dataset.type;
|
||||
const checkboxes = document.querySelectorAll('.rowCheckbox[data-type="' + programm + '"]');
|
||||
|
||||
master.addEventListener("change", function() {
|
||||
checkboxes.forEach(cb => cb.checked = master.checked);
|
||||
});
|
||||
|
||||
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>
|
||||
<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>
|
||||
<script>
|
||||
// Funktion, um den Tooltip-Container zu erstellen oder zurückzugeben
|
||||
const getOrCreateTooltip = (chart) => {
|
||||
// Suche nach einem Element mit einer spezifischen Klasse
|
||||
let tooltipEl = chart.canvas.parentNode.querySelector('.chartjs-tooltip');
|
||||
|
||||
if (!tooltipEl) {
|
||||
tooltipEl = document.createElement('div');
|
||||
// Füge eine spezifische Klasse hinzu, damit wir es später leicht finden können
|
||||
tooltipEl.classList.add('chartjs-tooltip');
|
||||
|
||||
// Setze die Basis-Stile
|
||||
tooltipEl.style.background = 'rgba(0, 0, 0, 0.7)';
|
||||
tooltipEl.style.borderRadius = '3px';
|
||||
tooltipEl.style.color = 'white';
|
||||
tooltipEl.style.opacity = 1;
|
||||
tooltipEl.style.pointerEvents = 'none';
|
||||
tooltipEl.style.position = 'absolute';
|
||||
tooltipEl.style.transform = 'translate(-50%, 0)';
|
||||
tooltipEl.style.transition = 'all .1s ease';
|
||||
|
||||
const table = document.createElement('table');
|
||||
table.style.margin = '0px';
|
||||
// Optional: Füge eine Klasse zum Table hinzu, falls du es global stylen möchtest
|
||||
table.classList.add('chartjs-tooltip-table');
|
||||
|
||||
tooltipEl.appendChild(table);
|
||||
chart.canvas.parentNode.appendChild(tooltipEl);
|
||||
}
|
||||
|
||||
return tooltipEl;
|
||||
};
|
||||
|
||||
const externalTooltipHandler = (context) => {
|
||||
const {chart, tooltip} = context;
|
||||
// Verwende die korrigierte Funktion
|
||||
const tooltipEl = getOrCreateTooltip(chart);
|
||||
|
||||
// Verstecken, wenn der Tooltip keine Daten hat
|
||||
if (tooltip.opacity === 0) {
|
||||
tooltipEl.style.opacity = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Text setzen
|
||||
if (tooltip.body) {
|
||||
const titleLines = tooltip.title || [];
|
||||
const bodyLines = tooltip.body.map(b => b.lines);
|
||||
|
||||
const tableHead = document.createElement('thead');
|
||||
|
||||
titleLines.forEach(title => {
|
||||
const tr = document.createElement('tr');
|
||||
tr.style.borderWidth = 0;
|
||||
|
||||
const th = document.createElement('th');
|
||||
th.style.borderWidth = 0;
|
||||
const text = document.createTextNode(title);
|
||||
th.appendChild(text);
|
||||
tr.appendChild(th);
|
||||
tableHead.appendChild(tr);
|
||||
});
|
||||
|
||||
const tableBody = document.createElement('tbody');
|
||||
bodyLines.forEach((body, i) => {
|
||||
const colors = tooltip.labelColors[i];
|
||||
|
||||
const span = document.createElement('span');
|
||||
span.style.background = colors.backgroundColor;
|
||||
span.style.borderColor = colors.borderColor;
|
||||
span.style.borderWidth = '2px';
|
||||
span.style.marginRight = '10px';
|
||||
span.style.height = '10px';
|
||||
span.style.width = '10px';
|
||||
span.style.display = 'inline-block';
|
||||
|
||||
const tr = document.createElement('tr');
|
||||
tr.style.backgroundColor = 'inherit';
|
||||
tr.style.borderWidth = 0;
|
||||
|
||||
const td = document.createElement('td');
|
||||
td.style.borderWidth = 0;
|
||||
|
||||
const text = document.createTextNode(body);
|
||||
td.appendChild(span);
|
||||
td.appendChild(text);
|
||||
tr.appendChild(td);
|
||||
tableBody.appendChild(tr);
|
||||
});
|
||||
|
||||
const tableRoot = tooltipEl.querySelector('table');
|
||||
|
||||
// Alte Inhalte entfernen
|
||||
while (tableRoot.firstChild) {
|
||||
tableRoot.firstChild.remove();
|
||||
}
|
||||
|
||||
// Neue Inhalte hinzufügen
|
||||
tableRoot.appendChild(tableHead);
|
||||
tableRoot.appendChild(tableBody);
|
||||
}
|
||||
|
||||
// Position berechnen
|
||||
const {offsetLeft: positionX, offsetTop: positionY} = chart.canvas;
|
||||
|
||||
// Tooltip anzeigen, positionieren und Stile setzen
|
||||
tooltipEl.style.opacity = 1;
|
||||
tooltipEl.style.left = positionX + tooltip.caretX + 'px';
|
||||
tooltipEl.style.top = positionY + tooltip.caretY + 'px';
|
||||
tooltipEl.style.font = tooltip.options.bodyFont.string;
|
||||
tooltipEl.style.padding = tooltip.options.padding + 'px ' + tooltip.options.padding + 'px';
|
||||
};
|
||||
|
||||
const ctx = document.getElementById('rechnungenOverviewChart').getContext('2d');
|
||||
new Chart(ctx, {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
labels: ['Pendant', 'Bezahlt'],
|
||||
datasets: [{
|
||||
// Die Daten bleiben hier als Zahlen (z.B. 150, 200)
|
||||
data: ["<?= $totalOrderVolumeArray[1] ?? 0 ?>", "<?= $totalOrderVolumeArray[2] ?? 0 ?>"],
|
||||
backgroundColor: ['#36a2eb', '#4bc0c0']
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'bottom'
|
||||
},
|
||||
tooltip: {
|
||||
enabled: false, // Standard-Tooltip deaktivieren
|
||||
external: externalTooltipHandler, // Ihren Handler verwenden
|
||||
callbacks: {
|
||||
// Diese Funktion wird aufgerufen, um den Text für jeden Datensatz im Tooltip zu formatieren
|
||||
label: function(context) {
|
||||
|
||||
// Der Wert (context.raw) wird als Zahl genommen, mit "CHF" versehen und gerundet
|
||||
const value = context.raw;
|
||||
let label = 'CHF ' + value;
|
||||
return label;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,431 @@
|
||||
<?php
|
||||
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
|
||||
|
||||
if (!isset($baseDir)) {
|
||||
$baseDir = $_SERVER['DOCUMENT_ROOT'];
|
||||
}
|
||||
|
||||
require_once $baseDir . '/../scripts/session_functions.php';
|
||||
|
||||
ini_wkvs_session(true);
|
||||
|
||||
$csrf_token = $_SESSION['csrf_token'] ?? '';
|
||||
|
||||
$access_granted_wkl = check_user_permission('wk_leitung', true) ?? false;
|
||||
|
||||
if (!$access_granted_wkl):
|
||||
|
||||
$logintype = 'wk_leitung';
|
||||
|
||||
require $baseDir . '/../scripts/login/login.php';
|
||||
|
||||
$logintype = '';
|
||||
|
||||
else:
|
||||
?>
|
||||
<!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 - Wettkämpfe</title>
|
||||
|
||||
|
||||
<link rel="stylesheet" href="/intern/css/einstellungen-ai.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">
|
||||
<link rel="stylesheet" href="/intern/css/user.css">
|
||||
<link rel="stylesheet" href="/externe-geraete/css/display.css">
|
||||
|
||||
<script src="/intern/js/ace/ace.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="/intern/css/coloris/coloris.min.css" />
|
||||
<script src="/intern/js/coloris/coloris.min.js"></script>
|
||||
|
||||
</head>
|
||||
|
||||
<body class="wettkaempfe">
|
||||
<?php
|
||||
|
||||
|
||||
require $baseDir . '/../scripts/db/db-functions.php';
|
||||
require $baseDir . '/../scripts/db/db-tables.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 = 'wettkaempfe';
|
||||
|
||||
require $baseDir . '/../scripts/sidebar/sidebar.php';
|
||||
|
||||
$wettkaempfe = db_select($mysqli, $db_tabelle_wettkaempfe, '*', '', [], 'id ASC');
|
||||
|
||||
$current_wk_id = (int) db_get_variable($mysqli, $db_tabelle_var, ['current_wk_id']) ?: 0;
|
||||
|
||||
?>
|
||||
<section class="bgSection">
|
||||
<?php sidebarRender('modal'); ?>
|
||||
<div class="headerDivTrainer">
|
||||
<div class="headingPanelDiv">
|
||||
<h2 class="headingPanel">Wettkämpfe</h2>
|
||||
</div>
|
||||
<div class="menuWrapper">
|
||||
<?php sidebarRender('button'); ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dashboardGrid">
|
||||
|
||||
<div class="containerDiv">
|
||||
<div>
|
||||
<h3 class="containerHeading">Wettkämpfe</h3>
|
||||
|
||||
<table id="programmTable" class="wkvsTabelle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Wettkampf ID</th>
|
||||
<th>Name</th>
|
||||
<th>Slug</th>
|
||||
<th>Status</th>
|
||||
<th>Löschen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($wettkaempfe as $s_wk) :
|
||||
$aktiv = ((int) $s_wk['id'] === $current_wk_id); ?>
|
||||
<tr>
|
||||
<td><?= (int) $s_wk['id'] ?></td>
|
||||
<td>
|
||||
<input class="ajaxInput" data-type="name" data-id="<?= (int) $s_wk['id'] ?>" value="<?= htmlspecialchars($s_wk['name'] ?? '') ?>">
|
||||
</td>
|
||||
<td>
|
||||
<input class="ajaxInput" data-type="slug" data-id="<?= (int) $s_wk['id'] ?>" value="<?= htmlspecialchars($s_wk['slug'] ?? '') ?>">
|
||||
</td>
|
||||
<td>
|
||||
<span class="statusSpan <?= $aktiv ? 'activeWk' : 'inactiveWk' ?>"><?= $aktiv ? 'Aktiv' : 'Inaktiv' ?></span>
|
||||
<span class="activateSpan <?= $aktiv ? 'hidden' : '' ?>" data-id="<?= (int) $s_wk['id'] ?>">Aktivieren</span>
|
||||
</td>
|
||||
<td>
|
||||
<button class="deleteWk deleteBtn" data-id="<?= (int) $s_wk['id'] ?>">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22"
|
||||
viewBox="0 0 24 24" fill="none" stroke="var(--ui-danger)"
|
||||
stroke-width="1.75" 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>
|
||||
<line x1="10" y1="11" x2="10" y2="17"></line>
|
||||
<line x1="14" y1="11" x2="14" y2="17"></line>
|
||||
</svg>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<tr class="addRow">
|
||||
<td></td>
|
||||
<td>
|
||||
<input type="text" required placeholder="Bezeichnung" class="inputNewWkName">
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" required placeholder="Bsp. wkvs-2026" class="inputNewWkSlug">
|
||||
</td>
|
||||
<td colspan="2">
|
||||
<button type="submit" class="blackBtn neuWkBtn">Neuen Wettkampf erstellen</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="msgDiv"></div>
|
||||
|
||||
</section>
|
||||
|
||||
<script>
|
||||
|
||||
const csrf_token = "<?= $csrf_token ?>";
|
||||
|
||||
$('.neuWkBtn').on('click', function () {
|
||||
const $btn = $(this);
|
||||
const $row = $btn.closest(".addRow");
|
||||
const $nameInput = $row.find(".inputNewWkName");
|
||||
const $slugInput = $row.find(".inputNewWkSlug");
|
||||
|
||||
if ($nameInput.length !== 1 || $slugInput.length !== 1) return;
|
||||
|
||||
const name = $nameInput.val().trim();
|
||||
|
||||
if (name === '') {
|
||||
displayMsg(2, "Der Wettkampfname darf nicht leer sein");
|
||||
return;
|
||||
}
|
||||
|
||||
const slug = $slugInput.val().trim();
|
||||
|
||||
if (slug === '') {
|
||||
displayMsg(2, "Der Slug darf nicht leer sein");
|
||||
return;
|
||||
}
|
||||
if (/\s/.test(slug)) {
|
||||
displayMsg(2, "Der Slug darf keine Leerzeichen enthalten");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!/^[a-z0-9-]+$/.test(slug)) {
|
||||
displayMsg(2, "Der Slug darf nur Kleinbuchstaben, Zahlen und Bindestriche enthalten");
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: '/intern/scripts/wettkaempfe/ajax-add-wettkampf.php',
|
||||
type: "POST",
|
||||
data: {
|
||||
csrf_token,
|
||||
name,
|
||||
slug
|
||||
},
|
||||
success: function(response) {
|
||||
if (!response.success) {
|
||||
displayMsg(0, response.message || "Der Wettkampf konnte nicht hinzugefügt werden");
|
||||
return;
|
||||
}
|
||||
|
||||
const $newRow = $('<tr>', {});
|
||||
|
||||
// 1. ID Column (Plain text)
|
||||
const $tdId = $('<td>', { text: response.id });
|
||||
|
||||
// 2. Name Column (with nested <input>)
|
||||
const $tdName = $('<td>');
|
||||
const $inputName = $('<input>', {
|
||||
class: 'ajaxInput',
|
||||
'data-type': 'name',
|
||||
'data-id': response.id,
|
||||
val: name // Sets the input value securely
|
||||
});
|
||||
$tdName.append($inputName);
|
||||
|
||||
// 3. Slug Column (with nested <input>)
|
||||
const $tdSlug = $('<td>');
|
||||
const $inputSlug = $('<input>', {
|
||||
class: 'ajaxInput',
|
||||
'data-type': 'slug',
|
||||
'data-id': response.id,
|
||||
val: slug // Sets the input value securely
|
||||
});
|
||||
$tdSlug.append($inputSlug);
|
||||
|
||||
// 4. Empty Column
|
||||
const $tdStatus = $('<td>', {});
|
||||
|
||||
const $spanStatus = $('<span>', {
|
||||
text: 'Inaktiv',
|
||||
class: "statusSpan inactiveWk"
|
||||
});
|
||||
|
||||
const $spanActivate = $('<span>', {
|
||||
text: 'aktivieren',
|
||||
'data-id': response.id,
|
||||
class: "activateSpan"
|
||||
});
|
||||
|
||||
$tdStatus.append($spanStatus, $spanActivate);
|
||||
|
||||
// 5. Action Column (Delete button with SVG)
|
||||
const $tdAction = $('<td>', {});
|
||||
|
||||
const $deleteBtn = $('<button>', {
|
||||
class: 'deleteWk deleteBtn',
|
||||
'data-id': response.id
|
||||
});
|
||||
|
||||
const $svgIcon = $(`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22"
|
||||
viewBox="0 0 24 24" fill="none" stroke="var(--ui-danger)"
|
||||
stroke-width="1.75" 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>
|
||||
<line x1="10" y1="11" x2="10" y2="17"></line>
|
||||
<line x1="14" y1="11" x2="14" y2="17"></line>
|
||||
</svg>
|
||||
`);
|
||||
|
||||
$deleteBtn.append($svgIcon);
|
||||
$tdAction.append($deleteBtn);
|
||||
|
||||
// 6. Assemble everything
|
||||
$newRow.append($tdId, $tdName, $tdSlug, $tdStatus, $tdAction);
|
||||
|
||||
// 7. Insert into the DOM
|
||||
// Note: If you want to insert the new row *before* an existing row ($row), use this:
|
||||
$newRow.insertBefore($row);
|
||||
|
||||
displayMsg(1, "Neuer Wettkampf hinzugefügt");
|
||||
|
||||
},
|
||||
error: () => {
|
||||
displayMsg(0, "Fehler beim Hinzufügen des Wettkampfes");
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
$(document).on('click', '.deleteWk', async function () {
|
||||
const $btn = $(this);
|
||||
const $row = $btn.closest("tr");
|
||||
const name = $row.find('.ajaxInput[data-type="name"]').val() || '';
|
||||
|
||||
if (!await displayConfirm(`Wettkampf "${name}" wirklich unwiederruflich löschen? Diese Aktion löscht alle Ergebnisse dieses Wettkampfes!`)) return;
|
||||
|
||||
const dataId = $btn.data('id');
|
||||
|
||||
$.ajax({
|
||||
url: '/intern/scripts/wettkaempfe/ajax-delete-wettkampf.php',
|
||||
type: "DELETE",
|
||||
data: {
|
||||
csrf_token,
|
||||
id: dataId
|
||||
},
|
||||
success: function(response) {
|
||||
if (!response.success) {
|
||||
displayMsg(0, response.message || "Der Wettkampf konnte nicht gelöscht werden");
|
||||
return;
|
||||
}
|
||||
|
||||
$row.remove();
|
||||
|
||||
displayMsg(1, "Wettkampf gelöscht");
|
||||
|
||||
},
|
||||
error: () => {
|
||||
displayMsg(0, "Fehler beim Löschen des Wettkampfes");
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
$(document).on('change', '.ajaxInput', function () {
|
||||
const $input = $(this);
|
||||
const value = $input.val().trim();
|
||||
const inputType = $input.data('type');
|
||||
const dataId = $input.data('id');
|
||||
|
||||
if (value === '') {
|
||||
displayMsg(2, "Dieses Feld darf nicht leer sein");
|
||||
return;
|
||||
}
|
||||
|
||||
if (inputType === "slug") {
|
||||
if (/\s/.test(value)) {
|
||||
displayMsg(2, "Der Slug darf keine Leerzeichen enthalten");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!/^[a-z0-9-]+$/.test(value)) {
|
||||
displayMsg(2, "Der Slug darf nur Kleinbuchstaben, Zahlen und Bindestriche enthalten");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: '/intern/scripts/wettkaempfe/ajax-update-wettkampf.php',
|
||||
type: "POST",
|
||||
data: {
|
||||
csrf_token,
|
||||
id: dataId,
|
||||
value,
|
||||
inputType
|
||||
},
|
||||
success: function(response) {
|
||||
if (!response.success) {
|
||||
displayMsg(0, response.message || "Wert konnte nicht geändert werden");
|
||||
return;
|
||||
}
|
||||
|
||||
displayMsg(1, "Wert geändert");
|
||||
|
||||
},
|
||||
error: () => {
|
||||
displayMsg(0, "Fehler beim Ändern des Wertes");
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
$(document).on('click', '.activateSpan', async function() {
|
||||
const $btn = $(this);
|
||||
const dataId = $btn.data('id');
|
||||
|
||||
if (!await displayConfirm(`Das wechseln des Wettkampfes lädt alle Daten in der Kampfrichteransicht und RankLive neu.`)) return;
|
||||
|
||||
$.ajax({
|
||||
url: '/intern/scripts/wettkaempfe/ajax-update-aktiv-wettkampf.php',
|
||||
type: "POST",
|
||||
data: {
|
||||
csrf_token,
|
||||
id: dataId
|
||||
},
|
||||
success: function(response) {
|
||||
if (!response.success) {
|
||||
displayMsg(0, response.message || "Aktiver Wettkampf konnte nicht geändert werden");
|
||||
return;
|
||||
}
|
||||
|
||||
const $tbody = $btn.closest('tbody');
|
||||
|
||||
// 1. Show ALL activation triggers in the table again
|
||||
$tbody.find('.activateSpan').removeClass('hidden');
|
||||
|
||||
// 2. Hide only the trigger that was just clicked
|
||||
$btn.addClass('hidden');
|
||||
|
||||
// 3. Find the single currently active swatch and turn it inactive
|
||||
$tbody.find('.statusSpan.activeWk')
|
||||
.removeClass('activeWk')
|
||||
.addClass('inactiveWk')
|
||||
.text('Inaktiv');
|
||||
|
||||
// 4. Find the swatch in the CURRENT row and turn it active
|
||||
// (Using .closest('tr') ensures we look inside the same row)
|
||||
$btn.closest('tr')
|
||||
.find('.statusSpan')
|
||||
.removeClass('inactiveWk')
|
||||
.addClass('activeWk')
|
||||
.text('Aktiv');
|
||||
|
||||
displayMsg(1, "Aktiver Wettkampf wurde gewechselt");
|
||||
|
||||
},
|
||||
error: () => {
|
||||
displayMsg(0, "Fehler beim Ändern des aktiven Wettkampfes");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
<?php endif; ?>
|
||||
Reference in New Issue
Block a user