New Filestructure for Docker
This commit is contained in:
@@ -0,0 +1,557 @@
|
||||
jQuery(document).ready(function($) {
|
||||
const csrf_token = window.CSDR_TOKEN;
|
||||
|
||||
const allowedRanglistenTypes = ['downloadRangliste', 'updateServerRangliste'];
|
||||
|
||||
$('.ranglisteExport').on('click', function() {
|
||||
const $button = $(this);
|
||||
const progId = $button.data('id');
|
||||
const fieldType = $button.data('field_type');
|
||||
|
||||
if (!allowedRanglistenTypes.includes(fieldType)) {
|
||||
displayMsg(0, "Dieser Button besitzt eine fehlerhafte Konfiguration");
|
||||
return;
|
||||
}
|
||||
|
||||
// Visual feedback (optional but recommended)
|
||||
$button.addClass('opacity50');
|
||||
|
||||
const url = '/intern/scripts/kampfrichter/ajax/ajax-neu_rangliste.php';
|
||||
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: new URLSearchParams({
|
||||
csrf_token,
|
||||
prog: progId,
|
||||
type: fieldType
|
||||
})
|
||||
})
|
||||
.then(res => {
|
||||
if (!res.ok) throw new Error('Network response was not ok');
|
||||
|
||||
// Return both the response and the blob/json promise
|
||||
// to keep the chain clean, or handle them based on fieldType here.
|
||||
if (fieldType === 'downloadRangliste') {
|
||||
return res.blob().then(blob => ({ type: 'blob', data: blob }));
|
||||
} else {
|
||||
return res.json().then(json => ({ type: 'json', data: json }));
|
||||
}
|
||||
})
|
||||
.then(result => {
|
||||
if (result.type === 'blob') {
|
||||
const url = window.URL.createObjectURL(result.data);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = "Rangliste.pdf";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
window.URL.revokeObjectURL(url);
|
||||
displayMsg(1, "Rangliste wurde erfolgreich heuntergeladen.");
|
||||
} else if (result.type === 'json') {
|
||||
if (result.data.success) {
|
||||
const $container = $button.closest(".singleAbtDiv");
|
||||
$container.find(".uploadRanglisteText").addClass("hidden");
|
||||
$container.find(".updateRanglisteText").removeClass("hidden");
|
||||
$container.find(".rangliseOnlineSpan").removeClass("hidden");
|
||||
$container.find(".ranglisteDelete").removeClass("hidden");
|
||||
displayMsg(1, "Die Rangliste wurde auf dem Server aktualisiert.");
|
||||
} else {
|
||||
displayMsg(0, "Fehler beim Aktualiseren der Rangliste.");
|
||||
}
|
||||
}
|
||||
$button.removeClass('opacity50');
|
||||
})
|
||||
.catch(err => {
|
||||
displayMsg(0, "Error processing request:", err);
|
||||
console.error("Error processing request:", err);
|
||||
$button.removeClass('opacity50');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
$('.ranglisteDelete').on('click', function() {
|
||||
const $button = $(this);
|
||||
const progId = $button.data('id');
|
||||
|
||||
// Visual feedback (optional but recommended)
|
||||
$button.addClass('opacity50');
|
||||
|
||||
const url = '/intern/scripts/kampfrichter/ajax/ajax-delete_rangliste.php';
|
||||
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: new URLSearchParams({
|
||||
csrf_token,
|
||||
prog: progId
|
||||
})
|
||||
})
|
||||
.then(res => {
|
||||
if (!res.ok) throw new Error('Network response was not ok');
|
||||
|
||||
|
||||
return res.json().then(json => ({ type: 'json', data: json }));
|
||||
|
||||
})
|
||||
.then(result => {
|
||||
if (result.data.success) {
|
||||
const $container = $button.closest(".singleAbtDiv");
|
||||
$container.find(".uploadRanglisteText").removeClass("hidden");
|
||||
$container.find(".updateRanglisteText").addClass("hidden");
|
||||
$container.find(".rangliseOnlineSpan").addClass("hidden");
|
||||
$container.find(".ranglisteDelete").addClass("hidden");
|
||||
displayMsg(1, "Die Rangliste wurde erfolgreich entfernt.");
|
||||
} else {
|
||||
displayMsg(0, "Fehler beim Löschen der Rangliste.");
|
||||
}
|
||||
$button.removeClass('opacity50');
|
||||
})
|
||||
.catch(err => {
|
||||
displayMsg(0, "Error processing request:", err);
|
||||
console.error("Error processing request:", err);
|
||||
$button.removeClass('opacity50');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
$('.protokollExport').on('click', function() {
|
||||
const $button = $(this);
|
||||
const abteilungId = $button.data('abteilung-id');
|
||||
|
||||
|
||||
const now = new Date();
|
||||
|
||||
const formattedDate = new Intl.DateTimeFormat('en-CA', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false
|
||||
}).format(now).replace(/,/g, '');
|
||||
|
||||
const url = '/intern/scripts/kampfrichter/ajax/ajax-neu_protokoll.php';
|
||||
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: new URLSearchParams({
|
||||
csrf_token,
|
||||
abteilungId: abteilungId,
|
||||
date: formattedDate
|
||||
})
|
||||
})
|
||||
.then(res => {
|
||||
if (!res.ok) throw new Error('Network response was not ok');
|
||||
|
||||
return res.blob().then(blob => ({ type: 'blob', data: blob }));
|
||||
})
|
||||
.then(result => {
|
||||
if (result.type === 'blob') {
|
||||
const url = window.URL.createObjectURL(result.data);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = "Protokoll.pdf";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
window.URL.revokeObjectURL(url);
|
||||
displayMsg(1, "Protokoll wurde erfolgreich heuntergeladen.");
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
displayMsg(0, "Error processing request:", err);
|
||||
console.error("Error processing request:", err);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
$('.protokollDeleteEntries').on('click', async function() {
|
||||
const $button = $(this);
|
||||
const abt = $button.data('abteilung-id');
|
||||
|
||||
if (!await displayConfirmImportant("Möchten Sie wirklich alle Protokolleinträge für diese Abteilung löschen? Diese Aktion kann nicht rückgängig gemacht werden.")) { return; }
|
||||
|
||||
const url = '/intern/scripts/kampfrichter/ajax/ajax-delete_protokoll_entries.php';
|
||||
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: new URLSearchParams({
|
||||
csrf_token,
|
||||
abteilungId: abt
|
||||
})
|
||||
})
|
||||
.then(res => {
|
||||
if (!res.ok) throw new Error('Network response was not ok');
|
||||
|
||||
|
||||
return res.json().then(json => ({ type: 'json', data: json }));
|
||||
|
||||
})
|
||||
.then(result => {
|
||||
if (result.data.success) {
|
||||
displayMsg(1, "Die Einträge wurden erfolgreich entfernt.");
|
||||
} else {
|
||||
displayMsg(0, "Fehler beim Löschen der Einträge.");
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
displayMsg(0, "Error processing request:", err);
|
||||
console.error("Error processing request:", err);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
let activeEdit = null;
|
||||
|
||||
$(document).on('click', '.editableValue', function () {
|
||||
const input = $(this);
|
||||
|
||||
// Already editing
|
||||
if (!input.prop('readonly')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Close another active edit
|
||||
if (activeEdit && activeEdit.get(0) !== input.get(0)) {
|
||||
cancelEdit(activeEdit);
|
||||
}
|
||||
|
||||
input.data('original', input.val());
|
||||
input.prop('readonly', false).focus().select();
|
||||
|
||||
activeEdit = input;
|
||||
|
||||
input.on('keydown.edit', function (e) {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
saveEdit(input);
|
||||
}
|
||||
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
cancelEdit(input);
|
||||
}
|
||||
});
|
||||
|
||||
input.on('blur.edit', function () {
|
||||
saveEdit(input);
|
||||
});
|
||||
});
|
||||
|
||||
function saveEdit(input) {
|
||||
const originalValue = input.data('original');
|
||||
const newValue = input.val();
|
||||
const type = input.data('type');
|
||||
const discipline = input.data('discipline');
|
||||
const dataId = input.data('id');
|
||||
|
||||
if (newValue === originalValue) {
|
||||
input.prop('readonly', true);
|
||||
cleanup(input);
|
||||
return;
|
||||
}
|
||||
|
||||
input.addClass('is-saving');
|
||||
|
||||
$.ajax({
|
||||
url: '/intern/scripts/kampfrichter/ajax/ajax-update_value_kampfrichter_admin.php',
|
||||
method: 'POST',
|
||||
data: {
|
||||
id: dataId,
|
||||
field_type: type,
|
||||
discipline: discipline,
|
||||
value: newValue
|
||||
},
|
||||
success: function () {
|
||||
input.prop('readonly', true);
|
||||
|
||||
const row = input.closest('td');
|
||||
if (row.length) {
|
||||
row.css(
|
||||
'background',
|
||||
'radial-gradient(circle at bottom right, #22c55e, transparent 55%)'
|
||||
);
|
||||
|
||||
setTimeout(() => row.css('background', ''), 2000);
|
||||
}
|
||||
|
||||
ws.send(JSON.stringify({
|
||||
type: "KAMPFRICHTER_UPDATE",
|
||||
payload: {
|
||||
discipline: discipline,
|
||||
id: dataId,
|
||||
val: newValue
|
||||
}
|
||||
}));
|
||||
},
|
||||
error: function () {
|
||||
input.val(originalValue).prop('readonly', true);
|
||||
alert('Speichern fehlgeschlagen');
|
||||
},
|
||||
complete: function () {
|
||||
input.removeClass('is-saving');
|
||||
cleanup(input);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function cancelEdit(input) {
|
||||
input.val(input.data('original')).prop('readonly', true);
|
||||
cleanup(input);
|
||||
}
|
||||
|
||||
function cleanup(input) {
|
||||
input.off('.edit');
|
||||
activeEdit = null;
|
||||
}
|
||||
|
||||
let rangNotenArray = JSON.parse(window.RANG_NOTEN_ARRAY) ?? [];
|
||||
|
||||
const rangNotenId = window.RANG_NOTE_ID ?? 0;
|
||||
|
||||
const rangSort = window.RANG_SORT ?? '';
|
||||
|
||||
const selecteddiscipline = window.FREIGABE;
|
||||
|
||||
ws.addEventListener("message", event => { // Use 'event' as it's more standard than 'blob'
|
||||
let msgOBJ;
|
||||
|
||||
try {
|
||||
msgOBJ = JSON.parse(event.data);
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure it's an UPDATE type (matches your sendToGroup logic)
|
||||
if (msgOBJ?.type === "UPDATE") {
|
||||
|
||||
const data = msgOBJ.payload;
|
||||
|
||||
// Check access rights
|
||||
if (selecteddiscipline === 'A') {
|
||||
const noten = data.noten;
|
||||
|
||||
|
||||
if (noten[0][rangNotenId][1] !== undefined && rangNotenArray[data.programmId][data.personId] !== undefined) {
|
||||
rangNotenArray[data.programmId][data.personId] = noten[0][rangNotenId][1];
|
||||
applyRanks(rankProgramm(rangNotenArray, data.programmId, rangSort), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function rankProgramm(dataObj, programmId, order = 'DESC') {
|
||||
if (!dataObj[programmId]) {
|
||||
console.error(`ID ${programmId} not found in data.`);
|
||||
return [];
|
||||
}
|
||||
|
||||
const values = dataObj[programmId];
|
||||
|
||||
// 1. Transform and Sort
|
||||
const sorted = Object.entries(values)
|
||||
.map(([id, val]) => ({ id, val }))
|
||||
.sort((a, b) => {
|
||||
// 1. Handle null values first (Always push them to the bottom)
|
||||
if (a.val === null && b.val === null) return 0;
|
||||
if (a.val === null) return 1; // Move 'a' to a higher index (down)
|
||||
if (b.val === null) return -1; // Move 'b' to a higher index (down)
|
||||
|
||||
// 2. Normal sorting logic for valid numbers
|
||||
return order.toUpperCase() === 'ASC'
|
||||
? a.val - b.val
|
||||
: b.val - a.val;
|
||||
});
|
||||
|
||||
let lastVal = null;
|
||||
let lastRank = 0;
|
||||
|
||||
let sortedMap = [];
|
||||
|
||||
sortedMap[programmId] = sorted.map((item, index) => {
|
||||
// If current value is different from the last, update rank to current position
|
||||
if (item.val !== lastVal) {
|
||||
lastRank = index + 1;
|
||||
}
|
||||
|
||||
lastVal = item.val;
|
||||
|
||||
return {
|
||||
rang: lastRank,
|
||||
...item
|
||||
};
|
||||
});
|
||||
|
||||
return sortedMap;
|
||||
}
|
||||
|
||||
|
||||
function rankAll(dataObj, order = 'DESC') {
|
||||
return Object.fromEntries(
|
||||
Object.entries(dataObj).map(([groupKey, values]) => {
|
||||
const sorted = Object.entries(values)
|
||||
.map(([id, val]) => ({ id, val }))
|
||||
.sort((a, b) => {
|
||||
// 1. Handle null values first (Always push them to the bottom)
|
||||
if (a.val === null && b.val === null) return 0;
|
||||
if (a.val === null) return 1; // Move 'a' to a higher index (down)
|
||||
if (b.val === null) return -1; // Move 'b' to a higher index (down)
|
||||
|
||||
// 2. Normal sorting logic for valid numbers
|
||||
return order.toUpperCase() === 'ASC'
|
||||
? a.val - b.val
|
||||
: b.val - a.val;
|
||||
});
|
||||
|
||||
let lastVal = null;
|
||||
let lastRank = 0;
|
||||
|
||||
const ranked = sorted.map((item, index) => {
|
||||
if (item.val !== lastVal) {
|
||||
lastRank = index + 1;
|
||||
}
|
||||
lastVal = item.val;
|
||||
|
||||
return {
|
||||
rang: lastRank,
|
||||
...item
|
||||
};
|
||||
});
|
||||
|
||||
return [groupKey, ranked];
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function applyRanks(array, sort = false) {
|
||||
for (const [key, entries] of Object.entries(array)) {
|
||||
const $tbody = $(`.customDisplayEditorTable tbody[data-programm-id="${key}"]`);
|
||||
|
||||
if ($tbody.length === 0) continue;
|
||||
|
||||
for (const [skey, entry] of Object.entries(entries)) {
|
||||
const $row = $tbody.find(`tr[data-person-id="${entry.id}"]:not(.notpaid)`);
|
||||
|
||||
if ($row.length === 0) continue;
|
||||
|
||||
$row.attr('data-sort-rank', entry.rang);
|
||||
|
||||
const $cell = $row.find('.rangField');
|
||||
|
||||
const points = parseFloat(rangNotenArray[key][entry.id]);
|
||||
|
||||
const text = !isNaN(points) ? entry.rang : '';
|
||||
|
||||
$cell.text(text);
|
||||
}
|
||||
|
||||
if (sort) {
|
||||
const rows = $tbody.find('tr').get();
|
||||
|
||||
rows.sort((a, b) => {
|
||||
const rankA = parseFloat($(a).attr('data-sort-rank')) || 999;
|
||||
const rankB = parseFloat($(b).attr('data-sort-rank')) || 999;
|
||||
return rankA - rankB;
|
||||
});
|
||||
|
||||
$.each(rows, (index, row) => {
|
||||
$tbody.append(row);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
applyRanks(rankAll(rangNotenArray, rangSort), true);
|
||||
|
||||
window.addEventListener('valueChanged', (e) => {
|
||||
// Use e.detail to get your object
|
||||
const { noten, programmId, personId } = e.detail;
|
||||
|
||||
if (noten[0][rangNotenId][1] !== undefined && rangNotenArray[programmId][personId] !== undefined) {
|
||||
rangNotenArray[programmId][personId] = noten[0][rangNotenId][1];
|
||||
applyRanks(rankProgramm(rangNotenArray, programmId, rangSort), true);
|
||||
}
|
||||
});
|
||||
|
||||
const $parent = $('.allAbtContainer');
|
||||
const $children = $parent.children('.shiftedGeraetTable');
|
||||
|
||||
$children.sort((a, b) => {
|
||||
const idA = parseFloat($(a).data('geraet-index'));
|
||||
const idB = parseFloat($(b).data('geraet-index'));
|
||||
|
||||
return idA - idB;
|
||||
});
|
||||
|
||||
$parent.append($children);
|
||||
|
||||
const wsMessage = window.WS_MESSAGE || '';
|
||||
|
||||
if (wsMessage !== '') {
|
||||
ws.addEventListener('open', () => {
|
||||
setTimeout(() => {
|
||||
ws.send(wsMessage);
|
||||
}, 300);
|
||||
});
|
||||
}
|
||||
|
||||
$('.deleteNotenAbt').on('click', async function () {
|
||||
if (!(await displayConfirmImportant('Alle Noten dieses Programmes wirklich löschen? Diese Aktion ist unumkehrbar!'))) return;
|
||||
|
||||
const $btn = $(this);
|
||||
|
||||
const programmId = parseInt($btn.data('programm-id') || 0);
|
||||
|
||||
$.ajax({
|
||||
url: '/intern/scripts/kampfrichter/ajax/ajax-delete-noten-turnerinnen.php',
|
||||
type: 'DELETE',
|
||||
data: {
|
||||
csrf_token,
|
||||
programmId
|
||||
},
|
||||
success: function (response) {
|
||||
if (response.success) {
|
||||
const $tbody = $btn.closest('.singleAbtDiv').find('tbody');
|
||||
|
||||
if ($tbody.length !== 1 || $tbody.data('programm-id') !== programmId) {
|
||||
location.reload();
|
||||
return;
|
||||
}
|
||||
|
||||
const $allValues = $tbody.find('.changebleValue:not(.emtyPlaceholder)');
|
||||
|
||||
$allValues.each((ind, el) => {
|
||||
$(el).text('-').addClass('emtyPlaceholder');
|
||||
updateDependentVisibility($(el));
|
||||
});
|
||||
|
||||
const $allRanks = $tbody.find('.rangField');
|
||||
|
||||
$allRanks.each((ind, el) => {
|
||||
$(el).text('');
|
||||
});
|
||||
|
||||
const $allRows = $tbody.find('tr');
|
||||
|
||||
$allRows.each((ind, el) => {
|
||||
$(el).attr('data-sort-rank', "0");
|
||||
rangNotenArray[programmId][$(el).attr('data-person-id')] = null;
|
||||
});
|
||||
|
||||
displayMsg(1, 'Noten gelöscht');
|
||||
|
||||
} else {
|
||||
displayMsg(0, response.message || 'Fehler beim Löschen');
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
displayMsg(0, 'Fehler beim Löschen');
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,856 @@
|
||||
document.addEventListener('keydown', function (e) {
|
||||
// Mac = Option, Windows/Linux = Alt
|
||||
const isOptionOrAlt = e.altKey || e.metaKey;
|
||||
|
||||
if (isOptionOrAlt && e.shiftKey && e.key.toLowerCase() === 'f') {
|
||||
toggleFullscreen();
|
||||
}
|
||||
|
||||
if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
function toggleFullscreen() {
|
||||
// If not in fullscreen → enter fullscreen
|
||||
if (!document.fullscreenElement) {
|
||||
document.documentElement.requestFullscreen()
|
||||
.catch(err => console.error("Fullscreen error:", err));
|
||||
}
|
||||
// If already fullscreen → exit fullscreen
|
||||
else {
|
||||
document.exitFullscreen();
|
||||
}
|
||||
}
|
||||
|
||||
let messagePosArray = [];
|
||||
const csrf_token = window.CSDR_TOKEN;
|
||||
|
||||
const text = document.getElementById('wsInfo');
|
||||
const rect = document.getElementById('wsInfoRectangle');
|
||||
|
||||
let ws;
|
||||
let firstConnect = true;
|
||||
let wsOpen = false;
|
||||
const RETRY_DELAY = 2000; // ms
|
||||
|
||||
const WSaccesstype = "kampfrichter";
|
||||
const WSaccess = window.FREIGABE;
|
||||
const WSaccessPermission = "W";
|
||||
|
||||
const urlAjaxNewWSToken = '/intern/scripts/ajax-create-ws-token.php';
|
||||
|
||||
async function fetchNewWSToken(accesstype, access, accessPermission) {
|
||||
try {
|
||||
const response = await fetch(urlAjaxNewWSToken, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
csrf_token,
|
||||
accesstype,
|
||||
access,
|
||||
accessPermission
|
||||
})
|
||||
});
|
||||
|
||||
if (response.status === 403) {
|
||||
console.warn("Please Re-Autenithicate. Reloading page...");
|
||||
location.reload();
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!response.ok) return null;
|
||||
|
||||
const data = await response.json();
|
||||
return data.success ? data.token : null;
|
||||
} catch (error) {
|
||||
console.error("Token fetch failed:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function startWebSocket() {
|
||||
console.log("Attempting WebSocket connection...");
|
||||
|
||||
let token;
|
||||
if (firstConnect) {
|
||||
token = window.WS_ACCESS_TOKEN;
|
||||
} else {
|
||||
token = await fetchNewWSToken(WSaccesstype, WSaccess, WSaccessPermission);
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
console.error("No valid token available. Retrying...");
|
||||
scheduleRetry();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
ws = new WebSocket(`wss://${window.location.hostname}/ws/?access=token&token=${token}`);
|
||||
} catch (err) {
|
||||
console.error("Malformed WebSocket URL", err);
|
||||
scheduleRetry();
|
||||
return;
|
||||
}
|
||||
|
||||
ws.onopen = () => {
|
||||
console.log("WebSocket connected!");
|
||||
if (!firstConnect) {
|
||||
displayMsg(1, "Live Syncronisation wieder verfügbar");
|
||||
}
|
||||
firstConnect = true;
|
||||
wsOpen = true;
|
||||
|
||||
rect.innerHTML =
|
||||
'<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="10" fill="#28a745"></circle><path d="M7 12l3 3 7-7" stroke="white" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"></path></svg>';
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
text.style.opacity = 1;
|
||||
});
|
||||
};
|
||||
|
||||
ws.onerror = (event) => {
|
||||
console.error("WebSocket error observed." + JSON.stringify(event));
|
||||
};
|
||||
|
||||
ws.onclose = (event) => {
|
||||
|
||||
if (firstConnect) {
|
||||
displayMsg(0, "Live Syncronisation verloren");
|
||||
console.log(event);
|
||||
}
|
||||
firstConnect = false;
|
||||
wsOpen = false;
|
||||
rect.innerHTML =
|
||||
'<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="10" fill="#dc3545"/><path d="M8 8l8 8M16 8l-8 8" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
text.style.opacity = 1;
|
||||
});
|
||||
|
||||
scheduleRetry();
|
||||
};
|
||||
}
|
||||
|
||||
function scheduleRetry() {
|
||||
console.log(`Retrying in ${RETRY_DELAY}ms...`);
|
||||
setTimeout(startWebSocket, RETRY_DELAY);
|
||||
}
|
||||
|
||||
// Start the initial connection attempt safely
|
||||
startWebSocket();
|
||||
|
||||
function updateRunButtons(targetCount, personId, $container) {
|
||||
|
||||
if (targetCount === 0) { return; }
|
||||
|
||||
const geraetId = $container.find('.submit-display-result').first().data('geraet-id') || "";
|
||||
|
||||
const currentCount = $container.find('.submit-display-result').length;
|
||||
|
||||
$container.find('.submit-display-result').removeClass('hidden');
|
||||
|
||||
if (targetCount > currentCount) {
|
||||
for (let i = currentCount + 1; i <= targetCount; i++) {
|
||||
const buttonHtml = `
|
||||
<input type="button" class="submit-display-result"
|
||||
data-person-id="${personId}"
|
||||
data-geraet-id="${geraetId}"
|
||||
data-run="${i}"
|
||||
value="Ergebnis anzeigen (Run ${i})">`;
|
||||
$container.append(buttonHtml);
|
||||
}
|
||||
$container.find('.submit-display-result[data-run="1"]').val('Ergebnis anzeigen (Run 1)');
|
||||
} else if (targetCount < currentCount) {
|
||||
for (let i = currentCount; i > targetCount; i--) {
|
||||
$container.find(`.submit-display-result[data-run="${i}"]`).remove();
|
||||
}
|
||||
if (targetCount === 1 && $container.find('.submit-display-result').length === 1) {
|
||||
$container.find('.submit-display-result').val('Ergebnis anzeigen');
|
||||
}
|
||||
}
|
||||
|
||||
$container.find('.submit-display-result').each(function() {
|
||||
$(this).attr('data-person-id', personId);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
$.fn.updateCurrentEdit = function() {
|
||||
return this.each(function() {
|
||||
const $input = $(this);
|
||||
|
||||
const url = '/intern/scripts/kampfrichter/ajax/ajax-kampfrichter_currentedit.php';
|
||||
|
||||
if ($input.attr('data-person-id') === "0"){
|
||||
$('<form>', {
|
||||
action: '/intern/kampfrichter',
|
||||
method: 'post'
|
||||
})
|
||||
.append($('<input>', {
|
||||
type: 'hidden',
|
||||
name: 'next_subabt',
|
||||
value: 1
|
||||
}))
|
||||
.append($('<input>', {
|
||||
type: 'hidden',
|
||||
name: 'next_subabt_submit',
|
||||
value: '>'
|
||||
}))
|
||||
.append($('<input>', {
|
||||
type: 'hidden',
|
||||
name: 'csrf_token',
|
||||
value: csrf_token
|
||||
}))
|
||||
.appendTo('body')
|
||||
.submit();
|
||||
}
|
||||
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: new URLSearchParams({
|
||||
csrf_token,
|
||||
editId: $input.attr('data-person-id'),
|
||||
geraet: $input.attr('data-geraet-id') ?? null
|
||||
})
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(response => {
|
||||
if (response.success) {
|
||||
|
||||
$(".current-turnerin-name").css({
|
||||
'color': '#209200ff',
|
||||
'transition': 'all 0.3s ease-out'
|
||||
});
|
||||
|
||||
setTimeout(() => $(".current-turnerin-name").css({
|
||||
'color': ''
|
||||
}), 2000);
|
||||
|
||||
$(".div_edit_values_user").css("display", "flex");
|
||||
|
||||
$(".heading_fv_selturnerin")[0].scrollIntoView();
|
||||
|
||||
$(".current-turnerin-name").text(response.titel);
|
||||
|
||||
$(".fv_nextturnerin").text(response.nturnerin?.name ?? '');
|
||||
$(".fv_nextturnerin").val(response.nturnerin?.id ?? 0).attr('data-person-id', response.nturnerin?.id ?? 0);
|
||||
|
||||
$(".submit-display-turnerin").css("opacity", "1");
|
||||
$(".submit-display-start").css("opacity", "1");
|
||||
$(".submit-display-result").css("opacity", "1");
|
||||
|
||||
const $editAllDiv = $('.div_edit_values_all_gereate');
|
||||
const noten = response.noten;
|
||||
const personId = response.id;
|
||||
const programmId = response.programm_id;
|
||||
|
||||
$editAllDiv.find(`.submit-display-turnerin`).closest('.all_vaules_div').addClass('hidden');
|
||||
|
||||
// 1. Loop directly through the 'noten' object
|
||||
for (const [geraetId, disciplineData] of Object.entries(noten)) {
|
||||
|
||||
// Find the specific DOM wrapper for this Geraet using the outer div
|
||||
// Assuming your PHP renders the tables with the correct geraetId on the button
|
||||
const $disciplineWrapper = $editAllDiv.find(`.submit-display-turnerin[data-geraet-id="${geraetId}"]`).closest('.all_vaules_div');
|
||||
|
||||
if ($disciplineWrapper.length === 0) continue;
|
||||
|
||||
$disciplineWrapper.removeClass('hidden');
|
||||
|
||||
// --- UPDATE GENERAL BUTTONS FOR THIS GERAET ---
|
||||
$disciplineWrapper.find(".submit-display-turnerin, .submit-display-start").attr({
|
||||
'data-person-id': personId,
|
||||
'data-geraet-id': geraetId
|
||||
});
|
||||
|
||||
$disciplineWrapper.find(".submit-musik-start, .submit-musik-stopp").attr({
|
||||
'data-id': personId,
|
||||
'data-geraet': geraetId
|
||||
});
|
||||
|
||||
// 2. Identify master containers for this specific discipline
|
||||
const $masterContainer = $disciplineWrapper.find('.singleNotentable').first();
|
||||
const $displayresultDiv = $disciplineWrapper.find('.div-submit-display-result');
|
||||
|
||||
$masterContainer.find('.note-container').addClass('hidden');
|
||||
|
||||
// 3. CLEANUP: Remove previously generated runs and buttons
|
||||
$disciplineWrapper.find('.singleNotentable').not(':first').remove();
|
||||
$displayresultDiv.find('.submit-display-result').not(':first').remove();
|
||||
|
||||
const $originalResultBtn = $displayresultDiv.find('.submit-display-result').first();
|
||||
$displayresultDiv.find('.submit-display-result').addClass('hidden');
|
||||
const runKeys = Object.keys(disciplineData).sort((a, b) => a - b);
|
||||
const totalRuns = runKeys.length;
|
||||
|
||||
// 4. Process each Run in the data
|
||||
runKeys.forEach(runNum => {
|
||||
const runInt = parseInt(runNum);
|
||||
let $currentRunContainer;
|
||||
|
||||
if (runInt === 1) {
|
||||
$currentRunContainer = $masterContainer;
|
||||
} else {
|
||||
// CLONE the entire container for Run 2, 3, etc.
|
||||
$currentRunContainer = $masterContainer.clone();
|
||||
$currentRunContainer.addClass(`run-container-block run-${runNum}`);
|
||||
$currentRunContainer.insertAfter($disciplineWrapper.find('.singleNotentable').last());
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 5. Update all Tables and Inputs inside this Run Container
|
||||
for (const [noteId, value] of Object.entries(disciplineData[runNum])) {
|
||||
const $table = $currentRunContainer.find(`.note-container[data-note-id="${noteId}"]`);
|
||||
$table.removeClass('hidden');
|
||||
|
||||
// Update Header to show Run Number
|
||||
if (runInt > 1) {
|
||||
const $header = $table.find('.note-name-header');
|
||||
if (!$header.find('.rm-tag').length) {
|
||||
$header.append(` <span class="rm-tag" style="font-size: 0.8em;">(R${runNum})</span>`);
|
||||
}
|
||||
}
|
||||
|
||||
// Update Input attributes and value
|
||||
const $input = $table.find('input');
|
||||
$input.attr({
|
||||
'data-run': runNum,
|
||||
'data-person-id': personId,
|
||||
'data-geraet-id': geraetId,
|
||||
'data-programm-id': programmId
|
||||
}).val(value ?? '');
|
||||
}
|
||||
|
||||
// 6. Remove tables cloned from Run 1 that don't exist in Run 2+
|
||||
if (runInt > 1) {
|
||||
$currentRunContainer.find('input[data-run="1"]').closest('.note-container').remove();
|
||||
}
|
||||
});
|
||||
|
||||
// Ensure the UI script tracking the buttons is updated last
|
||||
updateRunButtons(totalRuns, personId, $displayresultDiv);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//$(".submit-display-result").attr('data-person-id', response.id);
|
||||
|
||||
} else {
|
||||
displayMsg(0, response.message);
|
||||
$input.css({
|
||||
'background-color': '#f8d7da',
|
||||
'color': '#fff',
|
||||
'transition': 'all 0.3s ease-out'
|
||||
});
|
||||
setTimeout(() => $input.css({
|
||||
'background-color': '',
|
||||
'color': ''
|
||||
}), 2000);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
$input.css('background-color', '#f8d7da');
|
||||
console.error('AJAX fetch error:', err);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function getRandomInt(max) {
|
||||
return Math.floor(Math.random() * max);
|
||||
}
|
||||
|
||||
jQuery(document).ready(function($) {
|
||||
|
||||
|
||||
$('.editTurnerin').on('click', function() {
|
||||
$(this).updateCurrentEdit();
|
||||
});
|
||||
|
||||
const $ajaxInputDiv = $('.div_edit_values_all_gereate');
|
||||
|
||||
|
||||
$ajaxInputDiv.on('change', '.ajax-input', function(e) {
|
||||
const $input = $(this);
|
||||
|
||||
const url = `/intern/scripts/kampfrichter/ajax/ajax-update_value_kampfrichter.php`;
|
||||
|
||||
personId = $input.attr('data-person-id');
|
||||
fieldTypeId = $input.attr('data-field-type-id');
|
||||
programmId = $input.attr('data-programm-id');
|
||||
gereatId = $input.attr('data-geraet-id');
|
||||
runNum = $input.attr('data-run') || 1;
|
||||
jahr = window.AKTUELLES_JAHR;
|
||||
value = $input.val();
|
||||
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: new URLSearchParams({
|
||||
csrf_token,
|
||||
personId: personId,
|
||||
fieldTypeId: fieldTypeId,
|
||||
gereatId: gereatId,
|
||||
run: runNum,
|
||||
jahr: jahr,
|
||||
value: value
|
||||
})
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(response => {
|
||||
|
||||
if (response.success) {
|
||||
|
||||
let objValues = [];
|
||||
|
||||
const rowId = $input.attr('data-id');
|
||||
|
||||
$input.css({"color": "#0e670d", "font-weight": "600"});
|
||||
|
||||
|
||||
setTimeout(() => $input.css({'color': '', "font-weight": ""}), 2000);
|
||||
|
||||
const noten = response.noten;
|
||||
|
||||
for (const [keyG, noteGroup] of Object.entries(noten)) {
|
||||
for (const [key, runGroup] of Object.entries(noteGroup)) {
|
||||
for (const [run, value] of Object.entries(runGroup)) {
|
||||
|
||||
const $elements = $(`.changebleValue[data-field-type-id="${key}"][data-geraet-id="${keyG}"][data-person-id="${personId}"][data-run="${run}"]`);
|
||||
|
||||
$elements.each((ind, el) => {
|
||||
const $el = $(el);
|
||||
|
||||
if ($el.is('input')) {
|
||||
$el.val(value ?? '');
|
||||
} else {
|
||||
const isNullable = ($el.attr('data-noten-nullable') ?? 'false') === 'true';
|
||||
const floatValZero = parseFloat(value.replace(",", ".").replace("'", "")) === 0.0;
|
||||
|
||||
let hasValue = false;
|
||||
|
||||
if (isNullable && floatValZero || value === '') {
|
||||
$el.text('-').addClass('emtyPlaceholder');
|
||||
} else {
|
||||
$el.text(value).removeClass('emtyPlaceholder');
|
||||
hasValue = true;
|
||||
}
|
||||
|
||||
updateDependentVisibility($el, hasValue);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (selecteddiscipline === 'A') {
|
||||
const event = new CustomEvent('valueChanged', {
|
||||
detail: {
|
||||
noten: noten,
|
||||
programmId: programmId,
|
||||
personId: personId
|
||||
}
|
||||
});
|
||||
|
||||
window.dispatchEvent(event);
|
||||
}
|
||||
|
||||
if (wsOpen) {
|
||||
ws.send(JSON.stringify({
|
||||
type: "KAMPFRICHTER_UPDATE",
|
||||
payload: {
|
||||
discipline: window.FREIGABE,
|
||||
gereatId: gereatId,
|
||||
personId: personId,
|
||||
programmId: programmId,
|
||||
jahr: jahr,
|
||||
noten: noten
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
} else {
|
||||
// Flash red on error
|
||||
$input.css({'color': '#ff6a76ff'});
|
||||
displayMsg(0, response.message || 'Unknown error');
|
||||
console.error(response.message || 'Unknown error');
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
$input.css({'color': '#670d0d'});
|
||||
console.error('AJAX fetch error:', err);
|
||||
});
|
||||
});
|
||||
|
||||
$('.submit-display-turnerin').on('click', function() {
|
||||
const $input = $(this);
|
||||
const geraetId = $input.attr('data-geraet-id');
|
||||
|
||||
// Build the URL with GET parameters safely
|
||||
const url = '/intern/scripts/kampfrichter/ajax/displays/ajax-display-functions.php';
|
||||
|
||||
fetch(url,{
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: new URLSearchParams({
|
||||
csrf_token,
|
||||
personId: $input.attr('data-person-id'),
|
||||
geraetId,
|
||||
jahr: window.AKTUELLES_JAHR,
|
||||
type: "neu"
|
||||
})
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(response => {
|
||||
|
||||
if (response.success) {
|
||||
if (wsOpen) {
|
||||
ws.send(JSON.stringify({
|
||||
type: "UPDATE_SCORE",
|
||||
payload: {
|
||||
geraet: response.nameGeraet,
|
||||
geraetId,
|
||||
data: response.data
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
displayMsg(2, 'Keine live Syncrosation, Verzögerungen');
|
||||
}
|
||||
displayMsg(1, 'Neue Person wird angezigt');
|
||||
|
||||
$input.css('opacity', 0.5);
|
||||
} else {
|
||||
displayMsg(0, response.message);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
$input.css('background-color', '#f8d7da');
|
||||
displayMsg(0, 'AJAX fetch error:' + err);
|
||||
console.error('AJAX fetch error:', err);
|
||||
});
|
||||
});
|
||||
|
||||
$('.submit-display-start').on('click', function() {
|
||||
const $input = $(this);
|
||||
const geraetId = $input.attr('data-geraet-id');
|
||||
const personId = $input.attr('data-person-id')
|
||||
|
||||
const url = '/intern/scripts/kampfrichter/ajax/displays/ajax-display-functions.php';
|
||||
const dataType = $input.attr('data-type');
|
||||
|
||||
fetch(url,{
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: new URLSearchParams({
|
||||
csrf_token,
|
||||
geraetId,
|
||||
personId,
|
||||
dataType: dataType,
|
||||
type: "start"
|
||||
})
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(response => {
|
||||
|
||||
if (response.success) {
|
||||
if (wsOpen) {
|
||||
ws.send(JSON.stringify({
|
||||
type: "UPDATE_SCORE",
|
||||
payload: {
|
||||
geraet: response.nameGeraet,
|
||||
geraetId,
|
||||
data: response.data
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
displayMsg(2, 'Keine live Syncrosation, Verzögerungen');
|
||||
}
|
||||
|
||||
if (dataType == 1) {
|
||||
displayMsg(1, 'Start freigegeben');
|
||||
} else if (dataType == 0) {
|
||||
displayMsg(1, 'Startfreigabe entzogen');
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
displayMsg(0, response.message);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
$input.css('background-color', '#f8d7da');
|
||||
displayMsg(0, 'AJAX fetch error:' + err);
|
||||
console.error('AJAX fetch error:', err);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
$('.submit-musik-start').on('click', function() {
|
||||
const $input = $(this);
|
||||
const geraetId = $input.attr('data-geraet-id');
|
||||
|
||||
// Build the URL with GET parameters safely
|
||||
const url = `/intern/scripts/kampfrichter/ajax/ajax-update_kampfrichter_start_musik.php`;
|
||||
|
||||
fetch(url,{
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: new URLSearchParams({
|
||||
csrf_token,
|
||||
personId: $input.attr('data-id'),
|
||||
geraetId
|
||||
})
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(response => {
|
||||
if (wsOpen) {
|
||||
ws.send(JSON.stringify({
|
||||
type: "AUDIO",
|
||||
payload: {
|
||||
audioDiscipline: geraetId
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
displayMsg(2, 'Keine live Syncrosation, Verzögerungen');
|
||||
}
|
||||
|
||||
if (response.success) {
|
||||
displayMsg(1, 'Musik wird abgespielt werden');
|
||||
} else {
|
||||
displayMsg(0, 'Error: ' + response.message);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
$input.css('background-color', '#f8d7da');
|
||||
displayMsg(0, 'AJAX fetch error:' + err);
|
||||
console.error('AJAX fetch error:', err);
|
||||
});
|
||||
});
|
||||
|
||||
$('.submit-musik-stopp').on('click', function() {
|
||||
const $input = $(this);
|
||||
const geraetId = $input.attr('data-geraet-id');
|
||||
|
||||
// Build the URL with GET parameters safely
|
||||
const url = `/intern/scripts/kampfrichter/ajax/ajax-update_kampfrichter_stopp_musik.php`;
|
||||
|
||||
fetch(url,{
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: new URLSearchParams({
|
||||
csrf_token,
|
||||
geraetId
|
||||
})
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(response => {
|
||||
if (response.success) {
|
||||
if (wsOpen) {
|
||||
ws.send(JSON.stringify({
|
||||
type: "AUDIO",
|
||||
payload: {
|
||||
audioDiscipline: geraetId
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
displayMsg(2, 'Keine live Syncrosation, Verzögerungen');
|
||||
}
|
||||
displayMsg(1, 'Musik wird gestoppt werden');
|
||||
} else {
|
||||
alert('Error: ' + response.message);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
$input.css('background-color', '#f8d7da');
|
||||
displayMsg(0, 'AJAX fetch error:' + err);
|
||||
console.error('AJAX fetch error:', err);
|
||||
});
|
||||
});
|
||||
|
||||
$('.div-submit-display-result').on('click', '.submit-display-result', function() {
|
||||
$input = $(this);
|
||||
const geraetId = $input.attr('data-geraet-id');
|
||||
const personId = $input.attr('data-person-id');
|
||||
|
||||
// Build the URL with GET parameters safely
|
||||
const url = '/intern/scripts/kampfrichter/ajax/displays/ajax-display-functions.php';
|
||||
|
||||
fetch(url,{
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: new URLSearchParams({
|
||||
csrf_token,
|
||||
personId,
|
||||
geraetId,
|
||||
run: $input.attr("data-run"),
|
||||
jahr: window.AKTUELLES_JAHR,
|
||||
type: "result"
|
||||
})
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(response => {
|
||||
|
||||
if (response.success) {
|
||||
if (wsOpen) {
|
||||
ws.send(JSON.stringify({
|
||||
type: "UPDATE_SCORE",
|
||||
payload: {
|
||||
geraetId,
|
||||
geraet: response.nameGeraet,
|
||||
data: response.data
|
||||
}
|
||||
}));
|
||||
ws.send(JSON.stringify({
|
||||
type: "UPDATE_RANKLIVE_SCORE",
|
||||
payload: {
|
||||
geraetId,
|
||||
personId,
|
||||
jahr: window.AKTUELLES_JAHR,
|
||||
rankLive: response.rankLive
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
displayMsg(2, 'Keine live Syncrosation, Verzögerungen');
|
||||
}
|
||||
$input.css('opacity', 0.5);
|
||||
displayMsg(1, 'Resultat wird angezeigt');
|
||||
} else {
|
||||
alert('Error: ' + response.message);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
$input.css('background-color', '#f8d7da');
|
||||
displayMsg(0, 'AJAX fetch error:' + err);
|
||||
console.error('AJAX fetch error:', err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function updateDependentVisibility($changedEl, hasValue = false) {
|
||||
const uid = $changedEl.attr('data-uid');
|
||||
if (uid === undefined || uid === null) return;
|
||||
|
||||
const $row = $changedEl.closest('tr');
|
||||
|
||||
if ($row.length !== 1) return;
|
||||
|
||||
const isVisible = $changedEl.hasClass('emtyPlaceholder');
|
||||
const doesExist = $changedEl.hasClass('nonExistentEl');
|
||||
|
||||
const $linkedEls = $row.find(`.singleValueSpan[data-linked-el-uid="${uid}"]`);
|
||||
|
||||
$linkedEls.each((ind, el) => {
|
||||
const $linkedEl = $(el);
|
||||
if (!hasValue) {
|
||||
hasValue = $linkedEl.hasClass('changebleValue') && $linkedEl.text().trim() !== '-';
|
||||
}
|
||||
|
||||
$linkedEl.toggleClass('emtyPlaceholder', (isVisible && !hasValue)).toggleClass('nonExistentEl', doesExist);
|
||||
|
||||
const linkedElUid = $linkedEl.data('linked-el-uid');
|
||||
|
||||
const $subLinkedEls = $row.find(`.singleValueSpan[data-linked-el-uid="${linkedElUid}"]`);
|
||||
|
||||
if ($subLinkedEls.length > 0) updateDependentVisibility($linkedEl);
|
||||
});
|
||||
}
|
||||
|
||||
function initConditionalVisibility() {
|
||||
const $table = $('table.customDisplayEditorTable');
|
||||
|
||||
const $rows = $table.find('tr');
|
||||
|
||||
$rows.each((ind, el) => {
|
||||
const $row = $(el);
|
||||
$row.find('.singleValueSpan[data-linked-el-uid]').each(function() {
|
||||
|
||||
const linkedUid = $(this).data('linked-el-uid');
|
||||
|
||||
const $linkedEl = $row.find(`.singleValueSpan[data-uid="${linkedUid}"]`);
|
||||
|
||||
if ($linkedEl.length === 1) {
|
||||
updateDependentVisibility($linkedEl);
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
initConditionalVisibility();
|
||||
initConditionalVisibility();
|
||||
|
||||
const selecteddiscipline = window.FREIGABE;
|
||||
|
||||
ws.addEventListener("message", event => { // Use 'event' as it's more standard than 'blob'
|
||||
let msgOBJ;
|
||||
|
||||
try {
|
||||
msgOBJ = JSON.parse(event.data);
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure it's an UPDATE type (matches your sendToGroup logic)
|
||||
if (msgOBJ?.type === "UPDATE") {
|
||||
const data = msgOBJ.payload;
|
||||
|
||||
// Check access rights
|
||||
if (data.discipline === selecteddiscipline.toLowerCase() || selecteddiscipline === 'A') {
|
||||
const noten = data.noten;
|
||||
|
||||
for (const [keyG, noteGroup] of Object.entries(noten)) {
|
||||
for (const [key, runGroup] of Object.entries(noteGroup)) {
|
||||
|
||||
for (const [run, value] of Object.entries(runGroup)) {
|
||||
|
||||
// Select all matching elements (inputs and spans)
|
||||
const $elements = $(`.changebleValue[data-field-type-id="${key}"][data-geraet-id="${keyG}"][data-person-id="${data.personId}"][data-run="${run}"]`);
|
||||
|
||||
$elements.each((ind, el) => {
|
||||
const $el = $(el);
|
||||
|
||||
if ($el.is('input')) {
|
||||
$el.val(value ?? '');
|
||||
|
||||
// Visual feedback: Flash color
|
||||
const nativeEl = $el[0];
|
||||
nativeEl.style.transition = 'color 0.3s';
|
||||
nativeEl.style.color = '#008e85';
|
||||
|
||||
setTimeout(() => {
|
||||
nativeEl.style.color = '';
|
||||
}, 1000);
|
||||
} else {
|
||||
const isNullable = ($el.attr('data-noten-nullable') ?? 'false') === 'true';
|
||||
const floatValZero = parseFloat(value.replace(",", ".").replace("'", "")) === 0.0;
|
||||
|
||||
let hasValue = false;
|
||||
|
||||
if (isNullable && floatValZero || value === '') {
|
||||
$el.text('-').addClass('emtyPlaceholder');
|
||||
} else {
|
||||
$el.text(value).removeClass('emtyPlaceholder');
|
||||
hasValue = true;
|
||||
}
|
||||
|
||||
updateDependentVisibility($el, hasValue);
|
||||
|
||||
$el.removeClass('updated');
|
||||
$el.addClass('updated');
|
||||
|
||||
setTimeout(() => {
|
||||
$el.removeClass('updated');
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user