feat(MatchReportApiDialog): refactor double position selection for players and enhance result table layout
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 58s
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 58s
This commit is contained in:
@@ -307,29 +307,12 @@
|
||||
<div class="player-action">👆 {{ $t('matchReportApi.clickToRemove') }}</div>
|
||||
</button>
|
||||
<div class="double-selection">
|
||||
<button class="double-btn d1-btn"
|
||||
:class="{ active: player.positionDouble === 1 }"
|
||||
@click="toggleDoublePosition(player, 'home', 1)"
|
||||
:disabled="!canSelectDoublePosition(player, 'home', 1)">
|
||||
D1
|
||||
</button>
|
||||
<button class="double-btn d2-btn"
|
||||
:class="{ active: player.positionDouble === 2 }"
|
||||
@click="toggleDoublePosition(player, 'home', 2)"
|
||||
: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 v-for="doublePosition in getDoublePositions()" :key="`home-${player.nuLigaPersonId}-${doublePosition}`"
|
||||
class="double-btn"
|
||||
:class="{ active: player.positionDouble === doublePosition }"
|
||||
@click="toggleDoublePosition(player, 'home', doublePosition)"
|
||||
:disabled="!canSelectDoublePosition(player, 'home', doublePosition)">
|
||||
D{{ doublePosition }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -355,17 +338,12 @@
|
||||
<div class="player-action">👆 Klicken zum Hinzufügen</div>
|
||||
</button>
|
||||
<div class="double-selection">
|
||||
<button class="double-btn d1-btn"
|
||||
:class="{ active: player.positionDouble === 1 }"
|
||||
@click="toggleDoublePosition(player, 'home', 1)"
|
||||
:disabled="!canSelectDoublePosition(player, 'home', 1)">
|
||||
D1
|
||||
</button>
|
||||
<button class="double-btn d2-btn"
|
||||
:class="{ active: player.positionDouble === 2 }"
|
||||
@click="toggleDoublePosition(player, 'home', 2)"
|
||||
:disabled="!canSelectDoublePosition(player, 'home', 2)">
|
||||
D2
|
||||
<button v-for="doublePosition in getDoublePositions()" :key="`home-${player.nuLigaPersonId}-${doublePosition}`"
|
||||
class="double-btn"
|
||||
:class="{ active: player.positionDouble === doublePosition }"
|
||||
@click="toggleDoublePosition(player, 'home', doublePosition)"
|
||||
:disabled="!canSelectDoublePosition(player, 'home', doublePosition)">
|
||||
D{{ doublePosition }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -426,29 +404,12 @@
|
||||
<div class="player-action">👆 {{ $t('matchReportApi.clickToRemove') }}</div>
|
||||
</button>
|
||||
<div class="double-selection">
|
||||
<button class="double-btn d1-btn"
|
||||
:class="{ active: player.positionDouble === 1 }"
|
||||
@click="toggleDoublePosition(player, 'guest', 1)"
|
||||
:disabled="!canSelectDoublePosition(player, 'guest', 1)">
|
||||
D1
|
||||
</button>
|
||||
<button class="double-btn d2-btn"
|
||||
:class="{ active: player.positionDouble === 2 }"
|
||||
@click="toggleDoublePosition(player, 'guest', 2)"
|
||||
: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 v-for="doublePosition in getDoublePositions()" :key="`guest-${player.nuLigaPersonId}-${doublePosition}`"
|
||||
class="double-btn"
|
||||
:class="{ active: player.positionDouble === doublePosition }"
|
||||
@click="toggleDoublePosition(player, 'guest', doublePosition)"
|
||||
:disabled="!canSelectDoublePosition(player, 'guest', doublePosition)">
|
||||
D{{ doublePosition }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -474,17 +435,12 @@
|
||||
<div class="player-action">👆 Klicken zum Hinzufügen</div>
|
||||
</button>
|
||||
<div class="double-selection">
|
||||
<button class="double-btn d1-btn"
|
||||
:class="{ active: player.positionDouble === 1 }"
|
||||
@click="toggleDoublePosition(player, 'guest', 1)"
|
||||
:disabled="!canSelectDoublePosition(player, 'guest', 1)">
|
||||
D1
|
||||
</button>
|
||||
<button class="double-btn d2-btn"
|
||||
:class="{ active: player.positionDouble === 2 }"
|
||||
@click="toggleDoublePosition(player, 'guest', 2)"
|
||||
:disabled="!canSelectDoublePosition(player, 'guest', 2)">
|
||||
D2
|
||||
<button v-for="doublePosition in getDoublePositions()" :key="`guest-${player.nuLigaPersonId}-${doublePosition}`"
|
||||
class="double-btn"
|
||||
:class="{ active: player.positionDouble === doublePosition }"
|
||||
@click="toggleDoublePosition(player, 'guest', doublePosition)"
|
||||
:disabled="!canSelectDoublePosition(player, 'guest', doublePosition)">
|
||||
D{{ doublePosition }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -537,64 +493,66 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="result-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Heim</th>
|
||||
<th>Gast</th>
|
||||
<th>Satz 1</th>
|
||||
<th>Satz 2</th>
|
||||
<th>Satz 3</th>
|
||||
<th>Satz 4</th>
|
||||
<th>Satz 5</th>
|
||||
<th>Status</th>
|
||||
<th>Ergebnis</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-for="(m, idx) in results" :key="idx">
|
||||
<div class="result-table-wrap" tabindex="0" aria-label="Ergebnistabelle horizontal scrollen">
|
||||
<table class="result-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>{{ idx + 1 }}</td>
|
||||
<td>{{ displayMatchSide(m.homeName) }}</td>
|
||||
<td>{{ displayMatchSide(m.guestName) }}</td>
|
||||
<td v-for="(s, sIdx) in m.sets" :key="sIdx" class="set-cell">
|
||||
<div class="set-input-wrapper">
|
||||
<input
|
||||
class="set-input"
|
||||
:class="{ 'gap-warning': shouldHighlightSetInput(idx, sIdx), 'keyboard-open': isSetKeyboardOpen(idx, sIdx) }"
|
||||
v-model="m.sets[sIdx]"
|
||||
placeholder=":"
|
||||
:readonly="true"
|
||||
:disabled="isSetInputDisabled(idx, sIdx)"
|
||||
@click="openSetKeyboard(idx, sIdx)"
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
<td class="state-cell">
|
||||
<div class="state-content">
|
||||
<button v-if="!viewOnly && !m.completed && canAutoFillAbsentMatch(m)"
|
||||
class="state-btn absent-player-btn"
|
||||
@click="autoFillAbsentMatch(idx)"
|
||||
title="Nicht anwesend: Begegnung automatisch werten">⚡</button>
|
||||
<button v-if="!viewOnly && !m.completed" class="state-btn" @click="completeMatch(idx)" title="Match abschließen">✔</button>
|
||||
<button v-else-if="!viewOnly" class="state-btn" @click="reopenMatch(idx)" title="Match wieder öffnen">✎</button>
|
||||
<div class="set-ratio">{{ getSetRatio(m) }}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="result-cell">
|
||||
<div class="match-result">{{ getMatchResult(m) }}</div>
|
||||
</td>
|
||||
<th>#</th>
|
||||
<th>Heim</th>
|
||||
<th>Gast</th>
|
||||
<th>Satz 1</th>
|
||||
<th>Satz 2</th>
|
||||
<th>Satz 3</th>
|
||||
<th>Satz 4</th>
|
||||
<th>Satz 5</th>
|
||||
<th>Status</th>
|
||||
<th>Ergebnis</th>
|
||||
</tr>
|
||||
<!-- Fehlermeldung für diese Match-Zeile -->
|
||||
<tr v-if="getMatchError(idx)" class="error-row">
|
||||
<td colspan="10" class="error-cell">
|
||||
<div class="error-message">{{ getMatchError(idx) }}</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-for="(m, idx) in results" :key="idx">
|
||||
<tr>
|
||||
<td>{{ idx + 1 }}</td>
|
||||
<td>{{ displayMatchSide(m.homeName) }}</td>
|
||||
<td>{{ displayMatchSide(m.guestName) }}</td>
|
||||
<td v-for="(s, sIdx) in m.sets" :key="sIdx" class="set-cell">
|
||||
<div class="set-input-wrapper">
|
||||
<input
|
||||
class="set-input"
|
||||
:class="{ 'gap-warning': shouldHighlightSetInput(idx, sIdx), 'keyboard-open': isSetKeyboardOpen(idx, sIdx) }"
|
||||
v-model="m.sets[sIdx]"
|
||||
placeholder=":"
|
||||
:readonly="true"
|
||||
:disabled="isSetInputDisabled(idx, sIdx)"
|
||||
@click="openSetKeyboard(idx, sIdx)"
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
<td class="state-cell">
|
||||
<div class="state-content">
|
||||
<button v-if="!viewOnly && !m.completed && canAutoFillAbsentMatch(m)"
|
||||
class="state-btn absent-player-btn"
|
||||
@click="autoFillAbsentMatch(idx)"
|
||||
title="Nicht anwesend: Begegnung automatisch werten">⚡</button>
|
||||
<button v-if="!viewOnly && !m.completed" class="state-btn" @click="completeMatch(idx)" title="Match abschließen">✔</button>
|
||||
<button v-else-if="!viewOnly" class="state-btn" @click="reopenMatch(idx)" title="Match wieder öffnen">✎</button>
|
||||
<div class="set-ratio">{{ getSetRatio(m) }}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="result-cell">
|
||||
<div class="match-result">{{ getMatchResult(m) }}</div>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Fehlermeldung für diese Match-Zeile -->
|
||||
<tr v-if="getMatchError(idx)" class="error-row">
|
||||
<td colspan="10" class="error-cell">
|
||||
<div class="error-message">{{ getMatchError(idx) }}</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Auto-Fill Button -->
|
||||
<div v-if="!viewOnly" class="autofill-section">
|
||||
@@ -2859,6 +2817,8 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
return Boolean(normalized) && ![
|
||||
'nicht angegeben',
|
||||
'nicht angegeben / nicht angegeben',
|
||||
'nicht anwesend',
|
||||
'nicht anwesend / nicht anwesend',
|
||||
'not specified',
|
||||
'unknown',
|
||||
'unbekannt',
|
||||
@@ -4670,8 +4630,13 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
const playerIndex = this.meetingDetails[teamKey].findIndex(p => p.nuLigaPersonId === player.nuLigaPersonId);
|
||||
|
||||
if (playerIndex !== -1) {
|
||||
// Toggle selection status
|
||||
this.meetingDetails[teamKey][playerIndex].isSelected = !this.meetingDetails[teamKey][playerIndex].isSelected;
|
||||
const selectedPlayer = this.meetingDetails[teamKey][playerIndex];
|
||||
selectedPlayer.isSelected = !selectedPlayer.isSelected;
|
||||
// Eine Doppelposition ohne aufgestellten Spieler ist ungültig
|
||||
// und darf weder Plätze blockieren noch in Paarungen auftauchen.
|
||||
if (!selectedPlayer.isSelected) {
|
||||
selectedPlayer.positionDouble = null;
|
||||
}
|
||||
|
||||
// Recalculate all positions for selected players
|
||||
this.recalculatePositions(teamKey);
|
||||
@@ -4702,6 +4667,14 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
);
|
||||
},
|
||||
|
||||
getDoublePositions() {
|
||||
const positions = this.meetingDetails?.teamLineupDoublePositions;
|
||||
if (Array.isArray(positions) && positions.length > 0) {
|
||||
return [...new Set(positions.map(Number).filter(Number.isInteger))].sort((a, b) => a - b);
|
||||
}
|
||||
return [1, 2];
|
||||
},
|
||||
|
||||
toggleDoublePosition(player, team, doublePosition) {
|
||||
if (this.viewOnly) return;
|
||||
if (this.isLineupLocked(team)) return;
|
||||
@@ -4711,11 +4684,13 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
const playerIndex = this.meetingDetails[teamKey].findIndex(p => p.nuLigaPersonId === player.nuLigaPersonId);
|
||||
|
||||
if (playerIndex !== -1) {
|
||||
const currentDoublePosition = this.meetingDetails[teamKey][playerIndex].positionDouble;
|
||||
const selectedPlayer = this.meetingDetails[teamKey][playerIndex];
|
||||
if (!selectedPlayer.isSelected) return;
|
||||
const currentDoublePosition = selectedPlayer.positionDouble;
|
||||
|
||||
if (currentDoublePosition === doublePosition) {
|
||||
// Deselect if already selected
|
||||
this.meetingDetails[teamKey][playerIndex].positionDouble = null;
|
||||
selectedPlayer.positionDouble = null;
|
||||
} else {
|
||||
// Check if we can select this position
|
||||
if (!this.canSelectDoublePosition(player, team, doublePosition)) {
|
||||
@@ -4723,13 +4698,13 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
}
|
||||
|
||||
// Select new position (automatically removes from previous position)
|
||||
this.meetingDetails[teamKey][playerIndex].positionDouble = doublePosition;
|
||||
|
||||
selectedPlayer.positionDouble = doublePosition;
|
||||
}
|
||||
|
||||
// Ergebnisse neu berechnen, wenn nicht beide Aufstellungen zertifiziert sind
|
||||
if (!this.isHomeLineupCertified || !this.isGuestLineupCertified) {
|
||||
this.prepareResults();
|
||||
}
|
||||
// Sowohl Setzen als auch Entfernen muss die Doppelzeilen
|
||||
// unmittelbar aus den aktuellen Paaren neu auflösen.
|
||||
if (!this.isHomeLineupCertified || !this.isGuestLineupCertified) {
|
||||
this.prepareResults();
|
||||
}
|
||||
}
|
||||
this.persistLocalDraftDebounced();
|
||||
@@ -4743,15 +4718,20 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
|
||||
const teamKey = team === 'home' ? 'teamLineupHomePlayers' : 'teamLineupGuestPlayers';
|
||||
|
||||
// Doppelpartner müssen Teil der Aufstellung sein. Damit können
|
||||
// Karten in „Verfügbare Spieler“ keine unsichtbaren Doppel belegen.
|
||||
if (!player.isSelected) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if player is already selected for a different double position
|
||||
if (player.positionDouble && player.positionDouble !== doublePosition) {
|
||||
return false; // Player is already assigned to a different double position
|
||||
}
|
||||
|
||||
// Count how many players are already selected for this double position
|
||||
// Include ALL players (both selected and available) who have this double position
|
||||
// Pro Doppelposition sind genau zwei aufgestellte Spieler erlaubt.
|
||||
const playersInDoublePosition = this.meetingDetails[teamKey].filter(p =>
|
||||
p.positionDouble === doublePosition && p.nuLigaPersonId !== player.nuLigaPersonId && p.nuLigaPersonId > 0
|
||||
p.isSelected && p.positionDouble === doublePosition && p.nuLigaPersonId !== player.nuLigaPersonId && p.nuLigaPersonId > 0
|
||||
);
|
||||
|
||||
// Allow selection if less than 2 players are already selected for this position
|
||||
@@ -6061,7 +6041,16 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
}
|
||||
|
||||
.result-content { margin-top: 2px; }
|
||||
.result-table { width: 100%; border-collapse: collapse; background: #fff; border: 1px solid #e0e0e0; border-radius: 8px; overflow: hidden; }
|
||||
.result-table-wrap {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
overscroll-behavior-inline: contain;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
}
|
||||
.result-table-wrap:focus { outline: 2px solid rgba(var(--primary-rgb), 0.35); outline-offset: 2px; }
|
||||
.result-table { width: 100%; min-width: 960px; border-collapse: collapse; background: #fff; }
|
||||
.result-table th, .result-table td { border-bottom: 1px solid #eee; padding: 8px; text-align: left; }
|
||||
.result-table thead th { background: #f8f9fa; }
|
||||
.set-input-wrapper {
|
||||
@@ -6766,8 +6755,8 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
/* Doppel-Auswahl - Layout bereits oben definiert */
|
||||
|
||||
.player-main {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
@@ -6780,6 +6769,12 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.player-name,
|
||||
.player-details,
|
||||
.player-action {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.player-main:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
|
||||
Reference in New Issue
Block a user