diff --git a/frontend/src/components/MatchReportApiDialog.vue b/frontend/src/components/MatchReportApiDialog.vue index 623dfef1..0c8c5e56 100755 --- a/frontend/src/components/MatchReportApiDialog.vue +++ b/frontend/src/components/MatchReportApiDialog.vue @@ -39,12 +39,48 @@ -
-
- Originaleingabe anzeigen -
{{ formatTestData(originalTestInput) }}
+
+
+ Originalbericht zum Vergleich +
+ Status: {{ getOriginalReportStatus() }} + Spielstand: {{ getOriginalReportScore() }} +
+
+ + + + + + + + + + + + + + + + + + + + + +
SpielHeimGastSätzeErgebnis
{{ row.matchNumber }}{{ row.homeName || '—' }}{{ row.guestName || '—' }}{{ row.setsText || '—' }}{{ row.result }} + +
+
+ Originaldaten anzeigen (technisch) +
{{ formatTestData(originalTestReport || originalTestInput) }}
+
+
Aktuellen Zustand anzeigen
{{ formatTestData(buildStandaloneTestPayload()) }}
@@ -805,6 +841,9 @@ export default { isTestMode: this.standaloneTest, testVerification: null, originalTestInput: null, + // Unveränderliche Referenz auf den offiziellen Bericht, bevor der + // Testmodus seinen vollständig leeren lokalen Zustand erzeugt. + originalTestReport: null, standaloneTestSystem: 'braunschweiger system', standaloneTestSystems: [ { value: 'paarkreuz-system', label: 'Paarkreuz-System (6 gegen 6)' }, @@ -1231,7 +1270,7 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr }); }, async pollLatestMeetingDetails() { - if (this.loading || this.isMeetingDetailsPolling) { + if (this.isTestMode || this.loading || this.isMeetingDetailsPolling) { return; } @@ -2571,6 +2610,7 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr this.submitSucceeded = false; this.initialCompletionState = false; this.prepareResults(); + this.captureOriginalTestReport(); }, resetStandaloneTest() { @@ -2595,6 +2635,93 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr return JSON.stringify(data || {}, null, 2); }, + captureOriginalTestReport() { + // JSON-Kopie schützt die Vergleichsansicht vor allen lokalen + // Änderungen, die der Testmodus anschließend am Bericht vornimmt. + this.originalTestReport = JSON.parse(JSON.stringify({ + meetingData: this.meetingData || {}, + meetingDetails: this.meetingDetails || {}, + results: this.results || [] + })); + }, + + getOriginalReportRows() { + const details = this.originalTestReport?.meetingDetails || {}; + const matches = Array.isArray(details.matches) ? details.matches : []; + + return matches.map((match, index) => { + const sets = Array.from({ length: 5 }, (_, setIndex) => { + const home = Number(match?.[`set${setIndex + 1}A`] || 0); + const guest = Number(match?.[`set${setIndex + 1}B`] || 0); + return home > 0 || guest > 0 ? `${home}:${guest}` : ''; + }); + const homeWins = sets.filter(set => { + const [home, guest] = set.split(':').map(Number); + return home > guest; + }).length; + const guestWins = sets.filter(set => { + const [home, guest] = set.split(':').map(Number); + return guest > home; + }).length; + const homeName = this.formatMeetingMatchSide(match, 'home') || match?.homePlayer || ''; + const guestName = this.formatMeetingMatchSide(match, 'guest') || match?.guestPlayer || ''; + + return { + matchNumber: match?.matchNr || index + 1, + index, + homeName, + guestName, + sets, + setsText: sets.filter(Boolean).join(', '), + hasSets: sets.some(Boolean), + result: homeWins || guestWins ? `${homeWins}:${guestWins}` : '—', + completed: match?.isCompleted === true || match?.matchesA === 1 || match?.matchesB === 1 || homeWins >= 3 || guestWins >= 3 + }; + }); + }, + + getOriginalReportStatus() { + return this.originalTestReport?.meetingDetails?.isCompleted || this.originalTestReport?.meetingData?.isCompleted + ? 'Abgeschlossen' + : 'Offen'; + }, + + getOriginalReportScore() { + const details = this.originalTestReport?.meetingDetails || {}; + const home = details.homeMatches ?? this.originalTestReport?.meetingData?.homeMatches; + const guest = details.guestMatches ?? this.originalTestReport?.meetingData?.guestMatches; + if (Number.isFinite(Number(home)) && Number.isFinite(Number(guest))) { + return `${home}:${guest}`; + } + + const calculatedScore = this.getOriginalReportRows().reduce((score, row) => { + if (!row.completed) return score; + const [home, guest] = row.result.split(':').map(Number); + if (home > guest) score.home += 1; + if (guest > home) score.guest += 1; + return score; + }, { home: 0, guest: 0 }); + return `${calculatedScore.home}:${calculatedScore.guest}`; + }, + + copyOriginalPairingResults(row) { + if (!row?.hasSets) return; + + const targetIndex = this.results.findIndex((_, index) => { + const formation = this.meetingDetails?.meetingPlayMode?.matchFormations?.[index]; + return Number(formation?.matchNr || index + 1) === Number(row.matchNumber); + }); + const localMatch = this.results[targetIndex >= 0 ? targetIndex : row.index]; + if (!localMatch) return; + + localMatch.sets = [...row.sets]; + localMatch.completed = row.completed; + localMatch.result = row.result === '—' ? '' : (row.result === '3:0' || row.result === '3:1' || row.result === '3:2' ? '1:0' : '0:1'); + this.calculateMatchResult(targetIndex >= 0 ? targetIndex : row.index); + this.errors[targetIndex >= 0 ? targetIndex : row.index] = null; + this.$forceUpdate(); + }, + async loadData() { this.loading = true; this.error = null; @@ -3204,12 +3331,42 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr this.isDraftSyncing = false; this.lastDraftSyncError = ''; - // Ein Test beginnt mit dem aktuellen offiziellen Stand, damit ein - // alter lokaler Entwurf nicht versehentlich getestet wird. + // Den offiziellen Stand nur als Quelle für Teams, Aufstellung und + // Spielsystem laden. Ergebnisse, Abschluss und Signaturen dürfen + // im Testmodus nie übernommen werden. await this.loadData(); - this.prepareResults(); - this.populateResultsFromMeetingDetails(); + this.resetTestModeState(); + }, + + async resetTestModeState() { + if (!this.meetingDetails || !this.meetingData) { + return; + } + + this.captureOriginalTestReport(); + + [this.meetingDetails, this.meetingData].forEach((data) => { + data.isCompleted = false; + data.wo = null; + data.endDate = null; + data.signature = {}; + }); + + // Die vom Server gespeicherten Ergebnisse komplett verwerfen. Die + // leeren Zeilen werden anschließend aus der Spielmatrix erzeugt. + this.meetingDetails.matches = []; + this.results = []; + this.teamNotAppeared = null; + this.isHomeLineupCertified = false; + this.isGuestLineupCertified = false; + this.isGreetingCompleted = false; + this.submitSucceeded = false; + this.initialCompletionState = false; this.testVerification = null; + this.activeSection = 'general'; + + await this.$nextTick(); + this.prepareResults(); }, /** @@ -5024,6 +5181,84 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr font-size: 11px; } +.original-report-comparison { + border-color: rgba(33, 110, 57, 0.38) !important; + background: #f7fbf8 !important; +} + +.comparison-summary { + display: flex; + flex-wrap: wrap; + gap: 8px 18px; + margin: 10px 0; + color: #355044; + font-size: 13px; +} + +.comparison-table-wrap { + overflow-x: auto; + border: 1px solid #dce8df; + border-radius: 6px; + background: #fff; +} + +.comparison-table { + width: 100%; + min-width: 620px; + border-collapse: collapse; + font-size: 12px; +} + +.comparison-table th, +.comparison-table td { + padding: 7px 8px; + border-bottom: 1px solid #edf2ee; + text-align: left; + vertical-align: middle; +} + +.comparison-table th { + color: #496052; + background: #edf6ef; + font-size: 11px; + letter-spacing: 0.02em; + text-transform: uppercase; +} + +.comparison-table tbody tr:last-child td { + border-bottom: 0; +} + +.comparison-sets { + color: #355044; + font-family: monospace; + white-space: nowrap; +} + +.copy-original-btn { + padding: 5px 8px; + border: 1px solid #4f8d66; + border-radius: 5px; + background: #fff; + color: #216e39; + cursor: pointer; + font: inherit; + font-weight: 600; +} + +.copy-original-btn:hover:not(:disabled), +.copy-original-btn:focus-visible:not(:disabled) { + border-color: #216e39; + background: #e8f4eb; + outline: none; +} + +.copy-original-btn:disabled { + border-color: #d5ddd7; + color: #8a958d; + cursor: not-allowed; +} + .test-verification { margin: -8px 0 16px; padding: 12px 14px;