diff --git a/package.json b/package.json index dd657bc..d20fa95 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "harheimertc-website", - "version": "1.8.9", + "version": "1.8.10", "description": "Moderne Webseite für den Harheimer Tischtennis Club", "private": true, "type": "module", diff --git a/pages/mitgliederbereich/qttr.vue b/pages/mitgliederbereich/qttr.vue index ae3b5d0..d9e5f97 100755 --- a/pages/mitgliederbereich/qttr.vue +++ b/pages/mitgliederbereich/qttr.vue @@ -46,7 +46,7 @@ - Rang + Vereinsrang
(QTTR / TTR) Spieler @@ -69,7 +69,9 @@ :class="isOwnRow(row.playerName) ? 'bg-primary-100' : ''" > - {{ row.rank ?? '–' }} + + {{ formatClubRank(row) }} +
@@ -133,6 +135,20 @@ function isOwnRow(playerName) { return normalizeName(playerName) === current } +function formatClubRank(row) { + const qttrRank = row.clubQttrRank + const ttrRank = row.clubTtrRank + if (qttrRank == null) return '–' + return ttrRank == null ? String(qttrRank) : `${qttrRank} (${ttrRank})` +} + +function clubRankTitle(row) { + if (row.clubQttrRank == null) return 'Keine QTTR-Platzierung verfügbar' + return row.clubTtrRank == null + ? `QTTR-Platzierung im Verein: ${row.clubQttrRank}` + : `QTTR-Platzierung im Verein: ${row.clubQttrRank}; TTR-Platzierung im Verein: ${row.clubTtrRank}` +} + function getPlayerNameClass(row) { const minor = isMinor(row.birthdate) if (minor && isMaleGender(row.gender)) return 'text-blue-400' diff --git a/server/api/mitgliederbereich/qttr.get.js b/server/api/mitgliederbereich/qttr.get.js index 85ad60c..f9b4900 100755 --- a/server/api/mitgliederbereich/qttr.get.js +++ b/server/api/mitgliederbereich/qttr.get.js @@ -37,6 +37,26 @@ function buildBirthdateLookup(entries) { return lookup } +function addClubRanks(rows) { + const addRankFor = (valueKey, rankKey) => { + rows + .filter((row) => row[valueKey] != null) + .sort((a, b) => b[valueKey] - a[valueKey] || String(a.playerName || '').localeCompare(String(b.playerName || ''), 'de')) + .forEach((row, index) => { + row[rankKey] = index + 1 + }) + } + + addRankFor('currentQttr', 'clubQttrRank') + addRankFor('currentTtr', 'clubTtrRank') + + return rows.sort((a, b) => + (a.clubQttrRank ?? Number.POSITIVE_INFINITY) - (b.clubQttrRank ?? Number.POSITIVE_INFINITY) + || (a.clubTtrRank ?? Number.POSITIVE_INFINITY) - (b.clubTtrRank ?? Number.POSITIVE_INFINITY) + || String(a.playerName || '').localeCompare(String(b.playerName || ''), 'de') + ) +} + export default defineEventHandler(async (event) => { const token = getCookie(event, 'auth_token') || getHeader(event, 'authorization')?.replace(/^Bearer\s+/i, '') if (!token || !verifyToken(token)) { @@ -73,16 +93,16 @@ export default defineEventHandler(async (event) => { ].flatMap(entry => [entry?.name, `${entry?.firstName || ''} ${entry?.lastName || ''}`.trim()]).map(normalizeName).filter(Boolean)) const birthdateLookup = buildBirthdateLookup([...visibleManualMembers, ...visibleUsers]) + const rankedRows = addClubRanks(Array.isArray(payload.rows) ? payload.rows.map(row => ({ ...row })) : []) + return { ...payload, - rows: Array.isArray(payload.rows) - ? payload.rows + rows: rankedRows .filter(row => !hiddenNames.has(normalizeName(row.playerName))) .map((row) => ({ ...row, birthdate: birthdateLookup.get(normalizeName(row.playerName)) || row.birthdate || '' })) - : [] } } catch (error) { if (error?.code === 'ENOENT') { @@ -98,4 +118,4 @@ export default defineEventHandler(async (event) => { message: 'Fehler beim Laden der QTTR-Werte.' }) } -}) \ No newline at end of file +}) diff --git a/server/utils/qttr-import.js b/server/utils/qttr-import.js index 96ee9ab..d0ee057 100755 --- a/server/utils/qttr-import.js +++ b/server/utils/qttr-import.js @@ -67,6 +67,26 @@ function normalizeGender(value) { return normalized || null } +function addClubRanks(rows) { + const addRankFor = (valueKey, rankKey) => { + rows + .filter((row) => row[valueKey] != null) + .sort((a, b) => b[valueKey] - a[valueKey] || String(a.playerName || '').localeCompare(String(b.playerName || ''), 'de')) + .forEach((row, index) => { + row[rankKey] = index + 1 + }) + } + + addRankFor('currentQttr', 'clubQttrRank') + addRankFor('currentTtr', 'clubTtrRank') + + return rows.sort((a, b) => + (a.clubQttrRank ?? Number.POSITIVE_INFINITY) - (b.clubQttrRank ?? Number.POSITIVE_INFINITY) + || (a.clubTtrRank ?? Number.POSITIVE_INFINITY) - (b.clubTtrRank ?? Number.POSITIVE_INFINITY) + || String(a.playerName || '').localeCompare(String(b.playerName || ''), 'de') + ) +} + function extractTableBlocks(html) { return [...String(html || '').matchAll(/]*>[\s\S]*?<\/table>/gi)].map((match) => match[0]) } @@ -190,7 +210,7 @@ export async function importQttrValues(options = {}) { throw new Error('QTTR-Tabelle ist leer oder unvollständig') } - const parsedRows = rows.map((cells) => deriveQttrFields(headers, cells)) + const parsedRows = addClubRanks(rows.map((cells) => deriveQttrFields(headers, cells))) const payload = { format: 'harheimertc.qttr.v1', importedAt: new Date().toISOString(), @@ -243,6 +263,8 @@ async function importAuthenticatedTtrValues(connection) { }) }).filter(row => row.playerName && (row.currentTtr != null || row.currentQttr != null)) + addClubRanks(rows) + if (!rows.length) throw new Error('Keine verwertbaren TTR-Werte in der myTischtennis-Rangliste gefunden.') const previousQttrSignature = qttrListSignature(previousPayload.rows) const nextQttrSignature = qttrListSignature(rows)