fix new feature loser bracket groupstages

xcode16
Raz 1 year ago
parent 9a2d293b41
commit 40fc73e6bf
  1. 7
      PadelClub/Data/Tournament.swift
  2. 4
      PadelClub/Utils/PadelRule.swift
  3. 8
      PadelClub/Views/GroupStage/GroupStagesView.swift
  4. 68
      PadelClub/Views/GroupStage/LoserBracketFromGroupStageView.swift
  5. 12
      PadelClub/Views/Match/EditSharingView.swift
  6. 3
      PadelClub/Views/Match/MatchRowView.swift
  7. 8
      PadelClub/Views/Match/MatchSetupView.swift
  8. 3
      PadelClub/Views/Match/MatchSummaryView.swift
  9. 25
      PadelClub/Views/Team/TeamPickerView.swift

@ -1258,12 +1258,15 @@ defer {
} }
} }
groupStageLoserBracket()?.playedMatches().forEach({ match in if let groupStageLoserBracketPlayedMatches = groupStageLoserBracket()?.playedMatches() {
groupStageLoserBracketPlayedMatches.forEach({ match in
if match.hasEnded() { if match.hasEnded() {
let sameMatchIndexCount = groupStageLoserBracketPlayedMatches.filter({ $0.index == match.index }).count
teams.setOrAppend(match.winningTeamId, at: match.index) teams.setOrAppend(match.winningTeamId, at: match.index)
teams.setOrAppend(match.losingTeamId, at: match.index + 1) teams.setOrAppend(match.losingTeamId, at: match.index + sameMatchIndexCount)
} }
}) })
}
let groupStages = groupStages() let groupStages = groupStages()
let baseRank = teamCount - groupStageSpots() + qualifiedPerGroupStage * groupStageCount + groupStageAdditionalQualified let baseRank = teamCount - groupStageSpots() + qualifiedPerGroupStage * groupStageCount + groupStageAdditionalQualified

@ -293,8 +293,8 @@ enum TournamentLevel: Int, Hashable, Codable, CaseIterable, Identifiable {
func pointsRange(first: Int, last: Int, teamsCount: Int) -> String { func pointsRange(first: Int, last: Int, teamsCount: Int) -> String {
let range = [points(for: last - 1, count: teamsCount), let range = [points(for: first - 1, count: teamsCount),
points(for: first - 1, count: teamsCount)] points(for: last - 1, count: teamsCount)]
return range.map { $0.formatted(.number.sign(strategy: .always())) }.joined(separator: " / ") + " pts" return range.map { $0.formatted(.number.sign(strategy: .always())) }.joined(separator: " / ") + " pts"
} }

@ -9,8 +9,9 @@ import SwiftUI
import LeStorage import LeStorage
struct GroupStagesView: View { struct GroupStagesView: View {
var tournament: Tournament @State var tournament: Tournament
@State private var selectedDestination: GroupStageDestination? @State private var selectedDestination: GroupStageDestination?
@EnvironmentObject var dataStore: DataStore
enum GroupStageDestination: Selectable, Identifiable, Equatable { enum GroupStageDestination: Selectable, Identifiable, Equatable {
static func == (lhs: GroupStagesView.GroupStageDestination, rhs: GroupStagesView.GroupStageDestination) -> Bool { static func == (lhs: GroupStagesView.GroupStageDestination, rhs: GroupStagesView.GroupStageDestination) -> Bool {
@ -67,6 +68,7 @@ struct GroupStagesView: View {
return nil return nil
} }
case .loserBracket(let loserBracket): case .loserBracket(let loserBracket):
if loserBracket._matches().isEmpty { return nil }
return loserBracket.badgeImage() return loserBracket.badgeImage()
case .groupStage(let groupStage): case .groupStage(let groupStage):
return groupStage.badgeImage() return groupStage.badgeImage()
@ -78,8 +80,6 @@ struct GroupStagesView: View {
tournament.groupStagesMatches() tournament.groupStagesMatches()
} }
@State private var isEditingLoserBracketGroupStage: Bool
init(tournament: Tournament) { init(tournament: Tournament) {
self.tournament = tournament self.tournament = tournament
if tournament.shouldVerifyGroupStage { if tournament.shouldVerifyGroupStage {
@ -92,7 +92,6 @@ struct GroupStagesView: View {
_selectedDestination = State(wrappedValue: .groupStage(gs)) _selectedDestination = State(wrappedValue: .groupStage(gs))
} }
} }
_isEditingLoserBracketGroupStage = .init(wrappedValue: tournament.groupStageLoserBracket()?._matches().isEmpty ?? false)
} }
func allDestinations() -> [GroupStageDestination] { func allDestinations() -> [GroupStageDestination] {
@ -158,7 +157,6 @@ struct GroupStagesView: View {
GroupStageView(groupStage: groupStage).id(groupStage.id) GroupStageView(groupStage: groupStage).id(groupStage.id)
case .loserBracket(let loserBracket): case .loserBracket(let loserBracket):
LoserBracketFromGroupStageView(loserBracket: loserBracket).id(loserBracket.id) LoserBracketFromGroupStageView(loserBracket: loserBracket).id(loserBracket.id)
.environment(\.isEditingTournamentSeed, $isEditingLoserBracketGroupStage)
case nil: case nil:
GroupStagesSettingsView() GroupStagesSettingsView()
.navigationTitle("Réglages") .navigationTitle("Réglages")

@ -10,32 +10,31 @@ import LeStorage
struct LoserBracketFromGroupStageView: View { struct LoserBracketFromGroupStageView: View {
@Environment(\.isEditingTournamentSeed) var isEditingTournamentSeed
@Environment(Tournament.self) var tournament: Tournament @Environment(Tournament.self) var tournament: Tournament
@EnvironmentObject var dataStore: DataStore @EnvironmentObject var dataStore: DataStore
@Environment(NavigationViewModel.self) private var navigation: NavigationViewModel @Environment(NavigationViewModel.self) private var navigation: NavigationViewModel
@State var loserBracket: Round @State var loserBracket: Round
@State private var isEditingLoserBracketGroupStage: Bool
init(loserBracket: Round) {
self.loserBracket = loserBracket
_isEditingLoserBracketGroupStage = .init(wrappedValue: loserBracket._matches().isEmpty)
}
var tournamentStore: TournamentStore { var tournamentStore: TournamentStore {
return self.tournament.tournamentStore return self.tournament.tournamentStore
} }
var displayableMatches: [Match] {
loserBracket.playedMatches().sorted(by: \.index)
}
var body: some View { var body: some View {
List { List {
let displayableMatches = loserBracket.playedMatches().sorted(by: \.index) if isEditingLoserBracketGroupStage == true && displayableMatches.isEmpty == false {
if isEditingTournamentSeed.wrappedValue == true {
Section { Section {
RowButtonView("Ajouter un match", role: .destructive) { RowButtonView("Ajouter un match", role: .destructive) {
let placeCount = tournament.groupStageLoserBracketsInitialPlace() + displayableMatches.count * 2 _addNewMatch()
let match = Match(round: loserBracket.id, index: placeCount, matchFormat: loserBracket.matchFormat)
match.name = "\(placeCount)\(placeCount.ordinalFormattedSuffix()) place"
do {
try tournamentStore.matches.addOrUpdate(instance: match)
} catch {
Logger.error(error)
}
} }
} }
} }
@ -43,9 +42,10 @@ struct LoserBracketFromGroupStageView: View {
ForEach(displayableMatches) { match in ForEach(displayableMatches) { match in
Section { Section {
MatchRowView(match: match, matchViewStyle: .sectionedStandardStyle) MatchRowView(match: match, matchViewStyle: .sectionedStandardStyle)
.environment(\.isEditingTournamentSeed, $isEditingLoserBracketGroupStage)
} header: { } header: {
let tournamentTeamCount = tournament.teamCount let tournamentTeamCount = tournament.teamCount
let seedIntervalPointRange = tournament.tournamentLevel.pointsRange(first: match.index, last: match.index + 1, teamsCount: tournamentTeamCount) let seedIntervalPointRange = tournament.tournamentLevel.pointsRange(first: match.index, last: match.index + displayableMatches.filter({ $0.index == match.index }).count, teamsCount: tournamentTeamCount)
HStack { HStack {
Text(match.matchTitle(.wide)) Text(match.matchTitle(.wide))
Spacer() Spacer()
@ -53,7 +53,7 @@ struct LoserBracketFromGroupStageView: View {
.font(.caption) .font(.caption)
} }
} footer: { } footer: {
if isEditingTournamentSeed.wrappedValue == true { if isEditingLoserBracketGroupStage == true {
HStack { HStack {
if match.index > tournament.groupStageLoserBracketsInitialPlace() { if match.index > tournament.groupStageLoserBracketsInitialPlace() {
FooterButtonView("même place qu'au-dessus") { FooterButtonView("même place qu'au-dessus") {
@ -80,7 +80,7 @@ struct LoserBracketFromGroupStageView: View {
} }
Section { Section {
if displayableMatches.isEmpty == false && isEditingTournamentSeed.wrappedValue == true { if displayableMatches.count > 1 && isEditingLoserBracketGroupStage == true {
Section { Section {
RowButtonView("Effacer tous les matchs", role: .destructive) { RowButtonView("Effacer tous les matchs", role: .destructive) {
_deleteAllMatches() _deleteAllMatches()
@ -91,20 +91,48 @@ struct LoserBracketFromGroupStageView: View {
} }
} }
} }
.overlay {
if displayableMatches.isEmpty {
ContentUnavailableView {
Label("Aucun match de classement", systemImage: "figure.tennis")
} description: {
Text("Vous n'avez créé aucun match de classement entre les perdants de poules.")
} actions: {
RowButtonView("Ajouter un match") {
isEditingLoserBracketGroupStage = true
_addNewMatch()
}
.padding(.horizontal)
}
}
}
.headerProminence(.increased) .headerProminence(.increased)
.navigationTitle("Classement de poules") .navigationTitle("Classement de poules")
.toolbar { .toolbar {
ToolbarItem(placement: .topBarTrailing) { ToolbarItem(placement: .topBarTrailing) {
Button(isEditingTournamentSeed.wrappedValue == true ? "Valider" : "Modifier") { if displayableMatches.isEmpty == false {
if isEditingTournamentSeed.wrappedValue == true { Button(isEditingLoserBracketGroupStage == true ? "Valider" : "Modifier") {
isEditingTournamentSeed.wrappedValue = false if isEditingLoserBracketGroupStage == true {
isEditingLoserBracketGroupStage = false
} else { } else {
isEditingTournamentSeed.wrappedValue = true isEditingLoserBracketGroupStage = true
} }
} }
} }
} }
} }
}
private func _addNewMatch() {
let placeCount = tournament.groupStageLoserBracketsInitialPlace() + displayableMatches.count * 2
let match = Match(round: loserBracket.id, index: placeCount, matchFormat: loserBracket.matchFormat)
match.name = "\(placeCount)\(placeCount.ordinalFormattedSuffix()) place"
do {
try tournamentStore.matches.addOrUpdate(instance: match)
} catch {
Logger.error(error)
}
}
private func _deleteAllMatches() { private func _deleteAllMatches() {
let displayableMatches = loserBracket.playedMatches().sorted(by: \.index) let displayableMatches = loserBracket.playedMatches().sorted(by: \.index)

@ -24,18 +24,20 @@ struct EditSharingView: View {
func shareMessage(displayRank: Bool, displayTeamName: Bool) -> String { func shareMessage(displayRank: Bool, displayTeamName: Bool) -> String {
var messageData: [String] = [] var messageData: [String] = []
var locAndTime: String = "" if match.hasEnded() == false {
var locAndTime: String?
if let courtName = match.courtName() { if let courtName = match.courtName() {
locAndTime.append("\(courtName)") locAndTime = "\(courtName)"
} }
if let startDate = match.startDate { if let startDate = match.startDate {
locAndTime = locAndTime + " à " + startDate.formattedAsHourMinute() locAndTime = [locAndTime, startDate.formattedAsHourMinute()].compactMap({ $0 }).joined(separator: " à ")
} }
if locAndTime.isEmpty == false { if let locAndTime, locAndTime.isEmpty == false {
messageData.append(locAndTime) messageData.append(locAndTime)
} }
}
if let tournament = match.currentTournament() { if let tournament = match.currentTournament() {
messageData.append(tournament.tournamentTitle()) messageData.append(tournament.tournamentTitle())
@ -52,6 +54,8 @@ struct EditSharingView: View {
let players = "\(labelOne)\ncontre\n\(labelTwo)" let players = "\(labelOne)\ncontre\n\(labelTwo)"
messageData.append(players) messageData.append(players)
messageData.append(match.scoreLabel())
return messageData.joined(separator: "\n") return messageData.joined(separator: "\n")
} }

@ -9,7 +9,8 @@ import SwiftUI
struct MatchRowView: View { struct MatchRowView: View {
var match: Match @EnvironmentObject var dataStore: DataStore
@State var match: Match
let matchViewStyle: MatchViewStyle let matchViewStyle: MatchViewStyle
var title: String? = nil var title: String? = nil

@ -214,6 +214,14 @@ struct MatchSetupView: View {
} catch { } catch {
Logger.error(error) Logger.error(error)
} }
} else if match.isLoserBracket {
if let score = match.teamScore(ofTeam: team) {
do {
try tournamentStore.teamScores.delete(instance: score)
} catch {
Logger.error(error)
}
}
} else { } else {
match.teamWillBeWalkOut(team) match.teamWillBeWalkOut(team)
do { do {

@ -8,7 +8,8 @@
import SwiftUI import SwiftUI
struct MatchSummaryView: View { struct MatchSummaryView: View {
var match: Match @EnvironmentObject var dataStore: DataStore
@State var match: Match
let matchViewStyle: MatchViewStyle let matchViewStyle: MatchViewStyle
let matchTitle: String let matchTitle: String
let roundTitle: String? let roundTitle: String?

@ -68,19 +68,34 @@ struct TeamPickerView: View {
.toolbarBackground(.visible, for: .navigationBar) .toolbarBackground(.visible, for: .navigationBar)
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.toolbar { .toolbar {
ToolbarItem(placement: .topBarLeading) {
Button("Annuler", role: .cancel) {
presentTeamPickerView = false
}
}
ToolbarItem(placement: .topBarTrailing) { ToolbarItem(placement: .topBarTrailing) {
Menu {
Section {
Picker(selection: $sortOrder) { Picker(selection: $sortOrder) {
Label("Poids", systemImage: "arrow.up").tag(SortOrder.ascending) Label("Trier les équipes par poids croissant", systemImage: "chevron.up").tag(SortOrder.ascending)
.labelStyle(.titleAndIcon) .labelStyle(.titleAndIcon)
Label("Poids", systemImage: "arrow.down").tag(SortOrder.descending) Label("Trier les équipes par poids décroissant", systemImage: "chevron.down").tag(SortOrder.descending)
.labelStyle(.titleAndIcon) .labelStyle(.titleAndIcon)
} label: { } label: {
Label("Trier", systemImage: "arrow.up.arrow.down") Text("Trier les équipes par poids")
}
.pickerStyle(.inline)
} header: {
Text("Trier les équipes par poids")
}
} label: {
HStack {
Text("Poids")
Image(systemName: sortOrder == .ascending ? "chevron.up" : "chevron.down")
}
} }
.pickerStyle(.menu)
} }
} }
.headerProminence(.increased)
} }
.tint(.master) .tint(.master)
} }

Loading…
Cancel
Save