feat(match-report): implement original report comparison and enhance test mode functionality
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 55s
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 55s
This commit is contained in:
@@ -39,12 +39,48 @@
|
||||
<button type="button" class="btn-secondary" @click="resetStandaloneTest">Originaleingabe zurücksetzen</button>
|
||||
</div>
|
||||
|
||||
<div v-if="standaloneTest" class="test-data-inspector">
|
||||
<details>
|
||||
<summary>Originaleingabe anzeigen</summary>
|
||||
<pre>{{ formatTestData(originalTestInput) }}</pre>
|
||||
<div v-if="isTestMode" class="test-data-inspector">
|
||||
<details v-if="originalTestReport" class="original-report-comparison">
|
||||
<summary>Originalbericht zum Vergleich</summary>
|
||||
<div class="comparison-summary">
|
||||
<span><strong>Status:</strong> {{ getOriginalReportStatus() }}</span>
|
||||
<span><strong>Spielstand:</strong> {{ getOriginalReportScore() }}</span>
|
||||
</div>
|
||||
<div class="comparison-table-wrap">
|
||||
<table class="comparison-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Spiel</th>
|
||||
<th>Heim</th>
|
||||
<th>Gast</th>
|
||||
<th>Sätze</th>
|
||||
<th>Ergebnis</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="row in getOriginalReportRows()" :key="`original-match-${row.matchNumber}`">
|
||||
<td>{{ row.matchNumber }}</td>
|
||||
<td>{{ row.homeName || '—' }}</td>
|
||||
<td>{{ row.guestName || '—' }}</td>
|
||||
<td class="comparison-sets">{{ row.setsText || '—' }}</td>
|
||||
<td>{{ row.result }}</td>
|
||||
<td>
|
||||
<button type="button" class="copy-original-btn" :disabled="!row.hasSets"
|
||||
@click="copyOriginalPairingResults(row)">
|
||||
Übernehmen
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</details>
|
||||
<details>
|
||||
<summary>Originaldaten anzeigen (technisch)</summary>
|
||||
<pre>{{ formatTestData(originalTestReport || originalTestInput) }}</pre>
|
||||
</details>
|
||||
<details v-if="standaloneTest">
|
||||
<summary>Aktuellen Zustand anzeigen</summary>
|
||||
<pre>{{ formatTestData(buildStandaloneTestPayload()) }}</pre>
|
||||
</details>
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user