344 lines
10 KiB
JavaScript
344 lines
10 KiB
JavaScript
const selecteddiscipline = window.FREIGABE;
|
|
|
|
const csrf_token = window.CSDR_TOKEN;
|
|
|
|
let ws;
|
|
let firstConnect = true;
|
|
let wsOpen = false;
|
|
const RETRY_DELAY = 5000;
|
|
|
|
const WSaccesstype = "rankLive";
|
|
const WSaccess = window.FREIGABE;
|
|
const WSaccessPermission = "R";
|
|
|
|
const urlAjaxNewWSToken = '/intern/scripts/ajax-create-ws-token.php';
|
|
|
|
async function fetchNewWSToken(accesstype, access, accessPermission) {
|
|
try {
|
|
const response = await fetch(urlAjaxNewWSToken, {
|
|
method: "POST",
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
},
|
|
body: new URLSearchParams({
|
|
csrf_token,
|
|
accesstype,
|
|
access,
|
|
accessPermission
|
|
})
|
|
});
|
|
|
|
if (response.status === 403) {
|
|
console.warn("Please Re-Autenithicate. Reloading page...");
|
|
location.reload();
|
|
return null;
|
|
}
|
|
|
|
if (!response.ok) return null;
|
|
|
|
const data = await response.json();
|
|
return data.success ? data.token : null;
|
|
} catch (error) {
|
|
console.error("Token fetch failed:", error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async function startWebSocket() {
|
|
console.log("Attempting WebSocket connection...");
|
|
|
|
let token;
|
|
if (firstConnect) {
|
|
token = window.WS_ACCESS_TOKEN;
|
|
} else {
|
|
token = await fetchNewWSToken(WSaccesstype, WSaccess, WSaccessPermission);
|
|
}
|
|
|
|
if (!token) {
|
|
console.error("No valid token available. Retrying...");
|
|
scheduleRetry();
|
|
return;
|
|
}
|
|
|
|
try {
|
|
ws = new WebSocket(`wss://${window.location.hostname}/ws/?access=token&token=${token}`);
|
|
} catch (err) {
|
|
console.error("Malformed WebSocket URL", err);
|
|
scheduleRetry();
|
|
return;
|
|
}
|
|
|
|
ws.onopen = () => {
|
|
console.log("WebSocket connected!");
|
|
if (!firstConnect) {
|
|
displayMsg(1, "Live Syncronisation wieder verfügbar");
|
|
}
|
|
firstConnect = true;
|
|
wsOpen = true;
|
|
};
|
|
|
|
ws.onerror = (event) => {
|
|
console.error("WebSocket error observed." + JSON.stringify(event));
|
|
};
|
|
|
|
ws.onclose = (event) => {
|
|
|
|
if (firstConnect) {
|
|
displayMsg(0, "Live Syncronisation verloren");
|
|
}
|
|
firstConnect = false;
|
|
wsOpen = false;
|
|
|
|
scheduleRetry();
|
|
};
|
|
}
|
|
|
|
function scheduleRetry() {
|
|
console.log(`Retrying in ${RETRY_DELAY}ms...`);
|
|
setTimeout(startWebSocket, RETRY_DELAY);
|
|
}
|
|
|
|
// Start the initial connection attempt safely
|
|
startWebSocket();
|
|
|
|
let rangNotenArray = JSON.parse(window.RANG_NOTEN_ARRAY) ?? [];
|
|
|
|
const rangNotenId = window.RANG_NOTE_ID ?? 0;
|
|
|
|
const rangSort = window.RANG_SORT ?? '';
|
|
const isLive = parseInt(window.LIVE ?? 0);
|
|
|
|
ws.addEventListener("message", async function(event) {
|
|
let msgOBJ;
|
|
|
|
try {
|
|
msgOBJ = JSON.parse(event.data);
|
|
} catch (error) {
|
|
return;
|
|
}
|
|
|
|
// Ensure it's an UPDATE type (matches your sendToGroup logic)
|
|
if (msgOBJ?.type === "UPDATE") {
|
|
const data = msgOBJ.payload;
|
|
|
|
// Check access rights
|
|
if (data.discipline === selecteddiscipline.toLowerCase() || selecteddiscipline === 'A') {
|
|
const noten = data.rankLive;
|
|
|
|
const programmId = $(`tr[data-person-id="${data.personId}"]`).closest('tbody').attr('data-programm-id') ?? null;
|
|
|
|
if (!isLive && noten[0][1][rangNotenId] !== undefined && rangNotenArray[programmId][data.personId] !== undefined) {
|
|
rangNotenArray[programmId][data.personId] = noten[0][1][rangNotenId];
|
|
applyRanks(rankProgramm(rangNotenArray, programmId, rangSort), true);
|
|
}
|
|
|
|
for (const [keyG, noteGroup] of Object.entries(noten)) {
|
|
|
|
for (const [run, runGroup] of Object.entries(noteGroup)) {
|
|
|
|
for (const [key, value] of Object.entries(runGroup)) {
|
|
|
|
const $elements = $(`.changebleValue[data-field-type-id="${key}"][data-geraet-id="${keyG}"][data-person-id="${data.personId}"][data-run="${run}"]`);
|
|
$elements.each(function() {
|
|
const $el = $(this);
|
|
|
|
const isNullable = ($el.attr('data-noten-nullable') ?? 'false') === 'true';
|
|
const floatValZero = parseFloat(value.replace(",", ".").replace("'", "")) === 0.0;
|
|
|
|
if (isNullable && floatValZero || value === '') {
|
|
$el.text('-').addClass('emtyPlaceholder');
|
|
} else {
|
|
$el.text(value).removeClass('emtyPlaceholder');
|
|
}
|
|
|
|
$el.removeClass('updated');
|
|
$el.addClass('updated');
|
|
|
|
updateDependentVisibility($el);
|
|
|
|
setTimeout(() => {
|
|
$el.removeClass('updated');
|
|
}, 2000);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else if (msgOBJ?.type === "UPDATE_RANKLIVE_C_SUBABT") {
|
|
if (!(await displayConfirm('Eine neue Gruppe ist aktiv. Fenster aktualisieren?'))) return;
|
|
location.reload();
|
|
}
|
|
});
|
|
|
|
function rankProgramm(dataObj, programmId, order = 'DESC') {
|
|
if (!dataObj[programmId]) {
|
|
console.error(`ID ${programmId} not found in data.`);
|
|
return [];
|
|
}
|
|
|
|
const values = dataObj[programmId];
|
|
|
|
// 1. Transform and Sort
|
|
const sorted = Object.entries(values)
|
|
.map(([id, val]) => ({ id, val }))
|
|
.sort((a, b) => {
|
|
// 1. Handle null values first (Always push them to the bottom)
|
|
if (a.val === null && b.val === null) return 0;
|
|
if (a.val === null) return 1; // Move 'a' to a higher index (down)
|
|
if (b.val === null) return -1; // Move 'b' to a higher index (down)
|
|
|
|
// 2. Normal sorting logic for valid numbers
|
|
return order.toUpperCase() === 'ASC'
|
|
? a.val - b.val
|
|
: b.val - a.val;
|
|
});
|
|
|
|
let lastVal = null;
|
|
let lastRank = 0;
|
|
|
|
let sortedMap = [];
|
|
|
|
sortedMap[programmId] = sorted.map((item, index) => {
|
|
if (item.val !== lastVal) {
|
|
lastRank = index + 1;
|
|
}
|
|
|
|
lastVal = item.val;
|
|
|
|
return {
|
|
rang: lastRank,
|
|
...item
|
|
};
|
|
});
|
|
|
|
return sortedMap;
|
|
}
|
|
|
|
function rankAll(dataObj, order = 'DESC') {
|
|
return Object.fromEntries(
|
|
Object.entries(dataObj).map(([groupKey, values]) => {
|
|
const sorted = Object.entries(values)
|
|
.map(([id, val]) => ({ id, val }))
|
|
.sort((a, b) => {
|
|
// 1. Handle null values first (Always push them to the bottom)
|
|
if (a.val === null && b.val === null) return 0;
|
|
if (a.val === null) return 1; // Move 'a' to a higher index (down)
|
|
if (b.val === null) return -1; // Move 'b' to a higher index (down)
|
|
|
|
// 2. Normal sorting logic for valid numbers
|
|
return order.toUpperCase() === 'ASC'
|
|
? a.val - b.val
|
|
: b.val - a.val;
|
|
});
|
|
|
|
let lastVal = null;
|
|
let lastRank = 0;
|
|
|
|
const ranked = sorted.map((item, index) => {
|
|
if (item.val !== lastVal) {
|
|
lastRank = index + 1;
|
|
}
|
|
lastVal = item.val;
|
|
|
|
return {
|
|
rang: lastRank,
|
|
...item
|
|
};
|
|
});
|
|
|
|
return [groupKey, ranked];
|
|
})
|
|
);
|
|
}
|
|
|
|
function applyRanks(array, sort = false) {
|
|
for (const [key, entries] of Object.entries(array)) {
|
|
const $tbody = $(`tbody[data-programm-id="${key}"]`);
|
|
|
|
if ($tbody.length === 0) continue;
|
|
|
|
for (const [skey, entry] of Object.entries(entries)) {
|
|
const $row = $tbody.find(`tr[data-person-id="${entry.id}"]`);
|
|
|
|
if ($row.length === 0) continue;
|
|
|
|
$row.attr('data-sort-rank', entry.rang);
|
|
|
|
const $cell = $row.find('.rangField');
|
|
|
|
const points = parseFloat(rangNotenArray[key][entry.id]);
|
|
|
|
const text = !isNaN(points) ? entry.rang : '';
|
|
|
|
$cell.text(text);
|
|
}
|
|
|
|
if (sort) {
|
|
const rows = $tbody.find('tr').get();
|
|
|
|
rows.sort((a, b) => {
|
|
const rankA = parseFloat($(a).attr('data-sort-rank')) || 999;
|
|
const rankB = parseFloat($(b).attr('data-sort-rank')) || 999;
|
|
return rankA - rankB;
|
|
});
|
|
|
|
// Re-append the sorted rows back into the tbody
|
|
$.each(rows, (index, row) => {
|
|
$tbody.append(row);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
applyRanks(rankAll(rangNotenArray, rangSort), true);
|
|
|
|
function updateDependentVisibility($changedEl) {
|
|
const uid = $changedEl.attr('data-uid');
|
|
if (uid === undefined || uid === null) return;
|
|
|
|
const $row = $changedEl.closest('tr');
|
|
|
|
if ($row.length !== 1) return;
|
|
|
|
const isVisible = $changedEl.hasClass('emtyPlaceholder');
|
|
const doesExist = $changedEl.hasClass('nonExistentEl');
|
|
|
|
const $linkedEl = $row.find(`.singleValueSpan[data-linked-el-uid="${uid}"]`)
|
|
|
|
$linkedEl.toggleClass('emtyPlaceholder', isVisible).toggleClass('nonExistentEl', doesExist);
|
|
}
|
|
|
|
function initConditionalVisibility() {
|
|
const $table = $('table.customDisplayEditorTable');
|
|
|
|
const $rows = $table.find('tr');
|
|
|
|
$rows.each((ind, el) => {
|
|
const $row = $(el);
|
|
$row.find('.singleValueSpan[data-linked-el-uid]').each(function() {
|
|
const linkedUid = $(this).data('linked-el-uid');
|
|
|
|
const $linkedEl = $row.find(`.singleValueSpan[data-uid="${linkedUid}"]`);
|
|
|
|
if ($linkedEl.length === 1) {
|
|
updateDependentVisibility($linkedEl);
|
|
}
|
|
});
|
|
})
|
|
|
|
}
|
|
|
|
initConditionalVisibility();
|
|
initConditionalVisibility();
|
|
|
|
const $parent = $('.allAbtContainer');
|
|
const $children = $parent.children('.shiftedGeraetTable');
|
|
|
|
$children.sort((a, b) => {
|
|
const idA = parseFloat($(a).data('geraet-index'));
|
|
const idB = parseFloat($(b).data('geraet-index'));
|
|
|
|
return idA - idB;
|
|
});
|
|
|
|
$parent.append($children); |