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);