Files
WKVS/html/intern/wk-leitung/riegeneinteilung.php
T

1200 lines
44 KiB
PHP

<?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';
} 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, $db_tabelle_disziplinen, "name, start_index, id", "", [], "start_index ASC");
$allAbt = db_select($mysqli, $db_tabelle_gruppen, "name, id, order_index", "", [], "order_index ASC");
$zeitplanTypes = db_select($mysqli, $db_tabelle_zeitplan_types, '*', '', [], '`display_index` ASC');
$all_zeiten = db_select($mysqli, $db_tabelle_gruppen_zeiten, '*');
$indexed_zeitplanTypes = array_column($zeitplanTypes, null, 'id');
$indexed_all_zeiten = [];
foreach ($all_zeiten as $zeit) {
$indexed_all_zeiten[$zeit['abt_id']][] = $zeit;
}
// $allGeraete[] = ['name' => 'null', 'id' => 'null'];
// $allAbt[] = ['name' => 'null', 'id' => 'null'];
$stmt = $mysqli->prepare("SELECT
t.id,
t.name,
t.vorname,
t.programm,
p.id AS programm_id,
t.verein,
t.bezahlt,
t.bezahltoverride,
ta.turnerin_index AS start_index,
a.name AS abteilung,
g.name AS geraet
FROM $db_tabelle_teilnehmende t
LEFT JOIN $db_tabelle_teilnehmende_gruppen ta ON ta.turnerin_id = t.id
LEFT JOIN $db_tabelle_gruppen a ON a.id = ta.abteilung_id
LEFT JOIN $db_tabelle_disziplinen g ON g.id = ta.geraet_id
LEFT JOIN $db_tabelle_kategorien p ON p.programm = t.programm
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] = [];
}
}
$countAbt = count($allAbt);
?>
<!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="stylesheet" href="/intern/css/custom-msg-display.css">
<link rel="stylesheet" href="/intern/css/custom-select.css">
<link rel="icon" type="png" href="/intern/img/icon.png">
<link href="/files/fonts/fonts.css" rel="stylesheet">
<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>
<script src="/intern/js/custom-msg-display.js"></script>
<script src="/intern/js/custom-select.js"></script>
<link rel="stylesheet" href="/intern/css/user.css">
<!-- Flatpickr for modern date picking -->
<link rel="stylesheet" href="/intern/css/flatpickr/flatpickr.min.css">
<script src="/intern/js/flatpickr/flatpickr.min.js"></script>
<script src="/intern/js/flatpickr/de.min.js"></script>
<script src="/intern/js/flatpickr/fr.min.js"></script>
<script src="/intern/js/flatpickr/it.min.js"></script>
</head>
<body class="riegeneinteilung">
<?php
$currentPage = 'riegeneinteilung';
require $baseDir . '/../scripts/sidebar/sidebar.php';
?>
<?php sidebarRender('modal'); ?>
<div class="internMenuDiv">
<div class="innerInternMenuDiv">
<div>
<h3 class="normalTitle">Nicht eingeteilte Personen</h3>
<?php
$arrayRest = [];
foreach ($noAbt as $level1) {
foreach ($level1 as $level2) {
foreach ($level2 as $entry) {
$arrayRest[] = $entry;
}
}
}
$count = count($arrayRest); ?>
<div class="invalidAbtDiv">
<table class="geraet-table" data-abteilung="0">
<thead>
<tr>
<th>
Keine Valide Abt
<i><span>(<?= (int) $count ?>)</span></i>
</th>
</tr>
</thead>
<tbody>
<?php foreach ($arrayRest as $entry) : ?>
<tr class="turnerin-row"
data-id="<?= (int) $entry['id'] ?>"
data-verein="<?= htmlspecialchars($entry['verein']) ?>"
data-programm="<?= htmlspecialchars($entry['programm']) ?>"
data-programm-id="<?= (int) $entry['programm_id'] ?>">
<td><?= htmlspecialchars($entry['vorname'] . ' ' . $entry['name'] . ' - ' . $entry['programm'] . ' - ' . $entry['verein']) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<div class="headerOptionConatiner">
<label>Anzahl Abteilungen</label>
<input type="number" min="1" max="<?= count($allTurnerinnen) ?>" step="1" id="anzAbt" value="<?= $countAbt ?>">
<h3 class="normalTitle">Automatische Riegeneinteilung</h3>
<button class="autoEinteilung blackBtn" data-operation="overrite">
Alles überschreiben
</button>
<button class="autoEinteilung blackBtn" data-operation="add">
Abteilungsnamen beibehalten
</button>
</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";
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);
});
</script>
</div>
<div class="mainContentDiv">
<div class="abtWraperDiv">
<?php
foreach ($allAbt as $abt) :
$abtName = $abt['name'];
$abtId = $abt['id'];
$abtIndex = $abt['order_index'];
$max_button = $countAbt === intval($abtIndex) ? "disabled" : "";
$min_button = 1 === intval($abtIndex) ? "disabled" : "";
?>
<div class="singleAbtDiv" style="display:flex; flex-direction:column;">
<div class="headerAbt">
<h2>Abteilung: <input type="text" class="textInputAbtTitel abtTitelInput" data-abt-id="<?= (int) $abtId ?>"
value="<?= htmlspecialchars($abtName) ?>"></h2>
<h2>Index: <input type="number" class="textInputAbtTitel abtIndexInput" data-abt-id="<?= (int) $abtId ?>"
min="1" max="<?= $countAbt ?>" value="<?= (int) $abtIndex ?>"></h2>
<div class="buttonMoveDiv">
<button class="downAptIndex buttonMove <?= $min_button ?>" data-abt-id="<?= (int) $abtId ?>">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="18 15 12 9 6 15"></polyline>
</svg>
</button>
<button class="upAptIndex buttonMove <?= $max_button ?>" data-abt-id="<?= (int) $abtId ?>">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</button>
</div>
<button class="deleteProgramm" data-abt-id="<?= (int) $abtId ?>">
<!-- 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>
<div class="subHeaderAbt" data-abt-id="<?= (int) $abtId ?>">
<svg width="20" height="20" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" class="addTimetypeButton">
<g class="drawing-elements">
<circle cx="50.333343505859375" cy="50" r="48" fill="none" stroke="currentcolor" stroke-width="4"
opacity="1" id="circle-1779630104235" class="" />
<line x1="25" y1="49.74477767944336" x2="75" y2="50" fill="none" stroke="currentcolor" stroke-width="4"
stroke-linecap="round" stroke-linejoin="round" opacity="1" id="line-1779630151280" class="" />
<line x1="50" y1="25" x2="50" y2="75" fill="none" stroke="currentcolor" stroke-width="4"
stroke-linecap="round" stroke-linejoin="round" opacity="1" id="line-1779630187814" class="" />
</g>
</svg>
<div class="customSelect hidden" id="selectedOption">
<button type="button" id="selectTriggerZeitplantype" class="selectTrigger" aria-expanded="false">
<span class="selectLabel placeholder">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="selectOptions">
<?php foreach ($zeitplanTypes as $zpt): ?>
<li data-value="<?= (int) $zpt['id'] ?>" class="">
<?= htmlspecialchars($zpt['name']) ?>
</li>
<?php endforeach; ?>
</ul>
<input type="hidden" name="zeitplanType" class="selectValue selectValueZeitplanTypes" value="">
</div>
<?php if (isset($indexed_all_zeiten[$abtId])) :
$zeiten_entry = $indexed_all_zeiten[$abtId];
foreach ($zeiten_entry as $se):
if (!isset($indexed_zeitplanTypes[$se['zeitplan_id']]))
continue;
$zeitplan_type = $indexed_zeitplanTypes[$se['zeitplan_id']];
?>
<span data-type-id="<?= (int) $se['zeitplan_id'] ?>">
<?= htmlspecialchars($zeitplan_type['name']) ?> von
<input type="text" class="timePicker flatpickr-input" data-type="start" readonly="readonly"
value="<?= htmlspecialchars($se['start_time']) ?? '' ?>">
<?php if (intval($zeitplan_type['has_endtime']) === 1): ?>
bis
<input type="text" class="timePicker flatpickr-input" data-type="end" readonly="readonly"
value="<?= htmlspecialchars($se['end_time']) ?? '' ?>">
<?php endif; ?>
<svg width="15" height="15" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" class="removeTimetypeButton">
<g class="drawing-elements">
<circle cx="50.333343505859375" cy="50" r="48" fill="none" stroke="currentcolor" stroke-width="4"
opacity="1" id="circle-1779630104235" class="" />
<line x1="25" y1="49.74477767944336" x2="75" y2="50" fill="none" stroke="currentcolor"
stroke-width="4" stroke-linecap="round" stroke-linejoin="round" opacity="1" id="line-1779630151280"
class="" />
</g>
</svg>
</span>
<?php endforeach;
endif; ?>
</div>
<div class="singleAbtDataContainer">
<?php foreach ($allGeraete as $geraet) :
$geraetName = $geraet['name'];
$geraetId = $geraet['id'];
// Safely resolve entries
$entries = $grouped[$abtName][$geraetName] ?? [];
$count = count($entries); ?>
<table class="geraet-table"
data-abteilung-id="<?= (int) $abtId ?>"
data-geraet-id="<?= (int) $geraetId ?>">
<thead>
<tr>
<th>
Gerät: <?= htmlspecialchars($geraetName)?>
<i><span>(<?= $count ?>)</span></i>
</th>
</tr>
</thead>
<tbody>
<?php foreach ($entries as $entry):
$bezahlt = $entry['bezahltoverride'] == 5 || ($entry['bezahlt'] == 2 && $entry['bezahltoverride'] != 3 && $entry['bezahltoverride'] != 4);
$nichtbezahlt_string = !$bezahlt ? ' <span class="nichtBezahlt">*</span>' : '';
?>
<tr class="turnerin-row" data-id="<?= (int) $entry['id'] ?>"
data-verein="<?= htmlspecialchars($entry['verein']) ?>"
data-programm="<?= htmlspecialchars($entry['programm']) ?>"
data-programm-id="<?= (int) $entry['programm_id'] ?>"
data-start-index="<?= htmlspecialchars($entry['start_index']) ?>" data-payed="<?= (int) $bezahlt ?>">
<td>
<b> <?= htmlspecialchars($entry['verein']) ?></b><?= $nichtbezahlt_string ?>,<br>
<?= htmlspecialchars($entry['vorname']) ?>
<?= htmlspecialchars($entry['name']) ?>,
<?= htmlspecialchars($entry['programm']) ?>
</td>
</tr>
<?php endforeach;
$grouped[$abtName][$geraetName] = []; ?>
</tbody>
</table>
<?php endforeach; ?>
</div>
</div>
<?php endforeach; ?>
</div>
<p>Alle Personen, welche noch keine Startgebühren bezahlt haben werden mit einem <span
class="nichtBezahlt">*</span> gekennzeichnet. Diesen Personen können in der Kampfrichteransicht bis zum Bezahlen der Startgebühren keine Werte gegeben werden.</p>
<div class="buttonsDiv">
<button id="groupByVereinProgramm" class="blackBtn">
Vereine gruppiert anzeigen
</button>
<button id="ungroup" class="blackBtn hidden">
Einzelene Personen anzeigen
</button>
</div>
</div>
<script>
$(function () {
const csrf_token = "<?= $csrf_token ?>";
const countAbt = <?= count($allAbt) ?>;
const zeitplanTypes = <?= json_encode($indexed_zeitplanTypes) ?>;
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) {
return progColors[programm] ?? progColors[0];
}
$(".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 newTbody = item.closest("tbody");
const newTable = item.closest(".geraet-table");
const newAbt = newTable.data("abteilung-id");
const newGeraet = newTable.data("geraet-id");
const oldTable = item.data("sourceTbody");
const oldAbt = oldTable.closest(".geraet-table").data("abteilung-id");
const oldGeraet = oldTable.closest(".geraet-table").data("geraet-id");
item.removeClass("dragging");
let turnerinIds = [];
let neuFirstIndex = 0;
if (item.hasClass("group-row")) {
// --- GROUPED DROP ---
// 1. Get all gymnast IDs inside this group row
turnerinIds = item.find(".turnerin-row").map(function () {
return parseInt($(this).data("id"));
}).get();
// 2. Calculate how many gymnast rows exist ABOVE this dropped group
// We look at all turnerin-rows in the table and see which are before our item
const allGymnastsInTable = newTable.find(".turnerin-row");
const firstGymnastInGroup = item.find(".turnerin-row").first();
// The index of the first gymnast in our group relative to all others in the table
neuFirstIndex = allGymnastsInTable.index(firstGymnastInGroup) + 1;
} else {
// --- SINGLE ROW DROP ---
turnerinIds = [parseInt(item.data("id"))];
// Simply find the index of this row among all other gymnast rows in the tbody
// +1 because SQL indices start at 1, and .index() is 0-based
neuFirstIndex = newTable.find(".turnerin-row").index(item) + 1;
}
// === AJAX UPDATE ===
$.ajax({
url: "/intern/scripts/riegeneinteilung/ajax_update_turnerin.php",
method: "POST",
data: {
csrf_token,
turnerin_ids: turnerinIds,
neuAbteilungId: newAbt,
neuGeraetId: newGeraet,
altAbteilungId: oldAbt,
altGeraetId: oldGeraet,
neuFirstIndex: neuFirstIndex
},
success: function (response) {
responseArray = typeof response === "string" ? JSON.parse(response) : response;
if (responseArray.success) {
displayMsg(1, "Startindex erfolgreich aktualisiert.");
// Update New Table Indexes
if (responseArray.new_new_start_indexes) {
// Object.entries is safer if the response is an associative array/object
Object.entries(responseArray.new_new_start_indexes).forEach(([id, index]) => {
// Use .attr to ensure the DOM reflects the change for your CSS or selectors
newTable.find(`[data-id="${id}"]`).attr('data-start-index', index).data('startIndex', index);
});
}
// Update Old Table Indexes
if (responseArray.old_new_start_indexes) {
Object.entries(responseArray.old_new_start_indexes).forEach(([id, index]) => {
oldTable.find(`[data-id="${id}"]`).attr('data-start-index', index).data('startIndex', index);
});
}
// Now that data attributes are updated, trigger the visual re-sort
orderEntriesByStartIndex();
// Update the UI labels/counters
updateCounters();
if (item.hasClass("group-row")) { groupByVereinProgramm(); }
}
},
error: function () {
displayMsg(0, "Fehler beim Aktualisieren der Turnerin.");
}
});
// Placeholder Logic
newTbody.find(".empty-drop-row").remove();
if (oldTable.find("tr:not(.ui-sortable-placeholder)").length === 0) {
oldTable.append('<tr class="empty-drop-row"><td>&nbsp;</td></tr>');
}
},
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;
}
function orderEntriesByStartIndex() {
$(".geraet-table tbody").each(function () {
const tbody = $(this);
const rows = tbody.children('.turnerin-row').not('.empty-drop-row').get();
rows.sort(function (a, b) {
const indexA = parseFloat($(a).attr("data-start-index")) || 0;
const indexB = parseFloat($(b).attr("data-start-index")) || 0;
if (indexA < indexB) return -1;
if (indexA > indexB) return 1;
return 0;
});
$.each(rows, function (index, row) {
tbody.append(row);
});
tbody.append(tbody.find('.empty-drop-row'));
});
}
$(".turnerin-row").each(function () {
let color = getColorForProgramm($(this).data("programm-id"));
$(this).css({
background: color.background,
color: color.foreground
});
});
// Initial calculation on page load
updateCounters();
function groupByVereinProgramm() {
orderEntriesByStartIndex();
$(".geraet-table tbody").each(function () {
const tbody = $(this);
const groups = {};
let diffCounter = 0;
let lastVerein = null;
let lastProgramm = null;
tbody.find(".turnerin-row").each(function () {
const row = $(this);
if (lastVerein !== row.data("verein") || lastProgramm !== row.data("programm")) {
diffCounter++;
lastVerein = row.data("verein");
lastProgramm = row.data("programm");
}
if (!groups[diffCounter]) {
groups[diffCounter] = [];
}
if (row.data("payed") === 0) {
groups[diffCounter]['notPayed'] = true;
}
groups[diffCounter].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 programmId = rows[0].data("programm-id");
const color = getColorForProgramm(programmId);
const nichtbezahlt_string = rows['notPayed'] ? ' <span class="nichtBezahlt">*</span>' : '';
const groupRow = $(`
<div class="group-row" data-verein="${verein}" data-programm="${programm}" data-programm-id="${programmId}">
<td>
<div class="group-header"
style="
background:${color.background};
color:${color.foreground};
border: 1px solid ${color.borderTop};
">
<span><b>${verein}${nichtbezahlt_string}</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);
$('#groupByVereinProgramm').addClass('hidden');
$('#ungroup').removeClass('hidden');
});
});
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();
});
});
$('#groupByVereinProgramm').removeClass('hidden');
$('#ungroup').addClass('hidden');
updateCounters();
orderEntriesByStartIndex();
});
$(document).on("click", ".deleteProgramm", async function (e) {
e.preventDefault();
if (await displayConfirm("Bitte bestätige die Löschung der Abteilung")) {
$.ajax({
url: "/intern/scripts/riegeneinteilung/ajax_delete_specific_abt.php",
method: "POST",
data: {
csrf_token,
abtId: $(this).data('abt-id')
}
})
.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: {
csrf_token,
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", async function () {
const operation = $(this).data("operation");
const confirmMessage = operation === "overrite"
? "Bist du sicher, dass du eine automatische Riegeneinteilung durchführen möchtest? Alle bestehenden Abteilungen werden überschrieben. Bitte nutze die andere Option, wenn du die bestehenden Abteilungsnamen behalten möchtest."
: "Bist du sicher, dass du eine automatische Riegeneinteilung durchführen möchtest? Alle bestehenden Abteilungsnamen werden beibehalten. Alle Personen werden jedoch neu zugeordnet. Bitte nutze die andere Option, wenn du die bestehenden Abteilungen komplett überschreiben möchtest.";
if (!await displayConfirm(confirmMessage)) {
return;
}
$.ajax({
url: "/intern/scripts/riegeneinteilung/ajax_auto_riegeneinteilung.php",
method: "POST",
data: {
csrf_token,
operation
}
})
.done(function (data, textStatus, jqXHR) {
if (jqXHR.status === 200) {
console.log("200 OK received");
window.location.reload();;
}
})
.fail(function (jqXHR) {
console.error("Request failed:", jqXHR.status);
});
});
$('body').on('change', '.abtTitelInput', function () {
const abtId = $(this).data('abt-id');
const newName = $(this).val().trim();
$.ajax({
url: "/intern/scripts/riegeneinteilung/ajax_change_name_of_specific_abt.php",
method: "POST",
data: {
csrf_token,
abt: abtId,
newName: newName
},
success: function (response) {
responseArray = typeof response === "string" ? JSON.parse(response) : response;
if (responseArray.success) {
displayMsg(1, "Abteilungsname erfolgreich aktualisiert.");
}
},
error: function () {
displayMsg(0, "Fehler beim Aktualisieren des Abteilungsnamens.");
}
});
});
$('body').on('change', '.abtIndexInput', function () {
const abtId = $(this).data('abt-id');
const newOrderIndex = $(this).val();
if (newOrderIndex < 1) {
displayMsg(0, "Abteilungsindex muss grösser als 0 sein.");
return;
} else if (newOrderIndex > countAbt) {
displayMsg(0, "Abteilungsindex kann nicht größer als die Anzahl der Abteilungen sein.");
return;
}
$.ajax({
url: "/intern/scripts/riegeneinteilung/ajax_change_order_index_of_specific_abt.php",
method: "POST",
data: {
csrf_token,
abt: abtId,
newOrderIndex: newOrderIndex
},
success: function (response) {
responseArray = typeof response === "string" ? JSON.parse(response) : response;
if (responseArray.success) {
displayMsg(1, "Abteilungsindex erfolgreich aktualisiert.");
const newIndexes = responseArray.newIndexes;
const wraper = $('.abtWraperDiv');
const inputs = wraper.find('.abtIndexInput');
for (const [abtId, newIndex] of Object.entries(newIndexes)) {
const input = inputs.filter(`[data-abt-id="${abtId}"]`);
input.val(newIndex);
}
const rows = wraper.children('.singleAbtDiv').get();
rows.sort(function (a, b) {
const indexA = parseFloat($(a).find('.abtIndexInput').val()) || 0;
const indexB = parseFloat($(b).find('.abtIndexInput').val()) || 0;
if (indexA < indexB) return -1;
if (indexA > indexB) return 1;
return 0;
});
$.each(rows, function (index, row) {
wraper.append(row);
});
updateAbtIndexButtons();
}
},
error: function () {
displayMsg(0, "Fehler beim Aktualisieren des Abteilungsindex.");
}
});
});
$('body').on('click', '.upAptIndex', function () {
const $button = $(this);
const $input = $button.closest('.headerAbt').find('.abtIndexInput');
const val = parseInt($input.val()) || 1;
const max = parseInt($input.attr('max'));
if (val >= max) return;
const newVal = val + 1;
$input.val(newVal).trigger('change');
});
$('body').on('click', '.downAptIndex', function () {
const $button = $(this);
const $input = $button.closest('.headerAbt').find('.abtIndexInput');
const val = parseInt($input.val()) || 1;
if (val <= 1) return;
const newVal = val - 1;
$input.val(newVal).trigger('change');
});
function updateAbtIndexButtons() {
$('.singleAbtDiv').each(function () {
const $div = $(this);
const index = parseInt($div.find('.abtIndexInput').val()) || 1;
const max = parseInt($div.find('.abtIndexInput').attr('max'));
$div.find('.upAptIndex').toggleClass("disabled", index >= max);
$div.find('.downAptIndex').toggleClass("disabled", index <= 1);
});
}
//
const allAbtDivs = $('.singleAbtDiv');
allAbtDivs.each((i, el) => {
toggleAddTimeBtn($(el));
});
function toggleAddTimeBtn($div) {
let remainingZeitplanTypes = JSON.parse(JSON.stringify(zeitplanTypes));
const $items = $div.find('span');
const $li = $div.find('.customSelect li');
$li.show();
$items.each((i, el) => {
const $span = $(el);
const id = $span.attr('data-type-id');
$li.filter(`[data-value="${id}"]`).hide();
delete remainingZeitplanTypes[id];
});
const hasRemainingTypes = Object.keys(remainingZeitplanTypes).length > 0;
if (hasRemainingTypes == true) {
$div.find('.addTimetypeButton').removeClass('hidden');
} else {
$div.find('.addTimetypeButton').addClass('hidden');
}
}
$('body').on('click', '.addTimetypeButton', function () {
const $button = $(this);
const $customSelect = $button.siblings('.customSelect');
$customSelect.find('.selectLabel').addClass('placeholder').text('Bitte auswählen…');
$customSelect.find('li').removeClass('selected');
$customSelect.toggleClass('hidden');
});
$('body').on('change', '.selectValueZeitplanTypes', function () {
const $input = $(this);
const val = $input.val();
const type = zeitplanTypes[val] ?? null;
if (type == null) {
displayMsg(0, "Ungültiger Zeitplantyp ausgewählt.");
return;
}
const $span = $('<span>').text(type.name + ' von ').attr('data-type-id', val);
const $startTime = $('<input>', {
type: 'text',
class: 'timePicker flatpickr-input',
"data-type": 'start',
readonly: 'readonly'
});
$span.append($startTime);
if (type.has_endtime === 1) {
$span.append(' bis ');
const $endTime = $('<input>', {
type: 'text',
class: 'timePicker flatpickr-input',
"data-type": 'end',
readonly: 'readonly'
});
$span.append($endTime);
}
const $removeButton = $(`<svg width="15" height="15" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="removeTimetypeButton">
<g class="drawing-elements">
<circle cx="50.333343505859375" cy="50" r="48" fill="none" stroke="currentcolor" stroke-width="4" opacity="1" id="circle-1779630104235" class=""></circle>
<line x1="25" y1="49.74477767944336" x2="75" y2="50" fill="none" stroke="currentcolor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" opacity="1" id="line-1779630151280" class=""></line>
</g>
</svg>`);
$span.append($removeButton);
const $container = $input.closest('.customSelect');
$container.closest('.subHeaderAbt').append($span);
$span.find('.timePicker').flatpickr({
enableTime: true,
noCalendar: true,
dateFormat: "H:i",
time_24hr: true
});
$container.addClass('hidden');
toggleAddTimeBtn($container.closest('.subHeaderAbt'));
});
$('body').on('change', '.timePicker', function () {
const $input = $(this);
const val = $input.val();
const abtId = $input.closest('.subHeaderAbt').attr('data-abt-id') ?? 0;
const type = $input.attr('data-type') ?? 'invalid';
const typeId = $input.parent('span').attr('data-type-id') ?? '0';
$.ajax({
url: "/intern/scripts/riegeneinteilung/ajax-zeiten-management.php",
method: "POST",
data: {
csrf_token,
abt: abtId,
value: val,
type,
type_id: typeId,
action: 'update'
},
success: function (response) {
responseArray = typeof response === "string" ? JSON.parse(response) : response;
if (responseArray.success) {
displayMsg(1, "Zeiten erfolgreich aktualisiert.");
}
},
error: function () {
displayMsg(0, "Fehler beim Aktualisieren der Zeit.");
}
});
});
$('body').on('click', '.removeTimetypeButton', function () {
const $button = $(this);
const $span = $button.parent('span');
const typeId = $span.attr('data-type-id') ?? '0';
const abtId = $button.closest('.subHeaderAbt').attr('data-abt-id') ?? 0;
const $container = $span.closest('.subHeaderAbt');
$.ajax({
url: "/intern/scripts/riegeneinteilung/ajax-zeiten-management.php",
method: "POST",
data: {
csrf_token,
abt: abtId,
type_id: typeId,
action: 'delete'
},
success: function (response) {
responseArray = typeof response === "string" ? JSON.parse(response) : response;
if (responseArray.success) {
displayMsg(1, "Zeiten erfolgreich gelöscht.");
$span.remove();
toggleAddTimeBtn($container);
}
},
error: function () {
displayMsg(0, "Fehler beim Löschen der Zeit.");
}
});
});
});
const progColors = {
"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"
}
};
flatpickr(".timePicker", {
enableTime: true,
noCalendar: true,
dateFormat: "H:i",
time_24hr: true,
locale: "de"
});
</script>
</body>
</html>
<?php
}