Verbessert die Logik zur Zuordnung von Teilnehmern in TournamentService.js, indem manuelle Zuordnungen berücksichtigt werden. Implementiert eine zufällige Verteilung der Teilnehmer nur, wenn keine manuelle Zuordnung vorhanden ist. Aktualisiert die Erstellung von Matches, um sicherzustellen, dass nur Spieler aus derselben Gruppe gegeneinander antreten. In TournamentsView.vue wird die Teilnehmerliste jetzt kollabierbar, und es werden neue Funktionen zur Anzeige von Spielergebnissen und zur Hervorhebung von Matches hinzugefügt.
This commit is contained in:
@@ -180,32 +180,52 @@ class TournamentService {
|
||||
// 2) Alte Matches löschen
|
||||
await TournamentMatch.destroy({ where: { tournamentId } });
|
||||
|
||||
// 3) Shuffle + verteilen
|
||||
const shuffled = members.slice();
|
||||
for (let i = shuffled.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
||||
// 3) Prüfe, ob Spieler bereits manuell zugeordnet wurden
|
||||
const alreadyAssigned = members.filter(m => m.groupId !== null);
|
||||
const unassigned = members.filter(m => m.groupId === null);
|
||||
|
||||
if (alreadyAssigned.length > 0) {
|
||||
// Spieler sind bereits manuell zugeordnet - nicht neu verteilen
|
||||
console.log(`${alreadyAssigned.length} Spieler bereits zugeordnet, ${unassigned.length} noch nicht zugeordnet`);
|
||||
} else {
|
||||
// Keine manuellen Zuordnungen - zufällig verteilen
|
||||
const shuffled = members.slice();
|
||||
for (let i = shuffled.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
||||
}
|
||||
groups.forEach((g, idx) => {
|
||||
shuffled
|
||||
.filter((_, i) => i % groups.length === idx)
|
||||
.forEach(m => m.update({ groupId: g.id }));
|
||||
});
|
||||
}
|
||||
groups.forEach((g, idx) => {
|
||||
shuffled
|
||||
.filter((_, i) => i % groups.length === idx)
|
||||
.forEach(m => m.update({ groupId: g.id }));
|
||||
});
|
||||
|
||||
// 4) Round‑Robin anlegen wie gehabt
|
||||
// 4) Round‑Robin anlegen wie gehabt - NUR innerhalb jeder Gruppe
|
||||
for (const g of groups) {
|
||||
const gm = await TournamentMember.findAll({ where: { groupId: g.id } });
|
||||
if (gm.length < 2) {
|
||||
console.warn(`Gruppe ${g.id} hat nur ${gm.length} Teilnehmer - keine Matches erstellt`);
|
||||
continue;
|
||||
}
|
||||
const rounds = this.generateRoundRobinSchedule(gm);
|
||||
for (let roundIndex = 0; roundIndex < rounds.length; roundIndex++) {
|
||||
for (const [p1Id, p2Id] of rounds[roundIndex]) {
|
||||
await TournamentMatch.create({
|
||||
tournamentId,
|
||||
groupId: g.id,
|
||||
round: 'group',
|
||||
player1Id: p1Id,
|
||||
player2Id: p2Id,
|
||||
groupRound: roundIndex + 1
|
||||
});
|
||||
// Prüfe, ob beide Spieler zur gleichen Gruppe gehören
|
||||
const p1 = gm.find(p => p.id === p1Id);
|
||||
const p2 = gm.find(p => p.id === p2Id);
|
||||
if (p1 && p2 && p1.groupId === p2.groupId && p1.groupId === g.id) {
|
||||
await TournamentMatch.create({
|
||||
tournamentId,
|
||||
groupId: g.id,
|
||||
round: 'group',
|
||||
player1Id: p1Id,
|
||||
player2Id: p2Id,
|
||||
groupRound: roundIndex + 1
|
||||
});
|
||||
} else {
|
||||
console.warn(`Spieler gehören nicht zur gleichen Gruppe: ${p1Id} (${p1?.groupId}) vs ${p2Id} (${p2?.groupId}) in Gruppe ${g.id}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,34 +24,41 @@
|
||||
<span>Spielen in Gruppen</span>
|
||||
</label>
|
||||
<section class="participants">
|
||||
<h4>Teilnehmer</h4>
|
||||
<ul>
|
||||
<li v-for="participant in participants" :key="participant.id">
|
||||
{{ participant.member.firstName }}
|
||||
{{ participant.member.lastName }}
|
||||
<template v-if="isGroupTournament">
|
||||
<label class="inline-label">
|
||||
Gruppe:
|
||||
<select v-model.number="participant.groupNumber">
|
||||
<option :value="null">–</option>
|
||||
<option v-for="group in groups" :key="group.groupId" :value="group.groupNumber">
|
||||
Gruppe {{ group.groupNumber }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
</template>
|
||||
<button @click="removeParticipant(participant)" style="margin-left:0.5rem" class="trash-btn">
|
||||
🗑️
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<select v-model="selectedMember">
|
||||
<option v-for="member in clubMembers" :key="member.id" :value="member.id">
|
||||
{{ member.firstName }}
|
||||
{{ member.lastName }}
|
||||
</option>
|
||||
</select>
|
||||
<button @click="addParticipant">Hinzufügen</button>
|
||||
<div class="participants-header" @click="toggleParticipants">
|
||||
<h4>Teilnehmer</h4>
|
||||
<span class="collapse-icon" :class="{ 'expanded': showParticipants }">▼</span>
|
||||
</div>
|
||||
<div v-show="showParticipants" class="participants-content">
|
||||
<ul>
|
||||
<li v-for="participant in participants" :key="participant.id">
|
||||
{{ participant.member?.firstName || 'Unbekannt' }}
|
||||
{{ participant.member?.lastName || '' }}
|
||||
<template v-if="isGroupTournament">
|
||||
<label class="inline-label">
|
||||
Gruppe:
|
||||
<select v-model.number="participant.groupNumber" @change="updateParticipantGroup(participant, $event)">
|
||||
<option :value="null">–</option>
|
||||
<option v-for="group in groups" :key="group.groupId" :value="group.groupNumber">
|
||||
Gruppe {{ group.groupNumber }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
</template>
|
||||
<button @click="removeParticipant(participant)" style="margin-left:0.5rem" class="trash-btn">
|
||||
🗑️
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="add-participant">
|
||||
<select v-model="selectedMember">
|
||||
<option v-for="member in clubMembers" :key="member.id" :value="member.id">
|
||||
{{ member.firstName }}
|
||||
{{ member.lastName }}
|
||||
</option>
|
||||
</select>
|
||||
<button @click="addParticipant">Hinzufügen</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section v-if="isGroupTournament" class="group-controls">
|
||||
<label>
|
||||
@@ -78,22 +85,38 @@
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Platz</th>
|
||||
<th>Index</th>
|
||||
<th>Spieler</th>
|
||||
<th>Punkte</th>
|
||||
<th>Satz</th>
|
||||
<th>Diff</th>
|
||||
<th v-for="(opponent, idx) in groupRankings[group.groupId]" :key="`opp-${opponent.id}`">
|
||||
G{{ String.fromCharCode(96 + group.groupNumber) }}{{ idx + 1 }}
|
||||
</th>
|
||||
<th>Platz</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="pl in groupRankings[group.groupId]" :key="pl.id">
|
||||
<td>{{ pl.position }}.</td>
|
||||
<tr v-for="(pl, idx) in groupRankings[group.groupId]" :key="pl.id">
|
||||
<td><strong>G{{ String.fromCharCode(96 + group.groupNumber) }}{{ idx + 1 }}</strong></td>
|
||||
<td>{{ pl.name }}</td>
|
||||
<td>{{ pl.points }}</td>
|
||||
<td>{{ pl.setsWon }}:{{ pl.setsLost }}</td>
|
||||
<td>
|
||||
{{ pl.setDiff >= 0 ? '+' + pl.setDiff : pl.setDiff }}
|
||||
</td>
|
||||
<td v-for="(opponent, oppIdx) in groupRankings[group.groupId]"
|
||||
:key="`match-${pl.id}-${opponent.id}`"
|
||||
:class="['match-cell', { 'clickable': idx !== oppIdx }]"
|
||||
@click="idx !== oppIdx ? highlightMatch(pl.id, opponent.id, group.groupId) : null">
|
||||
<span v-if="idx === oppIdx" class="diagonal"></span>
|
||||
<span v-else-if="getMatchLiveResult(pl.id, opponent.id, group.groupId)"
|
||||
:class="getMatchCellClasses(pl.id, opponent.id, group.groupId)">
|
||||
{{ getMatchDisplayText(pl.id, opponent.id, group.groupId) }}
|
||||
</span>
|
||||
<span v-else class="no-match">-</span>
|
||||
</td>
|
||||
<td>{{ pl.position }}.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -122,7 +145,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="m in groupMatches" :key="m.id">
|
||||
<tr v-for="m in groupMatches" :key="m.id" :data-match-id="m.id">
|
||||
<td>{{ m.groupRound }}</td>
|
||||
<td>{{ m.groupNumber }}</td>
|
||||
<td>
|
||||
@@ -301,6 +324,7 @@ export default {
|
||||
groups: [],
|
||||
matches: [],
|
||||
showKnockout: false,
|
||||
showParticipants: false, // Kollaps-Status für Teilnehmerliste
|
||||
editingResult: {
|
||||
matchId: null, // aktuell bearbeitetes Match
|
||||
set: null, // aktuell bearbeitete Satz‑Nummer
|
||||
@@ -369,6 +393,15 @@ export default {
|
||||
return rankings;
|
||||
},
|
||||
|
||||
// Mapping von groupId zu groupNumber für die Teilnehmer-Auswahl
|
||||
groupIdToNumberMap() {
|
||||
const map = {};
|
||||
this.groups.forEach(g => {
|
||||
map[g.groupId] = g.groupNumber;
|
||||
});
|
||||
return map;
|
||||
},
|
||||
|
||||
rankingList() {
|
||||
const finalMatch = this.knockoutMatches.find(
|
||||
m => m.round.toLowerCase() === 'finale'
|
||||
@@ -435,6 +468,20 @@ export default {
|
||||
);
|
||||
this.clubMembers = m.data;
|
||||
},
|
||||
mounted() {
|
||||
// Event-Listener für das Entfernen des Highlights
|
||||
document.addEventListener('click', (e) => {
|
||||
// Entferne Highlight nur wenn nicht auf eine Matrix-Zelle geklickt wird
|
||||
if (!e.target.closest('.match-cell')) {
|
||||
this.clearHighlight();
|
||||
}
|
||||
});
|
||||
|
||||
// Event-Listener für Eingabefelder
|
||||
document.addEventListener('input', () => {
|
||||
this.clearHighlight();
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
normalizeResultInput(raw) {
|
||||
const s = raw.trim();
|
||||
@@ -493,11 +540,27 @@ export default {
|
||||
m[g.groupId] = g.groupNumber;
|
||||
return m;
|
||||
}, {});
|
||||
this.matches = mRes.data.map(m => ({
|
||||
...m,
|
||||
groupNumber: grpMap[m.groupId] || 0,
|
||||
resultInput: ''
|
||||
}));
|
||||
|
||||
this.matches = mRes.data.map(m => {
|
||||
// Bestimme groupId basierend auf den Spielern, da die Matches groupId: null haben
|
||||
const player1GroupId = m.player1?.groupId;
|
||||
const player2GroupId = m.player2?.groupId;
|
||||
const matchGroupId = player1GroupId || player2GroupId;
|
||||
|
||||
return {
|
||||
...m,
|
||||
groupId: matchGroupId, // Überschreibe null mit der korrekten groupId
|
||||
groupNumber: grpMap[matchGroupId] || 0,
|
||||
resultInput: ''
|
||||
};
|
||||
});
|
||||
|
||||
// Initialisiere groupNumber für Teilnehmer basierend auf groupId
|
||||
this.initializeParticipantGroupNumbers();
|
||||
|
||||
// Setze Kollaps-Status: ausgeklappt wenn keine Spiele, eingeklappt wenn Spiele vorhanden
|
||||
this.showParticipants = this.matches.length === 0;
|
||||
|
||||
this.showKnockout = this.matches.some(m => m.round !== 'group');
|
||||
},
|
||||
|
||||
@@ -785,6 +848,207 @@ export default {
|
||||
} catch (err) {
|
||||
alert('Fehler beim Zurücksetzen der K.o.-Runde');
|
||||
}
|
||||
},
|
||||
|
||||
getMatchResult(player1Id, player2Id, groupId) {
|
||||
const match = this.matches.find(m =>
|
||||
m.round === 'group' &&
|
||||
m.groupId === groupId &&
|
||||
((m.player1.id === player1Id && m.player2.id === player2Id) ||
|
||||
(m.player1.id === player2Id && m.player2.id === player1Id)) &&
|
||||
m.isFinished
|
||||
);
|
||||
|
||||
if (!match) return null;
|
||||
|
||||
// Bestimme, wer gewonnen hat
|
||||
const [sets1, sets2] = match.result.split(':').map(n => +n);
|
||||
const player1Won = sets1 > sets2;
|
||||
|
||||
// Gib das Ergebnis in der Sicht des ersten Spielers zurück
|
||||
if (match.player1.id === player1Id) {
|
||||
return player1Won ? 'W' : 'L';
|
||||
} else {
|
||||
return player1Won ? 'L' : 'W';
|
||||
}
|
||||
},
|
||||
|
||||
getMatchLiveResult(player1Id, player2Id, groupId) {
|
||||
const match = this.matches.find(m =>
|
||||
m.round === 'group' &&
|
||||
m.groupId === groupId &&
|
||||
((m.player1.id === player1Id && m.player2.id === player2Id) ||
|
||||
(m.player1.id === player2Id && m.player2.id === player1Id))
|
||||
);
|
||||
|
||||
if (!match) return null;
|
||||
|
||||
// Berechne aktuelle Sätze aus tournamentResults
|
||||
let sets1 = 0, sets2 = 0;
|
||||
if (match.tournamentResults && match.tournamentResults.length > 0) {
|
||||
match.tournamentResults.forEach(result => {
|
||||
if (result.pointsPlayer1 > result.pointsPlayer2) {
|
||||
sets1++;
|
||||
} else if (result.pointsPlayer2 > result.pointsPlayer1) {
|
||||
sets2++;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Bestimme die Anzeige basierend auf der Spieler-Reihenfolge
|
||||
if (match.player1.id === player1Id) {
|
||||
return {
|
||||
sets1: sets1,
|
||||
sets2: sets2,
|
||||
isFinished: match.isFinished,
|
||||
player1Won: sets1 > sets2,
|
||||
player2Won: sets2 > sets1,
|
||||
isTie: sets1 === sets2
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
sets1: sets2,
|
||||
sets2: sets1,
|
||||
isFinished: match.isFinished,
|
||||
player1Won: sets2 > sets1,
|
||||
player2Won: sets1 > sets2,
|
||||
isTie: sets1 === sets2
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
highlightMatch(player1Id, player2Id, groupId) {
|
||||
console.log('highlightMatch called:', { player1Id, player2Id, groupId });
|
||||
|
||||
// Finde das entsprechende Match (auch unbeendete)
|
||||
const match = this.matches.find(m =>
|
||||
m.round === 'group' &&
|
||||
m.groupId === groupId &&
|
||||
((m.player1.id === player1Id && m.player2.id === player2Id) ||
|
||||
(m.player1.id === player2Id && m.player2.id === player1Id))
|
||||
);
|
||||
|
||||
console.log('Found match:', match);
|
||||
|
||||
if (!match) {
|
||||
console.log('No match found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Setze Highlight-Klasse
|
||||
this.$nextTick(() => {
|
||||
const matchElement = document.querySelector(`tr[data-match-id="${match.id}"]`);
|
||||
console.log('Match element:', matchElement);
|
||||
|
||||
if (matchElement) {
|
||||
// Entferne vorherige Highlights
|
||||
document.querySelectorAll('.match-highlight').forEach(el => {
|
||||
el.classList.remove('match-highlight');
|
||||
});
|
||||
|
||||
// Füge Highlight hinzu
|
||||
matchElement.classList.add('match-highlight');
|
||||
|
||||
// Scrolle zum Element
|
||||
matchElement.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'center'
|
||||
});
|
||||
} else {
|
||||
console.log('Match element not found in DOM');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
clearHighlight() {
|
||||
// Entferne alle Highlights
|
||||
document.querySelectorAll('.match-highlight').forEach(el => {
|
||||
el.classList.remove('match-highlight');
|
||||
});
|
||||
},
|
||||
|
||||
async updateParticipantGroup(participant, event) {
|
||||
const groupNumber = parseInt(event.target.value);
|
||||
|
||||
// Aktualisiere lokal
|
||||
participant.groupNumber = groupNumber;
|
||||
|
||||
// Bereite alle Teilnehmer-Zuordnungen vor
|
||||
const assignments = this.participants.map(p => ({
|
||||
participantId: p.id,
|
||||
groupNumber: p.groupNumber || null
|
||||
}));
|
||||
|
||||
// Sende an Backend
|
||||
try {
|
||||
await apiClient.post('/tournament/groups/manual', {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: this.selectedDate,
|
||||
assignments: assignments,
|
||||
numberOfGroups: this.numberOfGroups,
|
||||
maxGroupSize: this.maxGroupSize
|
||||
});
|
||||
|
||||
// Lade Daten neu, um die aktualisierten Gruppen zu erhalten
|
||||
await this.loadTournamentData();
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Aktualisieren der Gruppe:', error);
|
||||
// Bei Fehler: Lade Daten neu
|
||||
await this.loadTournamentData();
|
||||
}
|
||||
},
|
||||
|
||||
// Initialisiere groupNumber basierend auf groupId
|
||||
initializeParticipantGroupNumbers() {
|
||||
this.participants.forEach(participant => {
|
||||
if (participant.groupId) {
|
||||
const group = this.groups.find(g => g.groupId === participant.groupId);
|
||||
if (group) {
|
||||
participant.groupNumber = group.groupNumber;
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
toggleParticipants() {
|
||||
this.showParticipants = !this.showParticipants;
|
||||
},
|
||||
|
||||
getMatchDisplayText(player1Id, player2Id, groupId) {
|
||||
const liveResult = this.getMatchLiveResult(player1Id, player2Id, groupId);
|
||||
if (!liveResult) return '-';
|
||||
|
||||
// Zeige Satzergebnis (z.B. 1:0, 2:1, 0:2)
|
||||
return `${liveResult.sets1}:${liveResult.sets2}`;
|
||||
},
|
||||
|
||||
getMatchCellClasses(player1Id, player2Id, groupId) {
|
||||
const liveResult = this.getMatchLiveResult(player1Id, player2Id, groupId);
|
||||
if (!liveResult) return ['no-match'];
|
||||
|
||||
const classes = ['match-result'];
|
||||
|
||||
if (liveResult.isFinished) {
|
||||
// Spiel beendet: Dunkle Farben
|
||||
if (liveResult.player1Won) {
|
||||
classes.push('match-finished-win');
|
||||
} else if (liveResult.player2Won) {
|
||||
classes.push('match-finished-loss');
|
||||
} else {
|
||||
classes.push('match-finished-tie');
|
||||
}
|
||||
} else {
|
||||
// Spiel läuft: Helle Farben
|
||||
if (liveResult.player1Won) {
|
||||
classes.push('match-live-win');
|
||||
} else if (liveResult.player2Won) {
|
||||
classes.push('match-live-loss');
|
||||
} else {
|
||||
classes.push('match-live-tie');
|
||||
}
|
||||
}
|
||||
|
||||
return classes;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -822,4 +1086,173 @@ td {
|
||||
button {
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
.diagonal {
|
||||
background-color: #000;
|
||||
color: #000;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 20px;
|
||||
}
|
||||
|
||||
.match-result {
|
||||
font-weight: bold;
|
||||
padding: 2px 4px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.match-result.win {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.match-result.loss {
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
/* Live-Ergebnisse während des Spiels */
|
||||
.match-live-win {
|
||||
background-color: #d1eca1; /* Hellgrün */
|
||||
color: #0c5460;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.match-live-loss {
|
||||
background-color: #ffeaa7; /* Orange */
|
||||
color: #d63031;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.match-live-tie {
|
||||
background-color: #f8f9fa; /* Neutral */
|
||||
color: #6c757d;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Beendete Spiele */
|
||||
.match-finished-win {
|
||||
background-color: #28a745; /* Dunkelgrün */
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.match-finished-loss {
|
||||
background-color: #fd7e14; /* Dunkelorange */
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.match-finished-tie {
|
||||
background-color: #6c757d; /* Grau */
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Kollabierbare Teilnehmerliste */
|
||||
.participants-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
padding: 0.5rem 0;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.participants-header:hover {
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.participants-header h4 {
|
||||
margin: 0;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.collapse-icon {
|
||||
font-size: 0.8em;
|
||||
color: #6c757d;
|
||||
transition: transform 0.3s ease;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.collapse-icon.expanded {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.participants-content {
|
||||
animation: slideDown 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
max-height: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
max-height: 1000px;
|
||||
}
|
||||
}
|
||||
|
||||
.add-participant {
|
||||
margin-top: 1rem;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid #dee2e6;
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.no-match {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.group-table table {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.group-table th,
|
||||
.group-table td {
|
||||
padding: 0.3em 0.5em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.group-table th:first-child,
|
||||
.group-table td:first-child {
|
||||
text-align: left;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.group-table th:nth-child(2),
|
||||
.group-table td:nth-child(2) {
|
||||
text-align: left;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.match-cell.clickable {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.match-cell.clickable:hover {
|
||||
background-color: #f8f9fa;
|
||||
transform: scale(1.02);
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.match-highlight {
|
||||
background-color: #fff3cd !important;
|
||||
border: 2px solid #ffc107 !important;
|
||||
animation: highlight-pulse 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes highlight-pulse {
|
||||
0% { transform: scale(1); }
|
||||
50% { transform: scale(1.02); }
|
||||
100% { transform: scale(1); }
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user