Fügt die Funktion zum Drehen von Mitgliedsbildern hinzu. Implementiert die Logik zur Bildrotation in MemberService und aktualisiert die entsprechenden Routen und Frontend-Komponenten, um die Benutzeroberfläche für die Bildbearbeitung zu verbessern. Ermöglicht das Drehen von Bildern über die Mitgliederansicht und aktualisiert die Anzeige nach der Bearbeitung.
This commit is contained in:
@@ -571,12 +571,61 @@ 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 }));
|
||||
|
||||
// Automatisch das Datum mit den meisten Einträgen auswählen
|
||||
await this.autoSelectDateWithEntries();
|
||||
|
||||
await this.loadTags();
|
||||
await this.loadPredefinedActivities();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
async autoSelectDateWithEntries() {
|
||||
if (this.dates.length === 0) {
|
||||
this.date = 'new';
|
||||
return;
|
||||
}
|
||||
|
||||
const today = new Date().toISOString().split('T')[0]; // YYYY-MM-DD Format
|
||||
|
||||
// 1. Zuerst prüfe das heutige Datum
|
||||
const todayEntry = this.dates.find(date => date.date === today);
|
||||
if (todayEntry) {
|
||||
const todayEntries = await this.countEntriesForDate(todayEntry.id);
|
||||
if (todayEntries > 0) {
|
||||
this.date = todayEntry;
|
||||
await this.handleDateChange();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Falls heutiges Datum nicht gefunden oder keine Einträge vorhanden
|
||||
// → Bleibe bei "Neu anlegen"
|
||||
this.date = 'new';
|
||||
this.showForm = true;
|
||||
},
|
||||
|
||||
async countEntriesForDate(dateId) {
|
||||
try {
|
||||
// Lade Teilnehmer
|
||||
const participantsResponse = await apiClient.get(`/participants/${dateId}`);
|
||||
const participantCount = participantsResponse.data.length;
|
||||
|
||||
// Lade Aktivitäten
|
||||
const activitiesResponse = await apiClient.get(`/activities/${dateId}`);
|
||||
const activityCount = activitiesResponse.data.length;
|
||||
|
||||
// Lade Trainingplan
|
||||
const trainingPlanResponse = await apiClient.get(`/diary-date-activities/${this.currentClub}/${dateId}`);
|
||||
const trainingPlanCount = trainingPlanResponse.data.length;
|
||||
|
||||
// Gesamtanzahl der Einträge
|
||||
return participantCount + activityCount + trainingPlanCount;
|
||||
} catch (error) {
|
||||
console.warn(`Fehler beim Laden der Einträge für Datum ${dateId}:`, error);
|
||||
return 0;
|
||||
}
|
||||
},
|
||||
|
||||
async refreshDates(selectId) {
|
||||
const response = await apiClient.get(`/diary/${this.currentClub}`);
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
<td>
|
||||
<img v-if="member.imageUrl" :src="member.imageUrl" alt="Mitgliedsbild"
|
||||
style="max-width: 50px; max-height: 50px;"
|
||||
@click.stop="openImageModal(member.imageUrl)">
|
||||
@click.stop="openImageModal(member.imageUrl, member.id)">
|
||||
<span>{{ member.picsInInternetAllowed ? '✓' : '' }}</span>
|
||||
</td>
|
||||
<td>{{ member.testMembership ? '*' : '' }}</td>
|
||||
@@ -107,9 +107,20 @@
|
||||
</div>
|
||||
|
||||
<div v-if="showImageModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<span class="close" @click="closeImageModal">×</span>
|
||||
<img :src="selectedImageUrl" alt="Großes Mitgliedsbild" style="max-width: 100%; max-height: 100%;">
|
||||
<div class="modal-content image-modal-content">
|
||||
<span class="close" @click="closeImageModal">×</span>
|
||||
<div class="image-container">
|
||||
<img :src="selectedImageUrl" alt="Großes Mitgliedsbild"
|
||||
class="modal-image">
|
||||
<div class="image-actions">
|
||||
<button @click="rotateImage('left')" class="rotate-btn" title="90° links drehen">
|
||||
↺ Links
|
||||
</button>
|
||||
<button @click="rotateImage('right')" class="rotate-btn" title="90° rechts drehen">
|
||||
↻ Rechts
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -163,6 +174,7 @@ export default {
|
||||
showNotesModal: false,
|
||||
showImageModal: false,
|
||||
selectedImageUrl: null,
|
||||
selectedMemberId: null,
|
||||
testMembership: false,
|
||||
showInactiveMembers: false,
|
||||
newPicsInInternetAllowed: false,
|
||||
@@ -342,13 +354,37 @@ export default {
|
||||
closeNotesModal() {
|
||||
this.showNotesModal = false;
|
||||
},
|
||||
openImageModal(imageUrl) {
|
||||
openImageModal(imageUrl, memberId) {
|
||||
this.selectedImageUrl = imageUrl;
|
||||
this.selectedMemberId = memberId;
|
||||
this.showImageModal = true;
|
||||
},
|
||||
closeImageModal() {
|
||||
this.showImageModal = false;
|
||||
this.selectedImageUrl = null;
|
||||
this.selectedMemberId = null;
|
||||
},
|
||||
async rotateImage(direction) {
|
||||
if (!this.selectedMemberId) return;
|
||||
|
||||
try {
|
||||
const response = await apiClient.post(`/clubmembers/rotate-image/${this.currentClub}/${this.selectedMemberId}`, {
|
||||
direction: direction
|
||||
});
|
||||
|
||||
if (response.data.success) {
|
||||
// Reload the member's image to show the rotated version
|
||||
const member = this.members.find(m => m.id === this.selectedMemberId);
|
||||
if (member) {
|
||||
await this.reloadMemberImage(member);
|
||||
}
|
||||
} else {
|
||||
alert('Fehler beim Drehen des Bildes: ' + response.data.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Drehen des Bildes:', error);
|
||||
alert('Fehler beim Drehen des Bildes: ' + (error.response?.data?.error || error.message));
|
||||
}
|
||||
},
|
||||
async loadMemberImage(member) {
|
||||
try {
|
||||
@@ -362,6 +398,32 @@ export default {
|
||||
member.imageUrl = null;
|
||||
}
|
||||
},
|
||||
|
||||
async reloadMemberImage(member) {
|
||||
try {
|
||||
// Revoke old blob URL to free memory
|
||||
if (member.imageUrl) {
|
||||
URL.revokeObjectURL(member.imageUrl);
|
||||
}
|
||||
|
||||
const response = await apiClient.get(`/clubmembers/image/${this.currentClub}/${member.id}`, {
|
||||
responseType: 'blob',
|
||||
});
|
||||
|
||||
// Create new blob URL with timestamp for cache busting
|
||||
const imageUrl = URL.createObjectURL(response.data);
|
||||
member.imageUrl = imageUrl;
|
||||
|
||||
// Also update selectedImageUrl if this is the currently viewed member
|
||||
if (member.id === this.selectedMemberId) {
|
||||
this.selectedImageUrl = imageUrl;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Neuladen des Bildes:', error);
|
||||
member.imageUrl = null;
|
||||
}
|
||||
},
|
||||
async createPhoneList() {
|
||||
const activeMembers = this.members.filter(member => member.active);
|
||||
const pdfGenerator = new PDFGenerator();
|
||||
@@ -579,4 +641,57 @@ table td {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
/* Image Modal Styles */
|
||||
.image-modal-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.image-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.modal-image {
|
||||
max-width: 400px;
|
||||
max-height: 400px;
|
||||
object-fit: contain;
|
||||
flex-shrink: 0;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.image-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.rotate-btn {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9em;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.rotate-btn:hover {
|
||||
background-color: #0056b3;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 8px rgba(0, 123, 255, 0.3);
|
||||
}
|
||||
|
||||
.rotate-btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user