feat(MatchService): enhance match filtering for own teams and update mobile app settings
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 43s
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 43s
- Added logic in MatchService to filter matches based on the user's own teams, ensuring only relevant matches are displayed. - Updated the mobile app's TODO list to reflect progress on ClubSettings and Predefined Activities features. - Enhanced the AppRoot and ClubStammdatenScreens to support new settings and permissions for club management. - Introduced new API methods for creating and updating training groups and times, improving the training management capabilities. - Refactored MembersManager to include methods for managing training groups and times, streamlining the member management process.
This commit is contained in:
@@ -211,6 +211,28 @@ class MatchService {
|
||||
throw new Error('Season not found');
|
||||
}
|
||||
}
|
||||
|
||||
// Nur Spiele mit eigener Mannschaft (gleiche Logik wie getMatchesForLeague mit scope "own"):
|
||||
// Mannschaften, deren Name mit dem Vereinsnamen beginnt, in Ligen dieser Saison.
|
||||
const club = await Club.findByPk(clubId, { attributes: ['name'] });
|
||||
const leaguesInSeason = await League.findAll({
|
||||
where: { clubId, seasonId: season.id },
|
||||
attributes: ['id']
|
||||
});
|
||||
const leagueIdList = leaguesInSeason.map((l) => l.id);
|
||||
let ownTeamIdSet = new Set();
|
||||
if (club?.name && leagueIdList.length) {
|
||||
const escaped = String(club.name).replace(/\\/g, '\\\\').replace(/%/g, '\\%').replace(/_/g, '\\_');
|
||||
const ownTeams = await Team.findAll({
|
||||
where: {
|
||||
leagueId: { [Op.in]: leagueIdList },
|
||||
name: { [Op.like]: `${escaped}%` }
|
||||
},
|
||||
attributes: ['id']
|
||||
});
|
||||
ownTeamIdSet = new Set(ownTeams.map((t) => t.id));
|
||||
}
|
||||
|
||||
const matches = await Match.findAll({
|
||||
where: {
|
||||
clubId: clubId,
|
||||
@@ -225,6 +247,13 @@ class MatchService {
|
||||
if (!league || league.seasonId !== season.id) {
|
||||
continue; // Skip matches from other seasons
|
||||
}
|
||||
|
||||
// Kalender: nur Punktspiele, an denen der Verein beteiligt ist (eigene Mannschaft Heim oder Gast)
|
||||
if (ownTeamIdSet.size > 0) {
|
||||
if (!ownTeamIdSet.has(match.homeTeamId) && !ownTeamIdSet.has(match.guestTeamId)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const enrichedMatch = {
|
||||
id: match.id,
|
||||
|
||||
@@ -172,9 +172,9 @@ Web: `DiaryView.vue` (sehr groß). API-Cluster (Auszug – in Web nach `apiClien
|
||||
|
||||
## Phase 9 – Vereins- & Stammdaten-Einstellungen
|
||||
|
||||
- [ ] **ClubSettings** (`ClubSettings.vue`) – alle Unterbereiche
|
||||
- [ ] **Predefined Activities Verwaltung** (`PredefinedActivities.vue`) – CRUD, Bilder/Zeichnungen falls API
|
||||
- [ ] **Mitgliedstransfer-Einstellungen** (`MemberTransferSettingsView.vue`)
|
||||
- [x] **ClubSettings** (`ClubSettings.vue`) – Kernfelder in `ClubStammdatenScreens.kt` + Link Web; tiefergehend nur Web
|
||||
- [x] **Predefined Activities** (`PredefinedActivities.vue`) – Liste, Neu, Bearbeiten (`PredefinedActivitiesApi`); Medien/Web-only falls nötig
|
||||
- [x] **Mitgliedstransfer** (`MemberTransferSettingsView.vue`) – Basis in App, erweitert über Web-Link
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -85,6 +85,8 @@ import de.tt_tagebuch.shared.api.toAbsoluteUrl
|
||||
import de.tt_tagebuch.shared.api.models.MemberGroupPhotoDto
|
||||
import de.tt_tagebuch.shared.api.models.canReadApprovals
|
||||
import de.tt_tagebuch.shared.api.models.canReadClubPermissions
|
||||
import de.tt_tagebuch.shared.api.models.canReadClubSettings
|
||||
import de.tt_tagebuch.shared.api.models.canReadPredefinedActivities
|
||||
import de.tt_tagebuch.shared.api.models.canReadDiary
|
||||
import de.tt_tagebuch.shared.api.models.canReadTeams
|
||||
import de.tt_tagebuch.shared.api.models.canReadMembers
|
||||
@@ -3969,6 +3971,7 @@ private fun RowSwitch(label: String, checked: Boolean, onChecked: (Boolean) -> U
|
||||
@Composable
|
||||
private fun SettingsScreen(dependencies: AppDependencies) {
|
||||
var clubAdminSection by remember { mutableStateOf<ClubAdminSettingsSection?>(null) }
|
||||
var stammdatenSection by remember { mutableStateOf<ClubStammdatenSection?>(null) }
|
||||
if (clubAdminSection != null) {
|
||||
ClubAdminFlowScreen(
|
||||
destination = clubAdminSection!!,
|
||||
@@ -3977,6 +3980,14 @@ private fun SettingsScreen(dependencies: AppDependencies) {
|
||||
)
|
||||
return
|
||||
}
|
||||
if (stammdatenSection != null) {
|
||||
ClubStammdatenFlowScreen(
|
||||
destination = stammdatenSection!!,
|
||||
dependencies = dependencies,
|
||||
onBack = { stammdatenSection = null },
|
||||
)
|
||||
return
|
||||
}
|
||||
val authState by dependencies.authManager.state.collectAsState()
|
||||
val clubState by dependencies.clubManager.state.collectAsState()
|
||||
var sessionStatus by rememberSaveable { mutableStateOf<String?>(null) }
|
||||
@@ -4026,6 +4037,27 @@ private fun SettingsScreen(dependencies: AppDependencies) {
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) { Text(tr("mobile.teamManagementWeb", "Team-Verwaltung (Web)")) }
|
||||
}
|
||||
if (perms.canReadClubSettings() || perms.canReadPredefinedActivities() || perms.canReadMembers()) {
|
||||
SectionTitle(tr("mobile.clubStammdaten", "Verein & Stammdaten"))
|
||||
if (perms.canReadClubSettings()) {
|
||||
TextButton(
|
||||
onClick = { stammdatenSection = ClubStammdatenSection.ClubSettings },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) { Text(tr("clubSettings.title", "Vereinseinstellungen")) }
|
||||
}
|
||||
if (perms.canReadPredefinedActivities()) {
|
||||
TextButton(
|
||||
onClick = { stammdatenSection = ClubStammdatenSection.PredefinedActivities },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) { Text(tr("mobile.predefinedActivities", "Standard-Aktivitäten")) }
|
||||
}
|
||||
if (perms.canReadMembers()) {
|
||||
TextButton(
|
||||
onClick = { stammdatenSection = ClubStammdatenSection.MemberTransfer },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) { Text(tr("mobile.memberTransfer", "Mitgliedstransfer")) }
|
||||
}
|
||||
}
|
||||
}
|
||||
SectionTitle(tr("mobile.language", "Sprache"))
|
||||
MobileStrings.supportedLanguages.forEach { language ->
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
@@ -46,16 +45,18 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import de.tt_tagebuch.app.AppDependencies
|
||||
import de.tt_tagebuch.shared.api.models.Club
|
||||
import de.tt_tagebuch.shared.api.models.MemberDataQualityRequirements
|
||||
import de.tt_tagebuch.shared.api.PredefinedActivitiesApi
|
||||
import de.tt_tagebuch.shared.api.models.MemberTransferConfigEnvelope
|
||||
import de.tt_tagebuch.shared.api.models.MemberTransferConfigSaveBody
|
||||
import de.tt_tagebuch.shared.api.models.PredefinedActivityDto
|
||||
import de.tt_tagebuch.shared.api.models.PredefinedActivityUpsertBody
|
||||
import de.tt_tagebuch.shared.api.models.TrainingGroupDto
|
||||
import de.tt_tagebuch.shared.api.models.TrainingTimeDto
|
||||
import de.tt_tagebuch.shared.api.models.UpdateClubSettingsBody
|
||||
import de.tt_tagebuch.shared.api.models.UpdateTrainingTimeBody
|
||||
import de.tt_tagebuch.shared.api.models.canReadClubSettings
|
||||
import de.tt_tagebuch.shared.api.models.canReadMembers
|
||||
import de.tt_tagebuch.shared.api.models.canReadPredefinedActivities
|
||||
import de.tt_tagebuch.shared.api.models.canWriteClubSettings
|
||||
import de.tt_tagebuch.shared.api.models.canWriteMembers
|
||||
import de.tt_tagebuch.shared.api.models.canWritePredefinedActivities
|
||||
import de.tt_tagebuch.shared.api.models.displayLabel
|
||||
@@ -67,27 +68,6 @@ import kotlinx.serialization.json.buildJsonObject
|
||||
private val StammdatenPad = 20.dp
|
||||
private val StammdatenTouchMin = 48.dp
|
||||
|
||||
private data class GermanStateOption(val code: String, val name: String)
|
||||
|
||||
private val germanStates: List<GermanStateOption> = listOf(
|
||||
GermanStateOption("DE-BW", "Baden-Württemberg"),
|
||||
GermanStateOption("DE-BY", "Bayern"),
|
||||
GermanStateOption("DE-BE", "Berlin"),
|
||||
GermanStateOption("DE-BB", "Brandenburg"),
|
||||
GermanStateOption("DE-HB", "Bremen"),
|
||||
GermanStateOption("DE-HH", "Hamburg"),
|
||||
GermanStateOption("DE-HE", "Hessen"),
|
||||
GermanStateOption("DE-MV", "Mecklenburg-Vorpommern"),
|
||||
GermanStateOption("DE-NI", "Niedersachsen"),
|
||||
GermanStateOption("DE-NW", "Nordrhein-Westfalen"),
|
||||
GermanStateOption("DE-RP", "Rheinland-Pfalz"),
|
||||
GermanStateOption("DE-SL", "Saarland"),
|
||||
GermanStateOption("DE-SN", "Sachsen"),
|
||||
GermanStateOption("DE-ST", "Sachsen-Anhalt"),
|
||||
GermanStateOption("DE-SH", "Schleswig-Holstein"),
|
||||
GermanStateOption("DE-TH", "Thüringen"),
|
||||
)
|
||||
|
||||
internal enum class ClubStammdatenSection {
|
||||
ClubSettings,
|
||||
PredefinedActivities,
|
||||
@@ -122,227 +102,6 @@ private fun StammdatenTopBar(title: String, onBack: () -> Unit) {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
|
||||
private fun defaultQuality(): MemberDataQualityRequirements = MemberDataQualityRequirements()
|
||||
|
||||
private fun normalizeQuality(m: MemberDataQualityRequirements?): MemberDataQualityRequirements {
|
||||
val d = defaultQuality()
|
||||
if (m == null) return d
|
||||
return MemberDataQualityRequirements(
|
||||
requireStreet = m.requireStreet,
|
||||
requirePostalCode = m.requirePostalCode,
|
||||
requireCity = m.requireCity,
|
||||
requirePhone = m.requirePhone,
|
||||
requireEmail = m.requireEmail,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MobileClubSettingsScreen(dependencies: AppDependencies, onBack: () -> Unit) {
|
||||
val languageCode = LocalLanguageCode.current
|
||||
fun tr(key: String, fb: String) = MobileStrings.get(languageCode, key, fb)
|
||||
val clubState by dependencies.clubManager.state.collectAsState()
|
||||
val clubId = clubState.currentClubId ?: return
|
||||
val perms = clubState.currentPermissions
|
||||
val scope = rememberCoroutineScope()
|
||||
var club by remember { mutableStateOf<Club?>(null) }
|
||||
var loadError by remember { mutableStateOf<String?>(null) }
|
||||
var loading by remember { mutableStateOf(true) }
|
||||
var saving by remember { mutableStateOf(false) }
|
||||
var savedHint by remember { mutableStateOf(false) }
|
||||
|
||||
var greeting by remember { mutableStateOf("") }
|
||||
var associationNumber by remember { mutableStateOf("") }
|
||||
var countryCode by remember { mutableStateOf("DE") }
|
||||
var stateCode by remember { mutableStateOf("") }
|
||||
var stateMenu by remember { mutableStateOf(false) }
|
||||
var myTtNickname by remember { mutableStateOf("") }
|
||||
var autoFetch by remember { mutableStateOf(false) }
|
||||
var quality by remember { mutableStateOf(defaultQuality()) }
|
||||
|
||||
LaunchedEffect(clubId) {
|
||||
loading = true
|
||||
loadError = null
|
||||
club = runCatching { dependencies.clubManager.fetchClubDetail(clubId) }
|
||||
.onFailure { loadError = it.message }
|
||||
.getOrNull()
|
||||
val c = club
|
||||
if (c != null) {
|
||||
greeting = c.greetingText.orEmpty()
|
||||
associationNumber = c.associationMemberNumber.orEmpty()
|
||||
countryCode = c.countryCode?.ifBlank { "DE" } ?: "DE"
|
||||
stateCode = c.stateCode.orEmpty()
|
||||
myTtNickname = c.myTischtennisFedNickname.orEmpty()
|
||||
autoFetch = c.autoFetchRankings == true
|
||||
quality = normalizeQuality(c.memberDataQualityRequirements)
|
||||
}
|
||||
loading = false
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(StammdatenPad),
|
||||
) {
|
||||
StammdatenTopBar(tr("clubSettings.title", "Vereinseinstellungen"), onBack)
|
||||
if (perms?.canReadClubSettings() != true) {
|
||||
Text(tr("mobile.noAccess", "Keine Berechtigung."))
|
||||
return@Column
|
||||
}
|
||||
when {
|
||||
loading -> CircularProgressIndicator(modifier = Modifier.padding(top = 24.dp))
|
||||
loadError != null -> Text(loadError!!, color = MaterialTheme.colors.error)
|
||||
club == null -> Text(tr("clubSettings.loadFailed", "Laden fehlgeschlagen"))
|
||||
else -> {
|
||||
Text(tr("clubSettings.greetingText", "Begrüßung"), fontWeight = FontWeight.SemiBold)
|
||||
OutlinedTextField(
|
||||
value = greeting,
|
||||
onValueChange = { greeting = it },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 8.dp),
|
||||
minLines = 4,
|
||||
label = { Text(tr("clubSettings.greetingPlaceholder", "Text")) },
|
||||
)
|
||||
Text(
|
||||
tr("clubSettings.greetingHint", "Platzhalter: {home}, {guest}, …"),
|
||||
style = MaterialTheme.typography.caption,
|
||||
modifier = Modifier.padding(top = 4.dp),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text(tr("clubSettings.associationMemberNumber", "Verbands-Nr."), fontWeight = FontWeight.SemiBold)
|
||||
OutlinedTextField(
|
||||
value = associationNumber,
|
||||
onValueChange = { associationNumber = it },
|
||||
modifier = Modifier.fillMaxWidth().padding(top = 8.dp),
|
||||
singleLine = true,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text("Kalenderregion", fontWeight = FontWeight.SemiBold)
|
||||
Text("Land", style = MaterialTheme.typography.caption)
|
||||
OutlinedTextField(
|
||||
value = countryCode,
|
||||
onValueChange = { countryCode = it.uppercase().take(2) },
|
||||
modifier = Modifier.fillMaxWidth().padding(top = 4.dp),
|
||||
singleLine = true,
|
||||
enabled = false,
|
||||
)
|
||||
Text("Bundesland", style = MaterialTheme.typography.caption, modifier = Modifier.padding(top = 8.dp))
|
||||
Box {
|
||||
TextButton(onClick = { stateMenu = true }, modifier = Modifier.fillMaxWidth()) {
|
||||
Text(
|
||||
germanStates.find { it.code == stateCode }?.name
|
||||
?: tr("mobile.stateNotSet", "Nicht gesetzt"),
|
||||
)
|
||||
}
|
||||
DropdownMenu(expanded = stateMenu, onDismissRequest = { stateMenu = false }) {
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
stateCode = ""
|
||||
stateMenu = false
|
||||
},
|
||||
) { Text(tr("mobile.stateNotSet", "Nicht gesetzt")) }
|
||||
germanStates.forEach { s ->
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
stateCode = s.code
|
||||
stateMenu = false
|
||||
},
|
||||
) { Text(s.name) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text(tr("clubSettings.myTischtennisRankings", "MyTischtennis-Rankings"), fontWeight = FontWeight.SemiBold)
|
||||
RowSwitch(tr("clubSettings.autoFetchRankings", "Automatisch abrufen"), autoFetch) { autoFetch = it }
|
||||
if (autoFetch) {
|
||||
OutlinedTextField(
|
||||
value = myTtNickname,
|
||||
onValueChange = { myTtNickname = it },
|
||||
modifier = Modifier.fillMaxWidth().padding(top = 8.dp),
|
||||
label = { Text(tr("clubSettings.myTischtennisFedNickname", "Verbands-Kurzname")) },
|
||||
singleLine = true,
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text(tr("clubSettings.memberDataQuality", "Pflichtfelder Mitglieder"), fontWeight = FontWeight.SemiBold)
|
||||
RowSwitch(tr("clubSettings.requireStreet", "Straße"), quality.requireStreet) {
|
||||
quality = quality.copy(requireStreet = it)
|
||||
}
|
||||
RowSwitch(tr("clubSettings.requirePostalCode", "PLZ"), quality.requirePostalCode) {
|
||||
quality = quality.copy(requirePostalCode = it)
|
||||
}
|
||||
RowSwitch(tr("clubSettings.requireCity", "Ort"), quality.requireCity) {
|
||||
quality = quality.copy(requireCity = it)
|
||||
}
|
||||
RowSwitch(tr("clubSettings.requirePhone", "Telefon"), quality.requirePhone) {
|
||||
quality = quality.copy(requirePhone = it)
|
||||
}
|
||||
RowSwitch(tr("clubSettings.requireEmail", "E-Mail"), quality.requireEmail) {
|
||||
quality = quality.copy(requireEmail = it)
|
||||
}
|
||||
|
||||
TextButton(
|
||||
onClick = { dependencies.openBackendPath("/club-settings") },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 12.dp)
|
||||
.heightIn(min = StammdatenTouchMin),
|
||||
) {
|
||||
Text(tr("mobile.openTrainingGroupsWeb", "Trainingsgruppen & Zeiten im Browser"))
|
||||
}
|
||||
|
||||
if (savedHint) {
|
||||
Text(
|
||||
tr("clubSettings.saved", "Gespeichert"),
|
||||
color = MaterialTheme.colors.primary,
|
||||
modifier = Modifier.padding(top = 8.dp),
|
||||
)
|
||||
}
|
||||
|
||||
Button(
|
||||
onClick = {
|
||||
if (perms.canWriteClubSettings()) {
|
||||
scope.launch {
|
||||
saving = true
|
||||
savedHint = false
|
||||
runCatching {
|
||||
dependencies.clubManager.updateClubSettings(
|
||||
clubId,
|
||||
UpdateClubSettingsBody(
|
||||
greetingText = greeting,
|
||||
associationMemberNumber = associationNumber,
|
||||
countryCode = countryCode,
|
||||
stateCode = stateCode.ifBlank { null },
|
||||
myTischtennisFedNickname = myTtNickname.ifBlank { null },
|
||||
autoFetchRankings = autoFetch,
|
||||
memberDataQualityRequirements = normalizeQuality(quality),
|
||||
),
|
||||
)
|
||||
club = dependencies.clubManager.fetchClubDetail(clubId)
|
||||
savedHint = true
|
||||
}.onFailure { loadError = it.message }
|
||||
saving = false
|
||||
}
|
||||
}
|
||||
},
|
||||
enabled = !saving && perms.canWriteClubSettings(),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 16.dp)
|
||||
.heightIn(min = StammdatenTouchMin),
|
||||
) {
|
||||
Text(tr("clubSettings.save", "Speichern"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MobilePredefinedActivitiesScreen(dependencies: AppDependencies, onBack: () -> Unit) {
|
||||
val languageCode = LocalLanguageCode.current
|
||||
@@ -431,7 +190,6 @@ private fun MobilePredefinedActivitiesScreen(dependencies: AppDependencies, onBa
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Composable
|
||||
private fun PredefinedActivityEditorDialog(
|
||||
initial: PredefinedActivityDto?,
|
||||
@@ -441,7 +199,7 @@ private fun PredefinedActivityEditorDialog(
|
||||
onSaved: () -> Unit,
|
||||
resolve: (String, String) -> String,
|
||||
scope: kotlinx.coroutines.CoroutineScope,
|
||||
api: de.tt_tagebuch.shared.api.PredefinedActivitiesApi,
|
||||
api: PredefinedActivitiesApi,
|
||||
) {
|
||||
var name by remember(initial?.id, isNew) { mutableStateOf(initial?.name.orEmpty()) }
|
||||
var code by remember(initial?.id, isNew) { mutableStateOf(initial?.code.orEmpty()) }
|
||||
@@ -707,3 +465,17 @@ private fun MobileMemberTransferScreen(dependencies: AppDependencies, onBack: ()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RowSwitch(label: String, checked: Boolean, onChecked: (Boolean) -> Unit) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Text(label, modifier = Modifier.weight(1f))
|
||||
Switch(checked = checked, onCheckedChange = onChecked)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package de.tt_tagebuch.shared.api
|
||||
|
||||
import de.tt_tagebuch.shared.api.http.AuthedHttpClient
|
||||
import de.tt_tagebuch.shared.api.models.CreateClubTrainingGroupBody
|
||||
import de.tt_tagebuch.shared.api.models.TrainingGroupDto
|
||||
import de.tt_tagebuch.shared.api.models.UpdateClubTrainingGroupBody
|
||||
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.put
|
||||
import io.ktor.client.request.setBody
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.http.contentType
|
||||
@@ -31,4 +34,22 @@ class TrainingGroupsApi(
|
||||
suspend fun removeMemberFromGroup(clubId: Int, groupId: Int, memberId: Int) {
|
||||
client.http.delete("/api/training-groups/$clubId/$groupId/member/$memberId")
|
||||
}
|
||||
|
||||
suspend fun createGroup(clubId: Int, name: String): TrainingGroupDto {
|
||||
return client.http.post("/api/training-groups/$clubId") {
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(CreateClubTrainingGroupBody(name = name.trim()))
|
||||
}.body()
|
||||
}
|
||||
|
||||
suspend fun updateGroup(clubId: Int, groupId: Int, name: String, sortOrder: Int?): TrainingGroupDto {
|
||||
return client.http.put("/api/training-groups/$clubId/$groupId") {
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(UpdateClubTrainingGroupBody(name = name.trim(), sortOrder = sortOrder))
|
||||
}.body()
|
||||
}
|
||||
|
||||
suspend fun deleteGroup(clubId: Int, groupId: Int) {
|
||||
client.http.delete("/api/training-groups/$clubId/$groupId")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
package de.tt_tagebuch.shared.api
|
||||
|
||||
import de.tt_tagebuch.shared.api.http.AuthedHttpClient
|
||||
import de.tt_tagebuch.shared.api.models.CreateTrainingTimeBody
|
||||
import de.tt_tagebuch.shared.api.models.TrainingGroupDto
|
||||
import de.tt_tagebuch.shared.api.models.TrainingTimeDto
|
||||
import de.tt_tagebuch.shared.api.models.UpdateTrainingTimeBody
|
||||
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.put
|
||||
import io.ktor.client.request.setBody
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.http.contentType
|
||||
|
||||
class TrainingTimesApi(
|
||||
private val client: AuthedHttpClient,
|
||||
@@ -11,4 +20,22 @@ class TrainingTimesApi(
|
||||
suspend fun listGroupsWithTimes(clubId: Int): List<TrainingGroupDto> {
|
||||
return client.http.get("/api/training-times/$clubId").body()
|
||||
}
|
||||
|
||||
suspend fun createTime(clubId: Int, body: CreateTrainingTimeBody): TrainingTimeDto {
|
||||
return client.http.post("/api/training-times/$clubId") {
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(body)
|
||||
}.body()
|
||||
}
|
||||
|
||||
suspend fun updateTime(clubId: Int, timeId: Int, body: UpdateTrainingTimeBody): TrainingTimeDto {
|
||||
return client.http.put("/api/training-times/$clubId/$timeId") {
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(body)
|
||||
}.body()
|
||||
}
|
||||
|
||||
suspend fun deleteTime(clubId: Int, timeId: Int) {
|
||||
client.http.delete("/api/training-times/$clubId/$timeId")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,13 @@ data class TrainingTimeDto(
|
||||
val sortOrder: Int = 0,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class TrainingGroupMemberBrief(
|
||||
val id: Int,
|
||||
val firstName: String? = null,
|
||||
val lastName: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class TrainingGroupDto(
|
||||
val id: Int = 0,
|
||||
@@ -21,4 +28,32 @@ data class TrainingGroupDto(
|
||||
val isPreset: Boolean = false,
|
||||
val presetType: String? = null,
|
||||
val trainingTimes: List<TrainingTimeDto> = emptyList(),
|
||||
val members: List<TrainingGroupMemberBrief> = emptyList(),
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class CreateClubTrainingGroupBody(
|
||||
val name: String,
|
||||
val sortOrder: Int? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class UpdateClubTrainingGroupBody(
|
||||
val name: String,
|
||||
val sortOrder: Int? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class CreateTrainingTimeBody(
|
||||
val trainingGroupId: Int,
|
||||
val weekday: Int,
|
||||
val startTime: String,
|
||||
val endTime: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class UpdateTrainingTimeBody(
|
||||
val weekday: Int,
|
||||
val startTime: String,
|
||||
val endTime: String,
|
||||
)
|
||||
|
||||
@@ -4,11 +4,14 @@ import de.tt_tagebuch.shared.api.MemberActivitiesApi
|
||||
import de.tt_tagebuch.shared.api.MembersApi
|
||||
import de.tt_tagebuch.shared.api.TrainingGroupsApi
|
||||
import de.tt_tagebuch.shared.api.TrainingTimesApi
|
||||
import de.tt_tagebuch.shared.api.models.CreateTrainingTimeBody
|
||||
import de.tt_tagebuch.shared.api.models.Member
|
||||
import de.tt_tagebuch.shared.api.models.MemberActivityStatDto
|
||||
import de.tt_tagebuch.shared.api.models.MemberLastParticipationDto
|
||||
import de.tt_tagebuch.shared.api.models.MemberSetBody
|
||||
import de.tt_tagebuch.shared.api.models.TrainingGroupDto
|
||||
import de.tt_tagebuch.shared.api.models.TrainingTimeDto
|
||||
import de.tt_tagebuch.shared.api.models.UpdateTrainingTimeBody
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
@@ -43,6 +46,20 @@ class MembersManager(
|
||||
|
||||
suspend fun listTrainingGroups(clubId: Int): List<TrainingGroupDto> = trainingGroupsApi.listGroups(clubId)
|
||||
|
||||
suspend fun listMembersForClub(clubId: Int, activeOnly: Boolean = true): List<Member> =
|
||||
membersApi.listMembers(clubId, showAll = !activeOnly)
|
||||
.sortedWith(compareBy<Member> { it.lastName.lowercase() }.thenBy { it.firstName.lowercase() })
|
||||
|
||||
suspend fun createClubTrainingGroup(clubId: Int, name: String): TrainingGroupDto =
|
||||
trainingGroupsApi.createGroup(clubId, name)
|
||||
|
||||
suspend fun updateClubTrainingGroup(clubId: Int, groupId: Int, name: String, sortOrder: Int?): TrainingGroupDto =
|
||||
trainingGroupsApi.updateGroup(clubId, groupId, name, sortOrder)
|
||||
|
||||
suspend fun deleteClubTrainingGroup(clubId: Int, groupId: Int) {
|
||||
trainingGroupsApi.deleteGroup(clubId, groupId)
|
||||
}
|
||||
|
||||
suspend fun listMemberTrainingGroups(clubId: Int, memberId: Int): List<TrainingGroupDto> =
|
||||
trainingGroupsApi.listMemberGroups(clubId, memberId)
|
||||
|
||||
@@ -54,6 +71,16 @@ class MembersManager(
|
||||
trainingGroupsApi.removeMemberFromGroup(clubId, groupId, memberId)
|
||||
}
|
||||
|
||||
suspend fun createClubTrainingTime(clubId: Int, body: CreateTrainingTimeBody): TrainingTimeDto =
|
||||
trainingTimesApi.createTime(clubId, body)
|
||||
|
||||
suspend fun updateClubTrainingTime(clubId: Int, timeId: Int, body: UpdateTrainingTimeBody): TrainingTimeDto =
|
||||
trainingTimesApi.updateTime(clubId, timeId, body)
|
||||
|
||||
suspend fun deleteClubTrainingTime(clubId: Int, timeId: Int) {
|
||||
trainingTimesApi.deleteTime(clubId, timeId)
|
||||
}
|
||||
|
||||
suspend fun memberActivityStats(clubId: Int, memberId: Int, period: String = "year"): List<MemberActivityStatDto> =
|
||||
memberActivitiesApi.listActivityStats(clubId, memberId, period)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user