Enhance member gallery functionality with latest image retrieval and JSON format support
This commit improves the member gallery feature by allowing users to request the latest member images and receive member information in JSON format. The backend has been updated to handle "latest" as a valid imageId, ensuring the most recent image is fetched. Additionally, the frontend has been modified to support displaying member details in an interactive gallery format, enhancing user experience and providing more flexibility in how member information is presented.
This commit is contained in:
@@ -59,7 +59,9 @@ const getMemberImage = async (req, res) => {
|
||||
try {
|
||||
const { clubId, memberId, imageId } = req.params;
|
||||
const { authcode: userToken } = req.headers;
|
||||
const result = await MemberService.getMemberImage(userToken, clubId, memberId, imageId || null);
|
||||
// Support "latest" as imageId to get the latest image
|
||||
const actualImageId = imageId === 'latest' ? null : (imageId || null);
|
||||
const result = await MemberService.getMemberImage(userToken, clubId, memberId, actualImageId);
|
||||
if (result.status === 200) {
|
||||
res.sendFile(result.imagePath);
|
||||
} else {
|
||||
@@ -121,8 +123,20 @@ const generateMemberGallery = async (req, res) => {
|
||||
const { clubId } = req.params;
|
||||
const { authcode: userToken } = req.headers;
|
||||
const size = parseInt(req.query.size) || 200; // Default: 200x200
|
||||
const format = req.query.format || 'image'; // 'image' or 'json'
|
||||
const result = await MemberService.generateMemberGallery(userToken, clubId, size);
|
||||
if (result.status === 200) {
|
||||
if (format === 'json') {
|
||||
// Return member information for interactive gallery
|
||||
return res.status(200).json({
|
||||
members: result.galleryEntries.map(entry => ({
|
||||
memberId: entry.memberId,
|
||||
firstName: entry.firstName,
|
||||
lastName: entry.lastName,
|
||||
fullName: entry.fullName
|
||||
}))
|
||||
});
|
||||
}
|
||||
res.setHeader('Content-Type', 'image/png');
|
||||
res.setHeader('Cache-Control', 'no-store');
|
||||
return res.status(200).send(result.buffer);
|
||||
|
||||
@@ -282,9 +282,10 @@ class MemberService {
|
||||
}
|
||||
|
||||
if (!imageRecord) {
|
||||
// Get latest image (highest sortOrder, then highest id)
|
||||
imageRecord = await MemberImage.findOne({
|
||||
where: { memberId },
|
||||
order: [['sortOrder', 'ASC'], ['id', 'ASC']]
|
||||
order: [['sortOrder', 'DESC'], ['id', 'DESC']]
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1124,7 +1125,10 @@ class MemberService {
|
||||
|
||||
galleryEntries.push({
|
||||
filePath,
|
||||
fullName: `${member.firstName || ''} ${member.lastName || ''}`.trim() || member.lastName || member.firstName || 'Unbekannt'
|
||||
fullName: `${member.firstName || ''} ${member.lastName || ''}`.trim() || member.lastName || member.firstName || 'Unbekannt',
|
||||
memberId: member.id,
|
||||
firstName: member.firstName || '',
|
||||
lastName: member.lastName || ''
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1194,7 +1198,8 @@ class MemberService {
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
buffer
|
||||
buffer,
|
||||
galleryEntries // Für interaktive Galerie
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('[generateMemberGallery] - Error:', error);
|
||||
|
||||
@@ -504,7 +504,7 @@
|
||||
<!-- Mitglieder-Galerie Dialog -->
|
||||
<BaseDialog
|
||||
v-model="showGalleryDialog"
|
||||
title="Mitglieder-Galerie"
|
||||
:title="date && date !== 'new' ? 'Mitglieder-Galerie - Klicken Sie auf ein Bild, um als Teilnehmer hinzuzufügen' : 'Mitglieder-Galerie'"
|
||||
size="large"
|
||||
:close-on-overlay="true"
|
||||
@close="closeGalleryDialog"
|
||||
@@ -512,18 +512,36 @@
|
||||
<div class="gallery-dialog-content">
|
||||
<div class="gallery-controls">
|
||||
<label for="gallery-size">Bildgröße:</label>
|
||||
<select id="gallery-size" v-model="gallerySize" @change="regenerateGallery" :disabled="galleryLoading">
|
||||
<select id="gallery-size" v-model="gallerySize" @change="loadGalleryMembers" :disabled="galleryLoading">
|
||||
<option :value="100">100x100 px</option>
|
||||
<option :value="150">150x150 px</option>
|
||||
<option :value="200">200x200 px</option>
|
||||
</select>
|
||||
</div>
|
||||
<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 v-if="galleryLoading" class="gallery-loading">Galerie wird geladen…</div>
|
||||
<div v-else-if="galleryMembers.length > 0" class="gallery-members-grid" :style="{ gridTemplateColumns: 'repeat(auto-fill, ' + gallerySize + 'px)' }">
|
||||
<div
|
||||
v-for="member in galleryMembers"
|
||||
:key="member.memberId"
|
||||
class="gallery-member-item"
|
||||
:class="{ 'is-participant': date && date !== 'new' && isParticipant(member.memberId) }"
|
||||
:style="{ width: gallerySize + 'px', minWidth: gallerySize + 'px', height: gallerySize + 'px' }"
|
||||
@click="handleGalleryMemberClick(member)"
|
||||
>
|
||||
<img
|
||||
v-if="member.imageUrl"
|
||||
:src="member.imageUrl"
|
||||
:alt="member.fullName"
|
||||
class="gallery-member-image"
|
||||
@error="handleImageError(member)"
|
||||
@load="handleImageLoad(member)"
|
||||
/>
|
||||
<div v-else class="gallery-member-placeholder">Kein Bild</div>
|
||||
<div class="gallery-member-name">{{ member.fullName }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="gallery-error">
|
||||
{{ galleryError || 'Keine Galerie verfügbar.' }}
|
||||
{{ galleryError || 'Keine Mitglieder mit Bildern gefunden.' }}
|
||||
</div>
|
||||
</div>
|
||||
</BaseDialog>
|
||||
@@ -676,6 +694,7 @@ export default {
|
||||
galleryImageUrl: null,
|
||||
galleryError: '',
|
||||
gallerySize: 200,
|
||||
galleryMembers: [],
|
||||
editShowDropdown: false,
|
||||
editSearchResults: [],
|
||||
editSearchForId: null,
|
||||
@@ -799,32 +818,108 @@ export default {
|
||||
return;
|
||||
}
|
||||
this.showGalleryDialog = true;
|
||||
await this.loadGallery();
|
||||
await this.loadGalleryMembers();
|
||||
},
|
||||
async loadGallery() {
|
||||
updateGallerySizeBasedOnCount() {
|
||||
const count = this.galleryMembers.length;
|
||||
if (count < 11) {
|
||||
this.gallerySize = 200;
|
||||
} else if (count < 22) {
|
||||
this.gallerySize = 150;
|
||||
} else {
|
||||
this.gallerySize = 100;
|
||||
}
|
||||
},
|
||||
async loadGalleryMembers() {
|
||||
if (!this.currentClub || this.galleryLoading) {
|
||||
return;
|
||||
}
|
||||
this.galleryLoading = true;
|
||||
this.galleryError = '';
|
||||
this.galleryImageUrl = null;
|
||||
this.revokeGalleryImage();
|
||||
try {
|
||||
const response = await apiClient.get(`/clubmembers/gallery/${this.currentClub}?size=${this.gallerySize}`, { responseType: 'blob' });
|
||||
this.revokeGalleryImage();
|
||||
this.galleryImageUrl = URL.createObjectURL(response.data);
|
||||
const response = await apiClient.get(`/clubmembers/gallery/${this.currentClub}?format=json&size=${this.gallerySize}`);
|
||||
const members = response.data.members || [];
|
||||
|
||||
// Setze Größe basierend auf Anzahl der Mitglieder (vor dem Laden der Bilder)
|
||||
this.galleryMembers = members; // Temporär setzen für count
|
||||
this.updateGallerySizeBasedOnCount();
|
||||
|
||||
// Lade Bilder als Blobs und erstelle ObjectURLs
|
||||
this.galleryMembers = await Promise.all(members.map(async (member) => {
|
||||
try {
|
||||
const imageUrl = await this.loadMemberImageAsBlob(member.memberId);
|
||||
console.log(`[loadGalleryMembers] Member ${member.memberId} (${member.fullName}): imageUrl =`, imageUrl ? 'OK' : 'null');
|
||||
return {
|
||||
...member,
|
||||
imageUrl: imageUrl || null
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`[loadGalleryMembers] Fehler beim Laden des Bildes für Mitglied ${member.memberId}:`, error);
|
||||
return {
|
||||
...member,
|
||||
imageUrl: null
|
||||
};
|
||||
}
|
||||
}));
|
||||
console.log('[loadGalleryMembers] Geladene Mitglieder:', this.galleryMembers.map(m => ({ id: m.memberId, name: m.fullName, hasImage: !!m.imageUrl })));
|
||||
} 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');
|
||||
console.error('Fehler beim Laden der Galerie:', error);
|
||||
this.galleryError = error?.response?.data?.error || 'Galerie konnte nicht geladen werden.';
|
||||
this.galleryMembers = [];
|
||||
} finally {
|
||||
this.galleryLoading = false;
|
||||
}
|
||||
},
|
||||
async loadMemberImageAsBlob(memberId) {
|
||||
try {
|
||||
const response = await apiClient.get(`/clubmembers/image/${this.currentClub}/${memberId}`, { responseType: 'blob' });
|
||||
if (!response.data || response.data.size === 0) {
|
||||
console.warn(`[loadMemberImageAsBlob] Leeres Blob für Mitglied ${memberId}`);
|
||||
return null;
|
||||
}
|
||||
const objectUrl = URL.createObjectURL(response.data);
|
||||
console.log(`[loadMemberImageAsBlob] ObjectURL erstellt für Mitglied ${memberId}:`, objectUrl.substring(0, 50) + '...');
|
||||
return objectUrl;
|
||||
} catch (error) {
|
||||
// 404 ist OK - Mitglied hat kein Bild
|
||||
if (error?.response?.status === 404) {
|
||||
console.log(`[loadMemberImageAsBlob] Kein Bild für Mitglied ${memberId} (404)`);
|
||||
return null;
|
||||
}
|
||||
console.error(`[loadMemberImageAsBlob] Fehler beim Laden des Bildes für Mitglied ${memberId}:`, error);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
async regenerateGallery() {
|
||||
await this.loadGallery();
|
||||
await this.loadGalleryMembers();
|
||||
},
|
||||
closeGalleryDialog() {
|
||||
this.showGalleryDialog = false;
|
||||
this.revokeGalleryImage();
|
||||
// Revoke all object URLs
|
||||
this.galleryMembers.forEach(member => {
|
||||
if (member.imageUrl) {
|
||||
URL.revokeObjectURL(member.imageUrl);
|
||||
}
|
||||
});
|
||||
this.galleryMembers = [];
|
||||
},
|
||||
async handleGalleryMemberClick(member) {
|
||||
if (!this.date || this.date === 'new') {
|
||||
return;
|
||||
}
|
||||
console.log('[handleGalleryMemberClick] Clicked member:', member);
|
||||
await this.toggleParticipant(member.memberId);
|
||||
},
|
||||
handleImageError(member) {
|
||||
console.error(`[handleImageError] Bild konnte nicht geladen werden für Mitglied ${member.memberId} (${member.fullName}), URL:`, member.imageUrl);
|
||||
// Setze imageUrl auf null, damit der Placeholder angezeigt wird
|
||||
member.imageUrl = null;
|
||||
},
|
||||
handleImageLoad(member) {
|
||||
console.log(`[handleImageLoad] Bild erfolgreich geladen für Mitglied ${member.memberId} (${member.fullName})`);
|
||||
},
|
||||
|
||||
hasActivityVisual(pa) {
|
||||
@@ -2510,6 +2605,8 @@ h3 {
|
||||
gap: 12px;
|
||||
background: #f9f9f9;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.gallery-controls label {
|
||||
@@ -2557,6 +2654,96 @@ h3 {
|
||||
color: var(--text-color, #333);
|
||||
}
|
||||
|
||||
.gallery-members-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, max-content);
|
||||
gap: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
align-items: start;
|
||||
box-sizing: border-box;
|
||||
justify-items: start;
|
||||
}
|
||||
|
||||
.gallery-member-item {
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
transition: all 0.2s ease;
|
||||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.gallery-member-item:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.gallery-member-item.is-participant {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.gallery-member-image {
|
||||
object-fit: cover;
|
||||
border-radius: 0;
|
||||
margin: 0;
|
||||
pointer-events: none; /* Prevent image from blocking clicks on parent */
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 0;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.gallery-member-name {
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
color: #ff6b6b;
|
||||
font-weight: 500;
|
||||
margin: 0;
|
||||
padding: 4px 8px;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
background-color: rgba(0, 0, 0, 0.5) !important;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.gallery-member-item.is-participant .gallery-member-name {
|
||||
color: #51cf66;
|
||||
}
|
||||
|
||||
.gallery-member-placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 0;
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.column:first-child {
|
||||
flex: 1;
|
||||
overflow: visible;
|
||||
|
||||
@@ -5,6 +5,14 @@
|
||||
<SeasonSelector v-model="selectedSeasonId" @season-change="onSeasonChange" :show-current-season="true" />
|
||||
|
||||
<button @click="openImportModal">Spielplanimport</button>
|
||||
<button
|
||||
v-if="playerSelectionDialog.match"
|
||||
@click="openGalleryDialog"
|
||||
class="btn-secondary"
|
||||
:disabled="galleryLoading"
|
||||
>
|
||||
{{ galleryLoading ? 'Galerie wird geladen…' : 'Mitglieder-Galerie' }}
|
||||
</button>
|
||||
<div v-if="hoveredMatch && hoveredMatch.location" class="hover-info">
|
||||
<p><strong>{{ hoveredMatch.location.name || 'N/A' }}</strong></p>
|
||||
<p>{{ hoveredMatch.location.address || 'N/A' }}</p>
|
||||
@@ -173,6 +181,46 @@
|
||||
:details="confirmDialog.details" :type="confirmDialog.type" @confirm="handleConfirmResult(true)"
|
||||
@cancel="handleConfirmResult(false)" />
|
||||
|
||||
<!-- Mitglieder-Galerie Dialog -->
|
||||
<BaseDialog
|
||||
v-model="showGalleryDialog"
|
||||
title="Mitglieder-Galerie - Klicken Sie auf ein Bild, um als 'Bereit' zu markieren"
|
||||
size="large"
|
||||
:close-on-overlay="true"
|
||||
@close="closeGalleryDialog"
|
||||
>
|
||||
<div class="gallery-dialog-content">
|
||||
<div class="gallery-controls">
|
||||
<label for="gallery-size">Bildgröße:</label>
|
||||
<select id="gallery-size" v-model="gallerySize" @change="loadGalleryMembers" :disabled="galleryLoading">
|
||||
<option :value="100">100x100 px</option>
|
||||
<option :value="150">150x150 px</option>
|
||||
<option :value="200">200x200 px</option>
|
||||
</select>
|
||||
</div>
|
||||
<div v-if="galleryLoading" class="gallery-loading">Galerie wird geladen…</div>
|
||||
<div v-else-if="galleryMembers.length > 0" class="gallery-members-grid">
|
||||
<div
|
||||
v-for="member in galleryMembers"
|
||||
:key="member.memberId"
|
||||
class="gallery-member-item"
|
||||
:class="{ 'is-ready': isMemberReady(member.memberId) }"
|
||||
@click="toggleMemberReady(member)"
|
||||
>
|
||||
<img
|
||||
:src="getMemberImageUrl(member.memberId)"
|
||||
:alt="member.fullName"
|
||||
class="gallery-member-image"
|
||||
/>
|
||||
<div class="gallery-member-name">{{ member.fullName }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="gallery-error">
|
||||
{{ galleryError || 'Keine Mitglieder mit Bildern gefunden.' }}
|
||||
</div>
|
||||
</div>
|
||||
</BaseDialog>
|
||||
|
||||
<!-- Player Selection Dialog -->
|
||||
<BaseDialog
|
||||
v-model="playerSelectionDialog.isOpen"
|
||||
@@ -306,6 +354,12 @@ export default {
|
||||
members: [],
|
||||
loading: false
|
||||
},
|
||||
// Gallery Dialog
|
||||
showGalleryDialog: false,
|
||||
galleryLoading: false,
|
||||
galleryMembers: [],
|
||||
galleryError: '',
|
||||
gallerySize: 200,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
@@ -425,7 +479,7 @@ export default {
|
||||
member.hasPlayed = !member.hasPlayed;
|
||||
},
|
||||
|
||||
async savePlayerSelection() {
|
||||
async savePlayerSelection(closeDialog = true) {
|
||||
const match = this.playerSelectionDialog.match;
|
||||
if (!match) return;
|
||||
|
||||
@@ -439,26 +493,144 @@ export default {
|
||||
.filter(m => m.hasPlayed)
|
||||
.map(m => m.id);
|
||||
|
||||
console.log('[savePlayerSelection] Saving players:', { playersReady, playersPlanned, playersPlayed, matchId: match.id });
|
||||
|
||||
try {
|
||||
await apiClient.patch(`/matches/${match.id}/players`, {
|
||||
const response = await apiClient.patch(`/matches/${match.id}/players`, {
|
||||
playersReady,
|
||||
playersPlanned,
|
||||
playersPlayed
|
||||
});
|
||||
console.log('[savePlayerSelection] API response:', response);
|
||||
|
||||
// Update local match data
|
||||
match.playersReady = playersReady;
|
||||
match.playersPlanned = playersPlanned;
|
||||
match.playersPlayed = playersPlayed;
|
||||
|
||||
await this.showInfo('Erfolg', 'Spielerauswahl gespeichert', '', 'success');
|
||||
this.closePlayerSelectionDialog();
|
||||
// Update all members in the list to reflect the current state
|
||||
this.playerSelectionDialog.members.forEach(m => {
|
||||
m.isReady = playersReady.includes(m.id);
|
||||
m.isPlanned = playersPlanned.includes(m.id);
|
||||
m.hasPlayed = playersPlayed.includes(m.id);
|
||||
});
|
||||
|
||||
if (closeDialog) {
|
||||
await this.showInfo('Erfolg', 'Spielerauswahl gespeichert', '', 'success');
|
||||
this.closePlayerSelectionDialog();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error saving player selection:', error);
|
||||
await this.showInfo('Fehler', 'Fehler beim Speichern der Spielerauswahl', '', 'error');
|
||||
}
|
||||
},
|
||||
|
||||
// Gallery Methods
|
||||
async openGalleryDialog() {
|
||||
if (!this.playerSelectionDialog.match) {
|
||||
await this.showInfo('Hinweis', 'Bitte wählen Sie zuerst ein Spiel aus', '', 'info');
|
||||
return;
|
||||
}
|
||||
this.showGalleryDialog = true;
|
||||
await this.loadGalleryMembers();
|
||||
},
|
||||
|
||||
async loadGalleryMembers() {
|
||||
if (!this.currentClub || this.galleryLoading) {
|
||||
return;
|
||||
}
|
||||
this.galleryLoading = true;
|
||||
this.galleryError = '';
|
||||
try {
|
||||
const response = await apiClient.get(`/clubmembers/gallery/${this.currentClub}?format=json&size=${this.gallerySize}`);
|
||||
this.galleryMembers = response.data.members || [];
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der Galerie:', error);
|
||||
this.galleryError = error?.response?.data?.error || 'Galerie konnte nicht geladen werden.';
|
||||
this.galleryMembers = [];
|
||||
} finally {
|
||||
this.galleryLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
closeGalleryDialog() {
|
||||
this.showGalleryDialog = false;
|
||||
this.galleryMembers = [];
|
||||
},
|
||||
|
||||
getMemberImageUrl(memberId) {
|
||||
const token = this.$store.getters.token;
|
||||
const backendBaseUrl = import.meta.env.VITE_BACKEND || 'http://localhost:3005';
|
||||
// Get primary image (latest will be handled by backend)
|
||||
return `${backendBaseUrl}/api/clubmembers/image/${this.currentClub}/${memberId}?authcode=${token}`;
|
||||
},
|
||||
|
||||
isMemberReady(memberId) {
|
||||
const match = this.playerSelectionDialog.match;
|
||||
if (!match || !match.playersReady) {
|
||||
return false;
|
||||
}
|
||||
return match.playersReady.includes(memberId);
|
||||
},
|
||||
|
||||
async toggleMemberReady(member) {
|
||||
console.log('[toggleMemberReady] Called with member:', member);
|
||||
const match = this.playerSelectionDialog.match;
|
||||
if (!match) {
|
||||
console.error('[toggleMemberReady] No match selected');
|
||||
return;
|
||||
}
|
||||
console.log('[toggleMemberReady] Match:', match.id);
|
||||
|
||||
// Find member in playerSelectionDialog.members
|
||||
let memberInList = this.playerSelectionDialog.members.find(m => m.id === member.memberId);
|
||||
if (!memberInList) {
|
||||
// Member not in list yet, add it
|
||||
try {
|
||||
const response = await apiClient.get(`/clubmembers/get/${this.currentClub}/true`);
|
||||
const allMembers = response.data;
|
||||
const foundMember = allMembers.find(m => m.id === member.memberId);
|
||||
if (foundMember) {
|
||||
// Check if member is already marked as ready
|
||||
const isCurrentlyReady = match.playersReady?.includes(member.memberId) || false;
|
||||
memberInList = {
|
||||
...foundMember,
|
||||
isReady: isCurrentlyReady,
|
||||
isPlanned: match.playersPlanned?.includes(member.memberId) || false,
|
||||
hasPlayed: match.playersPlayed?.includes(member.memberId) || false
|
||||
};
|
||||
this.playerSelectionDialog.members.push(memberInList);
|
||||
} else {
|
||||
console.error('Member not found:', member.memberId);
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading member:', error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle ready status
|
||||
const wasReady = memberInList.isReady;
|
||||
memberInList.isReady = !wasReady;
|
||||
|
||||
// Update match.playersReady immediately
|
||||
if (!match.playersReady) {
|
||||
match.playersReady = [];
|
||||
}
|
||||
const index = match.playersReady.indexOf(member.memberId);
|
||||
if (wasReady && index > -1) {
|
||||
match.playersReady.splice(index, 1);
|
||||
} else if (!wasReady && index === -1) {
|
||||
match.playersReady.push(member.memberId);
|
||||
}
|
||||
|
||||
// Auto-save (don't close dialog if it's open)
|
||||
console.log('[toggleMemberReady] Calling savePlayerSelection');
|
||||
await this.savePlayerSelection(false);
|
||||
console.log('[toggleMemberReady] savePlayerSelection completed');
|
||||
},
|
||||
|
||||
...mapActions(['openDialog']),
|
||||
openImportModal() {
|
||||
this.showImportModal = true;
|
||||
@@ -1263,6 +1435,99 @@ li {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Gallery Styles */
|
||||
.gallery-dialog-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0;
|
||||
min-height: 60vh;
|
||||
max-height: 70vh;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.gallery-controls {
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid var(--border-color, #ddd);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
background: #f9f9f9;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.gallery-controls label {
|
||||
font-weight: 500;
|
||||
color: var(--text-color, #333);
|
||||
}
|
||||
|
||||
.gallery-controls select {
|
||||
padding: 6px 12px;
|
||||
border: 1px solid var(--border-color, #ddd);
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
background: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.gallery-controls select:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.gallery-members-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||
gap: 16px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.gallery-member-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
padding: 8px;
|
||||
border: 2px solid transparent;
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.gallery-member-item:hover {
|
||||
background-color: #f0f0f0;
|
||||
border-color: #007bff;
|
||||
}
|
||||
|
||||
.gallery-member-item.is-ready {
|
||||
border-color: #28a745;
|
||||
background-color: #d4edda;
|
||||
}
|
||||
|
||||
.gallery-member-image {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
aspect-ratio: 1;
|
||||
object-fit: cover;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 8px;
|
||||
pointer-events: none; /* Prevent image from blocking clicks on parent */
|
||||
}
|
||||
|
||||
.gallery-member-name {
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
color: var(--text-color, #333);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.gallery-loading,
|
||||
.gallery-error {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
font-size: 1rem;
|
||||
color: var(--text-color, #333);
|
||||
}
|
||||
|
||||
.checkbox-cell {
|
||||
text-align: center;
|
||||
width: 100px;
|
||||
|
||||
Reference in New Issue
Block a user