Fixed time setting in diary - added pdf output for training

This commit is contained in:
Torsten Schulz
2024-09-17 09:46:11 +02:00
parent d0b42ca537
commit eeda29e6ed
4 changed files with 105 additions and 477 deletions

View File

@@ -36,11 +36,12 @@ const updateTrainingTimes = async (req, res) => {
try {
const { clubId } = req.params;
const { authcode: userToken } = req.headers;
const { date, trainingStart, trainingEnd } = req.body;
if (!date || !trainingStart) {
const { dateId, trainingStart, trainingEnd } = req.body;
if (!dateId || !trainingStart) {
console.log(dateId, trainingStart, trainingEnd);
throw new HttpError('notallfieldsfilled', 400);
}
const updatedDate = await diaryService.updateTrainingTimes(userToken, clubId, date, trainingStart, trainingEnd);
const updatedDate = await diaryService.updateTrainingTimes(userToken, clubId, dateId, trainingStart, trainingEnd);
res.status(200).json(updatedDate);
} catch (error) {
console.error('[updateTrainingTimes] - Error:', error);

View File

@@ -54,11 +54,11 @@ class DiaryService {
return newDate;
}
async updateTrainingTimes(userToken, clubId, date, trainingStart, trainingEnd) {
async updateTrainingTimes(userToken, clubId, dateId, trainingStart, trainingEnd) {
console.log('[DiaryService::updateTrainingTimes] - Check user access');
await checkAccess(userToken, clubId);
console.log('[DiaryService::updateTrainingTimes] - Validate date');
const diaryDate = await DiaryDate.findOne({ where: { clubId, date } });
const diaryDate = await DiaryDate.findOne({ where: { clubId, id: dateId } });
if (!diaryDate) {
throw new HttpError('Diary entry not found', 404);
}

View File

@@ -6,8 +6,8 @@ class PDFGenerator {
this.pdf = new jsPDF('p', 'mm', 'a4');
this.margin = margin;
this.columnGap = columnGap;
this.pageHeight = 295 - margin * 2; // A4 height in mm minus Ränder
this.columnWidth = (210 - margin * 2 - columnGap) / 2; // Zwei Spalten mit Lücke dazwischen
this.pageHeight = 295 - margin * 2;
this.columnWidth = (210 - margin * 2 - columnGap) / 2;
this.position = margin;
this.yPos = this.position;
this.xPos = margin;
@@ -17,14 +17,12 @@ class PDFGenerator {
async addSchedule(element) {
const canvas = await html2canvas(element, { scale: 2 });
const imgData = canvas.toDataURL('image/png');
const imgWidth = 210 - this.margin * 2; // A4 width in mm minus Ränder
const imgWidth = 210 - this.margin * 2;
const imgHeight = (canvas.height * imgWidth) / canvas.width;
let heightLeft = imgHeight;
let position = this.margin;
this.pdf.addImage(imgData, 'PNG', this.margin, position, imgWidth, imgHeight);
heightLeft -= this.pageHeight;
while (heightLeft >= 0) {
position = heightLeft - imgHeight + this.margin;
this.pdf.addPage();
@@ -52,8 +50,6 @@ class PDFGenerator {
addAddress(clubName, addressLines) {
this.pdf.setFontSize(10);
// Vereinname fett drucken
this.pdf.setFont('helvetica', 'bold');
this.pdf.text(clubName, this.xPos, this.yPos);
this.yPos += 5;
@@ -63,30 +59,76 @@ class PDFGenerator {
this.pdf.text(line, this.xPos, this.yPos);
this.yPos += 5;
});
this.yPos += 10; // Abstand zwischen den Adressen
// Spaltenwechsel oder neuer Seite bei Bedarf
this.yPos += 10;
this.checkColumnOverflow();
}
checkColumnOverflow() {
if (this.isLeftColumn) {
if (this.yPos > this.pageHeight) {
this.xPos += this.columnWidth + this.columnGap; // Zur rechten Spalte wechseln
this.yPos = this.position; // Zurück zum Anfang der neuen Spalte
this.xPos += this.columnWidth + this.columnGap;
this.yPos = this.position;
this.isLeftColumn = false;
}
} else {
if (this.yPos > this.pageHeight) {
this.pdf.addPage();
this.xPos = this.margin; // Zurück zur linken Spalte auf der neuen Seite
this.xPos = this.margin;
this.yPos = this.position;
this.isLeftColumn = true;
}
}
}
addTrainingPlan(clubName, trainingDate, trainingStart, trainingEnd, trainingPlan) {
this.pdf.setFontSize(14);
this.pdf.setFont('helvetica', 'bold');
this.pdf.text(`${clubName} - Trainingsplan`, this.margin, this.yPos);
this.yPos += 10;
this.pdf.setFontSize(12);
this.pdf.setFont('helvetica', 'normal');
this.pdf.text(`Datum: ${trainingDate}`, this.margin, this.yPos);
this.yPos += 7;
this.pdf.text(`Uhrzeit: ${trainingStart} - ${trainingEnd}`, this.margin, this.yPos);
this.yPos += 10;
this.pdf.setFont('helvetica', 'bold');
this.pdf.text('Uhrzeit', this.margin, this.yPos);
this.pdf.text('Aktivität', this.margin + 60, this.yPos);
this.pdf.text('Länge / Gesamtzeit (Min)', this.margin + 150, this.yPos);
this.yPos += 10;
this.pdf.setFont('helvetica', 'normal');
trainingPlan.forEach((item, index) => {
const time = this.calculatePlanItemTime(index, trainingStart, trainingPlan);
this.pdf.text(time, this.margin, this.yPos);
this.pdf.text(item.activity, this.margin + 60, this.yPos);
this.pdf.text(item.duration.toString(), this.margin + 150, this.yPos);
this.yPos += 7;
if (this.yPos > this.pageHeight) {
this.addNewPage();
}
});
}
calculatePlanItemTime(index, startTime, trainingPlan) {
let time = startTime;
for (let i = 0; i < index; i++) {
time = this.addDurationToTime(time, trainingPlan[i].duration);
}
return time;
}
addDurationToTime(startTime, duration) {
let [hours, minutes] = startTime.split(':').map(Number);
minutes += Number(duration);
if (minutes >= 60) {
hours += Math.floor(minutes / 60);
minutes = minutes % 60;
}
hours = hours % 24;
return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`;
}
save(filename) {
this.pdf.save(filename);
}

View File

@@ -3,13 +3,14 @@
<h2>Trainingstagebuch</h2>
<div>
<label>Datum:
<select v-model="date" @change="handleDateChange">
<select v-model="selectedDate" @change="handleDateChange">
<option value="new">Neu anlegen</option>
<option v-for="entry in dates" :key="entry.id" :value="entry">{{ entry.date }} </option>
</select>
</label>
</div>
<div v-if="showForm && date === 'new'">
<div v-if="showForm && selectedDate === 'new'">
<h3>Neues Datum anlegen</h3>
<form @submit.prevent="createDate">
<div>
@@ -28,7 +29,8 @@
<button type="submit">Datum anlegen</button>
</form>
</div>
<div v-if="!showForm && date !== null && date !== 'new'">
<div v-if="!showForm && selectedDate !== null && selectedDate !== 'new'">
<h3>Trainingszeiten bearbeiten</h3>
<form @submit.prevent="updateTrainingTimes">
<div>
@@ -43,7 +45,7 @@
</form>
</div>
<div v-if="date !== 'new' && date !== null">
<div v-if="selectedDate !== 'new' && selectedDate !== null">
<div class="columns">
<div class="column">
<h3>Trainingsplan</h3>
@@ -60,7 +62,7 @@
<tr v-for="(planItem, index) in trainingPlan" :key="planItem.id">
<td class="drag-handle"></td>
<td>{{ calculatePlanItemTime(index) }}</td>
<td>{{ planItem.predefinedActivity.name }}</td>
<td>{{ planItem.activity }}</td>
<td>
<span @click="removePlanItem(planItem.id)" class="add-plan-item">-</span>
{{ planItem.duration }}
@@ -76,7 +78,7 @@
<div v-for="activity in filteredPredefinedActivities" :key="activity.id"
@click="selectPredefinedActivity(activity)">
{{ activity.name }} ({{ activity.durationText || '' }} / {{
activity.duration }} Minuten)
activity.duration }} Minuten)
</div>
</div>
</td>
@@ -89,61 +91,7 @@
</tr>
</tbody>
</table>
</div>
<div class="column">
<h3>Teilnehmer</h3>
<ul>
<li v-for="member in members" :key="member.id">
<label>
<input type="checkbox" :value="member.id" @change="toggleParticipant(member.id)"
:checked="isParticipant(member.id)">
<span @click="openNotesModal(member)" class="clickable">{{ member.firstName }} {{
member.lastName }}</span>
</label>
</li>
</ul>
<h3>Aktivitäten</h3>
<textarea v-model="newActivity"></textarea>
<button @click="addActivity">Aktivität hinzufügen</button>
<ul>
<li v-for="activity in activities" :key="activity.id">
{{ activity.description }}
</li>
</ul>
<multiselect v-model="selectedActivityTags" :options="availableTags" placeholder="Tags auswählen"
label="name" track-by="id" multiple :close-on-select="true" @tag="addNewTag"
@remove="removeActivityTag" @input="updateActivityTags" :allow-empty="false"
@keydown.enter.prevent="addNewTagFromInput" />
</div>
</div>
</div>
<div v-if="showNotesModal" class="modal">
<div class="modal-content">
<span class="close" @click="closeNotesModal">&times;</span>
<h3>Notizen für {{ selectedMember.firstName }} {{ selectedMember.lastName }}</h3>
<div class="modal-body">
<div class="modal-left">
<img v-if="selectedMember.imageUrl" :src="selectedMember.imageUrl" alt="Mitgliedsbild"
style="width: 250px; height: 250px; object-fit: cover;" />
</div>
<div class="modal-right">
<multiselect v-model="selectedMemberTags" :options="availableTags" placeholder="Tags auswählen"
label="name" track-by="id" multiple :close-on-select="true" @tag="addNewTagForMember"
@remove="removeMemberTag" @input="updateMemberTags" :allow-empty="false"
@keydown.enter.prevent="addNewTagForMemberFromInput" />
<div>
<textarea v-model="newNoteContent" placeholder="Neue Notiz" rows="4" cols="30"></textarea>
<button @click="addMemberNote">Hinzufügen</button>
</div>
<ul>
<li v-for="note in notes" :key="note.id">
<button @click="deleteNote(note.id)" class="cancel-action">Löschen</button>
{{ note.content }}
</li>
</ul>
</div>
<button @click="generatePDF">PDF speichern</button>
</div>
</div>
</div>
@@ -153,33 +101,19 @@
<script>
import { mapGetters } from 'vuex';
import apiClient from '../apiClient.js';
import Multiselect from 'vue-multiselect';
import Sortable from 'sortablejs';
import PDFGenerator from '../components/PDFGenerator.js';
export default {
name: 'DiaryView',
components: { Multiselect },
data() {
return {
date: null,
selectedDate: null,
dates: [],
showForm: false,
newDate: '',
trainingStart: '',
trainingEnd: '',
members: [],
participants: [],
newActivity: '',
activities: [],
notes: [],
newNoteContent: '',
selectedMember: null,
showNotesModal: false,
selectedActivityTags: [],
selectedMemberTags: [],
availableTags: [],
previousActivityTags: [],
previousMemberTags: [],
trainingPlan: [],
newPlanItem: {
activity: '',
@@ -190,14 +124,6 @@ export default {
showDropdown: false,
};
},
watch: {
selectedMemberTags(newTags) {
this.updateMemberTags(newTags);
},
selectedActivityTags(newTags) {
this.updateActivityTags(newTags);
},
},
computed: {
...mapGetters(['isAuthenticated', 'currentClub']),
calculateNextTime() {
@@ -219,7 +145,6 @@ export default {
if (this.isAuthenticated && this.currentClub) {
const response = await apiClient.get(`/diary/${this.currentClub}`);
this.dates = response.data.map(entry => ({ id: entry.id, date: entry.date }));
this.loadTags();
this.loadPredefinedActivities();
}
},
@@ -228,24 +153,15 @@ export default {
this.newDate = today;
},
async handleDateChange() {
this.showForm = this.date === 'new';
if (this.date && this.date !== 'new') {
const dateId = this.date.id;
const response = await apiClient.get(`/diary/${this.currentClub}`);
const dateData = response.data.find(entry => entry.id === dateId);
this.showForm = this.selectedDate === 'new';
if (this.selectedDate && this.selectedDate !== 'new') {
const dateId = this.selectedDate.id;
const response = await apiClient.get(`/diary/${this.currentClub}/${dateId}`);
const dateData = response.data;
this.trainingStart = dateData.trainingStart;
this.trainingEnd = dateData.trainingEnd;
this.selectedActivityTags = dateData.diaryTags.map(tag => ({
id: tag.id,
name: tag.name
}));
this.previousActivityTags = [...this.selectedActivityTags]; // Hier setzen
await this.loadMembers();
await this.loadParticipants(dateId);
await this.loadActivities(dateId);
this.trainingPlan = await apiClient
.get(`/diary-date-activities/${this.currentClub}/${this.date.id}`)
.get(`/diary-date-activities/${this.currentClub}/${dateId}`)
.then(response => response.data);
this.initializeSortable();
@@ -253,7 +169,6 @@ export default {
this.newDate = '';
this.trainingStart = '';
this.trainingEnd = '';
this.participants = [];
}
},
initializeSortable() {
@@ -271,7 +186,7 @@ export default {
trainingEnd: this.trainingEnd || null,
});
this.dates.push({ id: response.data.id, date: response.data.date });
this.date = { id: response.data.id, date: response.data.date };
this.selectedDate = { id: response.data.id, date: response.data.date };
this.showForm = false;
this.newDate = '';
this.trainingStart = '';
@@ -282,7 +197,7 @@ export default {
},
async updateTrainingTimes() {
try {
const dateId = this.date.id;
const dateId = this.selectedDate.id;
await apiClient.put(`/diary/${this.currentClub}`, {
dateId,
trainingStart: this.trainingStart || null,
@@ -294,22 +209,6 @@ export default {
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
}
},
async loadMembers() {
const response = await apiClient.get(`/clubmembers/${this.currentClub}`);
this.members = response.data;
},
async loadParticipants(dateId) {
const response = await apiClient.get(`/participants/${dateId}`);
this.participants = response.data.map(participant => participant.memberId);
},
async loadActivities(dateId) {
const response = await apiClient.get(`/activities/${dateId}`);
this.activities = response.data;
},
async loadTags() {
const response = await apiClient.get('/tags');
this.availableTags = response.data;
},
async loadPredefinedActivities() {
try {
const response = await apiClient.get('/predefined-activities');
@@ -318,216 +217,6 @@ export default {
console.error('Fehler beim Laden der vordefinierten Aktivitäten:', error);
}
},
isParticipant(memberId) {
return this.participants.includes(memberId);
},
async toggleParticipant(memberId) {
const isParticipant = this.isParticipant(memberId);
const dateId = this.date.id;
if (isParticipant) {
await apiClient.post('/participants/remove', {
diaryDateId: dateId,
memberId
});
this.participants = this.participants.filter(id => id !== memberId);
} else {
await apiClient.post('/participants/add', {
diaryDateId: dateId,
memberId
});
this.participants.push(memberId);
}
},
async addActivity() {
const dateId = this.date.id;
if (this.newActivity) {
const response = await apiClient.post('/activities/add', {
diaryDateId: dateId,
description: this.newActivity,
tags: this.selectedActivityTags.map(tag => tag.id)
});
this.activities.push(response.data);
this.newActivity = '';
this.selectedActivityTags = [];
}
},
async openNotesModal(member) {
this.selectedMember = member;
await this.loadMemberImage(member);
this.loadMemberNotesAndTags(this.date.id, member.id);
this.showNotesModal = true;
},
async loadMemberNotesAndTags(diaryDateId, memberId) {
try {
const notesResponse = await apiClient.get(`/diarymember/${this.currentClub}/note`, {
params: { diaryDateId, memberId }
});
this.notes = notesResponse.data;
const tagsResponse = await apiClient.get(`/diarymember/${this.currentClub}/tag`, {
params: { diaryDateId, memberId }
});
this.selectedMemberTags = tagsResponse.data.map(tag => ({
id: tag.tag.id,
name: tag.tag.name
}));
} catch (error) {
console.error('Error loading member notes and tags:', error);
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
}
},
async addMemberNote() {
if (this.newNoteContent) {
const response = await apiClient.post(`/diarymember/${this.currentClub}/note`, {
memberId: this.selectedMember.id,
diaryDateId: this.date.id,
content: this.newNoteContent
});
this.notes = response.data;
this.newNoteContent = '';
this.selectedTagsNotes = [];
}
},
async deleteNote(noteId) {
const response = await apiClient.delete(`/diarymember/${this.currentClub}/note/${noteId}`, {
clubId: this.currentClub
});
this.notes = response.data;
},
closeNotesModal() {
this.showNotesModal = false;
},
async addNewTagFromInput(event) {
const inputValue = event.target.value.trim();
if (inputValue) {
await this.addNewTag(inputValue);
}
},
async addNewTag(newTagName) {
try {
const response = await apiClient.post('/tags', { name: newTagName });
const newTag = response.data;
this.availableTags.push(newTag);
this.selectedActivityTags.push(newTag);
} catch (error) {
console.error('Fehler beim Hinzufügen eines neuen Tags:', error);
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
}
},
async addNewTagForMemberFromInput(event) {
const inputValue = event.target.value.trim();
if (inputValue) {
await this.addNewTagForMember(inputValue);
}
},
async addNewTagForMember(newTagName) {
try {
const response = await apiClient.post('/tags', { name: newTagName });
const newTag = response.data;
this.availableTags.push(newTag);
this.selectedMemberTags.push(newTag);
await this.linkTagToMemberAndDate(newTag);
} catch (error) {
console.error('Fehler beim Hinzufügen eines neuen Tags für das Mitglied:', error);
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
}
},
async linkTagToDiaryDate(tag) {
try {
const tagId = tag.id;
await apiClient.post(`/diary/tag/${this.currentClub}/add-tag`, {
diaryDateId: this.date.id,
tagId: tagId
});
} catch (error) {
console.error('Fehler beim Verknüpfen des Tags mit dem Trainingstag:', error);
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
}
},
async linkTagToMemberAndDate(tag) {
try {
const tagId = tag.id;
await apiClient.post(`/diarymember/${this.currentClub}/tag`, {
diaryDateId: this.date.id,
memberId: this.selectedMember.id,
tagId: tagId
});
} catch (error) {
console.error('Fehler beim Verknüpfen des Tags mit dem Mitglied und Datum:', error);
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
}
},
async updateActivityTags() {
try {
const selectedTags = this.selectedActivityTags;
if (!selectedTags || !Array.isArray(selectedTags)) {
console.log(typeof selectedTags, JSON.stringify(selectedTags));
throw new TypeError('Expected selectedTags to be an array');
}
for (let tag of selectedTags) {
if (!this.previousActivityTags.includes(tag)) {
await this.linkTagToDiaryDate(tag);
}
}
this.previousActivityTags = [...selectedTags];
} catch (error) {
console.error('Fehler beim Verknüpfen der Tags mit dem Trainingstag:', error);
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
}
},
async updateMemberTags() {
try {
for (let tag of this.selectedMemberTags) {
if (!this.previousMemberTags.includes(tag)) {
await this.linkTagToMemberAndDate(tag);
}
}
this.previousMemberTags = [...this.selectedMemberTags];
} catch (error) {
console.error('Fehler beim Verknüpfen der Tags mit dem Mitglied und Datum:', error);
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
}
},
async removeMemberTag(tagId) {
try {
await apiClient.post(`/diarymember/${this.currentClub}/tag/remove`, {
diaryDateId: this.date.id,
memberId: this.selectedMember.id,
tagId: tagId
});
this.selectedMemberTags = this.selectedMemberTags.filter(tag => tag.id !== tagId);
} catch (error) {
console.error('Fehler beim Entfernen des Tags:', error);
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
}
},
async removeMemberNote(noteContent) {
try {
await apiClient.post(`/diarymember/${this.currentClub}/note/remove`, {
diaryDateId: this.date.id,
memberId: this.selectedMember.id,
content: noteContent
});
this.notes = this.notes.filter(note => note.content !== noteContent);
} catch (error) {
console.error('Fehler beim Entfernen der Notiz:', error);
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
}
},
async removeActivityTag(tag) {
try {
const tagId = tag.id;
await apiClient.delete(`/diary/${this.currentClub}/tag`, {
params: { tagId }
});
this.selectedActivityTags = this.selectedActivityTags.filter(t => t.id !== tagId);
} catch (error) {
console.error('Fehler beim Entfernen des Tags:', error);
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
}
},
handleActivityInput() {
if (this.newPlanItem.activity) {
this.showDropdown = true;
@@ -544,14 +233,14 @@ export default {
async addPlanItem() {
try {
await apiClient.post(`/diary-date-activities/${this.currentClub}`, {
diaryDateId: this.date.id,
diaryDateId: this.selectedDate.id,
activity: this.newPlanItem.activity,
duration: this.newPlanItem.duration,
durationText: this.newPlanItem.durationText,
orderId: this.trainingPlan.length
});
this.newPlanItem = { activity: '', duration: '', durationText: '' };
this.trainingPlan = await apiClient.get(`/diary-date-activities/${this.currentClub}/${this.date.id}`).then(response => response.data);
this.trainingPlan = await apiClient.get(`/diary-date-activities/${this.currentClub}/${this.selectedDate.id}`).then(response => response.data);
} catch (error) {
console.error('Fehler beim Hinzufügen des Planungsitems:', error);
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
@@ -559,10 +248,8 @@ export default {
},
async removePlanItem(planItemId) {
try {
await apiClient.delete(`/diary-date-activities/${this.currentClub}`, {
params: { planItemId }
});
this.planItems = this.planItems.filter(item => item.id !== planItemId);
await apiClient.delete(`/diary-date-activities/${this.currentClub}/${planItemId}`);
this.trainingPlan = this.trainingPlan.filter(item => item.id !== planItemId);
} catch (error) {
console.error('Fehler beim Entfernen des Planungsitems:', error);
}
@@ -575,39 +262,18 @@ export default {
return time;
},
addDurationToTime(startTime, duration) {
let [hours, minutes] = startTime.split(':').map(Number);
minutes += Number(duration);
if (minutes >= 60) {
hours += Math.floor(minutes / 60);
minutes = minutes % 60;
}
hours = hours % 24;
return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`;
},
calculateDuration() {
const input = this.newPlanItem.durationInput;
let calculatedDuration = 0;
const multiplyPattern = /(\d+)\s*[x*]\s*(\d+)/i;
const match = input.match(multiplyPattern);
if (match) {
const [, num1, num2] = match;
calculatedDuration = parseInt(num1) * parseInt(num2);
} else if (!isNaN(input)) {
calculatedDuration = parseInt(input);
}
calculatedDuration = Math.ceil(calculatedDuration / 5) * 5;
if (!this.newPlanItem.durationText || this.newPlanItem.durationText === input) {
this.newPlanItem.duration = calculatedDuration;
this.newPlanItem = { ...this.newPlanItem, duration: calculatedDuration };
}
},
async removePlanItem(planItemId) {
try {
await apiClient.delete(`/diary-date-activities/${this.currentClub}/${planItemId}`);
this.trainingPlan = this.trainingPlan.filter(item => item.id !== planItemId);
} catch (error) {
console.error('Fehler beim Entfernen des Planungsitems:', error);
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
let [hours, minutes] = startTime.split(':').map(Number);
minutes += Number(duration);
if (minutes >= 60) {
hours += Math.floor(minutes / 60);
minutes = minutes % 60;
}
hours = hours % 24;
return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`;
} catch(error) {
console.log(error);
return '';
}
},
async onDragEnd(evt) {
@@ -621,21 +287,18 @@ export default {
alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.');
}
},
async loadMemberImage(member) {
try {
const response = await apiClient.get(`/clubmembers/${this.currentClub}/image/${member.id}`, {
responseType: 'blob',
});
const imageUrl = URL.createObjectURL(response.data);
member.imageUrl = imageUrl;
} catch (error) {
console.error("Failed to load member image:", error);
member.imageUrl = null;
}
async generatePDF() {
const clubName = this.$store.getters.currentClubName;
const trainingDate = this.selectedDate.date;
const trainingStart = this.trainingStart;
const trainingEnd = this.trainingEnd;
const pdfGenerator = new PDFGenerator();
pdfGenerator.addTrainingPlan(clubName, trainingDate, trainingStart, trainingEnd, this.trainingPlan);
pdfGenerator.save('Trainingsplan.pdf');
}
},
async mounted() {
await this.init();
}
};
@@ -674,62 +337,6 @@ ul {
padding: 0;
}
.modal {
display: flex;
justify-content: center;
align-items: center;
position: fixed;
z-index: 1000;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 50%;
height: 50%;
overflow: auto;
background-color: rgba(200, 200, 200, 0.5);
}
.modal-content {
background-color: #fefefe;
padding: 20px;
border: 1px solid #555;
width: 100%;
height: 100%;
position: relative;
box-shadow: 4px 3px 2px #999;
}
.close {
position: absolute;
top: 10px;
right: 15px;
color: #aaa;
font-size: 28px;
font-weight: bold;
}
.close:hover,
.close:focus {
color: black;
text-decoration: none;
cursor: pointer;
}
ul {
list-style-type: none;
padding: 0;
}
li {
padding: 0;
margin-bottom: 5px;
}
.multiselect {
margin-bottom: 10px;
width: 100%;
}
table {
width: 100%;
border-collapse: collapse;
@@ -779,11 +386,6 @@ input[type="number"] {
background-color: #f0f0f0;
}
.clickable {
cursor: pointer;
color: #45a049;
}
.add-plan-item {
border: 1px solid black;
cursor: pointer;
@@ -804,21 +406,4 @@ input[type="number"] {
.drag-handle {
cursor: pointer;
}
.modal-body {
display: flex;
justify-content: space-between;
}
.modal-left {
flex: 0 0 250px;
display: flex;
justify-content: center;
align-items: center;
padding-right: 20px;
}
.modal-right {
flex: 1;
}
</style>