feat(MemberTtrHistory): implement TTR history management and UI enhancements

- Added new endpoints in the member controller for retrieving and refreshing TTR history.
- Integrated TTR history functionality into the member service, allowing for seamless data retrieval and updates.
- Updated the member model to include a field for TTR history player ID, enhancing data tracking.
- Enhanced the MembersView to display TTR history with a dedicated dialog for better user interaction.
- Improved the MyTischtennisClient to support fetching historical player IDs, enriching the data provided to users.
- Refactored various components to ensure consistent styling and functionality across the application.
This commit is contained in:
Torsten Schulz (local)
2026-03-18 15:34:10 +01:00
parent 79adad9564
commit 563a7e8dde
16 changed files with 1471 additions and 108 deletions

View File

@@ -869,7 +869,8 @@ class MyTischtennisClient {
* @param {string} fedNickname - Federation nickname (e.g., "HeTTV")
* @returns {Promise<Object>} Rankings with player entries (all pages)
*/
async getClubRankings(cookie, clubId, fedNickname, currentRanking = 'yes') {
async getClubRankings(cookie, clubId, fedNickname, currentRanking = 'yes', options = {}) {
const { includeHistoryPlayerIds = false } = options;
const allEntries = [];
let currentPage = 0;
let hasMorePages = true;
@@ -877,8 +878,6 @@ class MyTischtennisClient {
while (hasMorePages) {
const endpoint = `/rankings/andro-rangliste?all-players=on&clubnr=${clubId}&fednickname=${fedNickname}&current-ranking=${currentRanking}&results-per-page=100&page=${currentPage}&_data=routes%2F%24`;
const result = await this.authenticatedRequest(endpoint, cookie, {
method: 'GET'
});
@@ -917,15 +916,39 @@ class MyTischtennisClient {
error: 'Keine entries in blockLoaderData gefunden'
};
}
let historyPlayerIdsByName = null;
if (includeHistoryPlayerIds) {
const htmlEndpoint = `/rankings/andro-rangliste?clubnr=${clubId}&fednickname=${fedNickname}&all-players=on&continent=all&country=all&current-ranking=${currentRanking}&results-per-page=100&page=${currentPage + 1}`;
const htmlResult = await this.authenticatedRequest(htmlEndpoint, cookie, {
method: 'GET',
headers: {
Accept: 'text/html,application/xhtml+xml'
}
});
historyPlayerIdsByName = htmlResult.success
? this.extractHistoryPlayerIdsFromAndroRankingHtml(htmlResult.data)
: new Map();
}
const enrichedEntries = entries.map((entry) => {
const nameKey = this._buildRankingNameKey(entry?.firstname, entry?.lastname);
const historyPlayerId = historyPlayerIdsByName?.get(nameKey) || null;
return {
...entry,
historyPlayerId,
myTischtennisHistoryPlayerId: historyPlayerId
};
});
// Füge Entries hinzu
allEntries.push(...entries);
allEntries.push(...enrichedEntries);
// Prüfe ob es weitere Seiten gibt
// Wenn die aktuelle Seite weniger Einträge hat als das Limit, sind wir am Ende
// Oder wenn wir alle erwarteten Einträge haben
if (entries.length === 0) {
if (enrichedEntries.length === 0) {
hasMorePages = false;
} else if (rankingData.numberOfPages && currentPage >= rankingData.numberOfPages - 1) {
hasMorePages = false;
@@ -946,6 +969,45 @@ class MyTischtennisClient {
}
};
}
extractHistoryPlayerIdsFromAndroRankingHtml(html) {
const result = new Map();
const source = typeof html === 'string' ? html : String(html || '');
const anchorPattern = /href="\/community\/external-profile\?player-id=(P[A-Z0-9]+)"[^>]*>([^<]+)<\/a>/gi;
let match = null;
while ((match = anchorPattern.exec(source)) !== null) {
const playerId = match[1];
const fullName = this._decodeHtmlEntities(match[2] || '');
const key = this._buildRankingFullNameKey(fullName);
if (key && playerId && !result.has(key)) {
result.set(key, playerId);
}
}
return result;
}
_buildRankingNameKey(firstname, lastname) {
return this._buildRankingFullNameKey(`${firstname || ''} ${lastname || ''}`);
}
_buildRankingFullNameKey(name) {
return String(name || '')
.normalize('NFKC')
.replace(/\s+/g, ' ')
.trim()
.toLowerCase();
}
_decodeHtmlEntities(value) {
return String(value || '')
.replace(/&amp;/g, '&')
.replace(/&quot;/g, '"')
.replace(/&#39;/g, "'")
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>');
}
}
export default new MyTischtennisClient();