Fügt eine Funktion zur PDF-Generierung für ausgewählte Mitglieder in OfficialTournaments.vue hinzu. Implementiert ein Dialogfeld zur Auswahl von Mitgliedern und ermöglicht die Erstellung eines PDFs mit Wettbewerbsinformationen. Aktualisiert das Styling für die Benutzeroberfläche und die Modal-Komponenten.
This commit is contained in:
@@ -264,6 +264,35 @@ class PDFGenerator {
|
||||
});
|
||||
}
|
||||
|
||||
addMemberCompetitions(tournamentTitle, memberName, rows) {
|
||||
let y = this.margin;
|
||||
this.pdf.setFont('helvetica', 'bold');
|
||||
this.pdf.setFontSize(14);
|
||||
this.pdf.text(tournamentTitle || 'Offizielles Turnier', this.margin, y);
|
||||
y += 9;
|
||||
this.pdf.setFont('helvetica', 'normal');
|
||||
this.pdf.setFontSize(12);
|
||||
this.pdf.text(`Mitglied: ${memberName}`, this.margin, y);
|
||||
y += 8;
|
||||
this.pdf.setFont('helvetica', 'bold');
|
||||
this.pdf.text('Wettbewerb', this.margin, y);
|
||||
this.pdf.text('Datum', this.margin + 110, y);
|
||||
this.pdf.text('Startzeit', this.margin + 150, y);
|
||||
y += 7;
|
||||
this.pdf.setFont('helvetica', 'normal');
|
||||
for (const r of rows) {
|
||||
this.pdf.text(r.name || '', this.margin, y);
|
||||
this.pdf.text(r.date || '–', this.margin + 110, y);
|
||||
this.pdf.text(r.time || '–', this.margin + 150, y);
|
||||
y += 7;
|
||||
if (y > this.pageHeight) {
|
||||
this.addNewPage();
|
||||
y = this.margin;
|
||||
}
|
||||
}
|
||||
this.cursorY = y + 10;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default PDFGenerator;
|
||||
|
||||
@@ -42,6 +42,10 @@
|
||||
</div>
|
||||
<!-- ehemals 'Erkannte Einträge' entfernt -->
|
||||
</div>
|
||||
<div class="top-actions">
|
||||
<button class="btn-secondary" @click="openMemberDialog" :disabled="!parsed || !activeMembers.length">Mitglieder auswählen</button>
|
||||
<button class="btn-primary" :disabled="!selectedMemberIds.length" @click="generateMembersPdf">PDF für markierte Mitglieder</button>
|
||||
</div>
|
||||
|
||||
<div v-if="parsed && parsed.parsedData.competitions && parsed.parsedData.competitions.length">
|
||||
<h3>Konkurrenzen</h3>
|
||||
@@ -101,12 +105,36 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div v-if="showMemberDialog" class="modal-overlay" @click.self="closeMemberDialog">
|
||||
<div class="modal">
|
||||
<div class="modal-header">
|
||||
<h3>Mitglieder auswählen</h3>
|
||||
</div>
|
||||
<div class="modal-controls">
|
||||
<button class="btn-secondary" @click="selectAllMembers">Alle auswählen</button>
|
||||
<button class="btn-secondary" @click="deselectAllMembers">Alle abwählen</button>
|
||||
<div style="flex:1;"></div>
|
||||
<button class="btn-secondary" @click="closeMemberDialog">Schließen</button>
|
||||
<button class="btn-primary" :disabled="!selectedMemberIds.length" @click="generateMembersPdf; closeMemberDialog()">PDF erzeugen</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="checkbox-column">
|
||||
<label v-for="m in activeMembers" :key="m.id" class="check-item">
|
||||
<input type="checkbox" :value="m.id" v-model="selectedMemberIds" />
|
||||
<span>{{ m.firstName }} {{ m.lastName }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import apiClient from '../apiClient.js';
|
||||
import { mapGetters } from 'vuex';
|
||||
import PDFGenerator from '../components/PDFGenerator.js';
|
||||
|
||||
export default {
|
||||
name: 'OfficialTournaments',
|
||||
@@ -119,10 +147,15 @@ export default {
|
||||
matches: {},
|
||||
list: [],
|
||||
expanded: {},
|
||||
selectedMemberIds: [],
|
||||
showMemberDialog: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['currentClub'])
|
||||
...mapGetters(['currentClub']),
|
||||
activeMembers() {
|
||||
return (this.members || []).filter(m => m.active === true);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onFile(e) {
|
||||
@@ -158,6 +191,50 @@ export default {
|
||||
const r = await apiClient.get(`/official-tournaments/${this.currentClub}`);
|
||||
this.list = r.data;
|
||||
},
|
||||
// Auswahl Helfer + PDF-Generierung
|
||||
openMemberDialog() { this.showMemberDialog = true; },
|
||||
closeMemberDialog() { this.showMemberDialog = false; },
|
||||
toggleAllMembers() {
|
||||
const ids = this.activeMembers.map(m => m.id);
|
||||
if (this.selectedMemberIds.length === ids.length) this.selectedMemberIds = [];
|
||||
else this.selectedMemberIds = ids.slice();
|
||||
},
|
||||
selectAllMembers() { this.selectedMemberIds = this.activeMembers.map(m => m.id); },
|
||||
deselectAllMembers() { this.selectedMemberIds = []; },
|
||||
splitDateTime(str) {
|
||||
if (!str) return { date: '–', time: '–' };
|
||||
const s = String(str);
|
||||
const dm = s.match(/(\d{1,2}\.\d{1,2}\.\d{4})/);
|
||||
const tm = s.match(/(\d{1,2}:\d{2})/);
|
||||
return { date: dm ? dm[1] : '–', time: tm ? tm[1] : '–' };
|
||||
},
|
||||
competitionsForMember(member) {
|
||||
const comps = (this.parsed && this.parsed.parsedData && this.parsed.parsedData.competitions) ? this.parsed.parsedData.competitions : [];
|
||||
const rows = [];
|
||||
for (const c of comps) {
|
||||
if (this.isEligibleForCompetition(member, c)) {
|
||||
const title = c.ageClassCompetition || c.altersklasseWettbewerb || '';
|
||||
const st = this.splitDateTime(c.startTime || c.startzeit || '');
|
||||
rows.push({ name: title, date: st.date, time: st.time });
|
||||
}
|
||||
}
|
||||
return rows;
|
||||
},
|
||||
async generateMembersPdf() {
|
||||
if (!this.selectedMemberIds.length) return;
|
||||
const pdf = new PDFGenerator();
|
||||
const title = (this.parsed && this.parsed.parsedData && this.parsed.parsedData.title) ? this.parsed.parsedData.title : 'Offizielles Turnier';
|
||||
let first = true;
|
||||
for (const mid of this.selectedMemberIds) {
|
||||
const m = (this.members || []).find(x => x.id === mid);
|
||||
if (!m) continue;
|
||||
const rows = this.competitionsForMember(m);
|
||||
if (!first) pdf.addNewPage();
|
||||
first = false;
|
||||
pdf.addMemberCompetitions(title, `${m.firstName} ${m.lastName}`, rows);
|
||||
}
|
||||
pdf.save('turnier_mitglieder.pdf');
|
||||
},
|
||||
// Eligibility: Datum parsen (dd.mm.yyyy | yyyy-mm-dd | ISO)
|
||||
parseDateFlexible(s) {
|
||||
if (!s || typeof s !== 'string') return null;
|
||||
@@ -329,6 +406,7 @@ export default {
|
||||
|
||||
<style scoped>
|
||||
.official-tournaments { display: flex; flex-direction: column; gap: 0.75rem; }
|
||||
.top-actions { display: flex; gap: .5rem; margin-bottom: .5rem; }
|
||||
.uploader { display: flex; gap: 0.5rem; align-items: center; }
|
||||
table { width: 100%; border-collapse: collapse; }
|
||||
th, td { border-bottom: 1px solid var(--border-color); padding: 0.5rem; text-align: left; }
|
||||
@@ -339,6 +417,13 @@ th, td { border-bottom: 1px solid var(--border-color); padding: 0.5rem; text-ali
|
||||
.eligible-name { background: var(--background, #f1f1f1); border: 1px solid var(--border-color, #ddd); border-radius: 4px; padding: 2px 6px; }
|
||||
.eligible-table { width: 100%; border-collapse: collapse; margin-top: .25rem; }
|
||||
.eligible-table th, .eligible-table td { border-bottom: 1px solid var(--border-color); padding: .25rem .4rem; text-align: left; }
|
||||
.modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,.35); display: flex; align-items: center; justify-content: center; z-index: 1000; }
|
||||
.modal { background: #fff; border-radius: 8px; width: min(800px, 92vw); max-height: 85vh; display: flex; flex-direction: column; box-shadow: 0 10px 30px rgba(0,0,0,.2); }
|
||||
.modal-header { padding: .75rem 1rem; border-bottom: 1px solid var(--border-color); }
|
||||
.modal-controls { display: flex; align-items: center; gap: .5rem; padding: .5rem 1rem; border-bottom: 1px solid var(--border-color); }
|
||||
.modal-body { padding: .75rem 1rem; overflow: auto; }
|
||||
.checkbox-column { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: .5rem .75rem; }
|
||||
.check-item { display: flex; align-items: center; gap: .35rem; }
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user