Laurent 1 year ago
commit 78b71710c0
  1. 4
      PadelClub.xcodeproj/project.pbxproj
  2. 6
      PadelClub/Assets.xcassets/logoRed.colorset/Contents.json
  3. 7
      PadelClub/Data/Event.swift
  4. 6
      PadelClub/Data/GroupStage.swift
  5. 6
      PadelClub/Data/Match.swift
  6. 79
      PadelClub/Data/Round.swift
  7. 73
      PadelClub/Data/Tournament.swift
  8. 2
      PadelClub/Utils/PadelRule.swift
  9. 25
      PadelClub/ViewModel/SeedInterval.swift
  10. 2
      PadelClub/ViewModel/Selectable.swift
  11. 44
      PadelClub/Views/Components/FortuneWheelView.swift
  12. 4
      PadelClub/Views/Components/GenericDestinationPickerView.swift
  13. 2
      PadelClub/Views/Components/RowButtonView.swift
  14. 2
      PadelClub/Views/Match/Components/MatchTeamDetailView.swift
  15. 2
      PadelClub/Views/Navigation/Umpire/UmpireView.swift
  16. 2
      PadelClub/Views/Planning/CourtAvailabilitySettingsView.swift
  17. 100
      PadelClub/Views/Round/RoundView.swift
  18. 3
      PadelClub/Views/Team/Components/TeamHeaderView.swift
  19. 2
      PadelClub/Views/Team/TeamRowView.swift
  20. 2
      PadelClub/Views/Tournament/FileImportView.swift
  21. 8
      PadelClub/Views/Tournament/Screen/Components/InscriptionInfoView.swift
  22. 20
      PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift
  23. 2
      PadelClub/Views/Tournament/Screen/TournamentRankView.swift
  24. 4
      PadelClub/Views/Tournament/TournamentBuildView.swift
  25. 2
      PadelClub/Views/User/ChangePasswordView.swift
  26. 2
      PadelClub/Views/User/LoginView.swift

@ -1906,7 +1906,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 88; CURRENT_PROJECT_VERSION = 89;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
@ -1950,7 +1950,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 88; CURRENT_PROJECT_VERSION = 89;
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
DEVELOPMENT_TEAM = BQ3Y44M3Q6; DEVELOPMENT_TEAM = BQ3Y44M3Q6;

@ -23,9 +23,9 @@
"color-space" : "srgb", "color-space" : "srgb",
"components" : { "components" : {
"alpha" : "1.000", "alpha" : "1.000",
"blue" : "1.000", "blue" : "0.220",
"green" : "1.000", "green" : "0.251",
"red" : "1.000" "red" : "0.910"
} }
}, },
"idiom" : "universal" "idiom" : "universal"

@ -32,7 +32,12 @@ final class Event: ModelObject, Storable {
} }
override func deleteDependencies() throws { override func deleteDependencies() throws {
DataStore.shared.tournaments.deleteDependencies(self.tournaments) let tournaments = self.tournaments
for tournament in tournaments {
try tournament.deleteDependencies()
}
DataStore.shared.tournaments.deleteDependencies(tournaments)
DataStore.shared.dateIntervals.deleteDependencies(self.courtsUnavailability) DataStore.shared.dateIntervals.deleteDependencies(self.courtsUnavailability)
} }

@ -381,7 +381,11 @@ final class GroupStage: ModelObject, Storable {
} }
override func deleteDependencies() throws { override func deleteDependencies() throws {
self.tournamentStore.matches.deleteDependencies(self._matches()) let matches = self._matches()
for match in matches {
try match.deleteDependencies()
}
self.tournamentStore.matches.deleteDependencies(matches)
} }
func encode(to encoder: Encoder) throws { func encode(to encoder: Encoder) throws {

@ -78,7 +78,11 @@ final class Match: ModelObject, Storable {
guard let tournament = self.currentTournament() else { guard let tournament = self.currentTournament() else {
return return
} }
tournament.tournamentStore.teamScores.deleteDependencies(self.teamScores) let teamScores = self.teamScores
for teamScore in teamScores {
try teamScore.deleteDependencies()
}
tournament.tournamentStore.teamScores.deleteDependencies(teamScores)
} }
func indexInRound(in matches: [Match]? = nil) -> Int { func indexInRound(in matches: [Match]? = nil) -> Int {

@ -455,12 +455,12 @@ defer {
return nextRound()?.isRankDisabled() == false return nextRound()?.isRankDisabled() == false
} }
func seedInterval(expanded: Bool = false) -> SeedInterval? { func seedInterval(initialMode: Bool = false) -> SeedInterval? {
#if DEBUG_TIME //DEBUGING TIME #if DEBUG_TIME //DEBUGING TIME
let start = Date() let start = Date()
defer { defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
print("func seedInterval(expanded: Bool = false)", duration.formatted(.units(allowed: [.seconds, .milliseconds]))) print("func seedInterval(initialMode)", initialMode, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
} }
#endif #endif
@ -477,24 +477,24 @@ defer {
} }
if let previousRound = previousRound() { if let previousRound = previousRound() {
if previousRound.enabledMatches().isEmpty == false && expanded == false { if (previousRound.enabledMatches().isEmpty == false || initialMode) {
return previousRound.seedInterval()?.chunks()?.first return previousRound.seedInterval(initialMode: initialMode)?.chunks()?.first
} else { } else {
return previousRound.previousRound()?.seedInterval() return previousRound.previousRound()?.seedInterval(initialMode: initialMode)
} }
} else if let parentRound { } else if let parentRound {
if parentRound.parent == nil && expanded == false { if parentRound.parent == nil {
return parentRound.seedInterval() return parentRound.seedInterval(initialMode: initialMode)
} }
return parentRound.seedInterval()?.chunks()?.last return parentRound.seedInterval(initialMode: initialMode)?.chunks()?.last
} }
return nil return nil
} }
func roundTitle(_ displayStyle: DisplayStyle = .wide) -> String { func roundTitle(_ displayStyle: DisplayStyle = .wide, initialMode: Bool = false) -> String {
if parent != nil { if parent != nil {
if let seedInterval = seedInterval() { if let seedInterval = seedInterval(initialMode: initialMode) {
return seedInterval.localizedLabel(displayStyle) return seedInterval.localizedLabel(displayStyle)
} }
print("Round pas trouvé", id, parent, index) print("Round pas trouvé", id, parent, index)
@ -556,7 +556,11 @@ defer {
let currentRoundMatchCount = RoundRule.numberOfMatches(forRoundIndex: index) let currentRoundMatchCount = RoundRule.numberOfMatches(forRoundIndex: index)
guard currentRoundMatchCount > 1 else { return } guard currentRoundMatchCount > 1 else { return }
let roundCount = RoundRule.numberOfRounds(forTeams: currentRoundMatchCount) let roundCount = RoundRule.numberOfRounds(forTeams: currentRoundMatchCount)
let loserBracketMatchFormat = tournamentObject()?.loserBracketMatchFormat
var loserBracketMatchFormat = tournamentObject()?.loserBracketMatchFormat
if let parentRound {
loserBracketMatchFormat = tournamentObject()?.loserBracketSmartMatchFormat(parentRound.index)
}
let rounds = (0..<roundCount).map { //index 0 is the final let rounds = (0..<roundCount).map { //index 0 is the final
let round = Round(tournament: tournament, index: $0, matchFormat: loserBracketMatchFormat) let round = Round(tournament: tournament, index: $0, matchFormat: loserBracketMatchFormat)
@ -574,19 +578,8 @@ defer {
let matches = (0..<matchCount).map { //0 is final match let matches = (0..<matchCount).map { //0 is final match
let roundIndex = RoundRule.roundIndex(fromMatchIndex: $0) let roundIndex = RoundRule.roundIndex(fromMatchIndex: $0)
let round = rounds[roundIndex] let round = rounds[roundIndex]
return Match(round: round.id, index: $0, matchFormat: loserBracketMatchFormat) return Match(round: round.id, index: $0, matchFormat: loserBracketMatchFormat, name: round.roundTitle(initialMode: true))
} //initial mode let the roundTitle give a name without considering the playable match
do {
try self.tournamentStore.matches.addOrUpdate(contentOfs: matches)
} catch {
Logger.error(error)
}
matches.forEach { //0 is final match
let roundIndex = RoundRule.roundIndex(fromMatchIndex: $0.index)
let round = rounds[roundIndex]
$0.name = round.roundTitle()
} }
do { do {
@ -598,29 +591,6 @@ defer {
loserRounds().forEach { round in loserRounds().forEach { round in
round.buildLoserBracket() round.buildLoserBracket()
} }
/*
return Match(round: round.id, index: $0, matchFormat: loserBracketMatchFormat)
}
do {
try DataStore.shared.matches.addOrUpdate(contentOfs: matches)
} catch {
Logger.error(error)
}
matches.forEach {
$0.name = $0.roundObject?.roundTitle()
}
do {
try DataStore.shared.matches.addOrUpdate(contentOfs: matches)
} catch {
Logger.error(error)
}
*/
} }
var parentRound: Round? { var parentRound: Round? {
@ -657,8 +627,19 @@ defer {
} }
override func deleteDependencies() throws { override func deleteDependencies() throws {
self.tournamentStore.matches.deleteDependencies(self._matches()) let matches = self._matches()
self.tournamentStore.rounds.deleteDependencies(self.loserRoundsAndChildren()) for match in matches {
try match.deleteDependencies()
}
self.tournamentStore.matches.deleteDependencies(matches)
let loserRounds = self.loserRounds()
for round in loserRounds {
try round.deleteDependencies()
}
self.tournamentStore.rounds.deleteDependencies(loserRounds)
} }
enum CodingKeys: String, CodingKey { enum CodingKeys: String, CodingKey {

@ -347,9 +347,24 @@ final class Tournament : ModelObject, Storable {
override func deleteDependencies() throws { override func deleteDependencies() throws {
let store = self.tournamentStore let store = self.tournamentStore
store.teamRegistrations.deleteDependencies(self.unsortedTeams()) let teams = self.unsortedTeams()
store.groupStages.deleteDependencies(self.groupStages()) for team in teams {
store.rounds.deleteDependencies(self.rounds()) try team.deleteDependencies()
}
store.teamRegistrations.deleteDependencies(teams)
let groups = self.groupStages()
for group in groups {
try group.deleteDependencies()
}
store.groupStages.deleteDependencies(groups)
let rounds = self.rounds()
for round in rounds {
try round.deleteDependencies()
}
store.rounds.deleteDependencies(rounds)
store.matchSchedulers.deleteDependencies(self._matchSchedulers()) store.matchSchedulers.deleteDependencies(self._matchSchedulers())
} }
@ -792,6 +807,12 @@ defer {
return teams + waitingListTeams(in: teams) return teams + waitingListTeams(in: teams)
} }
func waitingListSortedTeams() -> [TeamRegistration] {
let teams = selectedSortedTeams()
return waitingListTeams(in: teams)
}
func selectedSortedTeams() -> [TeamRegistration] { func selectedSortedTeams() -> [TeamRegistration] {
#if DEBUG_TIME //DEBUGING TIME #if DEBUG_TIME //DEBUGING TIME
let start = Date() let start = Date()
@ -860,7 +881,7 @@ defer {
return waitingList.filter { $0.walkOut == false }.sorted(using: _defaultSorting(), order: .ascending) + waitingList.filter { $0.walkOut == true }.sorted(using: _defaultSorting(), order: .ascending) return waitingList.filter { $0.walkOut == false }.sorted(using: _defaultSorting(), order: .ascending) + waitingList.filter { $0.walkOut == true }.sorted(using: _defaultSorting(), order: .ascending)
} }
func bracketCut() -> Int { func bracketCut(teamCount: Int) -> Int {
return max(0, teamCount - groupStageCut()) return max(0, teamCount - groupStageCut())
} }
@ -868,10 +889,12 @@ defer {
return groupStageSpots() return groupStageSpots()
} }
func cutLabel(index: Int) -> String { func cutLabel(index: Int, teamCount: Int?) -> String {
if index < bracketCut() { let _teamCount = teamCount ?? selectedSortedTeams().count
let bracketCut = bracketCut(teamCount: _teamCount)
if index < bracketCut {
return "Tableau" return "Tableau"
} else if index - bracketCut() < groupStageCut() { } else if index - bracketCut < groupStageCut() && _teamCount > 0 {
return "Poule" return "Poule"
} else { } else {
return "Attente" return "Attente"
@ -1172,12 +1195,12 @@ defer {
let disabledIds = playedMatches.flatMap({ $0.teamScores.compactMap({ $0.teamRegistration }) }).filter({ ids.contains($0) == false }) let disabledIds = playedMatches.flatMap({ $0.teamScores.compactMap({ $0.teamRegistration }) }).filter({ ids.contains($0) == false })
if disabledIds.isEmpty == false { if disabledIds.isEmpty == false {
_removeStrings(from: &teams, stringsToRemove: disabledIds) _removeStrings(from: &teams, stringsToRemove: disabledIds)
teams[interval.computedLast] = disabledIds teams[interval.last] = disabledIds
let teamNames : [String] = disabledIds.compactMap { let teamNames : [String] = disabledIds.compactMap {
let t : TeamRegistration? = Store.main.findById($0) let t : TeamRegistration? = Store.main.findById($0)
return t return t
}.map { $0.canonicalName } }.map { $0.canonicalName }
print("winners.isEmpty", "\(interval.computedLast) : ", teamNames) print("winners.isEmpty", "\(interval.last) : ", teamNames)
disabledIds.forEach { disabledIds.forEach {
ids.insert($0) ids.insert($0)
} }
@ -1185,23 +1208,23 @@ defer {
} else { } else {
if winners.isEmpty == false { if winners.isEmpty == false {
_removeStrings(from: &teams, stringsToRemove: winners) _removeStrings(from: &teams, stringsToRemove: winners)
teams[interval.computedFirst + winners.count - 1] = winners teams[interval.first + winners.count - 1] = winners
let teamNames : [String] = winners.compactMap { let teamNames : [String] = winners.compactMap {
let t: TeamRegistration? = Store.main.findById($0) let t: TeamRegistration? = Store.main.findById($0)
return t return t
}.map { $0.canonicalName } }.map { $0.canonicalName }
print("winners", "\(interval.computedFirst + winners.count - 1) : ", teamNames) print("winners", "\(interval.last + winners.count - 1) : ", teamNames)
winners.forEach { ids.insert($0) } winners.forEach { ids.insert($0) }
} }
if losers.isEmpty == false { if losers.isEmpty == false {
_removeStrings(from: &teams, stringsToRemove: losers) _removeStrings(from: &teams, stringsToRemove: losers)
teams[interval.computedLast] = losers teams[interval.last] = losers
let loserTeamNames : [String] = losers.compactMap { let loserTeamNames : [String] = losers.compactMap {
let t: TeamRegistration? = Store.main.findById($0) let t: TeamRegistration? = Store.main.findById($0)
return t return t
}.map { $0.canonicalName } }.map { $0.canonicalName }
print("losers", "\(interval.computedLast) : ", loserTeamNames) print("losers", "\(interval.last) : ", loserTeamNames)
losers.forEach { ids.insert($0) } losers.forEach { ids.insert($0) }
} }
} }
@ -1514,6 +1537,7 @@ defer {
} }
func deleteAndBuildEverything() { func deleteAndBuildEverything() {
resetBracketPosition()
deleteStructure() deleteStructure()
deleteGroupStages() deleteGroupStages()
buildGroupStages() buildGroupStages()
@ -1527,7 +1551,7 @@ defer {
var _groupStages = [GroupStage]() var _groupStages = [GroupStage]()
for index in 0..<groupStageCount { for index in 0..<groupStageCount {
let groupStage = GroupStage(tournament: id, index: index, size: teamsPerGroupStage, matchFormat: groupStageMatchFormat) let groupStage = GroupStage(tournament: id, index: index, size: teamsPerGroupStage, matchFormat: groupStageSmartMatchFormat())
_groupStages.append(groupStage) _groupStages.append(groupStage)
} }
@ -1550,7 +1574,7 @@ defer {
let roundCount = RoundRule.numberOfRounds(forTeams: bracketTeamCount()) let roundCount = RoundRule.numberOfRounds(forTeams: bracketTeamCount())
let rounds = (0..<roundCount).map { //index 0 is the final let rounds = (0..<roundCount).map { //index 0 is the final
Round(tournament: id, index: $0) return Round(tournament: id, index: $0, matchFormat: roundSmartMatchFormat($0))
} }
do { do {
@ -1563,7 +1587,7 @@ defer {
let matches = (0..<matchCount).map { //0 is final match let matches = (0..<matchCount).map { //0 is final match
let roundIndex = RoundRule.roundIndex(fromMatchIndex: $0) let roundIndex = RoundRule.roundIndex(fromMatchIndex: $0)
let round = rounds[roundIndex] let round = rounds[roundIndex]
return Match(round: round.id, index: $0, matchFormat: matchFormat, name: Match.setServerTitle(upperRound: round, matchIndex: RoundRule.matchIndexWithinRound(fromMatchIndex: $0))) return Match(round: round.id, index: $0, matchFormat: round.matchFormat, name: Match.setServerTitle(upperRound: round, matchIndex: RoundRule.matchIndexWithinRound(fromMatchIndex: $0)))
} }
print(matches.map { print(matches.map {
@ -1576,7 +1600,7 @@ defer {
Logger.error(error) Logger.error(error)
} }
self.rounds().forEach { round in rounds.forEach { round in
round.buildLoserBracket() round.buildLoserBracket()
} }
} }
@ -1609,6 +1633,9 @@ defer {
} catch { } catch {
Logger.error(error) Logger.error(error)
} }
}
func resetBracketPosition() {
unsortedTeams().forEach({ $0.bracketPosition = nil }) unsortedTeams().forEach({ $0.bracketPosition = nil })
do { do {
try self.tournamentStore.teamRegistrations.addOrUpdate(contentOfs: unsortedTeams()) try self.tournamentStore.teamRegistrations.addOrUpdate(contentOfs: unsortedTeams())
@ -1623,16 +1650,6 @@ defer {
} catch { } catch {
Logger.error(error) Logger.error(error)
} }
do {
unsortedTeams().forEach({
$0.groupStage = nil
$0.groupStagePosition = nil
})
try self.tournamentStore.teamRegistrations.addOrUpdate(contentOfs: unsortedTeams())
} catch {
Logger.error(error)
}
} }
func refreshGroupStages() { func refreshGroupStages() {
@ -1774,7 +1791,7 @@ defer {
teamSorting = newValue.defaultTeamSortingType teamSorting = newValue.defaultTeamSortingType
groupStageMatchFormat = groupStageSmartMatchFormat() groupStageMatchFormat = groupStageSmartMatchFormat()
loserBracketMatchFormat = loserBracketSmartMatchFormat(1) loserBracketMatchFormat = loserBracketSmartMatchFormat(1)
matchFormat = roundSmartMatchFormat(1) matchFormat = roundSmartMatchFormat(5)
} }
} }

@ -329,7 +329,7 @@ enum TournamentLevel: Int, Hashable, Codable, CaseIterable, Identifiable {
case .p500: case .p500:
if roundIndex == 0 { //finale if roundIndex == 0 { //finale
return .twoSetsDecisivePoint return .twoSetsDecisivePointSuperTie
} else if roundIndex == 1 { //demi-finale } else if roundIndex == 1 { //demi-finale
return .twoSetsDecisivePointSuperTie return .twoSetsDecisivePointSuperTie
} else { } else {

@ -10,11 +10,10 @@ import Foundation
struct SeedInterval: Hashable, Comparable { struct SeedInterval: Hashable, Comparable {
let first: Int let first: Int
let last: Int let last: Int
var reduce: Int = 0
func pointsRange(tournamentLevel: TournamentLevel, teamsCount: Int) -> String { func pointsRange(tournamentLevel: TournamentLevel, teamsCount: Int) -> String {
let range = [tournamentLevel.points(for: last - 1 - reduce, count: teamsCount), let range = [tournamentLevel.points(for: last - 1, count: teamsCount),
tournamentLevel.points(for: first - 1 - reduce, count: teamsCount)] tournamentLevel.points(for: first - 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"
} }
@ -38,12 +37,12 @@ struct SeedInterval: Hashable, Comparable {
if dimension > 3 { if dimension > 3 {
let split = dimension / 2 let split = dimension / 2
if split%2 == 0 { if split%2 == 0 {
let firstHalf = SeedInterval(first: first, last: first + split - 1, reduce: reduce) let firstHalf = SeedInterval(first: first, last: first + split - 1)
let secondHalf = SeedInterval(first: first + split, last: last, reduce: reduce) let secondHalf = SeedInterval(first: first + split, last: last)
return [firstHalf, secondHalf] return [firstHalf, secondHalf]
} else { } else {
let firstHalf = SeedInterval(first: first, last: first + split, reduce: reduce) let firstHalf = SeedInterval(first: first, last: first + split)
let secondHalf = SeedInterval(first: first + split + 1, last: last, reduce: reduce) let secondHalf = SeedInterval(first: first + split + 1, last: last)
return [firstHalf, secondHalf] return [firstHalf, secondHalf]
} }
} else { } else {
@ -51,19 +50,11 @@ struct SeedInterval: Hashable, Comparable {
} }
} }
var computedLast: Int {
last - reduce
}
var computedFirst: Int {
first - reduce
}
func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String {
if dimension < 2 { if dimension < 2 {
return "#\(first - reduce) / #\(last - reduce)" return "#\(first) / #\(last)"
} else { } else {
return "#\(first - reduce) à #\(last - reduce)" return "#\(first) à #\(last)"
} }
} }
} }

@ -43,7 +43,7 @@ enum Badge {
case .checkmark: case .checkmark:
.green .green
case .xmark: case .xmark:
.red .logoRed
case .custom(_, let color): case .custom(_, let color):
color color
} }

@ -62,13 +62,18 @@ struct SpinDrawView: View {
let drawees: [any SpinDrawable] let drawees: [any SpinDrawable]
@State var segments: [any SpinDrawable] @State var segments: [any SpinDrawable]
var autoMode: Bool = false var autoMode: Bool = false
let completion: ([DrawResult]) async -> Void // Completion closure let completion: ([DrawResult]) -> Void // Completion closure
@State private var drawCount: Int = 0 @State private var drawCount: Int = 0
@State private var draws: [DrawResult] = [DrawResult]() @State private var draws: [DrawResult] = [DrawResult]()
@State private var drawOptions: [DrawOption] = [DrawOption]() @State private var drawOptions: [DrawOption] = [DrawOption]()
@State private var selectedIndex: Int? @State private var selectedIndex: Int?
@State private var disabled: Bool = false @State private var disabled: Bool = false
@State private var validating: Bool = false
var scrollDisabled: Bool {
drawCount < drawees.count || selectedIndex != nil
}
var body: some View { var body: some View {
List { List {
@ -77,11 +82,9 @@ struct SpinDrawView: View {
_validationLabelView(drawee: drawCount, result: segments[draws.last!.drawIndex]) _validationLabelView(drawee: drawCount, result: segments[draws.last!.drawIndex])
if autoMode == false || drawCount == drawees.count { if autoMode == false || drawCount == drawees.count {
RowButtonView("Valider le tirage") { RowButtonView("Valider le tirage") {
await completion(draws) completion(draws)
dismiss() dismiss()
} }
} else {
Text("Prochain tirage en préparation")
} }
} }
} else if drawCount < drawees.count { } else if drawCount < drawees.count {
@ -135,16 +138,17 @@ struct SpinDrawView: View {
} }
} }
} else { } else {
Section { ForEach(draws) { drawResult in
Text("Tous les tirages sont terminés") Section {
ForEach(draws) { drawResult in
_validationLabelView(drawee: drawResult.drawee, result: segments[drawResult.drawIndex]) _validationLabelView(drawee: drawResult.drawee, result: segments[drawResult.drawIndex])
} }
} }
RowButtonView("Valider les tirages") { Section {
await completion(draws) RowButtonView("Valider les tirages") {
dismiss() await completion(draws)
dismiss()
}
} }
} }
@ -164,6 +168,22 @@ struct SpinDrawView: View {
} }
.disabled(disabled || autoMode) .disabled(disabled || autoMode)
} }
if scrollDisabled == false {
ToolbarItem(placement: .topBarTrailing) {
Button {
validating = true
completion(draws)
dismiss()
} label: {
Text("Tout valider")
}
}
ToolbarItem(placement: .status) {
Text("Tous les tirages sont terminés")
}
}
} }
.navigationBarBackButtonHidden() .navigationBarBackButtonHidden()
.navigationTitle("Tirage au sort") .navigationTitle("Tirage au sort")
@ -171,7 +191,7 @@ struct SpinDrawView: View {
.toolbarBackground(.visible, for: .navigationBar) .toolbarBackground(.visible, for: .navigationBar)
.toolbar(.hidden, for: .tabBar) .toolbar(.hidden, for: .tabBar)
.listStyle(.insetGrouped) .listStyle(.insetGrouped)
.scrollDisabled(true) .scrollDisabled(scrollDisabled)
.interactiveDismissDisabled() .interactiveDismissDisabled()
.onAppear { .onAppear {
for (index, segment) in segments.enumerated() { for (index, segment) in segments.enumerated() {
@ -219,7 +239,7 @@ struct FortuneWheelContainerView: View {
.padding(.top, 5) .padding(.top, 5)
.overlay(alignment: .top) { .overlay(alignment: .top) {
Triangle() Triangle()
.fill(Color.red) .fill(Color.logoRed)
.stroke(Color.black, lineWidth: 2) .stroke(Color.black, lineWidth: 2)
.frame(width: 20, height: 20) .frame(width: 20, height: 20)
.rotationEffect(.degrees(180)) .rotationEffect(.degrees(180))

@ -62,7 +62,7 @@ struct GenericDestinationPickerView<T: Identifiable & Selectable & Equatable >:
.offset(x: 3, y: 3) .offset(x: 3, y: 3)
} else if let count, count > 0 { } else if let count, count > 0 {
Image(systemName: count <= 50 ? "\(String(count)).circle.fill" : "plus.circle.fill") Image(systemName: count <= 50 ? "\(String(count)).circle.fill" : "plus.circle.fill")
.foregroundColor(destination.badgeValueColor() ?? .red) .foregroundColor(destination.badgeValueColor() ?? .logoRed)
.imageScale(.medium) .imageScale(.medium)
.background ( .background (
Color(.systemBackground) Color(.systemBackground)
@ -82,7 +82,7 @@ struct GenericDestinationPickerView<T: Identifiable & Selectable & Equatable >:
.offset(x: 3, y: 3) .offset(x: 3, y: 3)
} else if let count = destination.badgeValue(), count > 0 { } else if let count = destination.badgeValue(), count > 0 {
Image(systemName: count <= 50 ? "\(String(count)).circle.fill" : "plus.circle.fill") Image(systemName: count <= 50 ? "\(String(count)).circle.fill" : "plus.circle.fill")
.foregroundColor(destination.badgeValueColor() ?? .red) .foregroundColor(destination.badgeValueColor() ?? .logoRed)
.imageScale(.medium) .imageScale(.medium)
.background ( .background (
Color(.systemBackground) Color(.systemBackground)

@ -87,7 +87,7 @@ struct RowButtonView: View {
.disabled(isLoading) .disabled(isLoading)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.buttonStyle(.borderedProminent) .buttonStyle(.borderedProminent)
.tint(role == .destructive ? Color.red : Color.master) .tint(role == .destructive ? Color.logoRed : Color.master)
.listRowBackground(Color.clear) .listRowBackground(Color.clear)
.listRowInsets(EdgeInsets(.zero)) .listRowInsets(EdgeInsets(.zero))
.confirmationDialog("Confirmation", .confirmationDialog("Confirmation",

@ -34,7 +34,7 @@ struct MatchTeamDetailView: View {
EditablePlayerView(player: player, editingOptions: [.licenceId, .name, .payment]) EditablePlayerView(player: player, editingOptions: [.licenceId, .name, .payment])
} }
} header: { } header: {
TeamHeaderView(team: team, teamIndex: tournament?.indexOf(team: team), tournament: nil) TeamHeaderView(team: team, teamIndex: tournament?.indexOf(team: team))
} }
} }

@ -134,7 +134,7 @@ struct UmpireView: View {
} else { } else {
LabeledContent { LabeledContent {
Image(systemName: "xmark.circle.fill") Image(systemName: "xmark.circle.fill")
.tint(.red) .tint(.logoRed)
} label: { } label: {
if let _mostRecentDateAvailable { if let _mostRecentDateAvailable {
Text(_mostRecentDateAvailable.monthYearFormatted) Text(_mostRecentDateAvailable.monthYearFormatted)

@ -67,7 +67,7 @@ struct CourtAvailabilitySettingsView: View {
VStack { VStack {
Image(systemName: "arrowshape.forward.fill") Image(systemName: "arrowshape.forward.fill")
.tint(.master) .tint(.master)
Text("indisponible").foregroundStyle(.red).font(.caption) Text("indisponible").foregroundStyle(.logoRed).font(.caption)
} }
Spacer() Spacer()
VStack(alignment: .trailing, spacing: 0) { VStack(alignment: .trailing, spacing: 0) {

@ -28,7 +28,7 @@ struct RoundView: View {
return self.tournament.tournamentStore return self.tournament.tournamentStore
} }
private func _getAvailableSeedGroup() async { private func _getAvailableSeedGroup() {
#if DEBUG_TIME //DEBUGING TIME #if DEBUG_TIME //DEBUGING TIME
let start = Date() let start = Date()
defer { defer {
@ -40,7 +40,7 @@ struct RoundView: View {
availableSeedGroup = tournament.seedGroupAvailable(atRoundIndex: upperRound.round.index) availableSeedGroup = tournament.seedGroupAvailable(atRoundIndex: upperRound.round.index)
} }
private func _getSpaceLeft() async { private func _getSpaceLeft() {
#if DEBUG_TIME //DEBUGING TIME #if DEBUG_TIME //DEBUGING TIME
let start = Date() let start = Date()
defer { defer {
@ -130,12 +130,11 @@ struct RoundView: View {
Section { Section {
RowButtonView("Placer \(availableSeedGroup.localizedLabel())" + ((availableSeedGroup.isFixed() == false) ? " au hasard" : "")) { RowButtonView("Placer \(availableSeedGroup.localizedLabel())" + ((availableSeedGroup.isFixed() == false) ? " au hasard" : "")) {
tournament.setSeeds(inRoundIndex: upperRound.round.index, inSeedGroup: availableSeedGroup) tournament.setSeeds(inRoundIndex: upperRound.round.index, inSeedGroup: availableSeedGroup)
await _save() _save()
if tournament.availableSeeds().isEmpty && tournament.availableQualifiedTeams().isEmpty { if tournament.availableSeeds().isEmpty && tournament.availableQualifiedTeams().isEmpty {
self.isEditingTournamentSeed.wrappedValue = false self.isEditingTournamentSeed.wrappedValue = false
} }
await _getSpaceLeft() _prepareRound()
await _getAvailableSeedGroup()
} }
} footer: { } footer: {
if availableSeedGroup.isFixed() == false { if availableSeedGroup.isFixed() == false {
@ -161,17 +160,14 @@ struct RoundView: View {
ForEach(availableQualifiedTeams) { team in ForEach(availableQualifiedTeams) { team in
NavigationLink { NavigationLink {
SpinDrawView(drawees: [team], segments: spaceLeft) { results in SpinDrawView(drawees: [team], segments: spaceLeft) { results in
Task { results.forEach { drawResult in
results.forEach { drawResult in team.setSeedPosition(inSpot: spaceLeft[drawResult.drawIndex], slot: nil, opposingSeeding: true)
team.setSeedPosition(inSpot: spaceLeft[drawResult.drawIndex], slot: nil, opposingSeeding: true)
}
await _save()
if tournament.availableSeeds().isEmpty && tournament.availableQualifiedTeams().isEmpty {
self.isEditingTournamentSeed.wrappedValue = false
}
await _getSpaceLeft()
await _getAvailableSeedGroup()
} }
_save()
if tournament.availableSeeds().isEmpty && tournament.availableQualifiedTeams().isEmpty {
self.isEditingTournamentSeed.wrappedValue = false
}
_prepareRound()
} }
} label: { } label: {
TeamRowView(team: team, displayCallDate: false) TeamRowView(team: team, displayCallDate: false)
@ -193,17 +189,14 @@ struct RoundView: View {
ForEach(availableSeeds) { team in ForEach(availableSeeds) { team in
NavigationLink { NavigationLink {
SpinDrawView(drawees: [team], segments: seedSpaceLeft) { results in SpinDrawView(drawees: [team], segments: seedSpaceLeft) { results in
Task { results.forEach { drawResult in
results.forEach { drawResult in team.setSeedPosition(inSpot: seedSpaceLeft[drawResult.drawIndex], slot: nil, opposingSeeding: false)
team.setSeedPosition(inSpot: seedSpaceLeft[drawResult.drawIndex], slot: nil, opposingSeeding: false) }
} _save()
await _save() if tournament.availableSeeds().isEmpty && tournament.availableQualifiedTeams().isEmpty {
if tournament.availableSeeds().isEmpty && tournament.availableQualifiedTeams().isEmpty { self.isEditingTournamentSeed.wrappedValue = false
self.isEditingTournamentSeed.wrappedValue = false
}
await _getSpaceLeft()
await _getAvailableSeedGroup()
} }
_prepareRound()
} }
} label: { } label: {
TeamRowView(team: team, displayCallDate: false) TeamRowView(team: team, displayCallDate: false)
@ -221,17 +214,14 @@ struct RoundView: View {
ForEach(availableSeeds) { team in ForEach(availableSeeds) { team in
NavigationLink { NavigationLink {
SpinDrawView(drawees: [team], segments: spaceLeft) { results in SpinDrawView(drawees: [team], segments: spaceLeft) { results in
Task { results.forEach { drawResult in
results.forEach { drawResult in team.setSeedPosition(inSpot: spaceLeft[drawResult.drawIndex], slot: nil, opposingSeeding: true)
team.setSeedPosition(inSpot: spaceLeft[drawResult.drawIndex], slot: nil, opposingSeeding: true)
}
await _save()
if tournament.availableSeeds().isEmpty && tournament.availableQualifiedTeams().isEmpty {
self.isEditingTournamentSeed.wrappedValue = false
}
await _getSpaceLeft()
await _getAvailableSeedGroup()
} }
_save()
if tournament.availableSeeds().isEmpty && tournament.availableQualifiedTeams().isEmpty {
self.isEditingTournamentSeed.wrappedValue = false
}
_prepareRound()
} }
} label: { } label: {
TeamRowView(team: team, displayCallDate: false) TeamRowView(team: team, displayCallDate: false)
@ -283,9 +273,7 @@ struct RoundView: View {
PrintSettingsView(tournament: tournament) PrintSettingsView(tournament: tournament)
} }
.onAppear { .onAppear {
Task { _prepareRound()
await _prepareRound()
}
let seeds = upperRound.round.seeds() let seeds = upperRound.round.seeds()
SlideToDeleteSeedTip.seeds = seeds.count SlideToDeleteSeedTip.seeds = seeds.count
PrintTip.seeds = seeds.count PrintTip.seeds = seeds.count
@ -294,17 +282,19 @@ struct RoundView: View {
.fullScreenCover(isPresented: showVisualDrawView) { .fullScreenCover(isPresented: showVisualDrawView) {
if let availableSeedGroup = selectedSeedGroup { if let availableSeedGroup = selectedSeedGroup {
let seeds = tournament.seeds(inSeedGroup: availableSeedGroup) let seeds = tournament.seeds(inSeedGroup: availableSeedGroup)
let availableSeedSpot = tournament.availableSeedSpot(inRoundIndex: upperRound.round.index) let opposingSeeding = seedSpaceLeft.isEmpty ? true : false
let availableSeedSpot = opposingSeeding ? spaceLeft : seedSpaceLeft
NavigationStack { NavigationStack {
SpinDrawView(drawees: seeds, segments: availableSeedSpot, autoMode: true) { draws in SpinDrawView(drawees: seeds, segments: availableSeedSpot, autoMode: true) { draws in
Task { draws.forEach { drawResult in
draws.forEach { drawResult in seeds[drawResult.drawee].setSeedPosition(inSpot: availableSeedSpot[drawResult.drawIndex], slot: nil, opposingSeeding: opposingSeeding)
seeds[drawResult.drawee].setSeedPosition(inSpot: availableSeedSpot[drawResult.drawIndex], slot: nil, opposingSeeding: false) }
}
await _save() _save()
if tournament.availableSeeds().isEmpty && tournament.availableQualifiedTeams().isEmpty { _prepareRound()
self.isEditingTournamentSeed.wrappedValue = false
} if tournament.availableSeeds().isEmpty && tournament.availableQualifiedTeams().isEmpty {
self.isEditingTournamentSeed.wrappedValue = false
} }
} }
} }
@ -315,9 +305,7 @@ struct RoundView: View {
ToolbarItem(placement: .topBarTrailing) { ToolbarItem(placement: .topBarTrailing) {
Button(isEditingTournamentSeed.wrappedValue == true ? "Valider" : "Modifier") { Button(isEditingTournamentSeed.wrappedValue == true ? "Valider" : "Modifier") {
if isEditingTournamentSeed.wrappedValue == true { if isEditingTournamentSeed.wrappedValue == true {
Task { _save()
await _save()
}
} }
isEditingTournamentSeed.wrappedValue.toggle() isEditingTournamentSeed.wrappedValue.toggle()
} }
@ -325,7 +313,7 @@ struct RoundView: View {
} }
} }
private func _save() async { private func _save() {
do { do {
try self.tournamentStore.teamRegistrations.addOrUpdate(contentOfs: tournament.unsortedTeams()) try self.tournamentStore.teamRegistrations.addOrUpdate(contentOfs: tournament.unsortedTeams())
} catch { } catch {
@ -348,13 +336,9 @@ struct RoundView: View {
} }
} }
private func _prepareRound() async { private func _prepareRound() {
Task { _getSpaceLeft()
await _getSpaceLeft() _getAvailableSeedGroup()
}
Task {
await _getAvailableSeedGroup()
}
} }
} }

@ -11,6 +11,7 @@ struct TeamHeaderView: View {
var team: TeamRegistration var team: TeamRegistration
var teamIndex: Int? var teamIndex: Int?
var tournament: Tournament? var tournament: Tournament?
var teamCount: Int?
var body: some View { var body: some View {
HStack(spacing: 16.0) { HStack(spacing: 16.0) {
@ -46,7 +47,7 @@ struct TeamHeaderView: View {
} else { } else {
Text("").font(.caption) Text("").font(.caption)
} }
Text(tournament.cutLabel(index: teamIndex)) Text(tournament.cutLabel(index: teamIndex, teamCount: teamCount))
} }
} }
} }

@ -26,7 +26,7 @@ struct TeamRowView: View {
} }
if let callDate = team.callDate, displayCallDate { if let callDate = team.callDate, displayCallDate {
Text("Déjà convoquée \(callDate.localizedDate())") Text("Déjà convoquée \(callDate.localizedDate())")
.foregroundStyle(.red) .foregroundStyle(.logoRed)
.italic() .italic()
.font(.caption) .font(.caption)
} }

@ -492,7 +492,7 @@ struct FileImportView: View {
} }
if let callDate = team.previousTeam?.callDate, let newDate = tournament.getStartDate(ofSeedIndex: newIndex), callDate != newDate { if let callDate = team.previousTeam?.callDate, let newDate = tournament.getStartDate(ofSeedIndex: newIndex), callDate != newDate {
Text("Attention, cette paire a déjà été convoquée à \(callDate.localizedDate())") Text("Attention, cette paire a déjà été convoquée à \(callDate.localizedDate())")
.foregroundStyle(.red) .foregroundStyle(.logoRed)
.italic() .italic()
.font(.caption) .font(.caption)
} }

@ -91,7 +91,7 @@ struct InscriptionInfoView: View {
Text("Dans le tableau") Text("Dans le tableau")
} }
} }
.listRowView(color: .red) .listRowView(color: .logoRed)
DisclosureGroup { DisclosureGroup {
ForEach(waitingListInGroupStage) { team in ForEach(waitingListInGroupStage) { team in
@ -104,7 +104,7 @@ struct InscriptionInfoView: View {
Text("En poule") Text("En poule")
} }
} }
.listRowView(color: .red) .listRowView(color: .logoRed)
} header: { } header: {
Text("Équipes ne devant plus être sélectionnées") Text("Équipes ne devant plus être sélectionnées")
} footer: { } footer: {
@ -123,7 +123,7 @@ struct InscriptionInfoView: View {
Text("Doublons") Text("Doublons")
} }
} }
.listRowView(color: .red) .listRowView(color: .logoRed)
} }
Section { Section {
@ -155,7 +155,7 @@ struct InscriptionInfoView: View {
Text("Joueurs trop bien classés") Text("Joueurs trop bien classés")
} }
} }
.listRowView(color: .red) .listRowView(color: .logoRed)
} footer: { } footer: {
Text("Il s'agit des joueurs ou joueuses dont le rang est inférieur à la limite fédérale.") Text("Il s'agit des joueurs ou joueuses dont le rang est inférieur à la limite fédérale.")
} }

@ -98,6 +98,7 @@ struct InscriptionManagerView: View {
var id: Int { self.rawValue } var id: Int { self.rawValue }
case all case all
case walkOut case walkOut
case waiting
func localizedLabel() -> String { func localizedLabel() -> String {
switch self { switch self {
@ -105,6 +106,8 @@ struct InscriptionManagerView: View {
return "Toutes les équipes" return "Toutes les équipes"
case .walkOut: case .walkOut:
return "Voir les WOs" return "Voir les WOs"
case .waiting:
return "Liste d'attente"
} }
} }
} }
@ -468,7 +471,11 @@ struct InscriptionManagerView: View {
print("func _prepareTeams", duration.formatted(.units(allowed: [.seconds, .milliseconds]))) print("func _prepareTeams", duration.formatted(.units(allowed: [.seconds, .milliseconds])))
} }
#endif #endif
sortedTeams = tournament.sortedTeams() if filterMode == .waiting {
sortedTeams = tournament.waitingListSortedTeams()
} else {
sortedTeams = tournament.sortedTeams()
}
} }
var filteredTeams: [TeamRegistration] { var filteredTeams: [TeamRegistration] {
@ -511,12 +518,19 @@ struct InscriptionManagerView: View {
private func _teamRegisteredView() -> some View { private func _teamRegisteredView() -> some View {
List { List {
let selectedSortedTeams = tournament.selectedSortedTeams()
if let closedRegistrationDate = tournament.closedRegistrationDate { if let closedRegistrationDate = tournament.closedRegistrationDate {
Section { Section {
CloseDatePicker(closedRegistrationDate: closedRegistrationDate) CloseDatePicker(closedRegistrationDate: closedRegistrationDate)
} footer: { } footer: {
Text("Toutes les équipes ayant été inscrites après la date de clôture seront en liste d'attente.") Text("Toutes les équipes ayant été inscrites après la date de clôture seront en liste d'attente.")
} }
if selectedSortedTeams.isEmpty {
Section {
ContentUnavailableView("Aucune équipe", systemImage: "person.2.slash", description: Text("Vous n'avez aucune équipe inscrite avant la date de clôture."))
}
}
} }
if presentSearch == false { if presentSearch == false {
@ -560,7 +574,7 @@ struct InscriptionManagerView: View {
Section { Section {
TeamDetailView(team: team) TeamDetailView(team: team)
} header: { } header: {
TeamHeaderView(team: team, teamIndex: teamIndex, tournament: tournament) TeamHeaderView(team: team, teamIndex: teamIndex, tournament: tournament, teamCount: filterMode == .waiting ? 0 : selectedSortedTeams.count)
} footer: { } footer: {
_teamFooterView(team) _teamFooterView(team)
} }
@ -722,7 +736,7 @@ struct InscriptionManagerView: View {
} }
Section { Section {
ContentUnavailableView("Aucune équipe", systemImage: "person.2.slash", description: Text("Vous n'avez encore aucune équipe dans votre liste d'attente.")) ContentUnavailableView("Aucune équipe", systemImage: "person.2.slash", description: Text("Vous n'avez encore aucune équipe inscrite dans votre tournoi."))
} }
_rankHandlerView() _rankHandlerView()

@ -213,7 +213,7 @@ struct TournamentRankView: View {
Image(systemName: "arrowtriangle.down.fill") Image(systemName: "arrowtriangle.down.fill")
.imageScale(.small) .imageScale(.small)
} }
.foregroundColor(.red) .foregroundColor(.logoRed)
} else { } else {
Text("--") Text("--")
} }

@ -102,7 +102,7 @@ struct TournamentBuildView: View {
} label: { } label: {
Text("Classement final des équipes") Text("Classement final des équipes")
if tournament.publishRankings == false { if tournament.publishRankings == false {
Text("Vérifiez le classement avant de publier").foregroundStyle(.red) Text("Vérifiez le classement avant de publier").foregroundStyle(.logoRed)
} }
} }
} }
@ -120,7 +120,7 @@ struct TournamentBuildView: View {
} label: { } label: {
Text("Classement final des équipes") Text("Classement final des équipes")
if tournament.publishRankings == false { if tournament.publishRankings == false {
Text("Vérifiez le classement avant de publier").foregroundStyle(.red) Text("Vérifiez le classement avant de publier").foregroundStyle(.logoRed)
} }
} }
} }

@ -46,7 +46,7 @@ struct ChangePasswordView: View {
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
} footer: { } footer: {
if self.errorMessage.count > 0 { if self.errorMessage.count > 0 {
Text(self.errorMessage).foregroundStyle(.red) Text(self.errorMessage).foregroundStyle(.logoRed)
} }
} }
} }

@ -131,7 +131,7 @@ struct LoginView: View {
} }
.disabled(password.isEmpty || username.isEmpty) .disabled(password.isEmpty || username.isEmpty)
// if let error = self.errorText { // if let error = self.errorText {
// Text(error).font(.callout).foregroundStyle(.red) // Text(error).font(.callout).foregroundStyle(.logoRed)
// } // }
} }

Loading…
Cancel
Save