Enhance official tournament listing and upload functionality

This commit updates the `listOfficialTournaments` function to ensure it returns an empty array if no tournaments are found, improving data handling. Additionally, the frontend `OfficialTournaments.vue` is enhanced with a file upload feature for PDF documents, along with improved error handling in the tournament list loading process. These changes enhance user experience by providing clearer feedback and functionality for managing official tournaments.
This commit is contained in:
Torsten Schulz (local)
2025-11-15 21:25:03 +01:00
parent 54ce09e9a9
commit f1321b18bb
4 changed files with 209 additions and 9 deletions

View File

@@ -233,9 +233,11 @@ export const listOfficialTournaments = async (req, res) => {
const { clubId } = req.params;
await checkAccess(userToken, clubId);
const list = await OfficialTournament.findAll({ where: { clubId } });
res.status(200).json(list);
res.status(200).json(Array.isArray(list) ? list : []);
} catch (e) {
res.status(500).json({ error: 'Failed to list tournaments' });
console.error('[listOfficialTournaments] Error:', e);
const errorMessage = e.message || 'Failed to list tournaments';
res.status(e.statusCode || 500).json({ error: errorMessage });
}
};

View File

@@ -0,0 +1,77 @@
# Liste aller Tabellen im Trainingstagebuch-Projekt
## Basis-Tabellen
1. `user` - Benutzer
2. `user_club` - Verknüpfung Benutzer ↔ Verein
3. `user_token` - Authentifizierungs-Tokens
4. `clubs` - Vereine
5. `log` - System-Logs
## Mitglieder-Verwaltung
6. `member` - Mitglieder
7. `member_contact` - Kontaktdaten der Mitglieder (Telefon, E-Mail)
8. `member_image` - Bilder der Mitglieder
9. `member_notes` - Notizen zu Mitgliedern
10. `member_transfer_config` - Konfiguration für Mitgliederübertragung
## Trainingsgruppen (NEU)
11. `training_group` - Trainingsgruppen
12. `member_training_group` - Verknüpfung Mitglied ↔ Trainingsgruppe
13. `club_disabled_preset_groups` - Deaktivierte Preset-Gruppen pro Verein
14. `training_times` - Trainingszeiten pro Gruppe (NEU)
## Tagebuch
15. `diary_dates` - Trainingstage
16. `participants` - Teilnehmer an Trainingstagen
17. `activities` - Aktivitäten
18. `diary_notes` - Notizen zu Trainingstagen
19. `diary_tags` - Tags für Tagebuch
20. `member_diary_tags` - Verknüpfung Mitglied ↔ Tagebuch-Tag
21. `diary_date_tags` - Verknüpfung Trainingstag ↔ Tag
22. `diary_member_notes` - Notizen zu Mitgliedern an Trainingstagen
23. `diary_member_tags` - Tags für Mitglieder an Trainingstagen
24. `diary_date_activities` - Aktivitäten an Trainingstagen
25. `diary_member_activities` - Verknüpfung Teilnehmer ↔ Aktivität
26. `group` - Gruppen (für Trainingsplan)
27. `group_activity` - Gruppenaktivitäten
## Vordefinierte Aktivitäten
28. `predefined_activities` - Vordefinierte Aktivitäten
29. `predefined_activity_images` - Bilder zu vordefinierten Aktivitäten
## Unfälle
30. `accident` - Unfälle
## Teams & Ligen
31. `season` - Saisons
32. `league` - Ligen
33. `team` - Teams
34. `club_team` - Verknüpfung Verein ↔ Team
35. `team_document` - Dokumente zu Teams
36. `match` - Spiele
37. `location` - Spielorte
## Turniere
38. `tournament` - Turniere
39. `tournament_class` - Turnierklassen
40. `tournament_group` - Turniergruppen
41. `tournament_member` - Teilnehmer an Turnieren
42. `tournament_match` - Spiele in Turnieren
43. `tournament_result` - Ergebnisse von Turnierspielen
44. `external_tournament_participant` - Externe Teilnehmer an Turnieren
## Offizielle Turniere (myTischtennis)
45. `official_tournaments` - Offizielle Turniere
46. `official_competitions` - Wettbewerbe in offiziellen Turnieren
47. `official_competition_members` - Teilnehmer an offiziellen Wettbewerben
## myTischtennis Integration
48. `my_tischtennis` - myTischtennis-Verbindungen
49. `my_tischtennis_update_history` - Update-Historie
50. `my_tischtennis_fetch_log` - Fetch-Logs
## API & Logging
51. `api_log` - API-Logs
## Gesamt: 51 Tabellen

View File

@@ -0,0 +1,107 @@
import mysql from 'mysql2/promise';
import dotenv from 'dotenv';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import { development } from '../config.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Load .env from backend directory
dotenv.config({ path: join(__dirname, '..', '.env') });
const dbConfig = {
host: process.env.DB_HOST || development.host || 'localhost',
user: process.env.DB_USER || development.username || 'root',
password: process.env.DB_PASSWORD || development.password || '',
database: process.env.DB_NAME || development.database || 'trainingdiary',
};
async function checkDatabase() {
let connection;
try {
console.log('=== Datenbank-Konfiguration ===');
console.log('Host:', dbConfig.host);
console.log('User:', dbConfig.user);
console.log('Database:', dbConfig.database);
console.log('Password:', dbConfig.password ? '***' : '(leer)');
console.log('');
connection = await mysql.createConnection({
host: dbConfig.host,
user: dbConfig.user,
password: dbConfig.password,
});
// Zeige alle verfügbaren Datenbanken
const [databases] = await connection.execute('SHOW DATABASES');
console.log('=== Verfügbare Datenbanken ===');
databases.forEach(db => {
const dbName = db.Database;
const isCurrent = dbName === dbConfig.database;
console.log(`${isCurrent ? '→' : ' '} ${dbName}${isCurrent ? ' (VERWENDET)' : ''}`);
});
console.log('');
// Verbinde mit der konfigurierten Datenbank
await connection.end();
connection = await mysql.createConnection(dbConfig);
// Prüfe, ob die Tabellen existieren
const [tables] = await connection.execute('SHOW TABLES');
console.log(`=== Tabellen in "${dbConfig.database}" ===`);
console.log(`Anzahl: ${tables.length}`);
console.log('');
// Prüfe spezifische Tabellen
const tableNames = tables.map(t => Object.values(t)[0]);
const requiredTables = [
'training_group',
'member_training_group',
'club_disabled_preset_groups',
'training_times',
'official_tournaments',
];
console.log('=== Prüfung wichtiger Tabellen ===');
for (const table of requiredTables) {
const exists = tableNames.includes(table);
console.log(`${exists ? '✓' : '✗'} ${table}`);
}
console.log('');
// Prüfe official_tournaments Daten
if (tableNames.includes('official_tournaments')) {
const [count] = await connection.execute('SELECT COUNT(*) as count FROM official_tournaments');
console.log(`=== official_tournaments Daten ===`);
console.log(`Anzahl Einträge: ${count[0].count}`);
if (count[0].count > 0) {
// Prüfe zuerst die Spaltennamen
const [columns] = await connection.execute('SHOW COLUMNS FROM official_tournaments');
console.log('Spalten in official_tournaments:');
columns.forEach(col => {
console.log(` - ${col.Field} (${col.Type})`);
});
console.log('');
const [rows] = await connection.execute('SELECT * FROM official_tournaments LIMIT 5');
console.log('Erste Einträge:');
rows.forEach(row => {
console.log(` - ID: ${row.id}, Daten:`, JSON.stringify(row, null, 2));
});
}
}
} catch (error) {
console.error('Fehler:', error.message);
if (error.code) {
console.error('Error Code:', error.code);
}
} finally {
if (connection) await connection.end();
}
}
checkDatabase();

View File

@@ -1,7 +1,13 @@
<template>
<div class="official-tournaments">
<h2>Turnierteilnahmen</h2>
<div v-if="list && list.length" class="list">
<div class="uploader">
<input type="file" accept="application/pdf" @change="onFile" />
<button class="btn-primary" :disabled="!selectedFile" @click="upload">PDF hochladen</button>
</div>
<div v-if="list && list.length > 0" class="list">
<div class="tabs">
<button :class="['tab', topActiveTab==='events' ? 'active' : '']" @click="switchTopTab('events')" title="Gespeicherte Veranstaltungen anzeigen">Veranstaltungen</button>
<button :class="['tab', topActiveTab==='participations' ? 'active' : '']" @click="switchTopTab('participations')" title="Turnierbeteiligungen anzeigen">Turnierbeteiligungen</button>
@@ -17,11 +23,13 @@
<button class="btn-secondary" @click.prevent="removeTournament(t)" title="Löschen">🗑</button>
</li>
</ul>
<div class="uploader">
<input type="file" accept="application/pdf" @change="onFile" />
<button class="btn-primary" :disabled="!selectedFile" @click="upload">PDF hochladen</button>
</div>
</div>
<div v-if="!list || list.length === 0" class="empty-state">
<p>Noch keine Veranstaltungen vorhanden. Laden Sie ein PDF hoch, um zu beginnen.</p>
</div>
<div v-if="parsed">
<div class="meta">
<div><strong>Titel:</strong> {{ parsed.parsedData.title || '' }}</div>
@@ -913,8 +921,14 @@ export default {
this.members = m.data;
},
async loadList() {
const r = await apiClient.get(`/official-tournaments/${this.currentClub}`);
this.list = r.data;
try {
const r = await apiClient.get(`/official-tournaments/${this.currentClub}`);
this.list = Array.isArray(r.data) ? r.data : [];
} catch (error) {
console.error('[loadList] Error:', error);
this.list = [];
// Fehler wird nicht angezeigt, damit die Seite trotzdem funktioniert
}
},
buildParticipationMap(entries) {
const map = {};