add stuff for rounds

multistore
Razmig Sarkissian 2 years ago
parent c0fb636621
commit 97e5d641fb
  1. 16
      PadelClub.xcodeproj/project.pbxproj
  2. 2
      PadelClub/Data/GroupStage.swift
  3. 29
      PadelClub/Data/Match.swift
  4. 6
      PadelClub/Data/MockData.swift
  5. 30
      PadelClub/Data/Round.swift
  6. 73
      PadelClub/Data/Tournament.swift
  7. 139
      PadelClub/Manager/PadelRule.swift
  8. 2
      PadelClub/Views/ClubView.swift
  9. 4
      PadelClub/Views/GroupStage/GroupStageView.swift
  10. 10
      PadelClub/Views/GroupStage/GroupStagesView.swift
  11. 2
      PadelClub/Views/Match/MatchDetailView.swift
  12. 4
      PadelClub/Views/Match/MatchSummaryView.swift
  13. 2
      PadelClub/Views/Navigation/Agenda/CalendarView.swift
  14. 29
      PadelClub/Views/Round/RoundView.swift
  15. 80
      PadelClub/Views/Round/RoundsView.swift
  16. 1
      PadelClub/Views/Tournament/Screen/Screen.swift
  17. 2
      PadelClub/Views/Tournament/Screen/TableStructureView.swift
  18. 11
      PadelClub/Views/Tournament/TournamentRunningView.swift
  19. 4
      PadelClub/Views/Tournament/TournamentView.swift

@ -169,6 +169,8 @@
FFC1E1082BAC29FC008D6F59 /* LocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1072BAC29FC008D6F59 /* LocationManager.swift */; }; FFC1E1082BAC29FC008D6F59 /* LocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1072BAC29FC008D6F59 /* LocationManager.swift */; };
FFC1E10A2BAC2A77008D6F59 /* NetworkFederalService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1092BAC2A77008D6F59 /* NetworkFederalService.swift */; }; FFC1E10A2BAC2A77008D6F59 /* NetworkFederalService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1092BAC2A77008D6F59 /* NetworkFederalService.swift */; };
FFC1E10C2BAC7FB0008D6F59 /* ClubImportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E10B2BAC7FB0008D6F59 /* ClubImportView.swift */; }; FFC1E10C2BAC7FB0008D6F59 /* ClubImportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E10B2BAC7FB0008D6F59 /* ClubImportView.swift */; };
FFC83D4F2BB807D100750834 /* RoundsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC83D4E2BB807D100750834 /* RoundsView.swift */; };
FFC83D512BB8087E00750834 /* RoundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC83D502BB8087E00750834 /* RoundView.swift */; };
FFD783FD2B91B9ED000F62A6 /* AgendaDestinationPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFD783FC2B91B9ED000F62A6 /* AgendaDestinationPickerView.swift */; }; FFD783FD2B91B9ED000F62A6 /* AgendaDestinationPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFD783FC2B91B9ED000F62A6 /* AgendaDestinationPickerView.swift */; };
FFD783FF2B91BA42000F62A6 /* PadelClubView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFD783FE2B91BA42000F62A6 /* PadelClubView.swift */; }; FFD783FF2B91BA42000F62A6 /* PadelClubView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFD783FE2B91BA42000F62A6 /* PadelClubView.swift */; };
FFD784022B91C1B4000F62A6 /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFD784012B91C1B4000F62A6 /* WelcomeView.swift */; }; FFD784022B91C1B4000F62A6 /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFD784012B91C1B4000F62A6 /* WelcomeView.swift */; };
@ -394,6 +396,8 @@
FFC1E1072BAC29FC008D6F59 /* LocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationManager.swift; sourceTree = "<group>"; }; FFC1E1072BAC29FC008D6F59 /* LocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationManager.swift; sourceTree = "<group>"; };
FFC1E1092BAC2A77008D6F59 /* NetworkFederalService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkFederalService.swift; sourceTree = "<group>"; }; FFC1E1092BAC2A77008D6F59 /* NetworkFederalService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkFederalService.swift; sourceTree = "<group>"; };
FFC1E10B2BAC7FB0008D6F59 /* ClubImportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClubImportView.swift; sourceTree = "<group>"; }; FFC1E10B2BAC7FB0008D6F59 /* ClubImportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClubImportView.swift; sourceTree = "<group>"; };
FFC83D4E2BB807D100750834 /* RoundsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundsView.swift; sourceTree = "<group>"; };
FFC83D502BB8087E00750834 /* RoundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundView.swift; sourceTree = "<group>"; };
FFD783FC2B91B9ED000F62A6 /* AgendaDestinationPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgendaDestinationPickerView.swift; sourceTree = "<group>"; }; FFD783FC2B91B9ED000F62A6 /* AgendaDestinationPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgendaDestinationPickerView.swift; sourceTree = "<group>"; };
FFD783FE2B91BA42000F62A6 /* PadelClubView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PadelClubView.swift; sourceTree = "<group>"; }; FFD783FE2B91BA42000F62A6 /* PadelClubView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PadelClubView.swift; sourceTree = "<group>"; };
FFD784002B91BF79000F62A6 /* Launch Screen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = "Launch Screen.storyboard"; sourceTree = "<group>"; }; FFD784002B91BF79000F62A6 /* Launch Screen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = "Launch Screen.storyboard"; sourceTree = "<group>"; };
@ -547,6 +551,7 @@
FF39719B2B8DE04B004C4E75 /* Navigation */, FF39719B2B8DE04B004C4E75 /* Navigation */,
FF8F26392BAD526A00650388 /* Event */, FF8F26392BAD526A00650388 /* Event */,
FF1DC54D2BAB34FA00FD8220 /* Club */, FF1DC54D2BAB34FA00FD8220 /* Club */,
FFC83D4B2BB807C200750834 /* Round */,
FF967CF92BAEE11500A9A3BD /* GroupStage */, FF967CF92BAEE11500A9A3BD /* GroupStage */,
FF967CFE2BAEEF5A00A9A3BD /* Match */, FF967CFE2BAEEF5A00A9A3BD /* Match */,
FF967D072BAF3D3000A9A3BD /* Team */, FF967D072BAF3D3000A9A3BD /* Team */,
@ -868,6 +873,15 @@
path = Team; path = Team;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
FFC83D4B2BB807C200750834 /* Round */ = {
isa = PBXGroup;
children = (
FFC83D4E2BB807D100750834 /* RoundsView.swift */,
FFC83D502BB8087E00750834 /* RoundView.swift */,
);
path = Round;
sourceTree = "<group>";
};
FFD783FB2B91B919000F62A6 /* Agenda */ = { FFD783FB2B91B919000F62A6 /* Agenda */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -1125,6 +1139,7 @@
FF8F263F2BAD7D5C00650388 /* Event.swift in Sources */, FF8F263F2BAD7D5C00650388 /* Event.swift in Sources */,
FF089EBF2BB0B14600F0AEC7 /* FileImportView.swift in Sources */, FF089EBF2BB0B14600F0AEC7 /* FileImportView.swift in Sources */,
C4A47D9F2B7D0BCE00ADC637 /* StepperView.swift in Sources */, C4A47D9F2B7D0BCE00ADC637 /* StepperView.swift in Sources */,
FFC83D4F2BB807D100750834 /* RoundsView.swift in Sources */,
FF1CBC1B2BB53D1F0036DAAB /* FederalTournament.swift in Sources */, FF1CBC1B2BB53D1F0036DAAB /* FederalTournament.swift in Sources */,
FF8F26412BADFC8700650388 /* TournamentInitView.swift in Sources */, FF8F26412BADFC8700650388 /* TournamentInitView.swift in Sources */,
C4A47D8A2B7BBB6500ADC637 /* SubscriptionView.swift in Sources */, C4A47D8A2B7BBB6500ADC637 /* SubscriptionView.swift in Sources */,
@ -1145,6 +1160,7 @@
FFDB1C6D2BB2A02000F1E467 /* AppSettings.swift in Sources */, FFDB1C6D2BB2A02000F1E467 /* AppSettings.swift in Sources */,
FF0EC5202BB16F680056B6D1 /* SwiftParser.swift in Sources */, FF0EC5202BB16F680056B6D1 /* SwiftParser.swift in Sources */,
C4A47DA92B85F82100ADC637 /* ChangePasswordView.swift in Sources */, C4A47DA92B85F82100ADC637 /* ChangePasswordView.swift in Sources */,
FFC83D512BB8087E00750834 /* RoundView.swift in Sources */,
FF6EC8F72B94773200EA7F5A /* RowButtonView.swift in Sources */, FF6EC8F72B94773200EA7F5A /* RowButtonView.swift in Sources */,
FF8F263B2BAD528600650388 /* EventCreationView.swift in Sources */, FF8F263B2BAD528600650388 /* EventCreationView.swift in Sources */,
FFC1E1082BAC29FC008D6F59 /* LocationManager.swift in Sources */, FFC1E1082BAC29FC008D6F59 /* LocationManager.swift in Sources */,

@ -44,7 +44,7 @@ class GroupStage: ModelObject, Storable {
Store.main.findById(tournament) Store.main.findById(tournament)
} }
func title(_ displayStyle: DisplayStyle = .wide) -> String { func groupStageTitle(_ displayStyle: DisplayStyle = .wide) -> String {
switch displayStyle { switch displayStyle {
case .wide: case .wide:
return "Poule \(index + 1)" return "Poule \(index + 1)"

@ -43,14 +43,37 @@ class Match: ModelObject, Storable {
self.order = order self.order = order
} }
func title(_ displayStyle: DisplayStyle = .wide) -> String { func indexInRound() -> Int {
if groupStage != nil {
return index
}
return RoundRule.matchIndexWithinRound(fromMatchIndex: index)
}
func matchTitle(_ displayStyle: DisplayStyle = .wide) -> String {
switch displayStyle { switch displayStyle {
case .wide: case .wide:
return "Match \(index + 1)" return "Match \(indexInRound() + 1)"
case .short: case .short:
return "#\(index + 1)" return "#\(indexInRound() + 1)"
} }
} }
func topPreviousRoundMatches() -> Int {
index * 2 + 1
}
func bottomPreviousRoundMatches() -> Int {
(index + 1) * 2
}
func previousMatches() -> [Match] {
guard let roundObject else { return [] }
return Store.main.filter { match in
(match.index == topPreviousRoundMatches() || match.index == bottomPreviousRoundMatches())
&& match.round == roundObject.previousRound()?.id
}.sorted(by: \.index)
}
var matchFormat: MatchFormat { var matchFormat: MatchFormat {
get { get {

@ -17,6 +17,12 @@ extension Club {
} }
} }
extension Round {
static func mock() -> Round {
Round(tournament: "", index: 0)
}
}
extension Tournament { extension Tournament {
static func mock() -> Tournament { static func mock() -> Tournament {
Tournament(groupStageSortMode: .snake, teamSorting: .inscriptionDate, federalCategory: .men, federalLevelCategory: .p100, federalAgeCategory: .senior) Tournament(groupStageSortMode: .snake, teamSorting: .inscriptionDate, federalCategory: .men, federalLevelCategory: .p100, federalAgeCategory: .senior)

@ -18,18 +18,44 @@ class Round: ModelObject, Storable {
var loser: String? var loser: String?
var format: Int? var format: Int?
internal init(id: String = Store.randomId(), tournament: String, index: Int, loser: String? = nil, format: Int? = nil) { internal init(tournament: String, index: Int, loser: String? = nil, format: Int? = nil) {
self.id = id
self.tournament = tournament self.tournament = tournament
self.index = index self.index = index
self.loser = loser self.loser = loser
self.format = format self.format = format
} }
func hasStarted() -> Bool {
matches.anySatisfy({ $0.hasStarted() })
}
func hasEnded() -> Bool {
matches.allSatisfy({ $0.hasEnded() })
}
var matches: [Match] { var matches: [Match] {
Store.main.filter { $0.round == self.id } Store.main.filter { $0.round == self.id }
} }
func previousRound() -> Round? {
Store.main.filter(isIncluded: { $0.tournament == tournament && $0.index == index + 1 }).first
}
func nextRound() -> Round? {
Store.main.filter(isIncluded: { $0.tournament == tournament && $0.index == index - 1 }).first
}
func roundTitle(_ displayStyle: DisplayStyle = .wide) -> String {
RoundRule.roundName(fromRoundIndex: index)
}
func roundStatus() -> String {
if hasStarted() && hasEnded() == false {
return "en cours"
} else {
return "à démarrer"
}
}
var loserRound: Round? { var loserRound: Round? {
guard let loser else { return nil } guard let loser else { return nil }

@ -139,6 +139,15 @@ class Tournament : ModelObject, Storable {
Store.main.filter { $0.tournament == self.id }.sorted(by: \.index) Store.main.filter { $0.tournament == self.id }.sorted(by: \.index)
} }
func getActiveRound() -> Round? {
let rounds = rounds()
return rounds.filter({ $0.hasStarted() && $0.hasEnded() == false }).sorted(by: \.index).reversed().first ?? rounds.first
}
func rounds() -> [Round] {
Store.main.filter { $0.tournament == self.id }.sorted(by: \.index).reversed()
}
func sortedTeams() -> [TeamRegistration] { func sortedTeams() -> [TeamRegistration] {
let teams = selectedSortedTeams() let teams = selectedSortedTeams()
return teams + waitingListTeams(in: teams) return teams + waitingListTeams(in: teams)
@ -177,7 +186,7 @@ class Tournament : ModelObject, Storable {
} }
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
print(id, title(), duration.formatted(.units(allowed: [.seconds, .milliseconds]))) print(id, tournamentTitle(), duration.formatted(.units(allowed: [.seconds, .milliseconds])))
return _sortedTeams return _sortedTeams
} }
@ -248,7 +257,7 @@ class Tournament : ModelObject, Storable {
} }
//todo //todo
var rounds: Int { func roundCount() -> Int {
4 4
} }
@ -367,7 +376,7 @@ class Tournament : ModelObject, Storable {
unsortedTeams().first(where: { $0.includes(players) }) unsortedTeams().first(where: { $0.includes(players) })
} }
func title(_ displayStyle: DisplayStyle = .wide) -> String { func tournamentTitle(_ displayStyle: DisplayStyle = .wide) -> String {
[tournamentLevel.localizedLabel(displayStyle), tournamentCategory.localizedLabel(displayStyle), name].compactMap({ $0 }).joined(separator: " ") [tournamentLevel.localizedLabel(displayStyle), tournamentCategory.localizedLabel(displayStyle), name].compactMap({ $0 }).joined(separator: " ")
} }
@ -416,6 +425,14 @@ class Tournament : ModelObject, Storable {
} }
func bracketStatus() -> String {
if let round = getActiveRound() {
return [round.roundTitle(), round.roundStatus()].joined(separator: " ")
} else {
return "à construire"
}
}
func groupStageStatus() -> String { func groupStageStatus() -> String {
let runningGroupStages = groupStages().filter({ $0.isRunning() }) let runningGroupStages = groupStages().filter({ $0.isRunning() })
if groupStagesAreOver() { return "terminées" } if groupStagesAreOver() { return "terminées" }
@ -442,6 +459,7 @@ class Tournament : ModelObject, Storable {
func buildStructure() { func buildStructure() {
buildGroupStages() buildGroupStages()
buildBracket()
} }
func buildGroupStages() { func buildGroupStages() {
@ -461,6 +479,41 @@ class Tournament : ModelObject, Storable {
refreshGroupStages() refreshGroupStages()
} }
func bracketTeamCount() -> Int {
let bracketTeamCount = teamCount - (teamsPerGroupStage - qualifiedPerGroupStage) * groupStageCount + (groupStageAdditionalQualified * (groupStageCount > 0 ? 1 : 0))
return bracketTeamCount
}
func buildBracket() {
try? DataStore.shared.rounds.delete(contentOfs: rounds())
// if let loserBrackets {
// removeFromLoserBrackets(loserBrackets)
// }
unsortedTeams().forEach({ $0.bracketPosition = nil })
let roundCount = RoundRule.numberOfRounds(forTeams: bracketTeamCount())
let rounds = (0..<roundCount).map { //index 0 is the final
Round(tournament: id, index: $0)
}
try? DataStore.shared.rounds.addOrUpdate(contentOfs: rounds)
let matchCount = RoundRule.numberOfMatches(forTeams: bracketTeamCount())
let matches = (0..<matchCount).map { //0 is final match
let roundIndex = RoundRule.roundIndex(fromMatchIndex: $0)
let round = rounds[roundIndex]
return Match(round: round.id, index: $0, matchFormat: matchFormat)
}
print(matches.map {
(RoundRule.roundName(fromMatchIndex: $0.index), RoundRule.matchIndexWithinRound(fromMatchIndex: $0.index))
})
try? DataStore.shared.matches.addOrUpdate(contentOfs: matches)
}
func resetStructure() { func resetStructure() {
} }
@ -478,16 +531,16 @@ class Tournament : ModelObject, Storable {
if groupStageCount > 0 { if groupStageCount > 0 {
switch groupStageOrderingMode { switch groupStageOrderingMode {
case .random: case .random:
setBrackets(randomize: true) setGroupStage(randomize: true)
case .snake: case .snake:
setBrackets(randomize: false) setGroupStage(randomize: false)
case .swiss: case .swiss:
setBrackets(randomize: true) setGroupStage(randomize: true)
} }
} }
} }
func setBrackets(randomize: Bool) { func setGroupStage(randomize: Bool) {
let groupStages = groupStages() let groupStages = groupStages()
let numberOfBracketsAsInt = groupStages.count let numberOfBracketsAsInt = groupStages.count
// let teamsPerBracket = teamsPerBracket // let teamsPerBracket = teamsPerBracket
@ -604,7 +657,7 @@ class Tournament : ModelObject, Storable {
} }
func loserBracketSmartMatchFormat(_ roundIndex: Int) -> MatchFormat { func loserBracketSmartMatchFormat(_ roundIndex: Int) -> MatchFormat {
let idx = rounds - roundIndex let idx = roundCount() - roundIndex
let format = tournamentLevel.federalFormatForLoserBracketRound(idx) let format = tournamentLevel.federalFormatForLoserBracketRound(idx)
if loserBracketMatchFormat.rank > format.rank { if loserBracketMatchFormat.rank > format.rank {
return format return format
@ -623,7 +676,7 @@ class Tournament : ModelObject, Storable {
} }
func roundSmartMatchFormat(_ roundIndex: Int) -> MatchFormat { func roundSmartMatchFormat(_ roundIndex: Int) -> MatchFormat {
let idx = rounds - roundIndex let idx = roundCount() - roundIndex
let format = tournamentLevel.federalFormatForBracketRound(idx) let format = tournamentLevel.federalFormatForBracketRound(idx)
if matchFormat.rank > format.rank { if matchFormat.rank > format.rank {
return format return format
@ -652,7 +705,7 @@ class Tournament : ModelObject, Storable {
override func deleteDependencies() throws { override func deleteDependencies() throws {
try Store.main.deleteDependencies(items: self.unsortedTeams()) try Store.main.deleteDependencies(items: self.unsortedTeams())
try Store.main.deleteDependencies(items: self.groupStages()) try Store.main.deleteDependencies(items: self.groupStages())
//try Store.main.deleteDependencies(items: self.rounds()) try Store.main.deleteDependencies(items: self.rounds())
} }
} }

@ -524,28 +524,28 @@ enum TournamentLevel: Int, Hashable, Codable, CaseIterable, Identifiable {
} }
} }
enum NewBallSystem { // enum NewBallSystem {
case perField // case perField
case perMatch(fromRound: Int?) // case perMatch(fromRound: Int?)
//
func localizedLabel(loserBracket: Bool = false) -> String { // func localizedLabel(loserBracket: Bool = false) -> String {
switch self { // switch self {
case .perField: // case .perField:
return "3 / piste" // return "3 / piste"
case .perMatch(let fromRound): // case .perMatch(let fromRound):
if fromRound != nil { // if fromRound != nil {
if loserBracket { // if loserBracket {
return "3 / match pour les perdants des \(RoundLabel.shortLabels[fromRound!].lowercased())s" // return "3 / match pour les perdants des \(RoundLabel.shortLabels[fromRound!].lowercased())s"
} else { // } else {
return "3 / match à partir des \(RoundLabel.shortLabels[fromRound!].lowercased())s" // return "3 / match à partir des \(RoundLabel.shortLabels[fromRound!].lowercased())s"
} // }
} else { // } else {
return "3 / match" // return "3 / match"
} // }
} // }
} // }
} // }
//
func minimumFormatFinalTableAndQualifier(roundIndex: Int) -> MatchFormat? { func minimumFormatFinalTableAndQualifier(roundIndex: Int) -> MatchFormat? {
switch self { switch self {
case .p25: case .p25:
@ -591,30 +591,30 @@ enum TournamentLevel: Int, Hashable, Codable, CaseIterable, Identifiable {
} }
func newBallsFinalTable() -> NewBallSystem? { // func newBallsFinalTable() -> NewBallSystem? {
switch self { // switch self {
case .p25, .p100: // case .p25, .p100:
return .perField // return .perField
case .p250: // case .p250:
return .perMatch(fromRound: 1) //demi // return .perMatch(fromRound: 1) //demi
case .p500: // case .p500:
return .perMatch(fromRound: 2) //quart // return .perMatch(fromRound: 2) //quart
case .p1000, .p1500, .p2000: // case .p1000, .p1500, .p2000:
return .perMatch(fromRound: nil) // return .perMatch(fromRound: nil)
} // }
} // }
//
func newBallsLoserBracket() -> NewBallSystem? { // func newBallsLoserBracket() -> NewBallSystem? {
switch self { // switch self {
case .p25, .p100: // case .p25, .p100:
return nil // return nil
case .p250: // case .p250:
return .perMatch(fromRound: 1) //demi // return .perMatch(fromRound: 1) //demi
case .p500, .p1000, .p1500, .p2000: // case .p500, .p1000, .p1500, .p2000:
return .perMatch(fromRound: 2) //quart // return .perMatch(fromRound: 2) //quart
} // }
} // }
//
} }
enum TournamentCategory: Int, Hashable, Codable, CaseIterable, Identifiable { enum TournamentCategory: Int, Hashable, Codable, CaseIterable, Identifiable {
@ -1378,12 +1378,7 @@ enum PlayersCountRange: Int, CaseIterable {
} }
} }
enum RoundLabel { enum RoundRule {
static let labels = ["Finale", "Demi-finale", "Quart de finale", "8ème de finale", "16ème de finale", "32ème de finale", "64ème de finale"]
static let shortLabels = ["Finale", "Demi", "Quart", "8ème", "16ème", "32ème", "64ème"]
static let colors = ["#d4afb9", "#d1cfe2", "#9cadce", "#7ec4cf", "#daeaf6", "#caffbf"]
static let freeMatchLabels = ["Finale", "Demi-finale", "Quart de finale", "8ème de finale", "16ème de finale", "32ème de finale", "Poule"]
static func loserBrackets(index: Int) -> [String] { static func loserBrackets(index: Int) -> [String] {
switch index { switch index {
case 1: case 1:
@ -1394,6 +1389,46 @@ enum RoundLabel {
return ["#9/#10", "#11/#12", "#13/#14", "#15/#16", "#17/#18", "#19/#20", "#21/#22", "#23/#24", "#25/#26", "#27/#28", "#29/#30", "#31/#32"] return ["#9/#10", "#11/#12", "#13/#14", "#15/#16", "#17/#18", "#19/#20", "#21/#22", "#23/#24", "#25/#26", "#27/#28", "#29/#30", "#31/#32"]
} }
} }
static func teamsInFirstRound(forTeams teams: Int) -> Int {
Int(pow(2.0, ceil(log2(Double(teams)))))
}
static func numberOfMatches(forTeams teams: Int) -> Int {
teamsInFirstRound(forTeams: teams) - 1
}
static func numberOfRounds(forTeams teams: Int) -> Int {
Int(log2(Double(teamsInFirstRound(forTeams: teams))))
}
static func roundIndex(fromMatchIndex matchIndex: Int) -> Int {
Int(log2(Double(matchIndex + 1)))
}
static func matchIndexWithinRound(fromMatchIndex matchIndex: Int) -> Int {
let roundIndex = roundIndex(fromMatchIndex: matchIndex)
let matchIndexWithinRound = matchIndex - (Int(pow(2.0, Double(roundIndex))) - 1)
return matchIndexWithinRound
}
static func roundName(fromMatchIndex matchIndex: Int) -> String {
let roundIndex = roundIndex(fromMatchIndex: matchIndex)
return roundName(fromRoundIndex: roundIndex)
}
static func roundName(fromRoundIndex roundIndex: Int) -> String {
switch roundIndex {
case 0:
return "Finale"
case 1:
return "Demi-finale"
case 2:
return "Quart de finale"
default:
return "\(Int(pow(2.0, Double(roundIndex))))ème"
}
}
} }
enum AnimationType: Int, CaseIterable, Hashable, Identifiable { enum AnimationType: Int, CaseIterable, Hashable, Identifiable {

@ -13,7 +13,7 @@ struct ClubView: View {
var body: some View { var body: some View {
List(club.tournaments) { tournament in List(club.tournaments) { tournament in
Text(tournament.title()) Text(tournament.tournamentTitle())
}.navigationTitle(club.name) }.navigationTitle(club.name)
} }
} }

@ -66,9 +66,9 @@ struct GroupStageView: View {
} header: { } header: {
HStack { HStack {
if groupStage.isBroadcasted() { if groupStage.isBroadcasted() {
Label(groupStage.title(), systemImage: "airplayvideo") Label(groupStage.groupStageTitle(), systemImage: "airplayvideo")
} else { } else {
Text(groupStage.title()) Text(groupStage.groupStageTitle())
} }
Spacer() Spacer()
if let startDate = groupStage.startDate { if let startDate = groupStage.startDate {

@ -22,7 +22,7 @@ struct GroupStagesView: View {
case -1: case -1:
return "Toutes les poules" return "Toutes les poules"
default: default:
return tournament.groupStages()[selectedGroupStageIndex].title() return tournament.groupStages()[selectedGroupStageIndex].groupStageTitle()
} }
} }
// //
@ -166,7 +166,7 @@ struct GroupStagesView: View {
MatchRowView(match: match, setupSeedContext: false, matchViewStyle: .sectionedStandardStyle) MatchRowView(match: match, setupSeedContext: false, matchViewStyle: .sectionedStandardStyle)
} }
} header: { } header: {
Text("Matchs de la " + groupStage.title()) Text("Matchs de la " + groupStage.groupStageTitle())
} }
} }
} }
@ -178,7 +178,7 @@ struct GroupStagesView: View {
Picker(selection: $selectedGroupStageIndex) { Picker(selection: $selectedGroupStageIndex) {
Text("Toutes").tag(-1) Text("Toutes").tag(-1)
ForEach(tournament.groupStages()) { groupStage in ForEach(tournament.groupStages()) { groupStage in
Text(groupStage.title(.short)).tag(groupStage.index) Text(groupStage.groupStageTitle(.short)).tag(groupStage.index)
} }
} label: { } label: {
@ -189,7 +189,7 @@ struct GroupStagesView: View {
Picker(selection: $selectedGroupStageIndex) { Picker(selection: $selectedGroupStageIndex) {
Image(systemName: "square.stack").tag(-1) Image(systemName: "square.stack").tag(-1)
ForEach(tournament.groupStages()) { groupStage in ForEach(tournament.groupStages()) { groupStage in
Text(groupStage.title(.short)).tag(groupStage.index) Text(groupStage.groupStageTitle(.short)).tag(groupStage.index)
} }
} label: { } label: {
@ -200,7 +200,7 @@ struct GroupStagesView: View {
Picker(selection: $selectedGroupStageIndex) { Picker(selection: $selectedGroupStageIndex) {
Text("Voir toutes les poules").tag(-1) Text("Voir toutes les poules").tag(-1)
ForEach(tournament.groupStages()) { groupStage in ForEach(tournament.groupStages()) { groupStage in
Text(groupStage.title()).tag(groupStage.index) Text(groupStage.groupStageTitle()).tag(groupStage.index)
} }
} label: { } label: {
Text("\(tournament.groupStages().count.formatted()) poules") Text("\(tournament.groupStages().count.formatted()) poules")

@ -304,7 +304,7 @@ struct MatchDetailView: View {
// } // }
// } // }
// } // }
.navigationTitle(match.title()) .navigationTitle(match.matchTitle())
.navigationBarTitleDisplayMode(.large) .navigationBarTitleDisplayMode(.large)
} }

@ -59,7 +59,7 @@ struct MatchSummaryView: View {
HStack { HStack {
if match.isGroupStage() && matchViewStyle != .feedStyle { if match.isGroupStage() && matchViewStyle != .feedStyle {
if let groupStage = match.groupStageObject, matchViewStyle == .standardStyle { if let groupStage = match.groupStageObject, matchViewStyle == .standardStyle {
Text(groupStage.title()) Text(groupStage.groupStageTitle())
} }
// if let index = match.entrantOne()?.bracketPositions?.first, let index2 = match.entrantTwo()?.bracketPositions?.first { // if let index = match.entrantOne()?.bracketPositions?.first, let index2 = match.entrantTwo()?.bracketPositions?.first {
// Text("#\(index) contre #\(index2)") // Text("#\(index) contre #\(index2)")
@ -68,7 +68,7 @@ struct MatchSummaryView: View {
if matchViewStyle == .feedStyle { if matchViewStyle == .feedStyle {
//tournamentHeaderView(currentTournament) //tournamentHeaderView(currentTournament)
} else if matchViewStyle != .sectionedStandardStyle { } else if matchViewStyle != .sectionedStandardStyle {
Text(match.title(.short)) Text(match.matchTitle(.short))
} }
} }
if matchViewStyle == .standardStyle || matchViewStyle == .sectionedStandardStyle if matchViewStyle == .standardStyle || matchViewStyle == .sectionedStandardStyle

@ -99,7 +99,7 @@ struct CalendarView: View {
// ForEach(tournamentsByDay) { tournament in // ForEach(tournamentsByDay) { tournament in
// NavigationLink(value: tournament) { // NavigationLink(value: tournament) {
// HStack { // HStack {
// Text(tournament.title()) // Text(tournament.tournamentTitle())
// Spacer() // Spacer()
// Text(tournament.sortedTeams().count.formatted()) // Text(tournament.sortedTeams().count.formatted())
// } // }

@ -0,0 +1,29 @@
//
// RoundView.swift
// PadelClub
//
// Created by Razmig Sarkissian on 30/03/2024.
//
import SwiftUI
struct RoundView: View {
var round: Round
var body: some View {
List {
ForEach(round.matches) { match in
Section {
MatchRowView(match: match, setupSeedContext: false, matchViewStyle: .sectionedStandardStyle)
} header: {
Text(match.matchTitle())
}
}
}
.headerProminence(.increased)
}
}
#Preview {
RoundView(round: Round.mock())
}

@ -0,0 +1,80 @@
//
// RoundsView.swift
// PadelClub
//
// Created by Razmig Sarkissian on 30/03/2024.
//
import SwiftUI
protocol Selectable {
func selectionLabel() -> String
}
extension Round: Selectable {
func selectionLabel() -> String {
roundTitle()
}
}
struct GenericDestinationPickerView<T: Identifiable & Selectable>: View {
@Binding var selectedDestination: T?
let destinations: [T]
var body: some View {
ScrollView(.horizontal) {
HStack {
ForEach(destinations) { destination in
Button {
selectedDestination = destination
} label: {
Text(destination.selectionLabel())
}
.padding()
.background {
Capsule()
.fill(Color.white)
.opacity(selectedDestination?.id == destination.id ? 1.0 : 0.5)
}
.buttonStyle(.plain)
}
}
.fixedSize()
.padding(8)
}
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
.background(Material.ultraThinMaterial)
.overlay {
VStack(spacing: 0) {
Spacer()
Divider()
}
}
}
}
struct RoundsView: View {
var tournament: Tournament
@State private var selectedRound: Round?
init(tournament: Tournament) {
self.tournament = tournament
_selectedRound = State(wrappedValue: tournament.getActiveRound())
}
var body: some View {
VStack(spacing: 0) {
GenericDestinationPickerView(selectedDestination: $selectedRound, destinations: tournament.rounds())
if let selectedRound {
RoundView(round: selectedRound)
}
}
.navigationTitle(selectedRound?.roundTitle() ?? "")
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.visible, for: .navigationBar)
}
}
#Preview {
RoundsView(tournament: Tournament.mock())
}

@ -10,6 +10,7 @@ import Foundation
enum Screen: String, Codable { enum Screen: String, Codable {
case inscription case inscription
case groupStage case groupStage
case round
case settings case settings
case structure case structure
} }

@ -234,6 +234,8 @@ struct TableStructureView: View {
} }
private func _save(rebuildEverything: Bool = false) { private func _save(rebuildEverything: Bool = false) {
_verifyValueIntegrity()
do { do {
let requirements = Set(updatedElements.compactMap { $0.requiresRebuilding }) let requirements = Set(updatedElements.compactMap { $0.requiresRebuilding })

@ -21,6 +21,17 @@ struct TournamentRunningView: View {
} }
} }
} }
Section {
NavigationLink(value: Screen.round) {
LabeledContent {
Text(tournament.bracketStatus())
} label: {
Text("Tableau")
}
}
}
} }
} }

@ -76,6 +76,8 @@ struct TournamentView: View {
InscriptionManagerView(tournament: tournament) InscriptionManagerView(tournament: tournament)
case .groupStage: case .groupStage:
GroupStagesView() GroupStagesView()
case .round:
RoundsView(tournament: tournament)
} }
} }
.environment(tournament) .environment(tournament)
@ -84,7 +86,7 @@ struct TournamentView: View {
.toolbar { .toolbar {
ToolbarItem(placement: .principal) { ToolbarItem(placement: .principal) {
VStack { VStack {
Text(tournament.title()).font(.headline) Text(tournament.tournamentTitle()).font(.headline)
Text(tournament.formattedDate()) Text(tournament.formattedDate())
.font(.subheadline).foregroundStyle(.secondary) .font(.subheadline).foregroundStyle(.secondary)
} }

Loading…
Cancel
Save