Fügt neue Funktionen zur Bearbeitung von Spielberichten im MatchReportApiDialog hinzu. Implementiert eine Anzeige für den Abschlussstatus des Spiels, einschließlich Warnungen und Deaktivierungen von Eingabefeldern, wenn das Match bereits abgeschlossen ist. Aktualisiert die Logik zur Berechnung der Gesamtpunkte und Sätze sowie zur Validierung von PINs. Verbessert die Benutzeroberfläche mit neuen CSS-Stilen für abgeschlossene Matches und Dialoge zur Anzeige von Match-Daten.
This commit is contained in:
@@ -51,6 +51,7 @@
|
||||
@click="setActiveSection('result')" :disabled="!canOpenNextStages">
|
||||
<div class="section-icon">⚽</div>
|
||||
<span>Ergebniserfassung</span>
|
||||
<span v-if="isMatchCompleted" class="locked-indicator">🔒</span>
|
||||
</button>
|
||||
|
||||
<button class="section-btn" :class="{ active: activeSection === 'completion', disabled: !canOpenNextStages }"
|
||||
@@ -304,6 +305,15 @@
|
||||
<div v-else-if="activeSection === 'result'" class="result-content">
|
||||
<h3>Ergebniserfassung</h3>
|
||||
|
||||
<!-- Warnung bei abgeschlossenem Match -->
|
||||
<div v-if="isMatchCompleted" class="completion-warning">
|
||||
<div class="warning-icon">🔒</div>
|
||||
<div class="warning-text">
|
||||
<strong>Spielbericht bereits abgesendet</strong><br>
|
||||
Diese Ergebniserfassung kann nicht mehr bearbeitet werden.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Spielstand oben -->
|
||||
<div class="score-summary">
|
||||
<div class="score-display">
|
||||
@@ -431,26 +441,33 @@
|
||||
<span class="time-value">{{ getFormattedTime(match.startDate) || 'nicht gesetzt' }}</span>
|
||||
</div>
|
||||
|
||||
<div class="time-input-group">
|
||||
<label>Endzeit:</label>
|
||||
<input
|
||||
type="time"
|
||||
:value="getFormattedTime(match.endDate)"
|
||||
@change="setEndTime($event.target.value)"
|
||||
class="time-input"
|
||||
/>
|
||||
<button @click="setCurrentEndTime" class="time-btn" title="Aktuelle Zeit setzen">🕐</button>
|
||||
</div>
|
||||
<div class="time-input-group">
|
||||
<label>Endzeit:</label>
|
||||
<input
|
||||
type="time"
|
||||
:value="getFormattedTime(match.endDate)"
|
||||
@change="setEndTime($event.target.value)"
|
||||
class="time-input"
|
||||
:disabled="isMatchCompleted"
|
||||
/>
|
||||
<button
|
||||
@click="setCurrentEndTime"
|
||||
class="time-btn"
|
||||
title="Aktuelle Zeit setzen"
|
||||
:disabled="isMatchCompleted"
|
||||
>🕐</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Protest-Eingabe -->
|
||||
<div class="protest-section">
|
||||
<label for="protestInput">Protest:</label>
|
||||
<textarea
|
||||
id="protestInput"
|
||||
v-model="protestText"
|
||||
<textarea
|
||||
id="protestInput"
|
||||
v-model="protestText"
|
||||
placeholder="Protestgrund eingeben..."
|
||||
class="protest-input"
|
||||
:disabled="isMatchCompleted"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
@@ -458,33 +475,43 @@
|
||||
<div class="final-pins">
|
||||
<div class="pin-group">
|
||||
<label for="finalHomePin">PIN Heimverein:</label>
|
||||
<input
|
||||
id="finalHomePin"
|
||||
v-model="finalHomePin"
|
||||
type="password"
|
||||
<input
|
||||
id="finalHomePin"
|
||||
v-model="finalHomePin"
|
||||
type="password"
|
||||
class="pin-input"
|
||||
:placeholder="match.homePin || 'PIN eingeben'"
|
||||
:disabled="isMatchCompleted"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="pin-group">
|
||||
<label for="finalGuestPin">PIN Gastverein:</label>
|
||||
<input
|
||||
id="finalGuestPin"
|
||||
v-model="finalGuestPin"
|
||||
type="password"
|
||||
<input
|
||||
id="finalGuestPin"
|
||||
v-model="finalGuestPin"
|
||||
type="password"
|
||||
class="pin-input"
|
||||
:placeholder="match.guestPin || 'PIN eingeben'"
|
||||
:disabled="isMatchCompleted"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Absenden-Button -->
|
||||
<div class="submit-section">
|
||||
<button @click="submitMatchReport" class="btn-primary submit-btn">
|
||||
📝 Spielbericht absenden
|
||||
</button>
|
||||
</div>
|
||||
<!-- Absenden-Button -->
|
||||
<div class="submit-section">
|
||||
<button
|
||||
@click="submitMatchReport"
|
||||
class="btn-primary submit-btn"
|
||||
:disabled="isMatchCompleted"
|
||||
:class="{ 'disabled': isMatchCompleted }"
|
||||
>
|
||||
{{ isMatchCompleted ? '✅ Spielbericht bereits abgesendet' : '📝 Spielbericht absenden' }}
|
||||
</button>
|
||||
<div v-if="isMatchCompleted" class="completion-notice">
|
||||
⚠️ Dieser Spielbericht wurde bereits abgesendet. Keine weiteren Änderungen möglich.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -653,6 +680,10 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
return norm(this.meetingData.league) === norm(this.meetingData.group);
|
||||
},
|
||||
canOpenNextStages() {
|
||||
// Wenn das Match bereits abgeschlossen ist, dürfen keine Änderungen mehr gemacht werden
|
||||
if (this.isMatchCompleted) {
|
||||
return false;
|
||||
}
|
||||
return this.isHomeLineupCertified && this.isGuestLineupCertified;
|
||||
},
|
||||
currentPlayMode() {
|
||||
@@ -777,22 +808,26 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
},
|
||||
|
||||
getOverallSetScore() {
|
||||
let totalHomeSets = 0;
|
||||
let totalGuestSets = 0;
|
||||
let totalHomeSetsWon = 0;
|
||||
let totalGuestSetsWon = 0;
|
||||
|
||||
this.results.forEach(match => {
|
||||
if (match.completed) {
|
||||
match.sets.forEach(set => {
|
||||
if (set && set.includes(':')) {
|
||||
const [home, guest] = set.split(':').map(s => parseInt(s) || 0);
|
||||
totalHomeSets += home;
|
||||
totalGuestSets += guest;
|
||||
|
||||
if (home > guest) {
|
||||
totalHomeSetsWon++;
|
||||
} else if (guest > home) {
|
||||
totalGuestSetsWon++;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return `(${totalHomeSets}:${totalGuestSets})`;
|
||||
return `(${totalHomeSetsWon}:${totalGuestSetsWon})`;
|
||||
},
|
||||
|
||||
initializeFinalPins() {
|
||||
@@ -803,22 +838,341 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
|
||||
async submitMatchReport() {
|
||||
try {
|
||||
const reportData = {
|
||||
matchId: this.match.id,
|
||||
startTime: this.match.startDate,
|
||||
endTime: this.match.endDate,
|
||||
protestText: this.protestText,
|
||||
homePin: this.finalHomePin,
|
||||
guestPin: this.finalGuestPin,
|
||||
results: this.results,
|
||||
finalScore: this.getOverallScore()
|
||||
};
|
||||
console.log('🚀 Starte Spielbericht-Absendung...');
|
||||
|
||||
// Validiere PINs (für Test: Gast-PIN "1234" akzeptieren)
|
||||
if (!this.finalHomePin || this.finalHomePin.trim() === '') {
|
||||
alert('Bitte geben Sie die PIN des Heimvereins ein.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.finalGuestPin || this.finalGuestPin.trim() === '') {
|
||||
alert('Bitte geben Sie die PIN des Gastvereins ein.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Test-Validierung: Gast-PIN "1234" akzeptieren
|
||||
if (this.finalGuestPin !== '1234') {
|
||||
alert('Für den Test wird nur die Gast-PIN "1234" akzeptiert.');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('✅ PIN-Validierung erfolgreich');
|
||||
|
||||
// Erstelle eine Kopie des ursprünglichen Match-Objekts
|
||||
console.log('📋 Erstelle Kopie des Match-Objekts...');
|
||||
const matchData = JSON.parse(JSON.stringify(this.match));
|
||||
console.log('✅ Match-Objekt kopiert');
|
||||
|
||||
// Aktualisiere die Match-Daten mit unseren Eingaben
|
||||
console.log('🔄 Aktualisiere Match-Daten...');
|
||||
this.updateMatchData(matchData);
|
||||
console.log('✅ Match-Daten aktualisiert');
|
||||
|
||||
// Zeige das vollständige Objekt in einem Dialog an
|
||||
console.log('📊 Zeige Match-Daten Dialog...');
|
||||
this.showMatchDataDialog(matchData);
|
||||
console.log('✅ Dialog angezeigt');
|
||||
|
||||
console.log('Spielbericht-Daten:', reportData);
|
||||
alert('Spielbericht erfolgreich abgesendet!');
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Absenden:', error);
|
||||
alert('Fehler beim Absenden des Spielberichts');
|
||||
console.error('❌ Fehler beim Absenden:', error);
|
||||
console.error('❌ Fehler-Stack:', error.stack);
|
||||
alert(`Fehler beim Absenden des Spielberichts: ${error.message}`);
|
||||
}
|
||||
},
|
||||
|
||||
showMatchDataDialog(matchData) {
|
||||
// Erstelle einen neuen Dialog für die Datenanzeige
|
||||
const dialog = document.createElement('div');
|
||||
dialog.className = 'match-data-dialog-overlay';
|
||||
|
||||
const dialogContent = document.createElement('div');
|
||||
dialogContent.className = 'match-data-dialog';
|
||||
|
||||
const header = document.createElement('div');
|
||||
header.className = 'match-data-dialog-header';
|
||||
header.innerHTML = `
|
||||
<h3>📋 Vollständiges Match-Objekt</h3>
|
||||
<button class="close-dialog-btn" onclick="this.closest('.match-data-dialog-overlay').remove()">✕</button>
|
||||
`;
|
||||
|
||||
const content = document.createElement('div');
|
||||
content.className = 'match-data-dialog-content';
|
||||
|
||||
// Pretty-print das JSON
|
||||
const jsonString = JSON.stringify(matchData, null, 2);
|
||||
const pre = document.createElement('pre');
|
||||
pre.textContent = jsonString;
|
||||
pre.className = 'json-display';
|
||||
|
||||
const copyButton = document.createElement('button');
|
||||
copyButton.className = 'copy-json-btn';
|
||||
copyButton.textContent = '📋 JSON kopieren';
|
||||
copyButton.onclick = () => {
|
||||
navigator.clipboard.writeText(jsonString).then(() => {
|
||||
copyButton.textContent = '✅ Kopiert!';
|
||||
setTimeout(() => {
|
||||
copyButton.textContent = '📋 JSON kopieren';
|
||||
}, 2000);
|
||||
});
|
||||
};
|
||||
|
||||
content.appendChild(pre);
|
||||
content.appendChild(copyButton);
|
||||
|
||||
dialogContent.appendChild(header);
|
||||
dialogContent.appendChild(content);
|
||||
dialog.appendChild(dialogContent);
|
||||
|
||||
// Dialog zum Body hinzufügen
|
||||
document.body.appendChild(dialog);
|
||||
|
||||
// Dialog schließen bei Klick außerhalb
|
||||
dialog.onclick = (e) => {
|
||||
if (e.target === dialog) {
|
||||
dialog.remove();
|
||||
}
|
||||
};
|
||||
|
||||
// ESC-Taste zum Schließen
|
||||
const handleEsc = (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
dialog.remove();
|
||||
document.removeEventListener('keydown', handleEsc);
|
||||
}
|
||||
};
|
||||
document.addEventListener('keydown', handleEsc);
|
||||
},
|
||||
|
||||
updateMatchData(matchData) {
|
||||
try {
|
||||
console.log('🔄 updateMatchData: Verwende kompletten Original-Meeting-Daten...');
|
||||
|
||||
// Verwende ausschließlich die Meeting-Details vom /meetingdetails/:uuid Endpoint
|
||||
if (!this.meetingDetails) {
|
||||
throw new Error('❌ meetingDetails nicht verfügbar - Meeting-Details müssen zuerst geladen werden');
|
||||
}
|
||||
const baseData = this.meetingDetails;
|
||||
|
||||
// Das gesamte Original-Objekt als Basis verwenden (bestehende matchData überschreiben)
|
||||
Object.assign(matchData, baseData);
|
||||
|
||||
// Unerwünschte Felder entfernen, die nicht vom /meetingdetails Endpoint kommen sollten:
|
||||
delete matchData.championsTieBreakSelected;
|
||||
delete matchData.code;
|
||||
delete matchData.date;
|
||||
delete matchData.guestMatchPoints;
|
||||
delete matchData.guestTeam;
|
||||
delete matchData.guestTeamId;
|
||||
delete matchData.homeMatchPoints;
|
||||
delete matchData.homeTeam;
|
||||
delete matchData.homeTeamId;
|
||||
delete matchData.id;
|
||||
delete matchData.leagueDetails;
|
||||
delete matchData.leagueId;
|
||||
delete matchData.locationId;
|
||||
delete matchData.time;
|
||||
|
||||
// NUR unsere spezifischen Änderungen eintragen:
|
||||
|
||||
// 1. Spieleranzahl aktualisieren (aus Aufstellung)
|
||||
matchData.playerCountHome = this.getSelectedPlayerCount('home');
|
||||
matchData.playerCountGuest = this.getSelectedPlayerCount('guest');
|
||||
|
||||
// 1.1 Player-Positionen aktualisieren (Einzel + Doppel Positionen erfassen)
|
||||
this.updatePlayerPositions(matchData);
|
||||
|
||||
// 2. Zeitangaben aktualisieren
|
||||
if (this.match.startDate) {
|
||||
matchData.startDate = this.match.startDate.toISOString();
|
||||
}
|
||||
if (this.match.endDate) {
|
||||
matchData.endDate = this.match.endDate.toISOString();
|
||||
}
|
||||
matchData.meetingsStartEndTimeEnabled = true;
|
||||
|
||||
// 3. Protest-Informationen
|
||||
if (this.protestText && this.protestText.trim() !== '') {
|
||||
matchData.protest = true;
|
||||
matchData.remarks = this.protestText.trim();
|
||||
} else {
|
||||
matchData.protest = false;
|
||||
matchData.remarks = null;
|
||||
}
|
||||
|
||||
// 4. PINs - Original-Hashes beibehalten (werden vom Backend beim Senden aktualisiert)
|
||||
// matchData.homePin und matchData.guestPin bleiben die ursprünglichen Hashes aus baseData
|
||||
|
||||
// 5. Match-Status auf abgeschlossen setzen
|
||||
matchData.isCompleted = true;
|
||||
|
||||
// 6. Gesamtstatistik berechnen und eintragen
|
||||
const overallScore = this.getOverallMatchScore();
|
||||
|
||||
// Gesamtpunkte berechnen und eintragen
|
||||
const [homeMatches, guestMatches] = overallScore.split(':').map(Number);
|
||||
matchData.homeMatches = homeMatches;
|
||||
matchData.guestMatches = guestMatches;
|
||||
|
||||
// Match-Punkte (für Liga-Tabelle) - diese Felder werden vom Backend berechnet
|
||||
// homeMatchPoints und guestMatchPoints bleiben ursprünglich aus baseData
|
||||
|
||||
// Gesamtsätze berechnen und eintragen
|
||||
const setScore = this.getOverallSetScore();
|
||||
const setScoreMatch = setScore.match(/\((\d+):(\d+)\)/);
|
||||
if (setScoreMatch) {
|
||||
matchData.homeSets = parseInt(setScoreMatch[1]);
|
||||
matchData.guestSets = parseInt(setScoreMatch[2]);
|
||||
}
|
||||
|
||||
// 7. Gesamtpunkte (Games) berechnen und eintragen
|
||||
let totalHomeGames = 0;
|
||||
let totalGuestGames = 0;
|
||||
|
||||
// Einzelne Matches aktualisieren - Originalarray verwenden und nur unsere Ergebnisse eintragen
|
||||
if (matchData.matches && Array.isArray(matchData.matches)) {
|
||||
this.results.forEach((result, index) => {
|
||||
if (matchData.matches[index]) {
|
||||
const match = matchData.matches[index];
|
||||
|
||||
// Reset aller Satzfelder auf 0 (Original hat meist schon Werte)
|
||||
const setFields = ['set1A', 'set2A', 'set3A', 'set4A', 'set5A', 'set1B', 'set2B', 'set3B', 'set4B', 'set5B'];
|
||||
setFields.forEach(field => {
|
||||
match[field] = 0;
|
||||
});
|
||||
|
||||
// Satzergebnisse aus unseren eingegebenen Daten eintragen
|
||||
result.sets.forEach((set, setIndex) => {
|
||||
if (set && set.includes(':')) {
|
||||
const [home, guest] = set.split(':').map(s => parseInt(s) || 0);
|
||||
const setFieldA = `set${setIndex + 1}A`;
|
||||
const setFieldB = `set${setIndex + 1}B`;
|
||||
|
||||
match[setFieldA] = home;
|
||||
match[setFieldB] = guest;
|
||||
|
||||
// Gesamtpunkte (Games) sammeln
|
||||
totalHomeGames += home;
|
||||
totalGuestGames += guest;
|
||||
}
|
||||
});
|
||||
|
||||
// Match-Gewinner berechnen und eintragen
|
||||
let homeWins = 0;
|
||||
let guestWins = 0;
|
||||
|
||||
if (result.completed) {
|
||||
result.sets.forEach(set => {
|
||||
if (set && set.includes(':')) {
|
||||
const [home, guest] = set.split(':').map(s => parseInt(s) || 0);
|
||||
if (home > guest) {
|
||||
homeWins++;
|
||||
} else if (guest > home) {
|
||||
guestWins++;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Nur unsere geänderten Werte aktualisieren (Original bleibt erhalten)
|
||||
match.matchesA = homeWins > guestWins ? 1 : 0;
|
||||
match.matchesB = guestWins > homeWins ? 1 : 0;
|
||||
match.setsA = homeWins;
|
||||
match.setsB = guestWins;
|
||||
match.isCompleted = result.completed;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 8. Gesamtpunkte (Games) eintragen
|
||||
matchData.homeGames = totalHomeGames;
|
||||
matchData.guestGames = totalGuestGames;
|
||||
|
||||
// 9. Champions Tie-Break - wird NICHT hinzugefügt (Feld bleibt aus Original)
|
||||
|
||||
console.log('✅ updateMatchData: Aktualisierung abgeschlossen');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ updateMatchData Fehler:', error);
|
||||
console.error('❌ updateMatchData Stack:', error.stack);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// Aktualisiere die Player-Positionen um alle Positionen (Einzel + Doppel) zu erfassen
|
||||
updatePlayerPositions(matchData) {
|
||||
console.log('🔄 Aktualisiere Player-Positionen...');
|
||||
|
||||
// Home Players Positionen aktualisieren
|
||||
if (matchData.teamLineupHomePlayers && Array.isArray(matchData.teamLineupHomePlayers)) {
|
||||
matchData.teamLineupHomePlayers.forEach(player => {
|
||||
const positions = [];
|
||||
|
||||
// Einzel-Position NUR hinzufügen wenn Spieler ausgewählt ist
|
||||
if (player.isSelected) {
|
||||
// Verwende positionSingle falls verfügbar, sonst rank für Einzel-Position
|
||||
const singlePos = player.positionSingle || player.rank;
|
||||
if (singlePos) {
|
||||
positions.push(`E${singlePos}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Doppel-Position NUR hinzufügen wenn Spieler Doppel-Position hat
|
||||
if (player.positionDouble) {
|
||||
positions.push(`D${player.positionDouble}`);
|
||||
}
|
||||
|
||||
player.positions = positions;
|
||||
console.log(`✅ Home Player ${player.firstname} ${player.lastname}: [${positions.join(', ')}] (selected: ${player.isSelected})`);
|
||||
});
|
||||
}
|
||||
|
||||
// Guest Players Positionen aktualisieren
|
||||
if (matchData.teamLineupGuestPlayers && Array.isArray(matchData.teamLineupGuestPlayers)) {
|
||||
matchData.teamLineupGuestPlayers.forEach(player => {
|
||||
const positions = [];
|
||||
|
||||
// Einzel-Position NUR hinzufügen wenn Spieler ausgewählt ist
|
||||
if (player.isSelected) {
|
||||
// Verwende positionSingle falls verfügbar, sonst rank für Einzel-Position
|
||||
const singlePos = player.positionSingle || player.rank;
|
||||
if (singlePos) {
|
||||
positions.push(`E${singlePos}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Doppel-Position NUR hinzufügen wenn Spieler Doppel-Position hat
|
||||
if (player.positionDouble) {
|
||||
positions.push(`D${player.positionDouble}`);
|
||||
}
|
||||
|
||||
player.positions = positions;
|
||||
console.log(`✅ Guest Player ${player.firstname} ${player.lastname}: [${positions.join(', ')}] (selected: ${player.isSelected})`);
|
||||
});
|
||||
}
|
||||
|
||||
console.log('✅ Player-Positionen aktualisiert');
|
||||
},
|
||||
|
||||
// Prüfe ob das Match bereits abgeschlossen ist
|
||||
get isMatchCompleted() {
|
||||
return this.match && this.match.isCompleted === true;
|
||||
},
|
||||
|
||||
// Zähle die ausgewählten Spieler für ein Team
|
||||
getSelectedPlayerCount(team) {
|
||||
try {
|
||||
if (team === 'home') {
|
||||
const homePlayers = this.meetingDetails?.teamLineupHomePlayers || this.teamLineupHomePlayers || [];
|
||||
return homePlayers.filter(p => p.isSelected === true).length;
|
||||
} else if (team === 'guest') {
|
||||
const guestPlayers = this.meetingDetails?.teamLineupGuestPlayers || this.teamLineupGuestPlayers || [];
|
||||
return guestPlayers.filter(p => p.isSelected === true).length;
|
||||
}
|
||||
return 0;
|
||||
} catch (error) {
|
||||
console.error('❌ getSelectedPlayerCount Fehler:', error);
|
||||
return 0;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1456,25 +1810,25 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
getOverallScore() {
|
||||
let homeWins = 0;
|
||||
let guestWins = 0;
|
||||
let totalHomeSets = 0;
|
||||
let totalGuestSets = 0;
|
||||
let totalHomeSetsWon = 0;
|
||||
let totalGuestSetsWon = 0;
|
||||
|
||||
this.results.forEach(match => {
|
||||
if (match.completed) {
|
||||
// Source gewonnene Sätze für dieses Match und Gesamtsätze
|
||||
// Zähle gewonnene Sätze für dieses Match und Gesamtsätze
|
||||
let matchHomeWins = 0;
|
||||
let matchGuestWins = 0;
|
||||
|
||||
match.sets.forEach(set => {
|
||||
if (set && set.includes(':')) {
|
||||
const [home, guest] = set.split(':').map(s => parseInt(s) || 0);
|
||||
totalHomeSets += home;
|
||||
totalGuestSets += guest;
|
||||
|
||||
if (home > guest) {
|
||||
matchHomeWins++;
|
||||
totalHomeSetsWon++;
|
||||
} else if (guest > home) {
|
||||
matchGuestWins++;
|
||||
totalGuestSetsWon++;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1488,7 +1842,7 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
}
|
||||
});
|
||||
|
||||
return `${homeWins}:${guestWins} (${totalHomeSets}:${totalGuestSets} Sätze)`;
|
||||
return `${homeWins}:${guestWins} (${totalHomeSetsWon}:${totalGuestSetsWon} Sätze)`;
|
||||
},
|
||||
|
||||
getMatchResult(match) {
|
||||
@@ -3054,6 +3408,194 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Match Data Dialog Styles */
|
||||
.match-data-dialog-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 10000;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.match-data-dialog {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
|
||||
max-width: 90vw;
|
||||
max-height: 90vh;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.match-data-dialog-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.match-data-dialog-header h3 {
|
||||
margin: 0;
|
||||
color: #333;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.close-dialog-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
color: #666;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.close-dialog-btn:hover {
|
||||
background: #e0e0e0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.match-data-dialog-content {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.json-display {
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin: 0 0 16px 0;
|
||||
overflow: auto;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
color: #333;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
max-height: 60vh;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.copy-json-btn {
|
||||
background: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.copy-json-btn:hover {
|
||||
background: #0056b3;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.copy-json-btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Deaktivierte Zustände für abgeschlossene Matches */
|
||||
.submit-btn.disabled {
|
||||
background-color: #6c757d;
|
||||
color: white;
|
||||
cursor: not-allowed;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.submit-btn.disabled:hover {
|
||||
background-color: #6c757d;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.completion-notice {
|
||||
margin-top: 8px;
|
||||
padding: 8px 12px;
|
||||
background-color: #fff3cd;
|
||||
border: 1px solid #ffeaa7;
|
||||
border-radius: 4px;
|
||||
color: #856404;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.time-input:disabled,
|
||||
.protest-input:disabled,
|
||||
.pin-input:disabled {
|
||||
background-color: #f8f9fa;
|
||||
color: #6c757d;
|
||||
cursor: not-allowed;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.time-btn:disabled {
|
||||
background-color: #f8f9fa;
|
||||
color: #6c757d;
|
||||
cursor: not-allowed;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.time-btn:disabled:hover {
|
||||
background-color: #f8f9fa;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
/* Locked indicator für deaktivierte Tabs */
|
||||
.locked-indicator {
|
||||
margin-left: 8px;
|
||||
font-size: 12px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* Completion warning auf Ergebniserfassungs-Seite */
|
||||
.completion-warning {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
margin-bottom: 20px;
|
||||
background-color: #fff3cd;
|
||||
border: 1px solid #ffeaa7;
|
||||
border-radius: 8px;
|
||||
color: #856404;
|
||||
}
|
||||
|
||||
.warning-icon {
|
||||
font-size: 24px;
|
||||
margin-right: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.warning-text {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.warning-text strong {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.pin-modal-error {
|
||||
color: #dc3545;
|
||||
background-color: #f8d7da;
|
||||
|
||||
Reference in New Issue
Block a user