feat(ScheduleView): enhance player selection dialog with saving state management
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 59s
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 59s
This commit is contained in:
@@ -1088,12 +1088,6 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
|
||||
// Spezielle Behandlung für Braunschweiger System
|
||||
if (matrixConfig && typeof matrixConfig === 'object' && matrixConfig.matrices) {
|
||||
const serverMatrix = this.getBraunschweigerServerMatrix();
|
||||
if (Array.isArray(serverMatrix) && serverMatrix.length > 0) {
|
||||
console.log('🎯 Braunschweiger System: verwende Server-Matrix', serverMatrix);
|
||||
return serverMatrix;
|
||||
}
|
||||
|
||||
const homePlayerCount = this.getEffectivePlayerCount('home');
|
||||
const guestPlayerCount = this.getEffectivePlayerCount('guest');
|
||||
|
||||
@@ -1103,8 +1097,22 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
console.log(`🎯 Braunschweiger System: ${homePlayerCount} vs ${guestPlayerCount} → Key: ${playerCountKey}`);
|
||||
this.logBraunschweigerDebugSnapshot(`Matrix-Auswahl ${playerCountKey}`);
|
||||
|
||||
// Verwende spezifische Matrix oder Fallback
|
||||
return matrixConfig.matrices[playerCountKey] || matrixConfig.default || [];
|
||||
// Die Matrix muss sich immer nach der aktuell gewählten
|
||||
// Aufstellung richten. NuScore liefert hier gelegentlich noch
|
||||
// die 4:4-Formation, obwohl eine Mannschaft nur zu zweit
|
||||
// antritt. Zwei Spieler werden dabei regelkonform als
|
||||
// 3er-Konstellation behandelt: 4:2 -> 4:3, 2:4 -> 3:4,
|
||||
// 3:2/2:3/2:2 -> 3:3. Die dadurch fehlenden Einzel werden
|
||||
// anschließend automatisch als Nichtantreten gewertet.
|
||||
const localMatrix = matrixConfig.matrices[playerCountKey];
|
||||
if (Array.isArray(localMatrix) && localMatrix.length > 0) {
|
||||
return localMatrix;
|
||||
}
|
||||
|
||||
// Nur für bislang unbekannte Konstellationen auf die
|
||||
// Serverformation ausweichen.
|
||||
const serverMatrix = this.getBraunschweigerServerMatrix();
|
||||
return serverMatrix || matrixConfig.default || [];
|
||||
}
|
||||
|
||||
// Standard-Verhalten für andere Systeme
|
||||
@@ -2426,6 +2434,11 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
// (etwa 5:3) umstellen.
|
||||
matchData.playerCountHome = this.getSinglePlayerCount('home');
|
||||
matchData.playerCountGuest = this.getSinglePlayerCount('guest');
|
||||
|
||||
// Die Begegnungsreihenfolge gehört zum Aufstellungsstand.
|
||||
// Insbesondere bei 4:2 darf kein veralteter 4:4-Entwurf mit
|
||||
// einem zweiten Doppel an NuScore zurückgehen.
|
||||
this.applyBraunschweigerMatchFormations(matchData);
|
||||
|
||||
// 1.1 Lokalen Aufstellungszustand in den Payload übernehmen
|
||||
this.applyCurrentLineupStateToPayload(matchData);
|
||||
@@ -3572,9 +3585,9 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
this.isDraftSyncing = false;
|
||||
this.lastDraftSyncError = '';
|
||||
|
||||
// Den offiziellen Stand nur als Quelle für Teams, Aufstellung und
|
||||
// Spielsystem laden. Ergebnisse, Abschluss und Signaturen dürfen
|
||||
// im Testmodus nie übernommen werden.
|
||||
// Den offiziellen Stand nur als Quelle für Teams, Spielerliste
|
||||
// und Spielsystem laden. Der Testmodus startet anschließend mit
|
||||
// einer vollständig leeren lokalen Eingabe.
|
||||
await this.loadData();
|
||||
this.resetTestModeState();
|
||||
},
|
||||
@@ -3593,11 +3606,31 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
data.signature = {};
|
||||
});
|
||||
|
||||
// Spieler bleiben als auswählbare Liste erhalten; ihre
|
||||
// Aufstellung (Einzel und Doppel) beginnt im Testmodus dagegen
|
||||
// immer leer. Damit lässt sich ein Spiel wirklich von vorne
|
||||
// durchspielen, ohne Daten an NuScore zu verändern.
|
||||
['teamLineupHomePlayers', 'teamLineupGuestPlayers'].forEach((teamKey) => {
|
||||
(this.meetingDetails[teamKey] || []).forEach((player) => {
|
||||
player.isSelected = false;
|
||||
player.positionSingle = null;
|
||||
player.positionDouble = null;
|
||||
player.positions = [];
|
||||
});
|
||||
});
|
||||
|
||||
// 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.homePin = '';
|
||||
this.guestPin = '';
|
||||
this.finalHomePin = '';
|
||||
this.finalGuestPin = '';
|
||||
this.protestText = '';
|
||||
this.match.startDate = null;
|
||||
this.match.endDate = null;
|
||||
this.isHomeLineupCertified = false;
|
||||
this.isGuestLineupCertified = false;
|
||||
this.isGreetingCompleted = false;
|
||||
@@ -5027,6 +5060,38 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
|
||||
return trimmedPosition;
|
||||
},
|
||||
applyBraunschweigerMatchFormations(target) {
|
||||
if (!target || !String(this.getPlayMode() || '').toLowerCase().includes('braunschweiger')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const formations = (this.currentPlayModeMatrix || []).map((label, index) => {
|
||||
const [homeToken = '', guestToken = ''] = String(label).split('–').map(token => token.trim());
|
||||
const toNuScorePosition = (token) => token
|
||||
.replace(/^DA/i, 'D')
|
||||
.replace(/^DB/i, 'D')
|
||||
.replace(/^A/i, 'E')
|
||||
.replace(/^B/i, 'E');
|
||||
const homeLineupPosition = toNuScorePosition(homeToken);
|
||||
const guestLineupPosition = toNuScorePosition(guestToken);
|
||||
|
||||
return {
|
||||
matchNr: index + 1,
|
||||
displayString: `${homeLineupPosition}-${guestLineupPosition}`,
|
||||
homeLineupPosition,
|
||||
guestLineupPosition
|
||||
};
|
||||
});
|
||||
|
||||
if (formations.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
target.meetingPlayMode = {
|
||||
...(target.meetingPlayMode && typeof target.meetingPlayMode === 'object' ? target.meetingPlayMode : {}),
|
||||
matchFormations: formations
|
||||
};
|
||||
},
|
||||
getBraunschweigerServerMatrix() {
|
||||
const mapFormationsToLabels = (formations) => {
|
||||
if (!Array.isArray(formations) || formations.length === 0) {
|
||||
|
||||
@@ -397,7 +397,7 @@
|
||||
<td v-if="!playerSelectionDialog.match?.isFriendly" class="checkbox-cell">
|
||||
<select
|
||||
:value="playerResponseValue(member)"
|
||||
:disabled="playerSelectionDialog.readonly"
|
||||
:disabled="playerSelectionDialog.readonly || playerSelectionDialog.saving"
|
||||
@change="setPlayerResponse(member, $event.target.value)"
|
||||
>
|
||||
<option value="">Keine Auswahl</option>
|
||||
@@ -409,7 +409,7 @@
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="member.isReady"
|
||||
:disabled="playerSelectionDialog.readonly || member.isCancelled"
|
||||
:disabled="playerSelectionDialog.readonly || playerSelectionDialog.saving || member.isCancelled"
|
||||
@change="togglePlayerReady(member)"
|
||||
/>
|
||||
</td>
|
||||
@@ -417,7 +417,7 @@
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="member.isPlanned"
|
||||
:disabled="playerSelectionDialog.readonly || member.isCancelled || !member.isReady"
|
||||
:disabled="playerSelectionDialog.readonly || playerSelectionDialog.saving || member.isCancelled || !member.isReady"
|
||||
@change="togglePlayerPlanned(member)"
|
||||
/>
|
||||
</td>
|
||||
@@ -425,7 +425,7 @@
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="member.hasPlayed"
|
||||
:disabled="playerSelectionDialog.readonly || member.isCancelled || !member.isReady || !member.isPlanned"
|
||||
:disabled="playerSelectionDialog.readonly || playerSelectionDialog.saving || member.isCancelled || !member.isReady || !member.isPlanned"
|
||||
@change="togglePlayerPlayed(member)"
|
||||
/>
|
||||
</td>
|
||||
@@ -440,8 +440,7 @@
|
||||
</div>
|
||||
|
||||
<div class="dialog-actions">
|
||||
<button v-if="!playerSelectionDialog.readonly" @click="savePlayerSelection" class="btn-save">{{ $t('schedule.save') }}</button>
|
||||
<button @click="closePlayerSelectionDialog" class="btn-cancel">{{ $t('schedule.cancel') }}</button>
|
||||
<button @click="closePlayerSelectionDialog" class="btn-cancel">{{ $t('common.close') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</BaseDialog>
|
||||
@@ -941,6 +940,7 @@ export default {
|
||||
match: null,
|
||||
members: [],
|
||||
loading: false,
|
||||
saving: false,
|
||||
readonly: false
|
||||
},
|
||||
matchObservationDialog: { isOpen: false, member: null, notes: [] },
|
||||
@@ -1364,6 +1364,7 @@ export default {
|
||||
this.playerSelectionDialog.readonly = this.isFriendlyMatchReadOnly(match);
|
||||
this.playerSelectionDialog.isOpen = true;
|
||||
this.playerSelectionDialog.loading = true;
|
||||
this.playerSelectionDialog.saving = false;
|
||||
|
||||
try {
|
||||
const normalizePlayersList = (value) => {
|
||||
@@ -1500,6 +1501,7 @@ export default {
|
||||
this.playerSelectionDialog.isOpen = false;
|
||||
this.playerSelectionDialog.match = null;
|
||||
this.playerSelectionDialog.members = [];
|
||||
this.playerSelectionDialog.saving = false;
|
||||
},
|
||||
async openMatchObservation(member) {
|
||||
if (!member?.id) return;
|
||||
@@ -1548,36 +1550,40 @@ export default {
|
||||
this.locationDialog.match = null;
|
||||
},
|
||||
|
||||
togglePlayerReady(member) {
|
||||
async togglePlayerReady(member) {
|
||||
member.isReady = !member.isReady;
|
||||
await this.savePlayerSelection(false);
|
||||
},
|
||||
playerResponseValue(member) {
|
||||
if (member.isCancelled) return 'cancelled';
|
||||
return member.isReady ? 'ready' : '';
|
||||
},
|
||||
setPlayerResponse(member, value) {
|
||||
async setPlayerResponse(member, value) {
|
||||
member.isReady = value === 'ready';
|
||||
member.isCancelled = value === 'cancelled';
|
||||
if (!member.isReady) {
|
||||
member.isPlanned = false;
|
||||
member.hasPlayed = false;
|
||||
}
|
||||
await this.savePlayerSelection(false);
|
||||
},
|
||||
|
||||
togglePlayerPlanned(member) {
|
||||
async togglePlayerPlanned(member) {
|
||||
member.isPlanned = !member.isPlanned;
|
||||
if (!member.isPlanned) {
|
||||
member.hasPlayed = false;
|
||||
}
|
||||
await this.savePlayerSelection(false);
|
||||
},
|
||||
|
||||
togglePlayerPlayed(member) {
|
||||
async togglePlayerPlayed(member) {
|
||||
member.hasPlayed = !member.hasPlayed;
|
||||
await this.savePlayerSelection(false);
|
||||
},
|
||||
|
||||
async savePlayerSelection(closeDialog = true) {
|
||||
const match = this.playerSelectionDialog.match;
|
||||
if (!match || this.isFriendlyMatchReadOnly(match)) return;
|
||||
if (!match || this.isFriendlyMatchReadOnly(match) || this.playerSelectionDialog.saving) return;
|
||||
|
||||
const normalizePlayersList = (value) => {
|
||||
if (Array.isArray(value)) return value.map((id) => Number(id)).filter((id) => Number.isFinite(id));
|
||||
@@ -1606,6 +1612,7 @@ export default {
|
||||
console.log('[savePlayerSelection] Saving players:', { playersReady, playersPlanned, playersPlayed, playersCancelled, matchId: match.id });
|
||||
|
||||
try {
|
||||
this.playerSelectionDialog.saving = true;
|
||||
const response = match.isFriendly
|
||||
? await apiClient.patch(`${match.isSharedFriendly ? `/friendly-matches/shared/${this.currentClub}/${match.id}/players` : `/friendly-matches/${this.currentClub}/${match.id}/players`}`, {
|
||||
playersReady,
|
||||
@@ -1646,6 +1653,8 @@ export default {
|
||||
} catch (error) {
|
||||
console.error('Error saving player selection:', error);
|
||||
await this.showInfo(this.$t('messages.error'), this.$t('schedule.errorSavingPlayerSelection'), '', 'error');
|
||||
} finally {
|
||||
this.playerSelectionDialog.saving = false;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user