fix refresh round title

fix refresh final rankings
fix update of loser bracket mode
sync2
Raz 1 year ago
parent d5ea4f5336
commit 8d67d7efab
  1. 2
      PadelClub/Data/Match.swift
  2. 228
      PadelClub/Data/Tournament.swift
  3. 178
      PadelClub/Views/Cashier/CashierSettingsView.swift
  4. 1
      PadelClub/Views/Match/MatchDetailView.swift
  5. 81
      PadelClub/Views/Round/LoserRoundSettingsView.swift
  6. 30
      PadelClub/Views/Round/RoundView.swift
  7. 2
      PadelClub/Views/Team/Components/TeamHeaderView.swift
  8. 20
      PadelClub/Views/Team/TeamRowView.swift
  9. 141
      PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift
  10. 22
      PadelClub/Views/Tournament/Screen/TournamentRankView.swift

@ -517,6 +517,7 @@ defer {
losingTeamId = teamScoreWalkout.teamRegistration
groupStageObject?.updateGroupStageState()
roundObject?.updateTournamentState()
currentTournament()?.updateTournamentState()
updateFollowingMatchTeamScore()
}
@ -542,6 +543,7 @@ defer {
groupStageObject?.updateGroupStageState()
roundObject?.updateTournamentState()
currentTournament()?.updateTournamentState()
updateFollowingMatchTeamScore()
}

@ -1230,115 +1230,143 @@ defer {
teams[groupStage.index * groupStage.size + 1 + teamIndex] = [groupStageTeams[teamIndex].id]
}
}
} else {
return teams
}
let final = rounds.last?.playedMatches().last
if let winner = final?.winningTeamId {
teams[1] = [winner]
ids.insert(winner)
}
if let finalist = final?.losingTeamId {
teams[2] = [finalist]
ids.insert(finalist)
}
let others: [Round] = rounds.flatMap { round in
let losers = round.losers()
let minimumFinalPosition = round.seedInterval()?.last ?? teamCount
if teams[minimumFinalPosition] == nil {
teams[minimumFinalPosition] = losers.map { $0.id }
} else {
teams[minimumFinalPosition]?.append(contentsOf: losers.map { $0.id })
let final = rounds.last?.playedMatches().last
if let winner = final?.winningTeamId {
teams[1] = [winner]
ids.insert(winner)
}
if let finalist = final?.losingTeamId {
teams[2] = [finalist]
ids.insert(finalist)
}
print("round", round.roundTitle())
let rounds = round.loserRoundsAndChildren().filter { $0.isRankDisabled() == false && $0.hasNextRound() == false }
print(rounds.count, rounds.map { $0.roundTitle() })
return rounds
}.compactMap({ $0 })
others.forEach { round in
print("round", round.roundTitle())
if let interval = round.seedInterval() {
print("interval", interval.localizedInterval())
let playedMatches = round.playedMatches().filter { $0.disabled == false || $0.isReady() }
print("playedMatches", playedMatches.count)
let winners = playedMatches.compactMap({ $0.winningTeamId }).filter({ ids.contains($0) == false })
print("winners", winners.count)
let losers = playedMatches.compactMap({ $0.losingTeamId }).filter({ ids.contains($0) == false })
print("losers", losers.count)
if winners.isEmpty {
let disabledIds = playedMatches.flatMap({ $0.teamScores.compactMap({ $0.teamRegistration }) }).filter({ ids.contains($0) == false })
if disabledIds.isEmpty == false {
_removeStrings(from: &teams, stringsToRemove: disabledIds)
teams[interval.last] = disabledIds
let teamNames : [String] = disabledIds.compactMap {
let t : TeamRegistration? = tournamentStore.teamRegistrations.findById($0)
return t
}.map { $0.canonicalName }
print("winners.isEmpty", "\(interval.last) : ", teamNames)
disabledIds.forEach {
ids.insert($0)
let others: [Round] = rounds.flatMap { round in
let losers = round.losers()
let minimumFinalPosition = round.seedInterval()?.last ?? teamCount
if teams[minimumFinalPosition] == nil {
teams[minimumFinalPosition] = losers.map { $0.id }
} else {
teams[minimumFinalPosition]?.append(contentsOf: losers.map { $0.id })
}
print("round", round.roundTitle())
let rounds = round.loserRoundsAndChildren().filter { $0.isRankDisabled() == false && $0.hasNextRound() == false }
print(rounds.count, rounds.map { $0.roundTitle() })
return rounds
}.compactMap({ $0 })
others.forEach { round in
print("round", round.roundTitle())
if let interval = round.seedInterval() {
print("interval", interval.localizedInterval())
let playedMatches = round.playedMatches().filter { $0.disabled == false || $0.isReady() }
print("playedMatches", playedMatches.count)
let winners = playedMatches.compactMap({ $0.winningTeamId }).filter({ ids.contains($0) == false })
print("winners", winners.count)
let losers = playedMatches.compactMap({ $0.losingTeamId }).filter({ ids.contains($0) == false })
print("losers", losers.count)
if winners.isEmpty {
let disabledIds = playedMatches.flatMap({ $0.teamScores.compactMap({ $0.teamRegistration }) }).filter({ ids.contains($0) == false })
if disabledIds.isEmpty == false {
_removeStrings(from: &teams, stringsToRemove: disabledIds)
teams[interval.last] = disabledIds
let teamNames : [String] = disabledIds.compactMap {
let t : TeamRegistration? = tournamentStore.teamRegistrations.findById($0)
return t
}.map { $0.canonicalName }
print("winners.isEmpty", "\(interval.last) : ", teamNames)
disabledIds.forEach {
ids.insert($0)
}
}
} else {
if winners.isEmpty == false {
_removeStrings(from: &teams, stringsToRemove: winners)
teams[interval.first + winners.count - 1] = winners
let teamNames : [String] = winners.compactMap {
let t: TeamRegistration? = tournamentStore.teamRegistrations.findById($0)
return t
}.map { $0.canonicalName }
print("winners", "\(interval.last + winners.count - 1) : ", teamNames)
winners.forEach { ids.insert($0) }
}
if losers.isEmpty == false {
_removeStrings(from: &teams, stringsToRemove: losers)
teams[interval.first + winners.count] = losers
let loserTeamNames : [String] = losers.compactMap {
let t: TeamRegistration? = tournamentStore.teamRegistrations.findById($0)
return t
}.map { $0.canonicalName }
print("losers", "\(interval.first + winners.count) : ", loserTeamNames)
losers.forEach { ids.insert($0) }
}
}
} else {
if winners.isEmpty == false {
_removeStrings(from: &teams, stringsToRemove: winners)
teams[interval.first + winners.count - 1] = winners
let teamNames : [String] = winners.compactMap {
let t: TeamRegistration? = tournamentStore.teamRegistrations.findById($0)
return t
}.map { $0.canonicalName }
print("winners", "\(interval.last + winners.count - 1) : ", teamNames)
winners.forEach { ids.insert($0) }
}
}
if let groupStageLoserBracketPlayedMatches = groupStageLoserBracket()?.playedMatches() {
groupStageLoserBracketPlayedMatches.forEach({ match in
if match.hasEnded() {
let sameMatchIndexCount = groupStageLoserBracketPlayedMatches.filter({ $0.index == match.index }).count
teams.setOrAppend(match.winningTeamId, at: match.index)
teams.setOrAppend(match.losingTeamId, at: match.index + sameMatchIndexCount)
}
if losers.isEmpty == false {
_removeStrings(from: &teams, stringsToRemove: losers)
teams[interval.first + winners.count] = losers
let loserTeamNames : [String] = losers.compactMap {
let t: TeamRegistration? = tournamentStore.teamRegistrations.findById($0)
return t
}.map { $0.canonicalName }
print("losers", "\(interval.first + winners.count) : ", loserTeamNames)
losers.forEach { ids.insert($0) }
})
}
let groupStages = groupStages()
let baseRank = teamCount - groupStageSpots() + qualifiedPerGroupStage * groupStageCount + groupStageAdditionalQualified
let alreadyPlaceTeams = Array(teams.values.flatMap({ $0 }))
groupStages.forEach { groupStage in
let groupStageTeams = groupStage.teams(true)
for (index, team) in groupStageTeams.enumerated() {
if team.qualified == false && alreadyPlaceTeams.contains(team.id) == false {
let groupStageWidth = max(((index == qualifiedPerGroupStage) ? groupStageCount - groupStageAdditionalQualified : groupStageCount) * (index - qualifiedPerGroupStage), 0)
let _index = baseRank + groupStageWidth + 1
if let existingTeams = teams[_index] {
teams[_index] = existingTeams + [team.id]
} else {
teams[_index] = [team.id]
}
}
}
}
}
if let groupStageLoserBracketPlayedMatches = groupStageLoserBracket()?.playedMatches() {
groupStageLoserBracketPlayedMatches.forEach({ match in
if match.hasEnded() {
let sameMatchIndexCount = groupStageLoserBracketPlayedMatches.filter({ $0.index == match.index }).count
teams.setOrAppend(match.winningTeamId, at: match.index)
teams.setOrAppend(match.losingTeamId, at: match.index + sameMatchIndexCount)
}
})
}
return teams
}
func setRankings(finalRanks: [Int: [String]]) async -> [Int: [TeamRegistration]] {
var rankings: [Int: [TeamRegistration]] = [:]
let groupStages = groupStages()
let baseRank = teamCount - groupStageSpots() + qualifiedPerGroupStage * groupStageCount + groupStageAdditionalQualified
let alreadyPlaceTeams = Array(teams.values.flatMap({ $0 }))
groupStages.forEach { groupStage in
let groupStageTeams = groupStage.teams(true)
for (index, team) in groupStageTeams.enumerated() {
if team.qualified == false && alreadyPlaceTeams.contains(team.id) == false {
let groupStageWidth = max(((index == qualifiedPerGroupStage) ? groupStageCount - groupStageAdditionalQualified : groupStageCount) * (index - qualifiedPerGroupStage), 0)
let _index = baseRank + groupStageWidth + 1
if let existingTeams = teams[_index] {
teams[_index] = existingTeams + [team.id]
} else {
teams[_index] = [team.id]
}
finalRanks.keys.sorted().forEach { rank in
if let rankedTeamIds = finalRanks[rank] {
let teams: [TeamRegistration] = rankedTeamIds.compactMap { self.tournamentStore.teamRegistrations.findById($0) }
rankings[rank] = teams
}
}
rankings.keys.sorted().forEach { rank in
if let rankedTeams = rankings[rank] {
rankedTeams.forEach { team in
team.finalRanking = rank
team.pointsEarned = isAnimation() ? nil : tournamentLevel.points(for: rank - 1, count: teamCount)
}
}
}
return teams
do {
try self.tournamentStore.teamRegistrations.addOrUpdate(contentOfs: unsortedTeams())
} catch {
Logger.error(error)
}
return rankings
}
func lockRegistration() {
@ -1977,6 +2005,7 @@ defer {
groupStageMatchFormat = groupStageSmartMatchFormat()
loserBracketMatchFormat = loserBracketSmartMatchFormat(5)
matchFormat = roundSmartMatchFormat(5)
entryFee = tournamentLevel.entryFee
}
func roundSmartMatchFormat(_ roundIndex: Int) -> MatchFormat {
@ -2150,6 +2179,19 @@ defer {
}
func updateTournamentState() {
Task {
if hasEnded() {
let fr = await finalRanking()
_ = await setRankings(finalRanks: fr)
}
}
}
func allLoserRoundMatches() -> [Match] {
rounds().flatMap { $0.loserRoundsAndChildren().flatMap({ $0._matches() }) }
}
// MARK: -
func insertOnServer() throws {

@ -11,34 +11,43 @@ import LeStorage
struct CashierSettingsView: View {
@EnvironmentObject var dataStore: DataStore
@State private var entryFee: Double? = nil
@Bindable var tournament: Tournament
@FocusState private var focusedField: Tournament.CodingKeys?
let priceTags: [Double] = [15.0, 20.0, 25.0]
var tournaments: [Tournament]
init(tournaments: [Tournament]) {
self.tournaments = tournaments
}
init(tournament: Tournament) {
self.tournaments = [tournament]
self.tournament = tournament
_entryFee = State(wrappedValue: tournament.entryFee)
}
var body: some View {
List {
Section {
LabeledContent {
TextField(tournament.isFree() ? "Gratuite" : "Inscription", value: $entryFee, format: .currency(code: Locale.current.currency?.identifier ?? "EUR"))
.keyboardType(.decimalPad)
.multilineTextAlignment(.trailing)
.frame(maxWidth: .infinity)
.focused($focusedField, equals: ._entryFee)
} label: {
Text("Inscription")
}
} footer: {
Text("Si vous souhaitez que Padel Club vous aide à suivre les encaissements, indiquer un prix d'inscription. Sinon Padel Club vous aidera à suivre simplement l'arrivée et la présence des joueurs.")
}
Section {
RowButtonView("Tout le monde est arrivé", role: .destructive) {
for tournament in self.tournaments {
let players = tournament.selectedPlayers() // tournaments.flatMap({ $0.selectedPlayers() })
players.forEach { player in
player.hasArrived = true
}
do {
try tournament.tournamentStore.playerRegistrations.addOrUpdate(contentOfs: players)
} catch {
Logger.error(error)
}
let players = tournament.selectedPlayers() // tournaments.flatMap({ $0.selectedPlayers() })
players.forEach { player in
player.hasArrived = true
}
do {
try tournament.tournamentStore.playerRegistrations.addOrUpdate(contentOfs: players)
} catch {
Logger.error(error)
}
}
} footer: {
Text("Indique tous les joueurs sont là")
@ -46,68 +55,107 @@ struct CashierSettingsView: View {
Section {
RowButtonView("Personne n'est là", role: .destructive) {
for tournament in self.tournaments {
let players = tournament.selectedPlayers() // tournaments.flatMap({ $0.selectedPlayers() })
players.forEach { player in
player.hasArrived = false
}
do {
try tournament.tournamentStore.playerRegistrations.addOrUpdate(contentOfs: players)
} catch {
Logger.error(error)
}
let players = tournament.selectedPlayers() // tournaments.flatMap({ $0.selectedPlayers() })
players.forEach { player in
player.hasArrived = false
}
do {
try tournament.tournamentStore.playerRegistrations.addOrUpdate(contentOfs: players)
} catch {
Logger.error(error)
}
}
} footer: {
Text("Indique qu'aucun joueur n'est arrivé")
}
if tournaments.count > 1 || tournaments.first?.isFree() == false {
Section {
RowButtonView("Tout le monde a réglé", role: .destructive) {
for tournament in self.tournaments {
let players = tournament.selectedPlayers() // tournaments.flatMap({ $0.selectedPlayers() })
players.forEach { player in
if player.hasPaid() == false {
player.paymentType = .gift
}
}
do {
try tournament.tournamentStore.playerRegistrations.addOrUpdate(contentOfs: players)
} catch {
Logger.error(error)
}
Section {
RowButtonView("Tout le monde a réglé", role: .destructive) {
let players = tournament.selectedPlayers() // tournaments.flatMap({ $0.selectedPlayers() })
players.forEach { player in
if player.hasPaid() == false {
player.paymentType = .gift
}
}
} footer: {
Text("Passe tous les joueurs qui n'ont pas réglé en offert")
do {
try tournament.tournamentStore.playerRegistrations.addOrUpdate(contentOfs: players)
} catch {
Logger.error(error)
}
}
Section {
RowButtonView("Personne n'a réglé", role: .destructive) {
for tournament in self.tournaments {
let store = tournament.tournamentStore
let players = tournament.selectedPlayers()
players.forEach { player in
player.paymentType = nil
} footer: {
Text("Passe tous les joueurs qui n'ont pas réglé en offert")
}
Section {
RowButtonView("Personne n'a réglé", role: .destructive) {
let store = tournament.tournamentStore
let players = tournament.selectedPlayers()
players.forEach { player in
player.paymentType = nil
}
do {
try store.playerRegistrations.addOrUpdate(contentOfs: players)
} catch {
Logger.error(error)
}
}
} footer: {
Text("Remet à zéro le type d'encaissement de tous les joueurs")
}
}
.navigationBarBackButtonHidden(focusedField != nil)
.toolbarBackground(.visible, for: .navigationBar)
.toolbar {
if focusedField != nil {
ToolbarItem(placement: .topBarLeading) {
Button("Annuler", role: .cancel) {
focusedField = nil
}
}
ToolbarItem(placement: .keyboard) {
HStack {
if tournament.isFree() {
ForEach(priceTags, id: \.self) { priceTag in
Button(priceTag.formatted(.currency(code: "EUR"))) {
entryFee = priceTag
tournament.entryFee = priceTag
focusedField = nil
}
.buttonStyle(.bordered)
}
do {
try store.playerRegistrations.addOrUpdate(contentOfs: players)
} catch {
Logger.error(error)
} else {
Button("Gratuit") {
entryFee = nil
tournament.entryFee = nil
focusedField = nil
}
.buttonStyle(.bordered)
}
Spacer()
Button("Valider") {
tournament.entryFee = entryFee
focusedField = nil
}
.buttonStyle(.bordered)
}
} footer: {
Text("Remet à zéro le type d'encaissement de tous les joueurs")
}
}
}
.onChange(of: tournament.entryFee) {
_save()
}
}
private func _save() {
do {
try dataStore.tournaments.addOrUpdate(instance: tournament)
} catch {
Logger.error(error)
}
}
}

@ -105,6 +105,7 @@ struct MatchDetailView: View {
RowButtonView("Saisir les résultats", systemImage: "list.clipboard") {
self._editScores()
}
.disabled(match.teams().count < 2)
}
if self.match.currentTournament()?.isFree() == false {

@ -13,7 +13,15 @@ struct LoserRoundSettingsView: View {
@Environment(\.isEditingTournamentSeed) private var isEditingTournamentSeed
@Environment(Tournament.self) var tournament: Tournament
@State var upperBracketRound: UpperRound
@State private var confirmationRequired: Bool = false
@State private var presentConfirmation: Bool = false
@State private var loserBracketMode: LoserBracketMode
init(upperBracketRound: UpperRound) {
self.upperBracketRound = upperBracketRound
_loserBracketMode = .init(wrappedValue: upperBracketRound.round.loserBracketMode)
}
var body: some View {
List {
Section {
@ -23,25 +31,31 @@ struct LoserRoundSettingsView: View {
}
Section {
@Bindable var round: Round = upperBracketRound.round
Picker(selection: $round.loserBracketMode) {
Picker(selection: $loserBracketMode) {
ForEach(LoserBracketMode.allCases) {
Text($0.localizedLoserBracketMode()).tag($0)
}
} label: {
Text("Position des perdants")
}
.onChange(of: round.loserBracketMode) {
do {
try self.tournament.tournamentStore.rounds.addOrUpdate(instance: upperBracketRound.round)
} catch {
Logger.error(error)
.onChange(of: loserBracketMode) {
if upperBracketRound.round.allLoserRoundMatches().anySatisfy({ $0.hasEnded() }) == false {
_refreshLoserBracketMode()
} else {
confirmationRequired = true
}
}
} header: {
Text("Matchs de classement")
} footer: {
Text(upperBracketRound.round.loserBracketMode.localizedLoserBracketModeDescription())
if confirmationRequired == false {
Text(upperBracketRound.round.loserBracketMode.localizedLoserBracketModeDescription())
} else {
_footerViewConfirmationRequired()
.onTapGesture(perform: {
presentConfirmation = true
})
}
}
Section {
@ -81,7 +95,58 @@ struct LoserRoundSettingsView: View {
//todo proposer ici l'impression des matchs de classements peut-être?
}
.confirmationDialog("Attention", isPresented: $presentConfirmation, actions: {
Button("Confirmer", role: .destructive) {
_refreshLoserBracketMode()
confirmationRequired = false
}
Button("Annuler", role: .cancel) {
loserBracketMode = upperBracketRound.round.loserBracketMode
}
})
}
private func _refreshLoserBracketMode() {
let matches = upperBracketRound.round.loserRoundsAndChildren().flatMap({ $0._matches() })
matches.forEach { match in
match.resetTeamScores(outsideOf: [])
match.resetMatch()
if loserBracketMode == .automatic {
match.updateTeamScores()
}
match.confirmed = false
}
upperBracketRound.round.loserBracketMode = loserBracketMode
if loserBracketMode == .automatic {
matches.forEach { match in
match.updateTeamScores()
}
}
do {
try self.tournament.tournamentStore.matches.addOrUpdate(contentOfs: matches)
} catch {
Logger.error(error)
}
do {
try self.tournament.tournamentStore.rounds.addOrUpdate(instance: upperBracketRound.round)
} catch {
Logger.error(error)
}
}
private func _footerViewConfirmationRequired() -> some View {
Text("Au moins un match de classement est terminé, en modifiant ce réglage, les résultats de ces matchs de classement seront perdus.")
+
Text(" Modifier quand même ?").foregroundStyle(.red)
}
}
//#Preview {

@ -275,6 +275,25 @@ struct RoundView: View {
}
}
}
if upperRound.round.index == 0, tournament.hasEnded() {
NavigationLink(value: Screen.rankings) {
LabeledContent {
if tournament.publishRankings == false {
Image(systemName: "exclamationmark.circle.fill")
.foregroundStyle(.logoYellow)
} else {
Image(systemName: "checkmark")
.foregroundStyle(.green)
}
} label: {
Text("Classement final des équipes")
if tournament.publishRankings == false {
Text("Vérifiez le classement avant de publier").foregroundStyle(.logoRed)
}
}
}
}
}
.navigationDestination(isPresented: $showPrintScreen) {
PrintSettingsView(tournament: tournament)
@ -327,13 +346,20 @@ struct RoundView: View {
match.name = Match.setServerTitle(upperRound: round, matchIndex: match.indexInRound(in: matches))
}
}
let loserMatches = self.upperRound.loserMatches()
loserMatches.forEach { match in
match.name = match.roundTitle()
}
let allRoundMatches = tournament.allRoundMatches()
do {
try self.tournamentStore.matches.addOrUpdate(contentOfs: allRoundMatches)
try tournament.tournamentStore.matches.addOrUpdate(contentOfs: allRoundMatches)
} catch {
Logger.error(error)
}
if tournament.availableSeeds().isEmpty && tournament.availableQualifiedTeams().isEmpty {
self.isEditingTournamentSeed.wrappedValue = false
}

@ -45,7 +45,7 @@ struct TeamHeaderView: View {
let positionLabel = team.positionLabel()
let cutLabel = tournament.cutLabel(index: teamIndex, teamCount: teamCount)
if team.isWildCard() {
Text("wildcard").font(.caption).italic()
Text("wildcard").foregroundStyle(.red).font(.caption).italic()
Text(positionLabel ?? cutLabel)
} else {
if let positionLabel {

@ -18,15 +18,21 @@ struct TeamRowView: View {
TeamWeightView(team: team, teamPosition: teamPosition)
} label: {
VStack(alignment: .leading) {
if let groupStage = team.groupStageObject() {
HStack {
Text(groupStage.groupStageTitle(.title))
if let finalPosition = groupStage.finalPosition(ofTeam: team) {
Text((finalPosition + 1).ordinalFormatted())
HStack {
if let groupStage = team.groupStageObject() {
HStack {
Text(groupStage.groupStageTitle(.title))
if let finalPosition = groupStage.finalPosition(ofTeam: team) {
Text((finalPosition + 1).ordinalFormatted())
}
}
} else if let round = team.initialRound() {
Text(round.roundTitle(.wide))
}
if team.isWildCard() {
Text("wildcard").italic().foregroundStyle(.red).font(.caption)
}
} else if let round = team.initialRound() {
Text(round.roundTitle(.wide))
}
if let name = team.name {

@ -14,10 +14,15 @@ struct TournamentGeneralSettingsView: View {
@Bindable var tournament: Tournament
@State private var tournamentName: String = ""
@State private var entryFee: Double? = nil
@State private var confirmationRequired: Bool = false
@State private var presentConfirmation: Bool = false
@State private var loserBracketMode: LoserBracketMode
@FocusState private var focusedField: Tournament.CodingKeys?
let priceTags: [Double] = [15.0, 20.0, 25.0]
init(tournament: Tournament) {
self.tournament = tournament
_loserBracketMode = .init(wrappedValue: tournament.loserBracketMode)
_tournamentName = State(wrappedValue: tournament.name ?? "")
_entryFee = State(wrappedValue: tournament.entryFee)
}
@ -25,6 +30,17 @@ struct TournamentGeneralSettingsView: View {
var body: some View {
@Bindable var tournament = tournament
Form {
Section {
TextField("Nom du tournoi", text: $tournamentName, axis: .vertical)
.lineLimit(2)
.frame(maxWidth: .infinity)
.keyboardType(.alphabet)
.focused($focusedField, equals: ._name)
} header: {
Text("Nom du tournoi")
}
Section {
TournamentDatePickerView()
TournamentDurationManagerView()
@ -37,17 +53,8 @@ struct TournamentGeneralSettingsView: View {
} label: {
Text("Inscription")
}
}
Section {
TextField("Nom du tournoi", text: $tournamentName, axis: .vertical)
.lineLimit(2)
.frame(maxWidth: .infinity)
.keyboardType(.alphabet)
.focused($focusedField, equals: ._name)
} header: {
Text("Nom du tournoi")
} footer: {
Text("Si vous souhaitez que Padel Club vous aide à suivre les encaissements, indiquer un prix d'inscription. Sinon Padel Club vous aidera à suivre simplement l'arrivée et la présence des joueurs.")
}
Section {
@ -55,42 +62,51 @@ struct TournamentGeneralSettingsView: View {
}
Section {
Picker(selection: $tournament.loserBracketMode) {
Picker(selection: $loserBracketMode) {
ForEach(LoserBracketMode.allCases) {
Text($0.localizedLoserBracketMode()).tag($0)
}
} label: {
Text("Position des perdants")
}
.onChange(of: tournament.loserBracketMode) {
_save()
let rounds = tournament.rounds()
rounds.forEach { round in
round.loserBracketMode = tournament.loserBracketMode
}
do {
try self.tournament.tournamentStore.rounds.addOrUpdate(contentOfs: rounds)
} catch {
Logger.error(error)
.onChange(of: loserBracketMode) {
if tournament.allLoserRoundMatches().anySatisfy({ $0.hasEnded() }) == false {
_refreshLoserBracketMode()
} else {
confirmationRequired = true
}
}
} header: {
Text("Matchs de classement")
} footer: {
if dataStore.user.loserBracketMode != tournament.loserBracketMode {
_footerView()
if confirmationRequired == false {
if dataStore.user.loserBracketMode != tournament.loserBracketMode {
_footerView()
.onTapGesture(perform: {
self.dataStore.user.loserBracketMode = tournament.loserBracketMode
self.dataStore.saveUser()
})
} else {
Text(tournament.loserBracketMode.localizedLoserBracketModeDescription())
}
} else {
_footerViewConfirmationRequired()
.onTapGesture(perform: {
self.dataStore.user.loserBracketMode = tournament.loserBracketMode
self.dataStore.saveUser()
presentConfirmation = true
})
} else {
Text(tournament.loserBracketMode.localizedLoserBracketModeDescription())
}
}
}
.confirmationDialog("Attention", isPresented: $presentConfirmation, actions: {
Button("Confirmer", role: .destructive) {
_refreshLoserBracketMode()
confirmationRequired = false
}
Button("Annuler", role: .cancel) {
loserBracketMode = tournament.loserBracketMode
}
})
.navigationBarBackButtonHidden(focusedField != nil)
.toolbar(content: {
if focusedField != nil {
@ -106,6 +122,26 @@ struct TournamentGeneralSettingsView: View {
if focusedField != nil {
ToolbarItem(placement: .keyboard) {
HStack {
if focusedField == ._entryFee {
if tournament.isFree() {
ForEach(priceTags, id: \.self) { priceTag in
Button(priceTag.formatted(.currency(code: "EUR"))) {
entryFee = priceTag
tournament.entryFee = priceTag
focusedField = nil
}
.buttonStyle(.bordered)
}
} else {
Button("Gratuit") {
entryFee = nil
tournament.entryFee = nil
focusedField = nil
}
.buttonStyle(.bordered)
}
}
Spacer()
Button("Valider") {
if focusedField == ._name {
@ -166,9 +202,50 @@ struct TournamentGeneralSettingsView: View {
}
}
private func _refreshLoserBracketMode() {
tournament.loserBracketMode = loserBracketMode
_save()
let rounds = tournament.rounds()
rounds.forEach { round in
let matches = round.loserRoundsAndChildren().flatMap({ $0._matches() })
matches.forEach { match in
match.resetTeamScores(outsideOf: [])
match.resetMatch()
match.confirmed = false
}
round.loserBracketMode = tournament.loserBracketMode
if loserBracketMode == .automatic {
matches.forEach { match in
match.updateTeamScores()
}
}
do {
try self.tournament.tournamentStore.matches.addOrUpdate(contentOfs: matches)
} catch {
Logger.error(error)
}
}
do {
try self.tournament.tournamentStore.rounds.addOrUpdate(contentOfs: rounds)
} catch {
Logger.error(error)
}
}
private func _footerView() -> some View {
Text(tournament.loserBracketMode.localizedLoserBracketModeDescription())
+
Text(" Modifier le réglage par défaut pour tous vos tournois").foregroundStyle(.blue)
}
private func _footerViewConfirmationRequired() -> some View {
Text("Au moins un match de classement est terminé, en modifiant ce réglage, les résultats de ces matchs de classement seront perdus.")
+
Text(" Modifier quand même ?").foregroundStyle(.red)
}
}

@ -311,26 +311,8 @@ struct TournamentRankView: View {
self.rankings.removeAll()
let finalRanks = await tournament.finalRanking()
finalRanks.keys.sorted().forEach { rank in
if let rankedTeamIds = finalRanks[rank] {
let teams: [TeamRegistration] = rankedTeamIds.compactMap { self.tournamentStore.teamRegistrations.findById($0) }
self.rankings[rank] = teams
}
}
await MainActor.run {
rankings.keys.sorted().forEach { rank in
if let rankedTeams = rankings[rank] {
rankedTeams.forEach { team in
team.finalRanking = rank
team.pointsEarned = tournament.isAnimation() ? nil : tournament.tournamentLevel.points(for: rank - 1, count: tournament.teamCount)
}
}
}
_save()
calculating = false
}
self.rankings = await tournament.setRankings(finalRanks: finalRanks)
calculating = false
}
private func _save() {

Loading…
Cancel
Save