feat(TeamManagement): enhance team management features and introduce planning phase for Android
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 44s

- Updated TODO.md to outline the new planning phase for Android, aligning it with existing web functionalities for team management.
- Refactored AppDependencies to include TeamDocumentsApi, improving API integration for team-related documents.
- Replaced MobileTeamsScreen with TeamManagementScreen in ClubStammdatenScreens for better navigation.
- Enhanced TeamManagementScreen with improved state management and UI updates for team editing and data loading.
- Added new API methods in ClubTeamsApi for managing team lineups, supporting better team planning and organization.
- Introduced new methods in MatchesApi and MyTischtennisApi to enhance match and team data handling.
This commit is contained in:
Torsten Schulz (local)
2026-05-14 18:31:15 +02:00
parent 2f3f4fb275
commit e0196a6617
12 changed files with 1960 additions and 328 deletions

View File

@@ -5,6 +5,7 @@ import de.tt_tagebuch.shared.api.models.ApiLogDetailDto
import de.tt_tagebuch.shared.api.models.ApiLogDetailEnvelopeDto
import de.tt_tagebuch.shared.api.models.ApiLogsListEnvelopeDto
import de.tt_tagebuch.shared.api.models.ApiLogsListPageDto
import de.tt_tagebuch.shared.api.models.SchedulerLastExecutionsEnvelopeDto
import io.ktor.client.call.body
import io.ktor.client.request.get
import io.ktor.client.request.parameter
@@ -35,4 +36,10 @@ class ApiLogsApi(
val env = client.http.get("/api/logs/$id").body<ApiLogDetailEnvelopeDto>()
return env.data
}
suspend fun getSchedulerLastExecutions(clubId: Int? = null): SchedulerLastExecutionsEnvelopeDto {
return client.http.get("/api/logs/scheduler/last-executions") {
clubId?.let { parameter("clubId", it) }
}.body()
}
}

View File

@@ -4,7 +4,9 @@ import de.tt_tagebuch.shared.api.http.AuthedHttpClient
import de.tt_tagebuch.shared.api.models.ClubLeagueOptionDto
import de.tt_tagebuch.shared.api.models.ClubTeamCreateBody
import de.tt_tagebuch.shared.api.models.ClubTeamDto
import de.tt_tagebuch.shared.api.models.ClubTeamLineupRowDto
import de.tt_tagebuch.shared.api.models.ClubTeamUpdateBody
import de.tt_tagebuch.shared.api.models.TeamLineupUpdateBody
import io.ktor.client.call.body
import io.ktor.client.request.delete
import io.ktor.client.request.get
@@ -47,4 +49,16 @@ class ClubTeamsApi(
suspend fun deleteClubTeam(clubTeamId: Int) {
client.http.delete("/api/club-teams/$clubTeamId")
}
suspend fun getLineup(clubTeamId: Int, half: String): List<ClubTeamLineupRowDto> {
return client.http.get("/api/club-teams/$clubTeamId/lineup") {
parameter("half", half)
}.body()
}
suspend fun updateLineup(clubTeamId: Int, body: TeamLineupUpdateBody): List<ClubTeamLineupRowDto> {
return client.http.put("/api/club-teams/$clubTeamId/lineup") {
setBody(body)
}.body()
}
}

View File

@@ -1,6 +1,7 @@
package de.tt_tagebuch.shared.api
import de.tt_tagebuch.shared.api.http.AuthedHttpClient
import de.tt_tagebuch.shared.api.models.LeaguePlayerStatDto
import de.tt_tagebuch.shared.api.models.LeagueTableRowDto
import de.tt_tagebuch.shared.api.models.ScheduleMatchDto
import de.tt_tagebuch.shared.api.models.UpdateMatchPlayersBody
@@ -29,6 +30,12 @@ class MatchesApi(
return client.http.get("/api/matches/leagues/$clubId/table/$leagueId").body()
}
suspend fun getLeaguePlayerStats(clubId: Int, leagueId: Int, seasonId: Int? = null): List<LeaguePlayerStatDto> {
return client.http.get("/api/matches/leagues/$clubId/stats/$leagueId") {
seasonId?.let { parameter("seasonid", it) }
}.body()
}
suspend fun updateMatchPlayers(matchId: Int, body: UpdateMatchPlayersBody) {
client.http.patch("/api/matches/$matchId/players") {
setBody(body)

View File

@@ -1,6 +1,12 @@
package de.tt_tagebuch.shared.api
import de.tt_tagebuch.shared.api.http.AuthedHttpClient
import de.tt_tagebuch.shared.api.models.MyTtConfigureLeagueBody
import de.tt_tagebuch.shared.api.models.MyTtConfigureTeamBody
import de.tt_tagebuch.shared.api.models.MyTtFetchJobEnvelopeDto
import de.tt_tagebuch.shared.api.models.MyTtFetchJobStartDto
import de.tt_tagebuch.shared.api.models.MyTtFetchTeamDataBody
import de.tt_tagebuch.shared.api.models.MyTtParseUrlBody
import de.tt_tagebuch.shared.api.models.MyTischtennisAccountEnvelope
import de.tt_tagebuch.shared.api.models.MyTischtennisAccountSaveResponse
import de.tt_tagebuch.shared.api.models.MyTischtennisAccountUpsertBody
@@ -46,4 +52,31 @@ class MyTischtennisApi(
suspend fun deleteAccount() {
client.http.delete("/api/mytischtennis/account")
}
suspend fun parseUrl(url: String): JsonObject =
client.http.post("/api/mytischtennis/parse-url") {
contentType(ContentType.Application.Json)
setBody(MyTtParseUrlBody(url))
}.body()
suspend fun configureTeam(body: MyTtConfigureTeamBody): JsonObject =
client.http.post("/api/mytischtennis/configure-team") {
contentType(ContentType.Application.Json)
setBody(body)
}.body()
suspend fun configureLeague(body: MyTtConfigureLeagueBody): JsonObject =
client.http.post("/api/mytischtennis/configure-league") {
contentType(ContentType.Application.Json)
setBody(body)
}.body()
suspend fun startFetchTeamDataJob(clubTeamId: Int): MyTtFetchJobStartDto =
client.http.post("/api/mytischtennis/fetch-team-data/async") {
contentType(ContentType.Application.Json)
setBody(MyTtFetchTeamDataBody(clubTeamId))
}.body()
suspend fun getFetchTeamDataJob(jobId: String): MyTtFetchJobEnvelopeDto =
client.http.get("/api/mytischtennis/fetch-team-data/jobs/$jobId").body()
}

View File

@@ -0,0 +1,66 @@
package de.tt_tagebuch.shared.api
import de.tt_tagebuch.shared.api.http.AuthedHttpClient
import de.tt_tagebuch.shared.api.models.TeamDocumentDto
import de.tt_tagebuch.shared.api.models.TeamDocumentParseEnvelopeDto
import io.ktor.client.call.body
import io.ktor.client.request.delete
import io.ktor.client.request.forms.MultiPartFormDataContent
import io.ktor.client.request.forms.formData
import io.ktor.client.request.get
import io.ktor.client.request.parameter
import io.ktor.client.request.post
import io.ktor.client.request.setBody
import io.ktor.http.ContentType
import io.ktor.http.Headers
import io.ktor.http.HttpHeaders
import io.ktor.http.contentType
class TeamDocumentsApi(
private val client: AuthedHttpClient,
) {
suspend fun listByClubTeam(clubTeamId: Int): List<TeamDocumentDto> {
return client.http.get("/api/team-documents/club-team/$clubTeamId").body()
}
suspend fun upload(
clubTeamId: Int,
documentType: String,
bytes: ByteArray,
fileName: String,
mimeType: String,
): TeamDocumentDto {
return client.http.post("/api/team-documents/club-team/$clubTeamId/upload") {
contentType(ContentType.MultiPart.FormData)
setBody(
MultiPartFormDataContent(
formData {
append("documentType", documentType)
append(
"document",
bytes,
Headers.build {
append(HttpHeaders.ContentType, mimeType)
append(HttpHeaders.ContentDisposition, "filename=\"$fileName\"")
},
)
},
),
)
}.body()
}
suspend fun parse(documentId: Int, leagueId: Int): TeamDocumentParseEnvelopeDto {
return client.http.post("/api/team-documents/$documentId/parse") {
parameter("leagueid", leagueId)
}.body()
}
suspend fun download(documentId: Int): ByteArray {
return client.http.get("/api/team-documents/$documentId/download").body()
}
suspend fun delete(documentId: Int) {
client.http.delete("/api/team-documents/$documentId")
}
}

View File

@@ -0,0 +1,139 @@
package de.tt_tagebuch.shared.api.models
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonObject
@Serializable
data class LeaguePlayerStatDto(
val memberId: Int,
val firstName: String = "",
val lastName: String = "",
val totalSeason: Int = 0,
val totalFirstHalf: Int = 0,
val totalSecondHalf: Int = 0,
)
@Serializable
data class ClubTeamLineupRowDto(
val id: Int? = null,
val clubTeamId: Int? = null,
val memberId: Int,
val lineupHalf: String? = null,
val position: Int = 0,
val member: Member? = null,
)
@Serializable
data class TeamLineupAssignmentItem(
val memberId: Int,
val position: Int,
)
@Serializable
data class TeamLineupUpdateBody(
val assignments: List<TeamLineupAssignmentItem>,
val lineupHalf: String,
)
@Serializable
data class TeamDocumentDto(
val id: Int,
val fileName: String = "",
val originalFileName: String = "",
val documentType: String = "",
val clubTeamId: Int? = null,
val createdAt: String? = null,
val mimeType: String? = null,
)
@Serializable
data class TeamDocumentParseResultDto(
val matchesFound: Int = 0,
)
@Serializable
data class TeamDocumentSaveResultDto(
val created: Int = 0,
val updated: Int = 0,
)
@Serializable
data class TeamDocumentParseEnvelopeDto(
val parseResult: TeamDocumentParseResultDto? = null,
val saveResult: TeamDocumentSaveResultDto? = null,
)
@Serializable
data class SchedulerLastExecutionsEnvelopeDto(
val success: Boolean = false,
val data: SchedulerLastExecutionsDataDto? = null,
)
@Serializable
data class SchedulerLastExecutionsDataDto(
@SerialName("rating_updates")
val ratingUpdates: SchedulerJobBlockDto? = null,
@SerialName("match_results")
val matchResults: SchedulerJobBlockDto? = null,
)
@Serializable
data class SchedulerJobBlockDto(
val lastRun: String? = null,
val success: Boolean? = null,
val executionTime: Int? = null,
val updatedCount: Int? = null,
val fetchedCount: Int? = null,
val errorMessage: String? = null,
val teamDetails: List<SchedulerTeamDetailDto> = emptyList(),
)
@Serializable
data class SchedulerTeamDetailDto(
val clubTeamId: Int = 0,
val teamName: String? = null,
val success: Boolean = false,
val lastRun: String? = null,
)
@Serializable
data class MyTtParseUrlBody(val url: String)
@Serializable
data class MyTtConfigureTeamBody(
val url: String,
val clubTeamId: Int,
val createLeague: Boolean? = null,
val createSeason: Boolean? = null,
)
@Serializable
data class MyTtConfigureLeagueBody(
val url: String,
val createSeason: Boolean? = null,
)
@Serializable
data class MyTtFetchTeamDataBody(val clubTeamId: Int)
@Serializable
data class MyTtFetchJobStartDto(
val success: Boolean = false,
val jobId: String? = null,
val status: String? = null,
)
@Serializable
data class MyTtFetchJobEnvelopeDto(
val success: Boolean = false,
val job: MyTtFetchJobDto? = null,
)
@Serializable
data class MyTtFetchJobDto(
val jobId: String? = null,
val status: String? = null,
val result: JsonObject? = null,
val error: String? = null,
)