feat(CalendarView): merge recurring training slots and enhance event filtering
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 42s

- Implemented a new method to merge recurring training slots with identical weekdays and time windows, improving calendar event management.
- Updated event filtering logic to exclude cancelled training sessions, ensuring only relevant training events are displayed.
- Enhanced the loading process to handle source errors more effectively, improving user experience in the CalendarView.
This commit is contained in:
Torsten Schulz (local)
2026-05-13 00:07:47 +02:00
parent 54d9b9fc86
commit 61b1f27e5e
6 changed files with 293 additions and 26 deletions

View File

@@ -0,0 +1,49 @@
package de.tt_tagebuch.shared.api
import de.tt_tagebuch.shared.api.http.AuthedHttpClient
import de.tt_tagebuch.shared.api.models.ClickTtAccountEnvelope
import de.tt_tagebuch.shared.api.models.ClickTtAccountSaveResponse
import de.tt_tagebuch.shared.api.models.ClickTtAccountStatusDto
import de.tt_tagebuch.shared.api.models.ClickTtAccountUpsertBody
import de.tt_tagebuch.shared.api.models.ClickTtVerifyBody
import de.tt_tagebuch.shared.api.models.ClickTtVerifyResponseDto
import io.ktor.client.call.body
import io.ktor.client.request.delete
import io.ktor.client.request.get
import io.ktor.client.request.post
import io.ktor.client.request.setBody
import io.ktor.http.ContentType
import io.ktor.http.contentType
import kotlinx.serialization.json.JsonObject
class ClickTtAccountApi(
private val client: AuthedHttpClient,
) {
suspend fun getAccount(): ClickTtAccountEnvelope =
client.http.get("/api/clicktt-account/account").body()
suspend fun getStatus(): ClickTtAccountStatusDto =
client.http.get("/api/clicktt-account/status").body()
suspend fun upsertAccount(body: ClickTtAccountUpsertBody): ClickTtAccountSaveResponse =
client.http.post("/api/clicktt-account/account") {
contentType(ContentType.Application.Json)
setBody(body)
}.body()
suspend fun verifyLogin(password: String? = null): ClickTtVerifyResponseDto =
client.http.post("/api/clicktt-account/verify") {
contentType(ContentType.Application.Json)
setBody(
if (password != null) {
ClickTtVerifyBody(password = password)
} else {
JsonObject(emptyMap())
},
)
}.body()
suspend fun deleteAccount() {
client.http.delete("/api/clicktt-account/account")
}
}

View File

@@ -0,0 +1,49 @@
package de.tt_tagebuch.shared.api
import de.tt_tagebuch.shared.api.http.AuthedHttpClient
import de.tt_tagebuch.shared.api.models.MyTischtennisAccountEnvelope
import de.tt_tagebuch.shared.api.models.MyTischtennisAccountSaveResponse
import de.tt_tagebuch.shared.api.models.MyTischtennisAccountUpsertBody
import de.tt_tagebuch.shared.api.models.MyTischtennisStatusDto
import de.tt_tagebuch.shared.api.models.MyTischtennisVerifyBody
import de.tt_tagebuch.shared.api.models.MyTischtennisVerifyResponseDto
import io.ktor.client.call.body
import io.ktor.client.request.delete
import io.ktor.client.request.get
import io.ktor.client.request.post
import io.ktor.client.request.setBody
import io.ktor.http.ContentType
import io.ktor.http.contentType
import kotlinx.serialization.json.JsonObject
class MyTischtennisApi(
private val client: AuthedHttpClient,
) {
suspend fun getAccount(): MyTischtennisAccountEnvelope =
client.http.get("/api/mytischtennis/account").body()
suspend fun getStatus(): MyTischtennisStatusDto =
client.http.get("/api/mytischtennis/status").body()
suspend fun upsertAccount(body: MyTischtennisAccountUpsertBody): MyTischtennisAccountSaveResponse =
client.http.post("/api/mytischtennis/account") {
contentType(ContentType.Application.Json)
setBody(body)
}.body()
suspend fun verifyLogin(password: String? = null): MyTischtennisVerifyResponseDto =
client.http.post("/api/mytischtennis/verify") {
contentType(ContentType.Application.Json)
setBody(
if (password != null) {
MyTischtennisVerifyBody(password = password)
} else {
JsonObject(emptyMap())
},
)
}.body()
suspend fun deleteAccount() {
client.http.delete("/api/mytischtennis/account")
}
}

View File

@@ -0,0 +1,118 @@
package de.tt_tagebuch.shared.api.models
import kotlinx.serialization.Serializable
@Serializable
data class MyTischtennisAccountDto(
val id: Int? = null,
val userId: Int? = null,
val email: String? = null,
val savePassword: Boolean? = null,
val autoUpdateRatings: Boolean? = null,
val lastLoginAttempt: String? = null,
val lastLoginSuccess: String? = null,
val lastUpdateRatings: String? = null,
val expiresAt: Long? = null,
val clubId: String? = null,
val clubName: String? = null,
val fedNickname: String? = null,
val createdAt: String? = null,
val updatedAt: String? = null,
)
@Serializable
data class MyTischtennisAccountEnvelope(
val account: MyTischtennisAccountDto? = null,
)
@Serializable
data class MyTischtennisStatusDto(
val exists: Boolean = false,
val hasEmail: Boolean = false,
val hasPassword: Boolean = false,
val hasValidSession: Boolean = false,
val needsConfiguration: Boolean = false,
val needsPassword: Boolean = false,
)
@Serializable
data class MyTischtennisAccountUpsertBody(
val email: String,
val password: String? = null,
val savePassword: Boolean = false,
val autoUpdateRatings: Boolean = false,
val userPassword: String? = null,
)
@Serializable
data class MyTischtennisVerifyBody(
val password: String? = null,
)
@Serializable
data class MyTischtennisVerifyResponseDto(
val message: String? = null,
val success: Boolean? = null,
val accessToken: String? = null,
val expiresAt: Long? = null,
val clubId: String? = null,
val clubName: String? = null,
)
@Serializable
data class MyTischtennisAccountSaveResponse(
val message: String? = null,
val account: MyTischtennisAccountDto? = null,
)
@Serializable
data class ClickTtAccountDto(
val id: Int? = null,
val userId: Int? = null,
val username: String? = null,
val savePassword: Boolean? = null,
val lastLoginAttempt: String? = null,
val lastLoginSuccess: String? = null,
val createdAt: String? = null,
val updatedAt: String? = null,
)
@Serializable
data class ClickTtAccountEnvelope(
val account: ClickTtAccountDto? = null,
)
@Serializable
data class ClickTtAccountStatusDto(
val exists: Boolean = false,
val hasUsername: Boolean = false,
val hasPassword: Boolean = false,
val hasValidSession: Boolean = false,
val needsConfiguration: Boolean = false,
val needsPassword: Boolean = false,
)
@Serializable
data class ClickTtAccountUpsertBody(
val username: String,
val password: String? = null,
val savePassword: Boolean = false,
val userPassword: String? = null,
)
@Serializable
data class ClickTtVerifyBody(
val password: String? = null,
)
@Serializable
data class ClickTtAccountSaveResponse(
val message: String? = null,
val account: ClickTtAccountDto? = null,
)
@Serializable
data class ClickTtVerifyResponseDto(
val success: Boolean? = null,
val message: String? = null,
)