feat: Implement filtering for regular matches and update related functionalities
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 48s
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 48s
This commit is contained in:
@@ -827,6 +827,9 @@ export default {
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
filterRegularScheduleMatches(matches) {
|
||||
return (Array.isArray(matches) ? matches : []).filter((match) => !match?.isFriendly);
|
||||
},
|
||||
getClubNameById(clubId) {
|
||||
const club = (this.clubs || []).find((item) => Number(item.id) === Number(clubId));
|
||||
return club?.name || `Verein ${clubId}`;
|
||||
@@ -1878,7 +1881,7 @@ export default {
|
||||
}
|
||||
try {
|
||||
const ownResponse = await apiClient.get(`/matches/leagues/${this.currentClub}/matches/${team.league.id}`);
|
||||
this.ownLeagueMatches = ownResponse.data;
|
||||
this.ownLeagueMatches = this.filterRegularScheduleMatches(ownResponse.data);
|
||||
await this.loadLeagueMatches(team.league.id);
|
||||
this.applyLeagueMatchScope();
|
||||
// Lade auch die Tabellendaten für diese Liga
|
||||
@@ -1897,7 +1900,7 @@ export default {
|
||||
},
|
||||
async loadLeagueMatches(leagueId) {
|
||||
const response = await apiClient.get(`/matches/leagues/${this.currentClub}/matches/${leagueId}?scope=all`);
|
||||
this.allLeagueMatches = response.data;
|
||||
this.allLeagueMatches = this.filterRegularScheduleMatches(response.data);
|
||||
},
|
||||
getCombinedLeagueMatches() {
|
||||
const combined = [...(this.allLeagueMatches || []), ...(this.ownLeagueMatches || [])];
|
||||
@@ -1959,7 +1962,7 @@ export default {
|
||||
try {
|
||||
const seasonParam = this.selectedSeasonId ? `?seasonid=${this.selectedSeasonId}` : '';
|
||||
const response = await apiClient.get(`/matches/leagues/${this.currentClub}/matches${seasonParam}`);
|
||||
this.matches = this.sortMatchesByDateTime(response.data);
|
||||
this.matches = this.sortMatchesByDateTime(this.filterRegularScheduleMatches(response.data));
|
||||
} catch (error) {
|
||||
this.showInfo(this.$t('messages.error'), this.$t('schedule.errorLoadingOverallSchedule'), '', 'error');
|
||||
this.matches = [];
|
||||
@@ -1976,7 +1979,7 @@ export default {
|
||||
const seasonParam = this.selectedSeasonId ? `?seasonid=${this.selectedSeasonId}` : '';
|
||||
const response = await apiClient.get(`/matches/leagues/${this.currentClub}/matches${seasonParam}`);
|
||||
// Filtere nur Erwachsenenligen (keine Jugendligen)
|
||||
const allMatches = response.data;
|
||||
const allMatches = this.filterRegularScheduleMatches(response.data);
|
||||
this.matches = this.sortMatchesByDateTime(allMatches.filter(match => {
|
||||
const leagueName = match.leagueDetails?.name || '';
|
||||
// Prüfe, ob es eine Jugendliga ist (J, M, Jugend im Namen)
|
||||
@@ -2282,6 +2285,10 @@ export default {
|
||||
return;
|
||||
}
|
||||
if (payload?.match && payload.matchId != null) {
|
||||
if (payload.match.isFriendly) {
|
||||
this.matches = this.matches.filter((match) => match.id !== payload.matchId);
|
||||
return;
|
||||
}
|
||||
const idx = this.matches.findIndex(m => m.id === payload.matchId);
|
||||
if (idx !== -1) {
|
||||
this.matches.splice(idx, 1, payload.match);
|
||||
|
||||
@@ -2761,7 +2761,10 @@ export default {
|
||||
// Lade die Teilnehmer für diesen Trainingstag über die Participant-API
|
||||
const participantsResponse = await apiClient.get(`/participants/${trainingForDate.id}`);
|
||||
|
||||
const participants = participantsResponse.data;
|
||||
const allTrainingParticipants = Array.isArray(participantsResponse.data)
|
||||
? participantsResponse.data
|
||||
: [];
|
||||
const participants = allTrainingParticipants.filter(participant => participant?.attendanceStatus === 'present');
|
||||
|
||||
if (participants && participants.length > 0) {
|
||||
// Lade die Member-Details für jeden Teilnehmer
|
||||
@@ -2794,6 +2797,8 @@ export default {
|
||||
} else {
|
||||
await this.showInfo('Hinweis', 'Keine gültigen Teilnehmer im Trainingstag für dieses Datum gefunden!', '', 'info');
|
||||
}
|
||||
} else if (allTrainingParticipants.length > 0) {
|
||||
await this.showInfo('Hinweis', 'Keine anwesenden Teilnehmer im Trainingstag für dieses Datum gefunden!', '', 'info');
|
||||
} else {
|
||||
await this.showInfo('Hinweis', 'Keine Teilnehmer im Trainingstag für dieses Datum gefunden!', '', 'info');
|
||||
}
|
||||
@@ -4312,4 +4317,4 @@ tbody tr:hover:not(.active-match) {
|
||||
background-color: #6c757d;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
Binary file not shown.
@@ -324,6 +324,7 @@ internal fun TournamentEditorParticipantsTab(
|
||||
classes: List<TournamentClassDto>,
|
||||
participants: List<TournamentParticipantRowDto>,
|
||||
externalParticipants: List<TournamentExternalParticipantRowDto>,
|
||||
tournamentDate: String?,
|
||||
dependencies: AppDependencies,
|
||||
tr: (String, String) -> String,
|
||||
onReload: () -> Unit,
|
||||
@@ -337,6 +338,8 @@ internal fun TournamentEditorParticipantsTab(
|
||||
var extDialog by remember { mutableStateOf(false) }
|
||||
var extFirst by remember { mutableStateOf("") }
|
||||
var extLast by remember { mutableStateOf("") }
|
||||
var importingFromTraining by remember { mutableStateOf(false) }
|
||||
var trainingImportMessage by remember { mutableStateOf<String?>(null) }
|
||||
|
||||
LaunchedEffect(clubId) {
|
||||
dependencies.membersManager.loadMembers(clubId)
|
||||
@@ -355,8 +358,73 @@ internal fun TournamentEditorParticipantsTab(
|
||||
TextButton(onClick = { showInternal = !showInternal }) { Text(if (showInternal) tr("mobile.hide", "Verbergen") else tr("mobile.show", "Anzeigen")) }
|
||||
}
|
||||
if (showInternal) {
|
||||
Row {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
OutlinedButton(onClick = { addMenu = true }) { Text(tr("tournaments.addParticipant", "Teilnehmer hinzufügen")) }
|
||||
Button(
|
||||
enabled = !importingFromTraining,
|
||||
onClick = {
|
||||
scope.launch {
|
||||
importingFromTraining = true
|
||||
trainingImportMessage = null
|
||||
runCatching {
|
||||
withContext(Dispatchers.IO) {
|
||||
val dateKey = normalizeTournamentDate(tournamentDate)
|
||||
if (dateKey.isBlank()) {
|
||||
return@withContext tr("tournaments.noTournamentDate", "Kein Turnierdatum vorhanden.")
|
||||
}
|
||||
val trainingDay = dependencies.diaryManager
|
||||
.listDates(clubId)
|
||||
.firstOrNull { normalizeTournamentDate(it.date) == dateKey }
|
||||
?: return@withContext tr("tournaments.noTrainingDayForDate", "Kein Trainingstag für dieses Datum gefunden.")
|
||||
val allTrainingParticipants = dependencies.diaryManager.listTrainingParticipants(trainingDay.id)
|
||||
val presentTrainingParticipants = allTrainingParticipants.filter { it.attendanceStatus == "present" }
|
||||
if (presentTrainingParticipants.isEmpty()) {
|
||||
return@withContext if (allTrainingParticipants.isEmpty()) {
|
||||
tr("tournaments.noTrainingParticipantsForDate", "Keine Teilnehmer im Trainingstag für dieses Datum gefunden.")
|
||||
} else {
|
||||
tr("tournaments.noPresentTrainingParticipantsForDate", "Keine anwesenden Teilnehmer im Trainingstag für dieses Datum gefunden.")
|
||||
}
|
||||
}
|
||||
val existing = participants.mapNotNull { it.clubMemberId }.toSet()
|
||||
val toAdd = presentTrainingParticipants
|
||||
.map { it.memberId }
|
||||
.filter { it !in existing }
|
||||
.distinct()
|
||||
if (toAdd.isEmpty()) {
|
||||
return@withContext tr("tournaments.trainingParticipantsAlreadyAdded", "Alle anwesenden Trainingsteilnehmer sind bereits im Turnier.")
|
||||
}
|
||||
toAdd.forEach { memberId ->
|
||||
api.addInternalParticipant(
|
||||
TournamentAddInternalParticipantBody(
|
||||
clubId = clubId,
|
||||
classId = null,
|
||||
participant = memberId,
|
||||
tournamentId = tournamentId,
|
||||
),
|
||||
)
|
||||
}
|
||||
tr("tournaments.trainingParticipantsLoaded", "Anwesende Trainingsteilnehmer übernommen: {count}")
|
||||
.replace("{count}", toAdd.size.toString())
|
||||
}
|
||||
}.fold(
|
||||
onSuccess = {
|
||||
trainingImportMessage = it
|
||||
onReload()
|
||||
},
|
||||
onFailure = { onError(it.message) },
|
||||
)
|
||||
importingFromTraining = false
|
||||
}
|
||||
},
|
||||
) {
|
||||
Text(
|
||||
if (importingFromTraining) {
|
||||
tr("mobile.loading", "Laden...")
|
||||
} else {
|
||||
tr("tournaments.loadParticipantsFromTraining", "Aus Trainingstag laden")
|
||||
},
|
||||
)
|
||||
}
|
||||
DropdownMenu(expanded = addMenu, onDismissRequest = { addMenu = false }) {
|
||||
val existing = participants.mapNotNull { it.clubMemberId }.toSet()
|
||||
membersState.members
|
||||
@@ -388,6 +456,7 @@ internal fun TournamentEditorParticipantsTab(
|
||||
}
|
||||
}
|
||||
}
|
||||
trainingImportMessage?.let { Text(it, style = MaterialTheme.typography.caption) }
|
||||
participants.forEach { p ->
|
||||
val label = p.member?.let { "${it.firstName.orEmpty()} ${it.lastName.orEmpty()}".trim() }.orEmpty().ifBlank { "#${p.id}" }
|
||||
Row(
|
||||
@@ -508,6 +577,10 @@ internal fun TournamentEditorParticipantsTab(
|
||||
}
|
||||
}
|
||||
|
||||
private fun normalizeTournamentDate(date: String?): String {
|
||||
return date.orEmpty().trim().substringBefore("T")
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ClassPickerChipRow(
|
||||
classes: List<TournamentClassDto>,
|
||||
|
||||
@@ -230,6 +230,7 @@ internal fun InternalTournamentEditorScreen(
|
||||
classes = classes,
|
||||
participants = participants,
|
||||
externalParticipants = externalParticipants,
|
||||
tournamentDate = d.date,
|
||||
dependencies = dependencies,
|
||||
tr = ::tr,
|
||||
onReload = { reloadAll() },
|
||||
|
||||
@@ -379,6 +379,15 @@ internal fun ScheduleScreen(dependencies: AppDependencies, friendlyOnly: Boolean
|
||||
}
|
||||
if (permissions?.canWriteSchedule() == true) {
|
||||
if (m.isFriendly) {
|
||||
TextButton(
|
||||
onClick = {
|
||||
playerError = null
|
||||
playerMatch = m
|
||||
detailMatch = null
|
||||
},
|
||||
) {
|
||||
Text(tr("schedule.players", "Aufstellung / Spieler"))
|
||||
}
|
||||
TextButton(
|
||||
onClick = {
|
||||
friendlyResultMatch = m
|
||||
@@ -525,9 +534,9 @@ internal fun ScheduleScreen(dependencies: AppDependencies, friendlyOnly: Boolean
|
||||
playerSaving = true
|
||||
playerError = null
|
||||
runCatching {
|
||||
dependencies.scheduleManager.updateMatchPlayers(
|
||||
dependencies.scheduleManager.updateMatchPlayersForMatch(
|
||||
clubId = clubId,
|
||||
matchId = m.id,
|
||||
match = m,
|
||||
ready = readyIds,
|
||||
planned = plannedIds,
|
||||
played = playedIds,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[versions]
|
||||
# composeApp (Play Store / „Über die App“-Build)
|
||||
appVersionCode = "20"
|
||||
appVersionName = "1.7.0"
|
||||
appVersionCode = "21"
|
||||
appVersionName = "1.7.1"
|
||||
agp = "9.2.1"
|
||||
android-compileSdk = "35"
|
||||
android-minSdk = "24"
|
||||
|
||||
@@ -80,6 +80,12 @@ class MatchesApi(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateFriendlyMatchPlayers(clubId: Int, matchId: Int, body: UpdateMatchPlayersBody) {
|
||||
client.http.patch("/api/friendly-matches/$clubId/$matchId/players") {
|
||||
setBody(body)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun deleteFriendlyMatch(clubId: Int, matchId: Int) {
|
||||
client.http.delete("/api/friendly-matches/$clubId/$matchId")
|
||||
}
|
||||
|
||||
@@ -59,6 +59,8 @@ data class ScheduleState(
|
||||
get() = ScheduleLogic.leagueTeamNames(ScheduleLogic.mergeUniqueMatches(allMatches, ownMatches))
|
||||
}
|
||||
|
||||
private fun List<ScheduleMatchDto>.withoutFriendly(): List<ScheduleMatchDto> = filterNot { it.isFriendly }
|
||||
|
||||
class ScheduleManager(
|
||||
private val clubTeamsApi: ClubTeamsApi,
|
||||
private val matchesApi: MatchesApi,
|
||||
@@ -155,8 +157,8 @@ class ScheduleManager(
|
||||
val leagueId = team.league?.id ?: return
|
||||
_state.update { it.copy(isLoading = true, error = null, viewMode = ScheduleViewMode.Team) }
|
||||
try {
|
||||
val own = matchesApi.listMatchesForLeague(clubId, leagueId, "own")
|
||||
val all = matchesApi.listMatchesForLeague(clubId, leagueId, "all")
|
||||
val own = matchesApi.listMatchesForLeague(clubId, leagueId, "own").withoutFriendly()
|
||||
val all = matchesApi.listMatchesForLeague(clubId, leagueId, "all").withoutFriendly()
|
||||
val table = runCatching { matchesApi.leagueTable(clubId, leagueId) }.getOrElse { emptyList() }
|
||||
_state.update {
|
||||
it.copy(
|
||||
@@ -198,7 +200,7 @@ class ScheduleManager(
|
||||
)
|
||||
}
|
||||
try {
|
||||
val matches = matchesApi.listMatchesForLeagues(clubId, _state.value.seasonId)
|
||||
val matches = matchesApi.listMatchesForLeagues(clubId, _state.value.seasonId).withoutFriendly()
|
||||
_state.update { it.copy(overallMatches = matches, isLoading = false, error = null) }
|
||||
} catch (t: Throwable) {
|
||||
_state.update {
|
||||
@@ -226,7 +228,7 @@ class ScheduleManager(
|
||||
)
|
||||
}
|
||||
try {
|
||||
val matches = matchesApi.listMatchesForLeagues(clubId, _state.value.seasonId)
|
||||
val matches = matchesApi.listMatchesForLeagues(clubId, _state.value.seasonId).withoutFriendly()
|
||||
_state.update { it.copy(overallMatches = matches, isLoading = false, error = null) }
|
||||
} catch (t: Throwable) {
|
||||
_state.update {
|
||||
@@ -299,6 +301,27 @@ class ScheduleManager(
|
||||
refresh(clubId)
|
||||
}
|
||||
|
||||
suspend fun updateMatchPlayersForMatch(
|
||||
clubId: Int,
|
||||
match: ScheduleMatchDto,
|
||||
ready: List<Int>,
|
||||
planned: List<Int>,
|
||||
played: List<Int>,
|
||||
) {
|
||||
val body = UpdateMatchPlayersBody(
|
||||
clubId = clubId,
|
||||
playersReady = ready,
|
||||
playersPlanned = planned,
|
||||
playersPlayed = played,
|
||||
)
|
||||
when {
|
||||
match.isFriendly && match.isSharedFriendly -> matchesApi.updateSharedFriendlyMatchPlayers(clubId, match.id, body)
|
||||
match.isFriendly -> matchesApi.updateFriendlyMatchPlayers(clubId, match.id, body)
|
||||
else -> matchesApi.updateMatchPlayers(match.id, body)
|
||||
}
|
||||
refresh(clubId)
|
||||
}
|
||||
|
||||
suspend fun createFriendlyMatch(clubId: Int, body: FriendlyMatchSaveBody) {
|
||||
val saved = matchesApi.createFriendlyMatch(clubId, body)
|
||||
_state.update { it.copy(friendlyMatches = ScheduleLogic.sortMatches(it.friendlyMatches + saved)) }
|
||||
|
||||
Reference in New Issue
Block a user