Teams handing in progress

This commit is contained in:
Claude Brisson
2024-04-15 16:33:17 +02:00
parent 2395c4e071
commit 179a502bbc
20 changed files with 249 additions and 68 deletions

View File

@@ -88,7 +88,7 @@ fun Tournament<*>.getSortedPairables(round: Int): List<Json.Object> {
Criterion.DC -> StandingsHandler.nullMap Criterion.DC -> StandingsHandler.nullMap
} }
} }
val pairables = pairables.values.filter { it.final }.map { it.toMutableJson() } val pairables = pairables.values.filter { it.final }.map { it.toDetailedJson() }
pairables.forEach { player -> pairables.forEach { player ->
for (crit in criteria) { for (crit in criteria) {
player[crit.first] = (crit.second[player.getID()] ?: 0.0).toInt() player[crit.first] = (crit.second[player.getID()] ?: 0.0).toInt()

View File

@@ -14,8 +14,8 @@ object PlayerHandler: PairgothApiHandler {
override fun get(request: HttpServletRequest, response: HttpServletResponse): Json? { override fun get(request: HttpServletRequest, response: HttpServletResponse): Json? {
val tournament = getTournament(request) val tournament = getTournament(request)
return when (val pid = getSubSelector(request)?.toIntOrNull()) { return when (val pid = getSubSelector(request)?.toIntOrNull()) {
null -> tournament.pairables.values.map { it.toJson() }.toJsonArray() null -> tournament.players.values.map { it.toJson() }.toJsonArray()
else -> tournament.pairables[pid]?.toJson() ?: badRequest("no player with id #${pid}") else -> tournament.players[pid]?.toJson() ?: badRequest("no player with id #${pid}")
} }
} }

View File

@@ -14,8 +14,8 @@ object TeamHandler: PairgothApiHandler {
val tournament = getTournament(request) val tournament = getTournament(request)
if (tournament !is TeamTournament) badRequest("tournament is not a team tournament") if (tournament !is TeamTournament) badRequest("tournament is not a team tournament")
return when (val pid = getSubSelector(request)?.toIntOrNull()) { return when (val pid = getSubSelector(request)?.toIntOrNull()) {
null -> tournament.teams.values.map { it.toJson() }.toJsonArray() null -> tournament.teams.values.map { it.toDetailedJson() }.toJsonArray()
else -> tournament.teams[pid]?.toJson() ?: badRequest("no team with id #${pid}") else -> tournament.teams[pid]?.toDetailedJson() ?: badRequest("no team with id #${pid}")
} }
} }

View File

@@ -26,12 +26,14 @@ object TournamentHandler: PairgothApiHandler {
else -> else ->
when { when {
ApiServlet.isJson(accept) -> { ApiServlet.isJson(accept) -> {
getStore(request).getTournament(id)?.let { getStore(request).getTournament(id)?.let { tour ->
if (accept == "application/pairgoth") { if (accept == "application/pairgoth") {
it.toFullJson() tour.toFullJson()
} else { } else {
it.toJson().also { json -> tour.toJson().also { json ->
(json as Json.MutableObject)["stats"] = it.stats() // additional attributes for the webapp
json["stats"] = tour.stats()
json["teamSize"] = tour.type.playersNumber
} }
} }
} ?: badRequest("no tournament with id #${id}") } ?: badRequest("no tournament with id #${id}")

View File

@@ -8,13 +8,14 @@ import java.util.*
// Pairable // Pairable
sealed class Pairable(val id: ID, val name: String, open val rating: Int, open val rank: Int, val final: Boolean, val mmsCorrection: Int = 0) { sealed class Pairable(val id: ID, val name: String, val rating: Int, val rank: Int, val final: Boolean, val mmsCorrection: Int = 0) {
companion object { companion object {
const val MIN_RANK: Int = -30 // 30k const val MIN_RANK: Int = -30 // 30k
const val MAX_RANK: Int = 8 // 9D const val MAX_RANK: Int = 8 // 9D
} }
abstract fun toJson(): Json.Object fun toJson(): Json.Object = toMutableJson()
abstract fun toMutableJson(): Json.MutableObject abstract fun toMutableJson(): Json.MutableObject
open fun toDetailedJson() = toMutableJson()
abstract val club: String? abstract val club: String?
abstract val country: String? abstract val country: String?
open fun fullName(separator: String = " "): String { open fun fullName(separator: String = " "): String {
@@ -28,9 +29,6 @@ sealed class Pairable(val id: ID, val name: String, open val rating: Int, open v
} }
object ByePlayer: Pairable(0, "bye", 0, Int.MIN_VALUE, true) { object ByePlayer: Pairable(0, "bye", 0, Int.MIN_VALUE, true) {
override fun toJson(): Json.Object {
throw Error("bye player should never be serialized")
}
override fun toMutableJson(): Json.MutableObject { override fun toMutableJson(): Json.MutableObject {
throw Error("bye player should never be serialized") throw Error("bye player should never be serialized")
} }
@@ -95,7 +93,6 @@ class Player(
} }
} }
override fun toJson(): Json.Object = toMutableJson()
override fun fullName(separator: String): String { override fun fullName(separator: String): String {
return name + separator + firstname return name + separator + firstname
} }

View File

@@ -8,7 +8,6 @@ import java.time.LocalDate
import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest
import org.jeudego.pairgoth.pairing.solver.MacMahonSolver import org.jeudego.pairgoth.pairing.solver.MacMahonSolver
import org.jeudego.pairgoth.pairing.solver.SwissSolver import org.jeudego.pairgoth.pairing.solver.SwissSolver
import org.jeudego.pairgoth.store.Store
import org.jeudego.pairgoth.store.nextPlayerId import org.jeudego.pairgoth.store.nextPlayerId
import org.jeudego.pairgoth.store.nextTournamentId import org.jeudego.pairgoth.store.nextTournamentId
import kotlin.math.max import kotlin.math.max
@@ -172,39 +171,53 @@ class TeamTournament(
gobanSize: Int = 19, gobanSize: Int = 19,
komi: Double = 7.5 komi: Double = 7.5
): Tournament<TeamTournament.Team>(id, type, name, shortName, startDate, endDate, director, country, location, online, timeSystem, rounds, pairing, rules, gobanSize, komi) { ): Tournament<TeamTournament.Team>(id, type, name, shortName, startDate, endDate, director, country, location, online, timeSystem, rounds, pairing, rules, gobanSize, komi) {
companion object {} companion object {
private val epsilon = 0.0001
}
override val players = mutableMapOf<ID, Player>() override val players = mutableMapOf<ID, Player>()
val teams: MutableMap<ID, Team> = _pairables val teams: MutableMap<ID, Team> = _pairables
inner class Team(id: ID, name: String, final: Boolean): Pairable(id, name, 0, 0, final) { private fun List<Int>.average(provider: (Player)->Int) = (sumOf {id -> provider(players[id]!!)} - epsilon / players.size).roundToInt()
inner class Team(id: ID, name: String, rating: Int, rank: Int, final: Boolean, mmsCorrection: Int = 0): Pairable(id, name, rating, rank, final, mmsCorrection) {
val playerIds = mutableSetOf<ID>() val playerIds = mutableSetOf<ID>()
val teamPlayers: Set<Player> get() = playerIds.mapNotNull { players[id] }.toSet() val teamPlayers: Set<Player> get() = playerIds.mapNotNull { players[it] }.toSet()
override val rating: Int get() = if (teamPlayers.isEmpty()) super.rating else (teamPlayers.sumOf { player -> player.rating.toDouble() } / players.size).roundToInt() override val club: String? get() = teamPlayers.map { it.club }.distinct().let { if (it.size == 1) it[0] else null }
override val rank: Int get() = if (teamPlayers.isEmpty()) super.rank else (teamPlayers.sumOf { player -> player.rank.toDouble() } / players.size).roundToInt() override val country: String? get() = teamPlayers.map { it.country }.distinct().let { if (it.size == 1) it[0] else null }
override val club: String? get() = teamPlayers.map { club }.distinct().let { if (it.size == 1) it[0] else null }
override val country: String? get() = teamPlayers.map { country }.distinct().let { if (it.size == 1) it[0] else null }
override fun toMutableJson() = Json.MutableObject( override fun toMutableJson() = Json.MutableObject(
"id" to id, "id" to id,
"name" to name, "name" to name,
"players" to playerIds.toList().toJsonArray() "players" to playerIds.toList().toJsonArray()
) )
override fun toJson(): Json.Object = toMutableJson()
override fun toDetailedJson() = toMutableJson().also { json ->
json["rank"] = rank
country?.also { json["country"] = it }
club?.also { json["club"] = it }
}
val teamOfIndividuals: Boolean get() = type.individual val teamOfIndividuals: Boolean get() = type.individual
} }
fun teamFromJson(json: Json.Object, default: TeamTournament.Team? = null) = Team( fun teamFromJson(json: Json.Object, default: TeamTournament.Team? = null): Team {
id = json.getInt("id") ?: default?.id ?: nextPlayerId, val teamPlayersIds = json.getArray("players")?.let { arr ->
name = json.getString("name") ?: default?.name ?: badRequest("missing name"), arr.map {
final = json.getBoolean("final") ?: default?.final ?: badRequest("missing final") if (it != null && it is Number) it.toInt().also { id ->
).apply { if (!players.containsKey(id)) badRequest("invalid player id: ${id}")
json.getArray("players")?.let { arr -> }
arr.mapTo(playerIds) {
if (it != null && it is Number) it.toInt().also { id -> players.containsKey(id) }
else badRequest("invalid players array") else badRequest("invalid players array")
} }
} ?: badRequest("missing players") } ?: badRequest("missing players")
return Team(
id = json.getInt("id") ?: default?.id ?: nextPlayerId,
name = json.getString("name") ?: default?.name ?: badRequest("missing name"),
rating = json.getInt("rating") ?: default?.rating ?: teamPlayersIds.average(Player::rating),
rank = json.getInt("rank") ?: default?.rank ?: teamPlayersIds.average(Player::rank),
final = teamPlayersIds.all { players[it]!!.final },
mmsCorrection = json.getInt("mmsCorrection") ?: default?.mmsCorrection ?: 0
).also {
it.playerIds.addAll(teamPlayersIds)
}
} }
} }
// Serialization // Serialization
@@ -250,10 +263,16 @@ fun Tournament.Companion.fromJson(json: Json.Object, default: Tournament<*>? = n
rounds = json.getInt("rounds") ?: default?.rounds ?: badRequest("missing rounds"), rounds = json.getInt("rounds") ?: default?.rounds ?: badRequest("missing rounds"),
pairing = json.getObject("pairing")?.let { Pairing.fromJson(it, default?.pairing) } ?: default?.pairing ?: badRequest("missing pairing") pairing = json.getObject("pairing")?.let { Pairing.fromJson(it, default?.pairing) } ?: default?.pairing ?: badRequest("missing pairing")
) )
(json["players"] as Json.Array?)?.forEach { obj -> json.getArray("players")?.forEach { obj ->
val pairable = obj as Json.Object val pairable = obj as Json.Object
tournament.players[pairable.getID("id")!!] = Player.fromJson(pairable) tournament.players[pairable.getID("id")!!] = Player.fromJson(pairable)
} }
if (tournament is TeamTournament) {
json.getArray("teams")?.forEach { obj ->
val team = obj as Json.Object
tournament.teams[team.getID("id")!!] = tournament.teamFromJson(team)
}
}
(json["games"] as Json.Array?)?.forEachIndexed { i, arr -> (json["games"] as Json.Array?)?.forEachIndexed { i, arr ->
val round = i + 1 val round = i + 1
val tournamentGames = tournament.games(round) val tournamentGames = tournament.games(round)
@@ -286,7 +305,7 @@ fun Tournament<*>.toJson() = Json.MutableObject(
) )
fun Tournament<*>.toFullJson(): Json.Object { fun Tournament<*>.toFullJson(): Json.Object {
val json = Json.MutableObject(toJson()) val json = toJson()
json["players"] = Json.Array(players.values.map { it.toJson() }) json["players"] = Json.Array(players.values.map { it.toJson() })
if (this is TeamTournament) { if (this is TeamTournament) {
json["teams"] = Json.Array(teams.values.map { it.toJson() }) json["teams"] = Json.Array(teams.values.map { it.toJson() })

View File

@@ -2,11 +2,13 @@ package org.jeudego.pairgoth.test
import com.republicate.kson.Json import com.republicate.kson.Json
import com.republicate.kson.toJsonObject import com.republicate.kson.toJsonObject
import com.republicate.kson.toMutableJsonObject
import org.jeudego.pairgoth.model.ID import org.jeudego.pairgoth.model.ID
import org.junit.jupiter.api.MethodOrderer.MethodName import org.junit.jupiter.api.MethodOrderer.MethodName
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.TestMethodOrder import org.junit.jupiter.api.TestMethodOrder
import java.io.Serializable
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertNotNull import kotlin.test.assertNotNull
import kotlin.test.assertTrue import kotlin.test.assertTrue
@@ -143,7 +145,13 @@ class BasicTests: TestBase() {
// filter out "id", and also "komi", "rules" and "gobanSize" which were provided by default // filter out "id", and also "komi", "rules" and "gobanSize" which were provided by default
// also filter out "pairing", which is filled by all default values // also filter out "pairing", which is filled by all default values
val cmp = Json.Object(*resp.entries.filter { it.key !in listOf("id", "komi", "rules", "gobanSize", "pairing") }.map { Pair(it.key, it.value) }.toTypedArray()) val cmp = Json.Object(*resp.entries.filter { it.key !in listOf("id", "komi", "rules", "gobanSize", "pairing") }.map { Pair(it.key, it.value) }.toTypedArray())
val expected = aTournament.entries.filter { it.key != "pairing" }.map { Pair(it.key, it.value) }.toMap().toJsonObject() val expected = aTournament.entries.filter { it.key != "pairing" }.map { Pair(it.key, it.value) }.toMap().toMutableJsonObject().also { map ->
map["stats"] = Json.Array(
Json.Object("participants" to 0, "paired" to 0, "games" to 0, "ready" to 0),
Json.Object("participants" to 0, "paired" to 0, "games" to 0, "ready" to 0)
)
map["teamSize"] = 1
}
assertEquals(expected.toString(), cmp.toString(), "tournament differs") assertEquals(expected.toString(), cmp.toString(), "tournament differs")
} }

View File

@@ -97,4 +97,11 @@ class PairgothTool {
} }
fun getRatingsDates() = RatingsManager.getRatingsDates() fun getRatingsDates() = RatingsManager.getRatingsDates()
fun getTeamables(players: Collection<Json.Object>, teams: Collection<Json.Object>): List<Json.Object> {
val teamed = teams.flatMap { team ->
team.getArray("players")!!.map { it -> it as Long }
}.toSet()
return players.filter { p -> !teamed.contains(p.getLong("id")) }
}
} }

View File

@@ -237,6 +237,45 @@
} }
} }
/* teams section */
#teams-content {
display: flex;
flex-flow: column;
justify-content: start;
align-items: center;
}
#teams-lists {
margin-top: 1em;
flex-grow: 2;
display: flex;
flex-flow: row wrap;
justify-content: center;
gap: 1em;
align-items: start;
}
#teams-buttons {
display: flex;
flex-flow: column nowrap;
justify-content: start;
align-items: stretch;
gap: 1em;
max-width: max(10em, 20vw);
}
#teams-right {
display: inline-flex;
flex-flow: row wrap;
gap: 1em;
flex-shrink: 1;
max-width: max(300px, 60vw);
justify-content: center;
}
#teams {
max-width: max(50vw, 20em);
}
/* pairing section */ /* pairing section */
#pairing-content { #pairing-content {
@@ -428,7 +467,7 @@
} }
} }
@media(max-width: 1400px) { @media(max-width: 1600px) {
.ui.steps > .step:not(.active) { .ui.steps > .step:not(.active) {
padding-left: 1.2em; padding-left: 1.2em;
padding-right: 0.8em; padding-right: 0.8em;

View File

@@ -81,36 +81,43 @@ function updatePairable() {
} }
onLoad(()=>{ onLoad(()=>{
// note - this handler is also in use for lists on Mac Mahon super groups and teams pages
$('.listitem').on('click', e => { $('.listitem').on('click', e => {
let listitem = e.target.closest('.listitem');
let box = e.target.closest('.multi-select');
if (e.shiftKey && typeof(focused) !== 'undefined') { if (e.shiftKey && typeof(focused) !== 'undefined') {
let from = focused.index('.listitem'); let from = focused.index('.listitem');
let to = e.target.closest('.listitem').index('.listitem'); let to = listitem.index('.listitem');
if (from > to) { if (from > to) {
let tmp = from; let tmp = from;
from = to; from = to;
to = tmp; to = tmp;
} }
let parent = e.target.closest('.multi-select'); let children = box.childNodes.filter('.listitem');
let children = parent.childNodes.filter('.listitem');
for (let j = from; j <= to; ++j) { new Tablesort($('#players')[0]); for (let j = from; j <= to; ++j) { new Tablesort($('#players')[0]);
children.item(j).addClass('selected'); children.item(j).addClass('selected');
children.item(j).attr('draggable', true); children.item(j).attr('draggable', true);
} }
} else { } else {
let target = e.target.closest('.listitem');
if (e.detail === 1) { if (e.detail === 1) {
focused = target.toggleClass('selected').attr('draggable', target.hasClass('selected')); // single click
} else if (target.closest('#paired')) { focused = listitem.toggleClass('selected').attr('draggable', listitem.hasClass('selected'));
focused = target.attr('draggable', target.hasClass('selected')); } else if (listitem.closest('#pairing-lists')) {
editGame(focused); // on pairing page
} else { if (listitem.closest('#paired')) {
editPairable(focused); // double click
focused = listitem.attr('draggable', listitem.hasClass('selected'));
editGame(focused);
} else if (listitem.closest('#pairables')) {
editPairable(focused);
}
} }
} }
box.dispatchEvent(new CustomEvent('listitems'));
}); });
$('#pair').on('click', e => { $('#pair').on('click', e => {
let parts = $('#pairables .selected.listitem').map(item => parseInt(item.data("id"))); let parts = $('#pairables .selected.listitem').map(item => parseInt(item.data("id")));
if (parts.length == 0) { if (parts.length) {
$('#pairables .listitem').addClass('selected'); $('#pairables .listitem').addClass('selected');
parts = $('#pairables .selected.listitem').map(item => parseInt(item.data("id"))); parts = $('#pairables .selected.listitem').map(item => parseInt(item.data("id")));
} }

View File

@@ -0,0 +1,44 @@
function teamUp(players) {
api.postJson(`tour/${tour_id}/team`, {
"name": $('#team-name')[0].value,
"players": players
}).then(rst => {
if (rst !== 'error') {
document.location.reload();
}
});
}
function split(teams) {
let promises = teams.map(team => api.deleteJson(`tour/${tour_id}/team/${team}`));
Promise.all(promises)
.then(rsts => {
for (let rst of rsts) {
if (!rst.success) console.error(rst.error)
}
document.location.reload();
});
}
onLoad(() => {
$('#teamup').on('click', e => {
let rows = $('#teamables .selected.listitem')
let players = rows.map(item => parseInt(item.data("id")));
if (players.length !== 0) teamUp(players);
});
$('#split').on('click', e => {
let rows = $('#teams .selected.listitem')
let teams = rows.map(item => parseInt(item.data("id")));
if (teams.length !== 0) split(teams);
});
$('#teamables').on('listitems', () => {
let rows = $('#teamables .selected.listitem');
if (rows.length === teamSize) {
$('#team-name')[0].value = rows.map(row => row.data('name')).join('-');
$('#teamup').removeClass('disabled');
} else {
$('#team-name')[0].value = '';
$('#teamup').addClass('disabled');
}
});
});

View File

@@ -11,7 +11,11 @@
#set($round = $math.min($math.max($round, 1), $tour.rounds)) #set($round = $math.min($math.max($round, 1), $tour.rounds))
#end #end
<div class="section"> <div class="section">
#set($parts = $api.get("tour/${params.id}/part")) #if($tour.type == 'INDIVIDUAL' || $tour.type.startsWith('TEAM'))
#set($parts = $api.get("tour/${params.id}/part"))
#else
#set($parts = $api.get("tour/${params.id}/team"))
#end
#set($pmap = $utils.toMap($parts)) #set($pmap = $utils.toMap($parts))
#set($roundPairing = $api.get("tour/${params.id}/pair/$round")) #set($roundPairing = $api.get("tour/${params.id}/pair/$round"))
#if($roundPairing.error) #if($roundPairing.error)
@@ -46,13 +50,13 @@
<div class="players"> <div class="players">
<div class="white player"> <div class="white player">
<div class="color">White</div> <div class="color">White</div>
<div class="name">$white.name $white.firstname #rank($white.rank)<br/>($white.country.toUpperCase(), $white.club)</div> <div class="name">$white.name $!white.firstname #rank($white.rank)<br/>#if($white.country)($white.country.toUpperCase()#if($white.club), $white.club#end)#end</div>
## <div class="pin">$white.egf</div> ## <div class="pin">$white.egf</div>
</div> </div>
<div class="equal">½-½</div> <div class="equal">½-½</div>
<div class="black player"> <div class="black player">
<div class="color">Black</div> <div class="color">Black</div>
<div class="name">$black.name $black.firstname #rank($black.rank)<br/>($black.country.toUpperCase(), $black.club)</div> <div class="name">$black.name $!black.firstname #rank($black.rank)<br/>#if($black.country)($black.country.toUpperCase()#if($black.club), $black.club#end)#end</div>
## <div class="pin">$black.egf</div> ## <div class="pin">$black.egf</div>
</div> </div>
</div> </div>

View File

@@ -62,8 +62,8 @@
<span class="info"></span> <span class="info"></span>
<select name="type"> <select name="type">
<option value="INDIVIDUAL" #if(!$tour || $tour.type == 'INDIVIDUAL') selected #end>Individual players</option> <option value="INDIVIDUAL" #if(!$tour || $tour.type == 'INDIVIDUAL') selected #end>Individual players</option>
#* TODO
<option value="PAIRGO" #if($tour && $tour.type == 'PAIRGO') selected #end>Pair-go tournament</option> <option value="PAIRGO" #if($tour && $tour.type == 'PAIRGO') selected #end>Pair-go tournament</option>
#* TODO
<option value="RENGO2" #if($tour && $tour.type == 'RENGO2') selected #end>Rengo with 2 players teams</option> <option value="RENGO2" #if($tour && $tour.type == 'RENGO2') selected #end>Rengo with 2 players teams</option>
<option value="RENGO3" #if($tour && $tour.type == 'RENGO3') selected #end>Rengo with 3 players team</option> <option value="RENGO3" #if($tour && $tour.type == 'RENGO3') selected #end>Rengo with 3 players team</option>
<option value="TEAM2" #if($tour && $tour.type == 'TEAM2') selected #end>Team of 2 individual players</option> <option value="TEAM2" #if($tour && $tour.type == 'TEAM2') selected #end>Team of 2 individual players</option>

View File

@@ -9,6 +9,13 @@
<div class="title">Registration</div> <div class="title">Registration</div>
</div> </div>
</div> </div>
#if($tour.type != 'INDIVIDUAL')
<div class="step" data-step="teams">
<div class="content">
<div class="title">Teams</div>
</div>
</div>
#end
<div class="step" data-step="pairing"> <div class="step" data-step="pairing">
<div class="content"> <div class="content">
<div class="title">Pairing</div> <div class="title">Pairing</div>

View File

@@ -27,13 +27,13 @@
<div id="pairables" class="multi-select" title="pairable players"> <div id="pairables" class="multi-select" title="pairable players">
#foreach($p in $pairables) #foreach($p in $pairables)
#set($part = $pmap[$p]) #set($part = $pmap[$p])
<div data-id="$part.id" class="listitem pairable"><span class="name">$part.name $part.firstname</span><span>#rank($part.rank) $part.country</span></div> <div data-id="$part.id" class="listitem pairable"><span class="name">$part.name#if($part.firstname) $part.firstname#end</span><span>#rank($part.rank)#if($part.country) $part.country#end</span></div>
#end #end
</div> </div>
<div id="unpairables" class="multi-select" title="unpairable players"> <div id="unpairables" class="multi-select" title="unpairable players">
#foreach($p in $unpairables) #foreach($p in $unpairables)
#set($part = $pmap[$p]) #set($part = $pmap[$p])
<div data-id="$part.id" class="listitem unpairable"><span class="name">$part.name $part.firstname</span><span>#rank($part.rank) $part.country</span></div> <div data-id="$part.id" class="listitem unpairable"><span class="name">$part.name#if($part.firstname) $part.firstname#end</span><span>#rank($part.rank)#if($part.country) $part.country#end</span></div>
#end #end
</div> </div>
</div> </div>
@@ -59,9 +59,9 @@
#set($black = $pmap[$game.b]) #set($black = $pmap[$game.b])
<div class="listitem game" data-id="$game.id"> <div class="listitem game" data-id="$game.id">
<div class="table" data-value="$game.t">#$game.t</div> <div class="table" data-value="$game.t">#$game.t</div>
<div class="white" data-id="$game.w">#if($white)$white.name $white.firstname#{else}BIP#end</div> <div class="white" data-id="$game.w">#if($white)$white.name#if($white.firstname) $white.firstname#end#{else}BIP#end</div>
<div class="levels">#if($white)#rank($white.rank)#end&nbsp;/&nbsp;#if($black)#rank($black.rank)#end</div> <div class="levels">#if($white)#rank($white.rank)#end&nbsp;/&nbsp;#if($black)#rank($black.rank)#end</div>
<div class="black" data-id="$game.b">#if($black)$black.name $black.firstname#{else}BIP#end</div> <div class="black" data-id="$game.b">#if($black)$black.name#if($black.firstname) $black.firstname#end#{else}BIP#end</div>
<div class="handicap" data-value="$game.h">#if($game.h)h$game.h#{else}&nbsp;#end</div> <div class="handicap" data-value="$game.h">#if($game.h)h$game.h#{else}&nbsp;#end</div>
</div> </div>
#end #end

View File

@@ -1,5 +1,12 @@
#set($parts = $api.get("tour/${params.id}/part")) #set($parts = $api.get("tour/${params.id}/part"))
#set($pmap = $utils.toMap($parts)) #if($tour.type == 'INDIVIDUAL' || $tour.type.startsWith('TEAM'))
#set($pmap = $utils.toMap($parts))
#else
#set($teams = $api.get("tour/${params.id}/team"))
$log.debug("@@@@@@@@@ teams = $teams")
#set($pmap = $utils.toMap($teams))
#end
<div class="tab-content" id="registration-tab"> <div class="tab-content" id="registration-tab">
<div id="reg-view"> <div id="reg-view">
<div id="list-header"> <div id="list-header">
@@ -27,11 +34,11 @@
<th>First name</th> <th>First name</th>
<th>Country</th> <th>Country</th>
<th>Club</th> <th>Club</th>
#if($tour.country == 'FR') ##if($tour.country == 'FR')
<th>FFG</th> ## <th>FFG</th>
#else ##else
<th>PIN</th> <th>PIN</th>
#end ##end
<th>Rank</th> <th>Rank</th>
## TableSort bug which inverts specified sort... ## TableSort bug which inverts specified sort...
<th data-sort-default="1" aria-sort="ascending">Rating</th> <th data-sort-default="1" aria-sort="ascending">Rating</th>
@@ -77,7 +84,7 @@
<i class="plus icon"></i> <i class="plus icon"></i>
Add player Add player
</button> </button>
#if($tour.pairing.type == 'MAC_MAHON') #if($tour.type == 'INDIVIDUAL' && $tour.pairing.type == 'MAC_MAHON')
<button id="edit-macmahon-groups" class="ui right labeled icon floating button"> <button id="edit-macmahon-groups" class="ui right labeled icon floating button">
<i class="pencil icon"></i> <i class="pencil icon"></i>
Mac Mahon groups Mac Mahon groups
@@ -238,7 +245,7 @@
<div class="success-feedback"><i class="big green check icon"></i></div> <div class="success-feedback"><i class="big green check icon"></i></div>
</div> </div>
</div> </div>
#if($tour.pairing.type == 'MAC_MAHON') #if($tour.type == 'INDIVIDUAL' && $tour.pairing.type == 'MAC_MAHON')
#set($mmbase = $api.get("tour/${params.id}/standings/0")) #set($mmbase = $api.get("tour/${params.id}/standings/0"))
#if($mmbase.isObject() && ($mmbase.error || $mmbase.message)) #if($mmbase.isObject() && ($mmbase.error || $mmbase.message))
#if($mmbase.error) #if($mmbase.error)

View File

@@ -30,8 +30,8 @@
#if($black && $white) #if($black && $white)
<tr id="result-$game.id" data-id="$game.id"> <tr id="result-$game.id" data-id="$game.id">
<td data-sort="$game.t">#$game.t</td> <td data-sort="$game.t">#$game.t</td>
<td class="white player #if($game.r == 'w' || $game.r == '#') winner #elseif($game.r == 'b' || $game.r == '0') looser #end" data-id="$white.id" data-sort="$white.name $white.firstname"><span>#if($white)$white.name $white.firstname #rank($white.rank)#{else}BIP#end</span></td> <td class="white player #if($game.r == 'w' || $game.r == '#') winner #elseif($game.r == 'b' || $game.r == '0') looser #end" data-id="$white.id" data-sort="$white.name#if($white.firstname) $white.firstname#end"><span>#if($white)$white.name#if($white.firstname) $white.firstname#end #rank($white.rank)#{else}BIP#end</span></td>
<td class="black player #if($game.r == 'b' || $game.r == '#') winner #elseif($game.r == 'w' || $game.r == '0') looser #end" data-id="$black.id" data-sort="$black.name $black.firstname"><span>#if($black)$black.name $black.firstname #rank($black.rank)#{else}BIP#end</span></td> <td class="black player #if($game.r == 'b' || $game.r == '#') winner #elseif($game.r == 'w' || $game.r == '0') looser #end" data-id="$black.id" data-sort="$black.name#if($black.firstname) $black.firstname#end"><span>#if($black)$black.name#if($black.firstname) $black.firstname#end #rank($black.rank)#{else}BIP#end</span></td>
<td class="result centered" data-sort="$game.r" data-result="$game.r">$dispRst[$game.r]</td> <td class="result centered" data-sort="$game.r" data-result="$game.r">$dispRst[$game.r]</td>
</tr> </tr>
#end #end

View File

@@ -65,9 +65,9 @@
<tr> <tr>
<td>$part.num</td> <td>$part.num</td>
<td>$part.place</td> <td>$part.place</td>
<td>$part.name $part.firstname</td> <td>$part.name#if($part.firstname) $part.firstname#end</td>
<td data-sort="$part.rank">#rank($part.rank)</td> <td data-sort="$part.rank">#rank($part.rank)</td>
<td>$part.country</td> <td>#if($part.country)$part.country#end</td>
<td>$number.format('0.#', $part.NBW)</td> <td>$number.format('0.#', $part.NBW)</td>
#set($mx = $round - 1) #set($mx = $round - 1)
#foreach($r in [0..$mx]) #foreach($r in [0..$mx])

View File

@@ -0,0 +1,34 @@
#set($teamables = $utils.getTeamables($parts, $teams))
<div class="tab-content" id="teams-tab">
<div id="teams-content">
<div id="teams-lists">
<div id="teams-left">
<div id="teamables" class="multi-select" title="team members">
#foreach($part in $teamables)
<div data-id="$part.id" data-name="$part.name" class="listitem pairable"><span class="name">$part.name $part.firstname</span><span>#rank($part.rank) $part.country</span></div>
#end
</div>
</div>
<div id="teams-right">
<div id="teams-buttons">
<div class="vert flex">
<input type="text" id="team-name" placeholder="team name"/>
<button id="teamup" class="ui blue right labeled icon floating disabled button">
<i class="angle double right icon"></i>
Team up
</button>
</div>
<button id="split" class="ui orange right labeled icon floating button">
<i class="angle double left icon"></i>
Split
</button>
</div>
<div id="teams" class="multi-select" title="teams">
#foreach($team in $teams)
<div data-id="$team.id" class="listitem team"><span class="name">$team.name</span><span>#rank($team.rank)#if($team.country) $team.country#end</span></div>
#end
</div>
</div>
</div>
</div>
</div>

View File

@@ -38,6 +38,9 @@
#translate('tour-information.inc.html') #translate('tour-information.inc.html')
#if($tour) #if($tour)
#translate('tour-registration.inc.html') #translate('tour-registration.inc.html')
#if($tour.type != 'INDIVIDUAL')
#translate('tour-teams.inc.html')
#end
#translate('tour-pairing.inc.html') #translate('tour-pairing.inc.html')
#translate('tour-results.inc.html') #translate('tour-results.inc.html')
#translate('tour-standings.inc.html') #translate('tour-standings.inc.html')
@@ -54,7 +57,7 @@
let correction = #if($tour.pairing.type == 'MAC_MAHON')${tour.pairing.handicap.correction}#{else}0#end; let correction = #if($tour.pairing.type == 'MAC_MAHON')${tour.pairing.handicap.correction}#{else}0#end;
let standingsUpToDate = true; let standingsUpToDate = true;
let pairablesUpToDate = true; let pairablesUpToDate = true;
// $params const teamSize = $tour.teamSize;
#end #end
#set($datepickerLocale = $translate.datepickerLocale($request.lang, $request.loc)) #set($datepickerLocale = $translate.datepickerLocale($request.lang, $request.loc))
const datepickerLocale = '$datepickerLocale'; const datepickerLocale = '$datepickerLocale';
@@ -162,6 +165,9 @@
#include('/js/tour-information.inc.js') #include('/js/tour-information.inc.js')
#if($tour) #if($tour)
#include('/js/tour-registration.inc.js') #include('/js/tour-registration.inc.js')
#if($tour.type != 'INDIVIDUAL')
#include('/js/tour-teams.inc.js')
#end
#include('/js/tour-pairing.inc.js') #include('/js/tour-pairing.inc.js')
#include('/js/tour-results.inc.js') #include('/js/tour-results.inc.js')
#include('/js/tour-standings.inc.js') #include('/js/tour-standings.inc.js')