feat(schedule): add match report simulator button and functionality
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 54s
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 54s
This commit is contained in:
@@ -20,7 +20,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label class="test-mode-control" :class="{ active: isTestMode }">
|
||||
<label v-if="!standaloneTest" class="test-mode-control" :class="{ active: isTestMode }">
|
||||
<input v-model="isTestMode" type="checkbox" @change="onTestModeChange" />
|
||||
<span>
|
||||
<strong>Testmodus</strong>
|
||||
@@ -28,6 +28,28 @@
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<div v-else class="standalone-test-banner">
|
||||
<span>🧪 Eigenständiger Simulator – keine Verbindung zu nuScore</span>
|
||||
<label>
|
||||
Spielsystem
|
||||
<select v-model="standaloneTestSystem" @change="initializeStandaloneTestData">
|
||||
<option v-for="system in standaloneTestSystems" :key="system.value" :value="system.value">{{ system.label }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<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>
|
||||
</details>
|
||||
<details>
|
||||
<summary>Aktuellen Zustand anzeigen</summary>
|
||||
<pre>{{ formatTestData(buildStandaloneTestPayload()) }}</pre>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<div v-if="isTestMode && (meetingDetails?.isCompleted || meetingData?.isCompleted)" class="test-verification">
|
||||
<button type="button" class="btn-secondary" @click="verifyCompletedMatchProjection">
|
||||
🧪 Gespeicherte Paarungen prüfen
|
||||
@@ -210,6 +232,18 @@
|
||||
:disabled="!canSelectDoublePosition(player, 'home', 2)">
|
||||
D2
|
||||
</button>
|
||||
<button v-if="meetingDetails.teamLineupDoublePositions.includes(3)" class="double-btn"
|
||||
:class="{ active: player.positionDouble === 3 }"
|
||||
@click="toggleDoublePosition(player, 'home', 3)"
|
||||
:disabled="!canSelectDoublePosition(player, 'home', 3)">
|
||||
D3
|
||||
</button>
|
||||
<button v-if="meetingDetails.teamLineupDoublePositions.includes(3)" class="double-btn"
|
||||
:class="{ active: player.positionDouble === 3 }"
|
||||
@click="toggleDoublePosition(player, 'home', 3)"
|
||||
:disabled="!canSelectDoublePosition(player, 'home', 3)">
|
||||
D3
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -304,6 +338,18 @@
|
||||
:disabled="!canSelectDoublePosition(player, 'guest', 2)">
|
||||
D2
|
||||
</button>
|
||||
<button v-if="meetingDetails.teamLineupDoublePositions.includes(3)" class="double-btn"
|
||||
:class="{ active: player.positionDouble === 3 }"
|
||||
@click="toggleDoublePosition(player, 'guest', 3)"
|
||||
:disabled="!canSelectDoublePosition(player, 'guest', 3)">
|
||||
D3
|
||||
</button>
|
||||
<button v-if="meetingDetails.teamLineupDoublePositions.includes(3)" class="double-btn"
|
||||
:class="{ active: player.positionDouble === 3 }"
|
||||
@click="toggleDoublePosition(player, 'guest', 3)"
|
||||
:disabled="!canSelectDoublePosition(player, 'guest', 3)">
|
||||
D3
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -732,14 +778,28 @@ export default {
|
||||
match: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
standaloneTest: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
error: null,
|
||||
isTestMode: false,
|
||||
isTestMode: this.standaloneTest,
|
||||
testVerification: null,
|
||||
originalTestInput: null,
|
||||
standaloneTestSystem: 'braunschweiger system',
|
||||
standaloneTestSystems: [
|
||||
{ value: 'paarkreuz-system', label: 'Paarkreuz-System (6 gegen 6)' },
|
||||
{ value: 'bundessystem', label: 'Bundessystem (4 gegen 4)' },
|
||||
{ value: 'werner-scheffler-system', label: 'Werner-Scheffler-System (4 gegen 4)' },
|
||||
{ value: 'braunschweiger system', label: 'Braunschweiger System (4 gegen 3)' },
|
||||
{ value: 'modifiziertes swaythling-cup-system', label: 'Modifiziertes Swaythling-Cup-System (3 gegen 3)' },
|
||||
{ value: 'corbillon-cup-system', label: 'Corbillon-Cup-System (2 gegen 2)' }
|
||||
],
|
||||
meetingData: null,
|
||||
meetingDetails: null,
|
||||
activeSection: 'general',
|
||||
@@ -972,6 +1032,11 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
if (this.standaloneTest) {
|
||||
this.initializeStandaloneTestData();
|
||||
this.initializeResults();
|
||||
return;
|
||||
}
|
||||
await this.loadData();
|
||||
this.initializeResults();
|
||||
this.initializeFinalPins();
|
||||
@@ -1779,6 +1844,11 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
console.log('🚀 Starte Spielbericht-Absendung...');
|
||||
|
||||
if (this.isTestMode) {
|
||||
if (this.standaloneTest) {
|
||||
this.submitSucceeded = true;
|
||||
this.meetingData.isCompleted = true;
|
||||
this.meetingDetails.isCompleted = true;
|
||||
}
|
||||
alert('🧪 Testmodus: Der Spielbericht wurde nur lokal geprüft und nicht an nuScore übermittelt.');
|
||||
return;
|
||||
}
|
||||
@@ -2410,6 +2480,102 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
});
|
||||
},
|
||||
|
||||
createStandaloneTestPlayer(id, firstname, lastname, rank, positionSingle = null, positionDouble = null) {
|
||||
const positions = [];
|
||||
if (positionSingle) positions.push(`E${positionSingle}`);
|
||||
if (positionDouble) positions.push(`D${positionDouble}`);
|
||||
return { nuLigaPersonId: id, firstname, lastname, rank, isSelected: true, positionSingle, positionDouble, positions };
|
||||
},
|
||||
|
||||
initializeStandaloneTestData() {
|
||||
const configs = {
|
||||
'paarkreuz-system': { home: 6, guest: 6, doubles: 3 },
|
||||
bundessystem: { home: 4, guest: 4, doubles: 2 },
|
||||
'werner-scheffler-system': { home: 4, guest: 4, doubles: 2 },
|
||||
'braunschweiger system': { home: 4, guest: 3, doubles: 2 },
|
||||
'modifiziertes swaythling-cup-system': { home: 3, guest: 3, doubles: 1 },
|
||||
'corbillon-cup-system': { home: 2, guest: 2, doubles: 1 }
|
||||
};
|
||||
const config = configs[this.standaloneTestSystem] || configs['braunschweiger system'];
|
||||
const matrixConfig = this.playModeMatrices[this.standaloneTestSystem];
|
||||
const rawMatrix = this.standaloneTestSystem === 'braunschweiger system'
|
||||
? matrixConfig.matrices['4-3']
|
||||
: matrixConfig;
|
||||
const formations = rawMatrix.map((label, index) => {
|
||||
const [homeToken, guestToken] = label.split('–').map(token => token.trim());
|
||||
const toNuScorePosition = token => token.replace(/^DA/, 'D').replace(/^DB/, 'D').replace(/^A/, 'E').replace(/^B/, 'E');
|
||||
const homeLineupPosition = toNuScorePosition(homeToken);
|
||||
const guestLineupPosition = toNuScorePosition(guestToken);
|
||||
return {
|
||||
matchNr: index + 1,
|
||||
displayString: `${homeLineupPosition}-${guestLineupPosition}`,
|
||||
homeLineupPosition,
|
||||
guestLineupPosition
|
||||
};
|
||||
});
|
||||
const makePlayers = (baseId, side, count) => Array.from({ length: count }, (_, index) => {
|
||||
const rank = index + 1;
|
||||
const doublePosition = rank <= config.doubles * 2 ? Math.ceil(rank / 2) : null;
|
||||
return this.createStandaloneTestPlayer(baseId + rank, side, ['Eins', 'Zwei', 'Drei', 'Vier', 'Fünf', 'Sechs'][index], rank, rank, doublePosition);
|
||||
});
|
||||
const homePlayers = makePlayers(0, 'Heim', config.home);
|
||||
const guestPlayers = makePlayers(100, 'Gast', config.guest);
|
||||
const matches = formations.map((formation) => ({
|
||||
matchNr: formation.matchNr,
|
||||
name: formation.displayString,
|
||||
matchType: formation.homeLineupPosition.startsWith('D') ? 'DOUBLE' : 'SINGLE',
|
||||
set1A: 0, set1B: 0, set2A: 0, set2B: 0, set3A: 0, set3B: 0, set4A: 0, set4B: 0, set5A: 0, set5B: 0,
|
||||
matchesA: 0, matchesB: 0, setsA: 0, setsB: 0, isCompleted: false
|
||||
}));
|
||||
this.meetingData = {
|
||||
scheduleDate: new Date().toISOString(), homeTeamname: 'Test Heim', guestTeamname: 'Test Gast',
|
||||
homeClub: 'Testverein Heim', guestClub: 'Testverein Gast', championship: 'Testwettbewerb',
|
||||
league: 'Testliga', group: '', playMode: this.standaloneTestSystem, playThrough: 'ALLGAMES', isCompleted: false
|
||||
};
|
||||
this.meetingDetails = {
|
||||
...this.meetingData,
|
||||
playerCountHome: config.home, playerCountGuest: config.guest, signature: {},
|
||||
meetingPlayMode: { matchFormations: formations, singleFormationUpTo: config.home },
|
||||
optionalPlayModes: [], matches,
|
||||
teamLineupHomePlayers: homePlayers, teamLineupGuestPlayers: guestPlayers,
|
||||
teamLineupSinglePositions: Array.from({ length: Math.max(config.home, config.guest) }, (_, index) => index + 1),
|
||||
teamLineupDoublePositions: Array.from({ length: config.doubles }, (_, index) => index + 1)
|
||||
};
|
||||
this.originalTestInput = JSON.parse(JSON.stringify(this.meetingDetails));
|
||||
this.homePin = 'test';
|
||||
this.guestPin = 'test';
|
||||
this.finalHomePin = 'test';
|
||||
this.finalGuestPin = 'test';
|
||||
this.isHomeLineupCertified = false;
|
||||
this.isGuestLineupCertified = false;
|
||||
this.isGreetingCompleted = false;
|
||||
this.submitSucceeded = false;
|
||||
this.initialCompletionState = false;
|
||||
this.prepareResults();
|
||||
},
|
||||
|
||||
resetStandaloneTest() {
|
||||
this.initializeStandaloneTestData();
|
||||
},
|
||||
|
||||
buildStandaloneTestPayload() {
|
||||
if (!this.standaloneTest) return null;
|
||||
return {
|
||||
playerCountHome: this.getSinglePlayerCount('home'),
|
||||
playerCountGuest: this.getSinglePlayerCount('guest'),
|
||||
teamLineupHomePlayers: this.meetingDetails?.teamLineupHomePlayers || [],
|
||||
teamLineupGuestPlayers: this.meetingDetails?.teamLineupGuestPlayers || [],
|
||||
meetingPlayMode: this.meetingDetails?.meetingPlayMode,
|
||||
signature: this.meetingDetails?.signature,
|
||||
isGreetingCompleted: this.isGreetingCompleted,
|
||||
matches: this.results
|
||||
};
|
||||
},
|
||||
|
||||
formatTestData(data) {
|
||||
return JSON.stringify(data || {}, null, 2);
|
||||
},
|
||||
|
||||
async loadData() {
|
||||
this.loading = true;
|
||||
this.error = null;
|
||||
@@ -2525,10 +2691,11 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
? this.meetingDetails.matches
|
||||
: [];
|
||||
if (serverMatches.length > 0) {
|
||||
const matrix = this.currentPlayModeMatrix;
|
||||
this.results = serverMatches.map((match, index) => ({
|
||||
label: (typeof match?.name === 'string' && match.name.trim()) || `Spiel ${index + 1}`,
|
||||
homeName: this.formatMeetingMatchSide(match, 'home') || match?.homePlayer || '',
|
||||
guestName: this.formatMeetingMatchSide(match, 'guest') || match?.guestPlayer || '',
|
||||
label: (typeof match?.name === 'string' && match.name.trim()) || matrix[index] || `Spiel ${index + 1}`,
|
||||
homeName: this.formatMeetingMatchSide(match, 'home') || match?.homePlayer || this.resolveSide(matrix[index] || '', 'home'),
|
||||
guestName: this.formatMeetingMatchSide(match, 'guest') || match?.guestPlayer || this.resolveSide(matrix[index] || '', 'guest'),
|
||||
sets: ['', '', '', '', ''],
|
||||
completed: false,
|
||||
result: '1:0'
|
||||
@@ -4188,6 +4355,11 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
async validatePin(team, pin) {
|
||||
if (!pin || !this.meetingDetails) return false;
|
||||
|
||||
if (this.standaloneTest) {
|
||||
this.showPinValidation(team, true, 'Testmodus');
|
||||
return true;
|
||||
}
|
||||
|
||||
const expectedHash = team === 'home' ? this.meetingDetails.homePin : this.meetingDetails.guestPin;
|
||||
|
||||
|
||||
@@ -4768,6 +4940,49 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.standalone-test-banner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin: 0 0 12px;
|
||||
padding: 10px 14px;
|
||||
border: 1px solid #d78a00;
|
||||
border-radius: 10px;
|
||||
background: #fff3cd;
|
||||
color: #795300;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.test-data-inspector {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
margin: 0 0 16px;
|
||||
}
|
||||
|
||||
.test-data-inspector details {
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
padding: 8px 10px;
|
||||
background: var(--surface-muted);
|
||||
}
|
||||
|
||||
.test-data-inspector summary {
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.test-data-inspector pre {
|
||||
max-height: 260px;
|
||||
overflow: auto;
|
||||
margin: 10px 0 0;
|
||||
padding: 10px;
|
||||
border-radius: 6px;
|
||||
background: #17202a;
|
||||
color: #f8f9f9;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.test-verification {
|
||||
margin: -8px 0 16px;
|
||||
padding: 12px 14px;
|
||||
|
||||
@@ -30,6 +30,14 @@
|
||||
>
|
||||
{{ $t('schedule.addMatch') }}
|
||||
</button>
|
||||
<button
|
||||
v-if="showScheduleActions"
|
||||
type="button"
|
||||
class="btn-secondary schedule-add-match-button"
|
||||
@click="$emit('open-match-report-simulator')"
|
||||
>
|
||||
🧪 Spielbericht simulieren
|
||||
</button>
|
||||
<button v-if="showFriendlyActions" @click="$emit('open-friendly-match-modal')" class="btn-secondary">Freundschaftsspiel</button>
|
||||
<button
|
||||
v-if="showGalleryButton"
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
@season-change="onSeasonChange"
|
||||
@open-import-modal="openImportModal"
|
||||
@open-manual-match-modal="openManualMatchDialog"
|
||||
@open-match-report-simulator="openMatchReportSimulator"
|
||||
@open-friendly-match-modal="openFriendlyMatchDialog"
|
||||
@open-gallery-dialog="openGalleryDialog"
|
||||
@update:team-search-query="teamSearchQuery = $event"
|
||||
@@ -2863,6 +2864,17 @@ export default {
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
openMatchReportSimulator() {
|
||||
this.openDialog({
|
||||
title: 'Spielbericht simulieren',
|
||||
component: 'MatchReportApiDialog',
|
||||
props: {
|
||||
match: { startDate: new Date(), endDate: null, matches: [] },
|
||||
standaloneTest: true
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
async loadLeagueTable(leagueId) {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user