Add training day PDF generation and summary functionality
This commit introduces a new method, `addTrainingDaySummary`, in the `PDFGenerator` class to create detailed summaries for training days, including member activities and their respective times. Additionally, the `DiaryView` component is updated with a new button to generate training day PDFs, enhancing the user experience by allowing easy access to training summaries. Debugging outputs are included for better tracking of data during PDF generation.
This commit is contained in:
@@ -1153,6 +1153,310 @@ class PDFGenerator {
|
||||
.join(', ');
|
||||
}
|
||||
|
||||
addTrainingDaySummary(clubName, trainingDate, trainingStart, trainingEnd, members, activities, activityMembersMap, groupActivityMembersMap, participantMapByMemberId, groups, trainingPlan = [], memberGroupsMap = {}) {
|
||||
// Header
|
||||
const formattedDate = new Date(trainingDate).toLocaleDateString('de-DE');
|
||||
const formattedStartTime = trainingStart ? trainingStart.slice(0, 5) : '';
|
||||
const formattedEndTime = trainingEnd ? trainingEnd.slice(0, 5) : '';
|
||||
|
||||
this.pdf.setFontSize(14);
|
||||
this.pdf.setFont('helvetica', 'bold');
|
||||
this.pdf.text(`${clubName} - Trainingstag`, this.margin, this.cursorY);
|
||||
this.cursorY += 8;
|
||||
this.pdf.setFontSize(12);
|
||||
this.pdf.setFont('helvetica', 'normal');
|
||||
this.pdf.text(`Datum: ${formattedDate}`, this.margin, this.cursorY);
|
||||
this.cursorY += 7;
|
||||
if (formattedStartTime && formattedEndTime) {
|
||||
this.pdf.text(`Uhrzeit: ${formattedStartTime} - ${formattedEndTime}`, this.margin, this.cursorY);
|
||||
this.cursorY += 7;
|
||||
}
|
||||
this.cursorY += 5;
|
||||
|
||||
// Debug-Ausgaben
|
||||
console.log('[addTrainingDaySummary] members:', members?.length || 0);
|
||||
console.log('[addTrainingDaySummary] activities:', activities?.length || 0);
|
||||
console.log('[addTrainingDaySummary] activityMembersMap:', activityMembersMap);
|
||||
console.log('[addTrainingDaySummary] participantMapByMemberId:', participantMapByMemberId);
|
||||
|
||||
// Erstelle Mapping: memberId -> Liste von Aktivitäten
|
||||
const memberActivitiesMap = new Map();
|
||||
|
||||
// Initialisiere für alle Teilnehmer
|
||||
members.forEach(member => {
|
||||
if (member && member.id) {
|
||||
memberActivitiesMap.set(member.id, []);
|
||||
}
|
||||
});
|
||||
|
||||
// Erstelle umgekehrtes Mapping: participantId -> memberId (einmalig)
|
||||
const participantToMemberMap = {};
|
||||
if (participantMapByMemberId) {
|
||||
Object.keys(participantMapByMemberId).forEach(memberId => {
|
||||
const participantId = participantMapByMemberId[memberId];
|
||||
if (participantId) {
|
||||
participantToMemberMap[participantId] = parseInt(memberId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Erstelle Mapping von activityId zu startTime aus trainingPlan
|
||||
const activityIdToStartTime = new Map();
|
||||
trainingPlan.forEach(item => {
|
||||
if (item && item.id && item.startTime) {
|
||||
activityIdToStartTime.set(item.id, item.startTime);
|
||||
}
|
||||
});
|
||||
|
||||
// Durchlaufe alle Aktivitäten
|
||||
let activitiesProcessed = 0;
|
||||
let activitiesWithMembers = 0;
|
||||
activities.forEach(activity => {
|
||||
if (!activity || !activity.id) return;
|
||||
activitiesProcessed++;
|
||||
|
||||
const activityName = activity.predefinedActivity
|
||||
? activity.predefinedActivity.name
|
||||
: (activity.description || 'Unbekannte Aktivität');
|
||||
// Verwende startTime aus trainingPlan, falls vorhanden, sonst leer
|
||||
const activityStartTime = activityIdToStartTime.get(activity.id) || activity.startTime || '';
|
||||
const activityDuration = activity.duration ? `${activity.duration} Min` : '';
|
||||
|
||||
// Finde alle Teilnehmer dieser Aktivität
|
||||
const participantIds = activityMembersMap[activity.id];
|
||||
let hasDirectAssignments = false;
|
||||
|
||||
if (participantIds) {
|
||||
// Handle both Set and Array
|
||||
const idsArray = participantIds instanceof Set ? Array.from(participantIds) : (Array.isArray(participantIds) ? participantIds : []);
|
||||
if (idsArray.length > 0) {
|
||||
hasDirectAssignments = true;
|
||||
activitiesWithMembers++;
|
||||
idsArray.forEach(participantId => {
|
||||
// Finde memberId für diesen participantId
|
||||
const memberId = participantToMemberMap[participantId];
|
||||
if (memberId) {
|
||||
if (!memberActivitiesMap.has(memberId)) {
|
||||
memberActivitiesMap.set(memberId, []);
|
||||
}
|
||||
memberActivitiesMap.get(memberId).push({
|
||||
name: activityName,
|
||||
startTime: activityStartTime,
|
||||
duration: activityDuration
|
||||
});
|
||||
} else {
|
||||
console.log('[addTrainingDaySummary] Kein memberId gefunden für participantId:', participantId);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Wenn keine direkten Zuordnungen vorhanden sind, verwende Gruppen-Zuordnung
|
||||
if (!hasDirectAssignments) {
|
||||
const activityGroupId = activity.groupActivity ? activity.groupActivity.id : null;
|
||||
|
||||
if (activityGroupId) {
|
||||
// Aktivität hat eine Gruppe: nehme alle Members dieser Gruppe
|
||||
members.forEach(member => {
|
||||
if (member && member.id) {
|
||||
const memberGroupId = memberGroupsMap[member.id];
|
||||
// Prüfe ob memberGroupId als String oder Number vorliegt
|
||||
const memberGroupIdStr = memberGroupId ? String(memberGroupId) : '';
|
||||
const activityGroupIdStr = String(activityGroupId);
|
||||
|
||||
if (memberGroupIdStr === activityGroupIdStr) {
|
||||
if (!memberActivitiesMap.has(member.id)) {
|
||||
memberActivitiesMap.set(member.id, []);
|
||||
}
|
||||
memberActivitiesMap.get(member.id).push({
|
||||
name: activityName,
|
||||
startTime: activityStartTime,
|
||||
duration: activityDuration
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Aktivität hat keine Gruppe: nehme alle Teilnehmer
|
||||
members.forEach(member => {
|
||||
if (member && member.id) {
|
||||
if (!memberActivitiesMap.has(member.id)) {
|
||||
memberActivitiesMap.set(member.id, []);
|
||||
}
|
||||
memberActivitiesMap.get(member.id).push({
|
||||
name: activityName,
|
||||
startTime: activityStartTime,
|
||||
duration: activityDuration
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
console.log('[addTrainingDaySummary] Aktivitäten verarbeitet:', activitiesProcessed, 'mit Mitgliedern:', activitiesWithMembers);
|
||||
|
||||
// Durchlaufe alle Gruppenaktivitäten
|
||||
// WICHTIG: Auch aus Zeitblöcken, daher trainingPlan verwenden statt activities
|
||||
if (groupActivityMembersMap && Object.keys(groupActivityMembersMap).length > 0) {
|
||||
// Finde Gruppenaktivitäten in trainingPlan (auch in Zeitblöcken)
|
||||
trainingPlan.forEach(activity => {
|
||||
if (!activity || !activity.groupActivities) return;
|
||||
|
||||
activity.groupActivities.forEach(groupActivity => {
|
||||
if (!groupActivity || !groupActivity.id) return;
|
||||
|
||||
const activityName = groupActivity.groupPredefinedActivity
|
||||
? groupActivity.groupPredefinedActivity.name
|
||||
: (groupActivity.description || 'Unbekannte Gruppenaktivität');
|
||||
// Verwende startTime aus trainingPlan für die übergeordnete Aktivität
|
||||
const activityStartTime = activityIdToStartTime.get(activity.id) || activity.startTime || '';
|
||||
const activityDuration = activity.duration ? `${activity.duration} Min` : '';
|
||||
|
||||
// Finde alle Teilnehmer dieser Gruppenaktivität
|
||||
const participantIds = groupActivityMembersMap[groupActivity.id];
|
||||
let hasDirectGroupActivityAssignments = false;
|
||||
|
||||
if (participantIds) {
|
||||
// Handle both Set and Array
|
||||
const idsArray = participantIds instanceof Set ? Array.from(participantIds) : (Array.isArray(participantIds) ? participantIds : []);
|
||||
if (idsArray.length > 0) {
|
||||
hasDirectGroupActivityAssignments = true;
|
||||
idsArray.forEach(participantId => {
|
||||
// Finde memberId für diesen participantId
|
||||
const memberId = participantToMemberMap[participantId];
|
||||
if (memberId) {
|
||||
if (!memberActivitiesMap.has(memberId)) {
|
||||
memberActivitiesMap.set(memberId, []);
|
||||
}
|
||||
memberActivitiesMap.get(memberId).push({
|
||||
name: activityName,
|
||||
startTime: activityStartTime,
|
||||
duration: activityDuration
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Wenn keine direkten Zuordnungen vorhanden sind, verwende Gruppen-Zuordnung
|
||||
if (!hasDirectGroupActivityAssignments) {
|
||||
const groupActivityGroupId = groupActivity.groupsGroupActivity ? groupActivity.groupsGroupActivity.id : null;
|
||||
|
||||
if (groupActivityGroupId) {
|
||||
// Gruppenaktivität hat eine Gruppe: nehme alle Members dieser Gruppe
|
||||
members.forEach(member => {
|
||||
if (member && member.id) {
|
||||
const memberGroupId = memberGroupsMap[member.id];
|
||||
// Prüfe ob memberGroupId als String oder Number vorliegt
|
||||
const memberGroupIdStr = memberGroupId ? String(memberGroupId) : '';
|
||||
const groupActivityGroupIdStr = String(groupActivityGroupId);
|
||||
|
||||
if (memberGroupIdStr === groupActivityGroupIdStr) {
|
||||
if (!memberActivitiesMap.has(member.id)) {
|
||||
memberActivitiesMap.set(member.id, []);
|
||||
}
|
||||
memberActivitiesMap.get(member.id).push({
|
||||
name: activityName,
|
||||
startTime: activityStartTime,
|
||||
duration: activityDuration
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Gruppenaktivität hat keine Gruppe: nehme alle Teilnehmer
|
||||
members.forEach(member => {
|
||||
if (member && member.id) {
|
||||
if (!memberActivitiesMap.has(member.id)) {
|
||||
memberActivitiesMap.set(member.id, []);
|
||||
}
|
||||
memberActivitiesMap.get(member.id).push({
|
||||
name: activityName,
|
||||
startTime: activityStartTime,
|
||||
duration: activityDuration
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Sortiere Mitglieder alphabetisch
|
||||
const sortedMembers = members
|
||||
.filter(m => m && m.id && memberActivitiesMap.has(m.id))
|
||||
.sort((a, b) => {
|
||||
const lastNameCompare = (a.lastName || '').localeCompare(b.lastName || '');
|
||||
if (lastNameCompare !== 0) return lastNameCompare;
|
||||
return (a.firstName || '').localeCompare(b.firstName || '');
|
||||
});
|
||||
|
||||
console.log('[addTrainingDaySummary] Mitglieder mit Aktivitäten:', sortedMembers.length);
|
||||
console.log('[addTrainingDaySummary] memberActivitiesMap:', Array.from(memberActivitiesMap.entries()).map(([id, acts]) => ({ id, count: acts.length })));
|
||||
|
||||
// Zeige für jeden Teilnehmer seine Aktivitäten
|
||||
sortedMembers.forEach(member => {
|
||||
const memberActivities = memberActivitiesMap.get(member.id);
|
||||
if (!memberActivities || memberActivities.length === 0) return;
|
||||
|
||||
// Prüfe ob neue Seite nötig
|
||||
if (this.cursorY > 250) {
|
||||
this.addNewPage();
|
||||
this.cursorY = this.margin;
|
||||
}
|
||||
|
||||
// Mitgliedsname
|
||||
this.pdf.setFont('helvetica', 'bold');
|
||||
this.pdf.setFontSize(12);
|
||||
const memberName = `${member.firstName || ''} ${member.lastName || ''}`.trim();
|
||||
this.pdf.text(memberName, this.margin, this.cursorY);
|
||||
this.cursorY += 8;
|
||||
|
||||
// Aktivitäten
|
||||
this.pdf.setFont('helvetica', 'normal');
|
||||
this.pdf.setFontSize(10);
|
||||
memberActivities.forEach(activity => {
|
||||
if (this.cursorY > 280) {
|
||||
this.addNewPage();
|
||||
this.cursorY = this.margin;
|
||||
// Mitgliedsname erneut anzeigen bei Seitenwechsel
|
||||
this.pdf.setFont('helvetica', 'bold');
|
||||
this.pdf.setFontSize(12);
|
||||
this.pdf.text(memberName, this.margin, this.cursorY);
|
||||
this.cursorY += 8;
|
||||
this.pdf.setFont('helvetica', 'normal');
|
||||
this.pdf.setFontSize(10);
|
||||
}
|
||||
|
||||
let activityText = ` • ${activity.name}`;
|
||||
if (activity.startTime) {
|
||||
activityText += ` (${activity.startTime}`;
|
||||
if (activity.duration) {
|
||||
activityText += `, ${activity.duration}`;
|
||||
}
|
||||
activityText += ')';
|
||||
} else if (activity.duration) {
|
||||
activityText += ` (${activity.duration})`;
|
||||
}
|
||||
|
||||
this.pdf.text(activityText, this.margin + 5, this.cursorY);
|
||||
this.cursorY += 6;
|
||||
});
|
||||
|
||||
// Abstand nach Mitglied
|
||||
this.cursorY += 3;
|
||||
});
|
||||
|
||||
// Falls keine Aktivitäten vorhanden
|
||||
if (sortedMembers.length === 0) {
|
||||
this.pdf.setFont('helvetica', 'normal');
|
||||
this.pdf.setFontSize(11);
|
||||
this.pdf.text('Keine Aktivitäten für diesen Trainingstag erfasst.', this.margin, this.cursorY);
|
||||
this.cursorY += 10;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default PDFGenerator;
|
||||
|
||||
@@ -432,7 +432,11 @@
|
||||
</table>
|
||||
<div style="margin-top: 1rem; margin-bottom: 2rem; padding-bottom: 1rem;">
|
||||
<button v-if="trainingPlan && trainingPlan.length && trainingPlan.length > 0"
|
||||
@click="generatePDF">Als PDF
|
||||
@click="generatePDF">Trainingsplan als PDF
|
||||
herunterladen</button>
|
||||
<button v-if="date && participants && participants.length > 0"
|
||||
@click="generateTrainingDayPDF"
|
||||
style="margin-left: 1rem;">Trainingstag als PDF
|
||||
herunterladen</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1919,6 +1923,72 @@ export default {
|
||||
pdf.addTrainingPlan(this.currentClubName, this.date.date, this.trainingStart, this.trainingEnd, this.trainingPlan);
|
||||
pdf.save('trainingsplan.pdf');
|
||||
},
|
||||
async generateTrainingDayPDF() {
|
||||
if (!this.date || !this.participants || this.participants.length === 0) {
|
||||
this.showInfo('Fehler', 'Keine Teilnehmer für diesen Trainingstag vorhanden.', '', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Lade alle Aktivitäts-Mitglieder-Zuordnungen
|
||||
await this.loadAllActivityMembers();
|
||||
|
||||
// Filtere Mitglieder: nur die, die auch Teilnehmer sind
|
||||
const participantMembers = this.members.filter(m =>
|
||||
m && m.id && this.participants.includes(m.id)
|
||||
);
|
||||
|
||||
// Verwende trainingPlan statt activities, da die Aktivitäten dort sind
|
||||
// Filtere Zeitblöcke komplett aus, da sie immer mehrere Gruppen umfassen
|
||||
const activitiesForPDF = this.trainingPlan.filter(item => !item.isTimeblock);
|
||||
|
||||
// Debug-Ausgaben
|
||||
console.log('[generateTrainingDayPDF] participantMembers:', participantMembers.length);
|
||||
console.log('[generateTrainingDayPDF] trainingPlan items:', this.trainingPlan.length);
|
||||
console.log('[generateTrainingDayPDF] activitiesForPDF:', activitiesForPDF.length);
|
||||
console.log('[generateTrainingDayPDF] activityMembersMap keys:', Object.keys(this.activityMembersMap || {}));
|
||||
console.log('[generateTrainingDayPDF] groupActivityMembersMap keys:', Object.keys(this.groupActivityMembersMap || {}));
|
||||
console.log('[generateTrainingDayPDF] participantMapByMemberId:', this.participantMapByMemberId);
|
||||
|
||||
const pdf = new PDFGenerator();
|
||||
pdf.addTrainingDaySummary(
|
||||
this.currentClubName,
|
||||
this.date.date,
|
||||
this.trainingStart,
|
||||
this.trainingEnd,
|
||||
participantMembers,
|
||||
activitiesForPDF,
|
||||
this.activityMembersMap,
|
||||
this.groupActivityMembersMap,
|
||||
this.participantMapByMemberId,
|
||||
this.groups,
|
||||
this.trainingPlan,
|
||||
this.memberGroupsMap
|
||||
);
|
||||
|
||||
const dateStr = new Date(this.date.date).toLocaleDateString('de-DE').replace(/\./g, '-');
|
||||
pdf.save(`trainingstag-${dateStr}.pdf`);
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Generieren des Trainingstag-PDFs:', error);
|
||||
this.showInfo('Fehler', 'Fehler beim Generieren des PDFs.', error.message || '', 'error');
|
||||
}
|
||||
},
|
||||
async loadAllActivityMembers() {
|
||||
// Lade für alle Aktivitäten im trainingPlan die Mitglieder-Zuordnungen
|
||||
for (const activity of this.trainingPlan) {
|
||||
if (activity && activity.id) {
|
||||
await this.ensureActivityMembersLoaded(activity.id);
|
||||
}
|
||||
// Lade auch für Gruppenaktivitäten
|
||||
if (activity && activity.groupActivities) {
|
||||
for (const groupActivity of activity.groupActivities) {
|
||||
if (groupActivity && groupActivity.id) {
|
||||
await this.ensureGroupActivityMembersLoaded(groupActivity.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
sortedMembers() {
|
||||
// Erstelle eine Kopie des Arrays, um Mutation zu vermeiden
|
||||
return [...this.members].sort((a, b) => {
|
||||
|
||||
Reference in New Issue
Block a user