feat: add TTR simulator and calculation engine, enhance member model with TTR adjustment constant
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 59s
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 59s
This commit is contained in:
@@ -5259,20 +5259,36 @@ private fun MembersScreen(
|
||||
}
|
||||
.sortedWith(
|
||||
Comparator { a, b ->
|
||||
val base = when (selectedSort) {
|
||||
"ttr" -> (a.ttr ?: Int.MIN_VALUE).compareTo(b.ttr ?: Int.MIN_VALUE)
|
||||
"qttr" -> (a.qttr ?: Int.MIN_VALUE).compareTo(b.qttr ?: Int.MIN_VALUE)
|
||||
val compareNames = {
|
||||
val byLast = a.lastName.lowercase().compareTo(b.lastName.lowercase())
|
||||
if (byLast != 0) byLast else a.firstName.lowercase().compareTo(b.firstName.lowercase())
|
||||
}
|
||||
if (selectedSort == "ttr" || selectedSort == "qttr") {
|
||||
val ratingA = if (selectedSort == "ttr") a.ttr else a.qttr
|
||||
val ratingB = if (selectedSort == "ttr") b.ttr else b.qttr
|
||||
// Wie in der Web-App: nicht bewertete Mitglieder stehen
|
||||
// unabhängig von der Sortierrichtung immer am Listenende.
|
||||
when {
|
||||
ratingA == null && ratingB != null -> 1
|
||||
ratingA != null && ratingB == null -> -1
|
||||
ratingA == null && ratingB == null -> compareNames()
|
||||
else -> {
|
||||
val direction = if (sortAscending) 1 else -1
|
||||
val ratingDiff = ratingA!!.compareTo(ratingB!!) * direction
|
||||
if (ratingDiff != 0) ratingDiff else compareNames()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val base = when (selectedSort) {
|
||||
"lastTraining" -> a.lastTraining.orEmpty().compareTo(b.lastTraining.orEmpty())
|
||||
"birthDate" -> a.birthDate.orEmpty().compareTo(b.birthDate.orEmpty())
|
||||
"firstName" -> a.firstName.lowercase().compareTo(b.firstName.lowercase())
|
||||
"age" -> (memberAgeYears(a) ?: Int.MIN_VALUE).compareTo(memberAgeYears(b) ?: Int.MIN_VALUE)
|
||||
"openTasks" -> memberOpenTasksCount(a, qualityReq).compareTo(memberOpenTasksCount(b, qualityReq))
|
||||
else -> {
|
||||
val byLast = a.lastName.lowercase().compareTo(b.lastName.lowercase())
|
||||
if (byLast != 0) byLast else a.firstName.lowercase().compareTo(b.firstName.lowercase())
|
||||
}
|
||||
else -> compareNames()
|
||||
}
|
||||
if (sortAscending) base else -base
|
||||
}
|
||||
if (sortAscending) base else -base
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -128,6 +128,15 @@ internal object TeamEditorLineupLogic {
|
||||
return q.toString()
|
||||
}
|
||||
|
||||
/** QTTR-Reihenfolge für Meldungen: bewertete Spieler zuerst, danach Name. */
|
||||
fun compareForLineup(a: Member, b: Member): Int {
|
||||
val ratingDiff = lineupRatingValue(b).compareTo(lineupRatingValue(a))
|
||||
if (ratingDiff != 0) return ratingDiff
|
||||
val byLast = a.lastName.lowercase().compareTo(b.lastName.lowercase())
|
||||
if (byLast != 0) return byLast
|
||||
return a.firstName.lowercase().compareTo(b.firstName.lowercase())
|
||||
}
|
||||
|
||||
fun eligibilityLabel(member: Member, teamAgeGroup: String, seasonLabel: String): String {
|
||||
val mag = memberAgeGroupCode(member, seasonLabel)
|
||||
if (teamAgeGroup != "adult" || mag == "adult") return "OK"
|
||||
@@ -194,11 +203,7 @@ internal object TeamEditorLineupLogic {
|
||||
byCode.getOrPut(code) { mutableListOf() }.add(row)
|
||||
}
|
||||
for (list in byCode.values) {
|
||||
list.sortWith(
|
||||
compareByDescending<MemberDisplay> { lineupRatingValue(it.member) }
|
||||
.thenBy { it.member.lastName.lowercase() }
|
||||
.thenBy { it.member.firstName.lowercase() },
|
||||
)
|
||||
list.sortWith { a, b -> compareForLineup(a.member, b.member) }
|
||||
}
|
||||
return groupOrder.mapNotNull { code ->
|
||||
val rows = byCode[code] ?: return@mapNotNull null
|
||||
|
||||
@@ -176,7 +176,7 @@ internal fun TeamPlanningScreen(
|
||||
if (q.isEmpty()) true
|
||||
else "${it.firstName} ${it.lastName}".lowercase().contains(q)
|
||||
}
|
||||
.sortedWith(compareBy({ it.lastName.lowercase() }, { it.firstName.lowercase() }))
|
||||
.sortedWith(TeamEditorLineupLogic::compareForLineup)
|
||||
}
|
||||
|
||||
val assignedIds = remember(assignments) { assignments.map { it.memberId }.toSet() }
|
||||
@@ -189,7 +189,7 @@ internal fun TeamPlanningScreen(
|
||||
members.filter { m ->
|
||||
m.active && m.testMembership != true && m.id !in interestedIds &&
|
||||
(q.isEmpty() || "${m.firstName} ${m.lastName}".lowercase().contains(q))
|
||||
}.sortedWith(compareBy({ it.lastName.lowercase() }, { it.firstName.lowercase() }))
|
||||
}.sortedWith(TeamEditorLineupLogic::compareForLineup)
|
||||
}
|
||||
|
||||
fun persistTeam(teamId: Int, nextAssignments: List<PlanningAssignment>, onDone: () -> Unit = {}) {
|
||||
@@ -281,6 +281,10 @@ internal fun TeamPlanningScreen(
|
||||
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) {
|
||||
Column(Modifier.weight(1f)) {
|
||||
Text("${m.firstName} ${m.lastName}", fontWeight = FontWeight.Medium)
|
||||
Text(
|
||||
"QTTR ${TeamEditorLineupLogic.lineupRatingLabel(m)}",
|
||||
style = MaterialTheme.typography.caption,
|
||||
)
|
||||
}
|
||||
if (canWrite) {
|
||||
var addMenu by remember(key1 = m.id) { mutableStateOf(false) }
|
||||
@@ -344,10 +348,12 @@ internal fun TeamPlanningScreen(
|
||||
enabled = canWrite && !markingInterest,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Text(
|
||||
addInterestMemberId?.let { id ->
|
||||
membersById[id]?.let { "${it.firstName} ${it.lastName}" }
|
||||
} ?: t("teamManagement.selectMember", "Mitglied wählen"),
|
||||
Text(
|
||||
addInterestMemberId?.let { id ->
|
||||
membersById[id]?.let {
|
||||
"${it.firstName} ${it.lastName} (QTTR ${TeamEditorLineupLogic.lineupRatingLabel(it)})"
|
||||
}
|
||||
} ?: t("teamManagement.selectMember", "Mitglied wählen"),
|
||||
)
|
||||
}
|
||||
DropdownMenu(expanded = addInterestMenu, onDismissRequest = { addInterestMenu = false }) {
|
||||
@@ -357,7 +363,7 @@ internal fun TeamPlanningScreen(
|
||||
addInterestMemberId = m.id
|
||||
addInterestMenu = false
|
||||
},
|
||||
) { Text("${m.firstName} ${m.lastName}") }
|
||||
) { Text("${m.firstName} ${m.lastName} (QTTR ${TeamEditorLineupLogic.lineupRatingLabel(m)})") }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -604,7 +610,7 @@ private fun CardTeamPlanningBlock(
|
||||
Text(t("teamManagement.selectedLineup", "Meldung"), fontWeight = FontWeight.Medium)
|
||||
ordered.forEachIndexed { index, m ->
|
||||
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) {
|
||||
Text("${index + 1}. ${m.firstName} ${m.lastName}")
|
||||
Text("${index + 1}. ${m.firstName} ${m.lastName} (${TeamEditorLineupLogic.lineupRatingLabel(m)})")
|
||||
if (canWrite) {
|
||||
Row {
|
||||
TextButton(onClick = { onMoveInTeam(m.id, -1) }, enabled = index > 0) { Text("↑") }
|
||||
@@ -635,7 +641,7 @@ private fun CardTeamPlanningBlock(
|
||||
onPersistLineup(next)
|
||||
}
|
||||
},
|
||||
) { Text("${m.firstName} ${m.lastName}") }
|
||||
) { Text("${m.firstName} ${m.lastName} (${TeamEditorLineupLogic.lineupRatingLabel(m)})") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user