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. 48
      PadelClub/Data/Tournament.swift
  3. 92
      PadelClub/Views/Cashier/CashierSettingsView.swift
  4. 1
      PadelClub/Views/Match/MatchDetailView.swift
  5. 79
      PadelClub/Views/Round/LoserRoundSettingsView.swift
  6. 28
      PadelClub/Views/Round/RoundView.swift
  7. 2
      PadelClub/Views/Team/Components/TeamHeaderView.swift
  8. 6
      PadelClub/Views/Team/TeamRowView.swift
  9. 127
      PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift
  10. 20
      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,9 +1230,7 @@ defer {
teams[groupStage.index * groupStage.size + 1 + teamIndex] = [groupStageTeams[teamIndex].id]
}
}
return teams
}
} else {
let final = rounds.last?.playedMatches().last
if let winner = final?.winningTeamId {
@ -1337,10 +1335,40 @@ defer {
}
}
}
}
return teams
}
func setRankings(finalRanks: [Int: [String]]) async -> [Int: [TeamRegistration]] {
var rankings: [Int: [TeamRegistration]] = [:]
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)
}
}
}
do {
try self.tournamentStore.teamRegistrations.addOrUpdate(contentOfs: unsortedTeams())
} catch {
Logger.error(error)
}
return rankings
}
func lockRegistration() {
closedRegistrationDate = Date()
let count = selectedSortedTeams().count
@ -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,23 +11,34 @@ import LeStorage
struct CashierSettingsView: View {
@EnvironmentObject var dataStore: DataStore
var tournaments: [Tournament]
init(tournaments: [Tournament]) {
self.tournaments = tournaments
}
@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]
init(tournament: Tournament) {
self.tournaments = [tournament]
self.tournament = tournament
_entryFee = State(wrappedValue: tournament.entryFee)
}
var body: some View {
List {
Section {
RowButtonView("Tout le monde est arrivé", role: .destructive) {
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.")
}
for tournament in self.tournaments {
Section {
RowButtonView("Tout le monde est arrivé", role: .destructive) {
let players = tournament.selectedPlayers() // tournaments.flatMap({ $0.selectedPlayers() })
players.forEach { player in
player.hasArrived = true
@ -38,16 +49,12 @@ struct CashierSettingsView: View {
Logger.error(error)
}
}
}
} footer: {
Text("Indique tous les joueurs sont là")
}
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
@ -58,17 +65,12 @@ struct CashierSettingsView: View {
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 {
@ -81,15 +83,12 @@ struct CashierSettingsView: View {
Logger.error(error)
}
}
}
} footer: {
Text("Passe tous les joueurs qui n'ont pas réglé en offert")
}
Section {
RowButtonView("Personne n'a réglé", role: .destructive) {
for tournament in self.tournaments {
let store = tournament.tournamentStore
let players = tournament.selectedPlayers()
@ -102,11 +101,60 @@ struct CashierSettingsView: View {
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)
}
} else {
Button("Gratuit") {
entryFee = nil
tournament.entryFee = nil
focusedField = nil
}
.buttonStyle(.bordered)
}
Spacer()
Button("Valider") {
tournament.entryFee = entryFee
focusedField = nil
}
.buttonStyle(.bordered)
}
}
}
}
.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,6 +13,14 @@ 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 {
@ -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: {
if confirmationRequired == false {
Text(upperBracketRound.round.loserBracketMode.localizedLoserBracketModeDescription())
} else {
_footerViewConfirmationRequired()
.onTapGesture(perform: {
presentConfirmation = true
})
}
}
Section {
@ -81,9 +95,60 @@ 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 {
// LoserRoundSettingsView()
//}

@ -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,9 +346,16 @@ 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)
}

@ -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,6 +18,7 @@ struct TeamRowView: View {
TeamWeightView(team: team, teamPosition: teamPosition)
} label: {
VStack(alignment: .leading) {
HStack {
if let groupStage = team.groupStageObject() {
HStack {
Text(groupStage.groupStageTitle(.title))
@ -29,6 +30,11 @@ struct TeamRowView: View {
Text(round.roundTitle(.wide))
}
if team.isWildCard() {
Text("wildcard").italic().foregroundStyle(.red).font(.caption)
}
}
if let name = team.name {
Text(name).font(.title3)
if team.players().isEmpty {

@ -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,31 +62,24 @@ 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 confirmationRequired == false {
if dataStore.user.loserBracketMode != tournament.loserBracketMode {
_footerView()
.onTapGesture(perform: {
@ -89,8 +89,24 @@ struct TournamentGeneralSettingsView: View {
} else {
Text(tournament.loserBracketMode.localizedLoserBracketModeDescription())
}
} else {
_footerViewConfirmationRequired()
.onTapGesture(perform: {
presentConfirmation = true
})
}
}
}
.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,27 +311,9 @@ 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()
self.rankings = await tournament.setRankings(finalRanks: finalRanks)
calculating = false
}
}
private func _save() {
do {

Loading…
Cancel
Save