Add member gallery generation feature in backend and frontend

This commit introduces a new API endpoint for generating a member gallery, allowing users to retrieve a composite image of active members' latest images. The backend has been updated with a new method in MemberService to handle gallery creation, while the frontend has been enhanced with a dialog for displaying the generated gallery. This feature improves the user experience by providing a visual representation of club members.
This commit is contained in:
Torsten Schulz (local)
2025-11-11 16:22:47 +01:00
parent 2bf5c0137b
commit ed15137003
4 changed files with 330 additions and 3 deletions

View File

@@ -1,7 +1,7 @@
<template>
<div class="diary">
<h2>Trainingstagebuch</h2>
<div>
<div class="diary-header-row">
<label>Datum:
<select v-model="date" @change="handleDateChange">
<option value="new">Neu anlegen</option>
@@ -11,6 +11,13 @@
<button v-if="date && date !== 'new' && canDeleteCurrentDate" class="btn-secondary"
@click="deleteCurrentDate">Datum löschen</button>
</label>
<button
class="btn-secondary gallery-trigger"
:disabled="!currentClub || galleryLoading"
@click="openGalleryDialog"
>
{{ galleryLoading ? 'Galerie wird erstellt…' : 'Mitglieder-Galerie' }}
</button>
</div>
<div v-if="showForm && date === 'new'">
<h3>Neues Datum anlegen</h3>
@@ -493,6 +500,25 @@
:initial-drawing-data="null"
@ok="handleDrawingDialogOkForDiary"
/>
<!-- Mitglieder-Galerie Dialog -->
<BaseDialog
v-model="showGalleryDialog"
title="Mitglieder-Galerie"
size="large"
:close-on-overlay="true"
@close="closeGalleryDialog"
>
<div class="gallery-dialog-content">
<div v-if="galleryLoading" class="gallery-loading">Galerie wird erstellt</div>
<div v-else-if="galleryImageUrl" class="gallery-image-wrapper">
<img :src="galleryImageUrl" alt="Mitglieder-Galerie" class="gallery-dialog-image">
</div>
<div v-else class="gallery-error">
{{ galleryError || 'Keine Galerie verfügbar.' }}
</div>
</div>
</BaseDialog>
</div>
<!-- Info Dialog -->
@@ -637,6 +663,10 @@ export default {
accidents: [],
editingActivityId: null, // ID der Aktivität, die gerade bearbeitet wird
// Suche für Inline-Edit
showGalleryDialog: false,
galleryLoading: false,
galleryImageUrl: null,
galleryError: '',
editShowDropdown: false,
editSearchResults: [],
editSearchForId: null,
@@ -749,6 +779,35 @@ export default {
}
this.confirmDialog.isOpen = false;
},
revokeGalleryImage() {
if (this.galleryImageUrl) {
URL.revokeObjectURL(this.galleryImageUrl);
this.galleryImageUrl = null;
}
},
async openGalleryDialog() {
if (!this.currentClub || this.galleryLoading) {
return;
}
this.galleryLoading = true;
this.galleryError = '';
try {
const response = await apiClient.get(`/clubmembers/gallery/${this.currentClub}`, { responseType: 'blob' });
this.revokeGalleryImage();
this.galleryImageUrl = URL.createObjectURL(response.data);
this.showGalleryDialog = true;
} catch (error) {
console.error('Fehler beim Erstellen der Mitglieder-Galerie:', error);
this.galleryError = error?.response?.data?.error || 'Galerie konnte nicht erstellt werden.';
this.showInfo('Fehler', 'Galerie konnte nicht erstellt werden.', this.galleryError, 'error');
} finally {
this.galleryLoading = false;
}
},
closeGalleryDialog() {
this.showGalleryDialog = false;
this.revokeGalleryImage();
},
hasActivityVisual(pa) {
if (!pa) return false;
@@ -2355,6 +2414,7 @@ export default {
if (this.timeChecker) {
clearInterval(this.timeChecker);
}
this.revokeGalleryImage();
}
};
</script>
@@ -2400,6 +2460,44 @@ h3 {
height: 100%;
}
.diary-header-row {
display: flex;
align-items: center;
gap: 1rem;
flex-wrap: wrap;
margin-bottom: 1rem;
}
.gallery-trigger {
align-self: flex-end;
}
.gallery-dialog-content {
display: flex;
justify-content: center;
align-items: center;
min-height: 60vh;
max-height: 70vh;
}
.gallery-image-wrapper {
max-width: 100%;
max-height: 100%;
}
.gallery-dialog-image {
max-width: 100%;
max-height: 100%;
border-radius: 8px;
box-shadow: 0 4px 18px rgba(0, 0, 0, 0.35);
}
.gallery-loading,
.gallery-error {
font-size: 1rem;
color: var(--text-color, #333);
}
.column:first-child {
flex: 1;
overflow: visible;