multistore
Razmig Sarkissian 1 year ago
parent fb3fe585b9
commit 4fece3c67e
  1. 4
      PadelClub.xcodeproj/project.pbxproj
  2. 42
      PadelClub/Data/GroupStage.swift
  3. 40
      PadelClub/Data/Match.swift
  4. 18
      PadelClub/Data/Round.swift
  5. 69
      PadelClub/Data/Tournament.swift
  6. 101
      PadelClub/Views/Cashier/Event/EventCreationView.swift
  7. 22
      PadelClub/Views/Components/MatchListView.swift
  8. 14
      PadelClub/Views/GroupStage/GroupStageView.swift
  9. 16
      PadelClub/Views/GroupStage/GroupStagesView.swift
  10. 8
      PadelClub/Views/Match/MatchSetupView.swift
  11. 26
      PadelClub/Views/Match/MatchSummaryView.swift
  12. 36
      PadelClub/Views/Navigation/Agenda/ActivityView.swift
  13. 6
      PadelClub/Views/Navigation/Agenda/EventListView.swift
  14. 2
      PadelClub/Views/Navigation/MainView.swift
  15. 1
      PadelClub/Views/Navigation/Ongoing/OngoingView.swift
  16. 38
      PadelClub/Views/Round/RoundSettingsView.swift
  17. 204
      PadelClub/Views/Round/RoundView.swift
  18. 2
      PadelClub/Views/Round/RoundsView.swift
  19. 4
      PadelClub/Views/Team/Components/TeamHeaderView.swift
  20. 67
      PadelClub/Views/Tournament/FileImportView.swift
  21. 42
      PadelClub/Views/Tournament/Screen/Components/InscriptionInfoView.swift
  22. 319
      PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift
  23. 72
      PadelClub/Views/Tournament/TournamentBuildView.swift

@ -1935,7 +1935,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 30; CURRENT_PROJECT_VERSION = 31;
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
DEVELOPMENT_TEAM = BQ3Y44M3Q6; DEVELOPMENT_TEAM = BQ3Y44M3Q6;
@ -1973,7 +1973,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 30; CURRENT_PROJECT_VERSION = 31;
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
DEVELOPMENT_TEAM = BQ3Y44M3Q6; DEVELOPMENT_TEAM = BQ3Y44M3Q6;

@ -79,7 +79,7 @@ class GroupStage: ModelObject, Storable {
guard teams().count == size else { return false } guard teams().count == size else { return false }
let _matches = _matches() let _matches = _matches()
if _matches.isEmpty { return false } if _matches.isEmpty { return false }
return _matches.allSatisfy { $0.hasEnded() } return _matches.anySatisfy { $0.hasEnded() == false } == false
} }
func buildMatches() { func buildMatches() {
@ -178,20 +178,50 @@ class GroupStage: ModelObject, Storable {
return _matches().first(where: { matchIndexes.contains($0.index) }) return _matches().first(where: { matchIndexes.contains($0.index) })
} }
func availableToStart(playedMatches: [Match], in runningMatches: [Match]) -> [Match] { func availableToStart(playedMatches: [Match], in runningMatches: [Match]) async -> [Match] {
let start = Date()
defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
print("func group stage availableToStart", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
return playedMatches.filter({ $0.canBeStarted(inMatches: runningMatches) && $0.isRunning() == false }) return playedMatches.filter({ $0.canBeStarted(inMatches: runningMatches) && $0.isRunning() == false })
} }
func runningMatches(playedMatches: [Match]) -> [Match] { func runningMatches(playedMatches: [Match]) -> [Match] {
playedMatches.filter({ $0.isRunning() }).sorted(by: \.computedStartDateForSorting) let start = Date()
defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
print("func group stage runningMatches", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
return playedMatches.filter({ $0.isRunning() }).sorted(by: \.computedStartDateForSorting)
}
func asyncRunningMatches(playedMatches: [Match]) async -> [Match] {
let start = Date()
defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
print("func group stage runningMatches", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
return playedMatches.filter({ $0.isRunning() }).sorted(by: \.computedStartDateForSorting)
} }
func readyMatches(playedMatches: [Match]) -> [Match] {
playedMatches.filter({ $0.isReady() && $0.isRunning() == false && $0.hasEnded() == false }) func readyMatches(playedMatches: [Match]) async -> [Match] {
let start = Date()
defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
print("func group stage readyMatches", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
return playedMatches.filter({ $0.isReady() && $0.isRunning() == false && $0.hasEnded() == false })
} }
func finishedMatches(playedMatches: [Match]) -> [Match] { func finishedMatches(playedMatches: [Match]) -> [Match] {
playedMatches.filter({ $0.hasEnded() }).sorted(by: \.computedEndDateForSorting).reversed() let start = Date()
defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
print("func group stage finishedMatches", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
return playedMatches.filter({ $0.hasEnded() }).sorted(by: \.computedEndDateForSorting).reversed()
} }
private func _matchOrder() -> [Int] { private func _matchOrder() -> [Int] {

@ -69,10 +69,10 @@ class Match: ModelObject, Storable {
try Store.main.deleteDependencies(items: self.teamScores) try Store.main.deleteDependencies(items: self.teamScores)
} }
func indexInRound() -> Int { func indexInRound(in matches: [Match]? = nil) -> Int {
if groupStage != nil { if groupStage != nil {
return index return index
} else if let index = roundObject?.playedMatches().sorted(by: \.index).firstIndex(where: { $0.id == id }) { } else if let index = (matches ?? roundObject?.playedMatches().sorted(by: \.index))?.firstIndex(where: { $0.id == id }) {
return index return index
} }
return RoundRule.matchIndexWithinRound(fromMatchIndex: index) return RoundRule.matchIndexWithinRound(fromMatchIndex: index)
@ -86,16 +86,16 @@ class Match: ModelObject, Storable {
[roundTitle(), matchTitle(.short), startDate?.localizedDate(), courtName()].compacted().joined(separator: "\n") [roundTitle(), matchTitle(.short), startDate?.localizedDate(), courtName()].compacted().joined(separator: "\n")
} }
func matchTitle(_ displayStyle: DisplayStyle = .wide) -> String { func matchTitle(_ displayStyle: DisplayStyle = .wide, inMatches matches: [Match]? = nil) -> String {
if let groupStageObject { if let groupStageObject {
return groupStageObject.localizedMatchUpLabel(for: index) return groupStageObject.localizedMatchUpLabel(for: index)
} }
switch displayStyle { switch displayStyle {
case .wide: case .wide:
return "Match \(indexInRound() + 1)" return "Match \(indexInRound(in: matches) + 1)"
case .short: case .short:
return "#\(indexInRound() + 1)" return "#\(indexInRound(in: matches) + 1)"
} }
} }
@ -627,7 +627,7 @@ class Match: ModelObject, Storable {
} }
func hasEnded() -> Bool { func hasEnded() -> Bool {
endDate != nil || hasWalkoutTeam() || winningTeamId != nil endDate != nil
} }
func isGroupStage() -> Bool { func isGroupStage() -> Bool {
@ -705,6 +705,34 @@ class Match: ModelObject, Storable {
} }
func team(_ team: TeamPosition) -> TeamRegistration? { func team(_ team: TeamPosition) -> TeamRegistration? {
// let start = Date()
// defer {
// let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
// print("func match get team", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
// }
if groupStage != nil {
switch team {
case .one:
return groupStageProjectedTeam(.one)
case .two:
return groupStageProjectedTeam(.two)
}
} else {
switch team {
case .one:
return roundProjectedTeam(.one)
case .two:
return roundProjectedTeam(.two)
}
}
}
func asyncTeam(_ team: TeamPosition) async -> TeamRegistration? {
// let start = Date()
// defer {
// let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
// print("func match get team", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
// }
if groupStage != nil { if groupStage != nil {
switch team { switch team {
case .one: case .one:

@ -56,7 +56,7 @@ class Round: ModelObject, Storable {
} }
func hasEnded() -> Bool { func hasEnded() -> Bool {
playedMatches().allSatisfy({ $0.hasEnded() }) playedMatches().anySatisfy({ $0.hasEnded() == false }) == false
} }
func upperMatches(ofMatch match: Match) -> [Match] { func upperMatches(ofMatch match: Match) -> [Match] {
@ -221,10 +221,15 @@ class Round: ModelObject, Storable {
} }
func playedMatches() -> [Match] { func playedMatches() -> [Match] {
// let start = Date()
// defer {
// let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
// print("func round playedMatches", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
// }
if parent == nil { if parent == nil {
enabledMatches() return enabledMatches()
} else { } else {
_matches() return _matches()
} }
} }
@ -398,9 +403,10 @@ class Round: ModelObject, Storable {
} }
func roundStatus() -> String { func roundStatus() -> String {
if hasStarted() && hasEnded() == false { let hasEnded = hasEnded()
if hasStarted() && hasEnded == false {
return "en cours" return "en cours"
} else if hasEnded() { } else if hasEnded {
return "terminée" return "terminée"
} else { } else {
return "à démarrer" return "à démarrer"
@ -548,7 +554,7 @@ extension Round: Selectable {
if let parentRound { if let parentRound {
return "Tour #\(parentRound.loserRounds().count - index)" return "Tour #\(parentRound.loserRounds().count - index)"
} else { } else {
return roundTitle() return roundTitle(.short)
} }
} }

@ -538,11 +538,11 @@ class Tournament : ModelObject, Storable {
} }
func availableSeedSpot(inRoundIndex roundIndex: Int) -> [Match] { func availableSeedSpot(inRoundIndex roundIndex: Int) -> [Match] {
getRound(atRoundIndex: roundIndex)?.playedMatches().filter { $0.teams().count == 0 } ?? [] getRound(atRoundIndex: roundIndex)?.playedMatches().filter { $0.isEmpty() } ?? []
} }
func availableSeedOpponentSpot(inRoundIndex roundIndex: Int) -> [Match] { func availableSeedOpponentSpot(inRoundIndex roundIndex: Int) -> [Match] {
getRound(atRoundIndex: roundIndex)?.playedMatches().filter { $0.teams().count == 1 } ?? [] getRound(atRoundIndex: roundIndex)?.playedMatches().filter { $0.hasSpaceLeft() } ?? []
} }
func availableSeedGroups() -> [SeedInterval] { func availableSeedGroups() -> [SeedInterval] {
@ -615,7 +615,7 @@ class Tournament : ModelObject, Storable {
if availableSeeds.count == availableSeedSpot.count && availableSeedGroup.count == availableSeeds.count { if availableSeeds.count == availableSeedSpot.count && availableSeedGroup.count == availableSeeds.count {
return availableSeedGroup return availableSeedGroup
} else if (availableSeeds.count == availableSeedOpponentSpot.count && availableSeeds.count == self.availableSeeds().count) && availableSeedGroup.count == availableSeedOpponentSpot.count { } else if availableSeeds.count == availableSeedOpponentSpot.count && availableSeedGroup.count == availableSeedOpponentSpot.count {
return availableSeedGroup return availableSeedGroup
} else if let chunks = availableSeedGroup.chunks() { } else if let chunks = availableSeedGroup.chunks() {
if let chunk = chunks.first(where: { seedInterval in if let chunk = chunks.first(where: { seedInterval in
@ -727,7 +727,12 @@ class Tournament : ModelObject, Storable {
} }
func selectedSortedTeams() -> [TeamRegistration] { func selectedSortedTeams() -> [TeamRegistration] {
//let start = Date() // let start = Date()
// defer {
// let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
// print("func selectedSortedTeams", id, tournamentTitle(), duration.formatted(.units(allowed: [.seconds, .milliseconds])))
// }
var _sortedTeams : [TeamRegistration] = [] var _sortedTeams : [TeamRegistration] = []
let _teams = unsortedTeams().filter({ $0.walkOut == false }) let _teams = unsortedTeams().filter({ $0.walkOut == false })
@ -757,9 +762,6 @@ class Tournament : ModelObject, Storable {
let groupStageTeams = Set(_completeTeams).subtracting(bracketTeams).sorted(using: defaultSorting, order: .ascending).prefix(groupStageTeamCount).sorted(using: _currentSelectionSorting, order: .ascending) + wcGroupStage let groupStageTeams = Set(_completeTeams).subtracting(bracketTeams).sorted(using: defaultSorting, order: .ascending).prefix(groupStageTeamCount).sorted(using: _currentSelectionSorting, order: .ascending) + wcGroupStage
_sortedTeams = bracketTeams.sorted(using: _currentSelectionSorting, order: .ascending) + groupStageTeams.sorted(using: _currentSelectionSorting, order: .ascending) _sortedTeams = bracketTeams.sorted(using: _currentSelectionSorting, order: .ascending) + groupStageTeams.sorted(using: _currentSelectionSorting, order: .ascending)
} }
//let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
//print("func selectedSortedTeams", id, tournamentTitle(), duration.formatted(.units(allowed: [.seconds, .milliseconds])))
return _sortedTeams return _sortedTeams
} }
@ -817,10 +819,6 @@ class Tournament : ModelObject, Storable {
unsortedTeams().flatMap { $0.unsortedPlayers() }.sorted(by: \.computedRank) unsortedTeams().flatMap { $0.unsortedPlayers() }.sorted(by: \.computedRank)
} }
func femalePlayers() -> [PlayerRegistration] {
unsortedPlayers().filter({ $0.isMalePlayer() == false })
}
func unrankValue(for malePlayer: Bool) -> Int? { func unrankValue(for malePlayer: Bool) -> Int? {
switch tournamentCategory { switch tournamentCategory {
case .men: case .men:
@ -930,7 +928,7 @@ class Tournament : ModelObject, Storable {
} }
} }
func registrationIssues() -> Int { func registrationIssues() async -> Int {
let players : [PlayerRegistration] = unsortedPlayers() let players : [PlayerRegistration] = unsortedPlayers()
let selectedTeams : [TeamRegistration] = selectedSortedTeams() let selectedTeams : [TeamRegistration] = selectedSortedTeams()
let callDateIssue : [TeamRegistration] = selectedTeams.filter { $0.callDate != nil && isStartDateIsDifferentThanCallDate($0) } let callDateIssue : [TeamRegistration] = selectedTeams.filter { $0.callDate != nil && isStartDateIsDifferentThanCallDate($0) }
@ -957,19 +955,48 @@ class Tournament : ModelObject, Storable {
return Store.main.filter(isIncluded: { $0.groupStage != nil && groupStageIds.contains($0.groupStage!) }) return Store.main.filter(isIncluded: { $0.groupStage != nil && groupStageIds.contains($0.groupStage!) })
} }
func availableToStart(_ allMatches: [Match], in runningMatches: [Match]) -> [Match] { func availableToStart(_ allMatches: [Match], in runningMatches: [Match]) async -> [Match] {
let start = Date()
defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
print("func tournament availableToStart", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
return allMatches.filter({ $0.canBeStarted(inMatches: runningMatches) && $0.isRunning() == false }).sorted(by: \.computedStartDateForSorting) return allMatches.filter({ $0.canBeStarted(inMatches: runningMatches) && $0.isRunning() == false }).sorted(by: \.computedStartDateForSorting)
} }
func asyncRunningMatches(_ allMatches: [Match]) async -> [Match] {
let start = Date()
defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
print("func tournament runningMatches", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
return allMatches.filter({ $0.isRunning() && $0.isReady() }).sorted(by: \.computedStartDateForSorting)
}
func runningMatches(_ allMatches: [Match]) -> [Match] { func runningMatches(_ allMatches: [Match]) -> [Match] {
allMatches.filter({ $0.isRunning() && $0.isReady() }).sorted(by: \.computedStartDateForSorting) let start = Date()
defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
print("func tournament runningMatches", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
return allMatches.filter({ $0.isRunning() && $0.isReady() }).sorted(by: \.computedStartDateForSorting)
} }
func readyMatches(_ allMatches: [Match]) -> [Match] { func readyMatches(_ allMatches: [Match]) async -> [Match] {
let start = Date()
defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
print("func tournament readyMatches", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
return allMatches.filter({ $0.isReady() && $0.isRunning() == false && $0.hasEnded() == false }).sorted(by: \.computedStartDateForSorting) return allMatches.filter({ $0.isReady() && $0.isRunning() == false && $0.hasEnded() == false }).sorted(by: \.computedStartDateForSorting)
} }
func finishedMatches(_ allMatches: [Match], limit: Int? = nil) -> [Match] { func finishedMatches(_ allMatches: [Match], limit: Int? = nil) -> [Match] {
let start = Date()
defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
print("func tournament finishedMatches", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
let _limit = limit ?? courtCount let _limit = limit ?? courtCount
return Array(allMatches.filter({ $0.hasEnded() }).sorted(by: \.computedEndDateForSorting).reversed().prefix(_limit)) return Array(allMatches.filter({ $0.hasEnded() }).sorted(by: \.computedEndDateForSorting).reversed().prefix(_limit))
} }
@ -1181,7 +1208,7 @@ class Tournament : ModelObject, Storable {
} }
typealias TournamentStatus = (label:String, completion: String) typealias TournamentStatus = (label:String, completion: String)
func cashierStatus() -> TournamentStatus { func cashierStatus() async -> TournamentStatus {
let selectedPlayers = selectedPlayers() let selectedPlayers = selectedPlayers()
let paid = selectedPlayers.filter({ $0.hasPaid() }) let paid = selectedPlayers.filter({ $0.hasPaid() })
let label = paid.count.formatted() + " / " + selectedPlayers.count.formatted() + " joueurs encaissés" let label = paid.count.formatted() + " / " + selectedPlayers.count.formatted() + " joueurs encaissés"
@ -1190,7 +1217,7 @@ class Tournament : ModelObject, Storable {
return TournamentStatus(label: label, completion: completionLabel) return TournamentStatus(label: label, completion: completionLabel)
} }
func scheduleStatus() -> TournamentStatus { func scheduleStatus() async -> TournamentStatus {
let allMatches = allMatches() let allMatches = allMatches()
let ready = allMatches.filter({ $0.startDate != nil }) let ready = allMatches.filter({ $0.startDate != nil })
let label = ready.count.formatted() + " / " + allMatches.count.formatted() + " matchs programmés" let label = ready.count.formatted() + " / " + allMatches.count.formatted() + " matchs programmés"
@ -1199,7 +1226,7 @@ class Tournament : ModelObject, Storable {
return TournamentStatus(label: label, completion: completionLabel) return TournamentStatus(label: label, completion: completionLabel)
} }
func callStatus() -> TournamentStatus { func callStatus() async -> TournamentStatus {
let selectedSortedTeams = selectedSortedTeams() let selectedSortedTeams = selectedSortedTeams()
let called = selectedSortedTeams.filter { isStartDateIsDifferentThanCallDate($0) == false } let called = selectedSortedTeams.filter { isStartDateIsDifferentThanCallDate($0) == false }
let label = called.count.formatted() + " / " + selectedSortedTeams.count.formatted() + " convoquées au bon horaire" let label = called.count.formatted() + " / " + selectedSortedTeams.count.formatted() + " convoquées au bon horaire"
@ -1208,7 +1235,7 @@ class Tournament : ModelObject, Storable {
return TournamentStatus(label: label, completion: completionLabel) return TournamentStatus(label: label, completion: completionLabel)
} }
func confirmedSummonStatus() -> TournamentStatus { func confirmedSummonStatus() async -> TournamentStatus {
let selectedSortedTeams = selectedSortedTeams() let selectedSortedTeams = selectedSortedTeams()
let called = selectedSortedTeams.filter { $0.confirmationDate != nil } let called = selectedSortedTeams.filter { $0.confirmationDate != nil }
let label = called.count.formatted() + " / " + selectedSortedTeams.count.formatted() + " confirmées" let label = called.count.formatted() + " / " + selectedSortedTeams.count.formatted() + " confirmées"
@ -1217,7 +1244,7 @@ class Tournament : ModelObject, Storable {
return TournamentStatus(label: label, completion: completionLabel) return TournamentStatus(label: label, completion: completionLabel)
} }
func bracketStatus() -> String { func bracketStatus() async -> String {
let availableSeeds = availableSeeds() let availableSeeds = availableSeeds()
if availableSeeds.isEmpty == false { if availableSeeds.isEmpty == false {
return "placer \(availableSeeds.count) tête\(availableSeeds.count.pluralSuffix) de série" return "placer \(availableSeeds.count) tête\(availableSeeds.count.pluralSuffix) de série"
@ -1233,7 +1260,7 @@ class Tournament : ModelObject, Storable {
} }
} }
func groupStageStatus() -> String { func groupStageStatus() async -> String {
let groupStageTeamsCount = groupStageTeams().count let groupStageTeamsCount = groupStageTeams().count
if groupStageTeamsCount == 0 || groupStageTeamsCount != teamsPerGroupStage * groupStageCount { if groupStageTeamsCount == 0 || groupStageTeamsCount != teamsPerGroupStage * groupStageCount {
return "à faire" return "à faire"

@ -90,39 +90,6 @@ struct EventCreationView: View {
case .animation: case .animation:
animationEditorView animationEditorView
} }
Section {
RowButtonView("Valider") {
let event = Event(creator: Store.main.userId, name: eventName)
event.club = selectedClub?.id
tournaments.forEach { tournament in
tournament.event = event.id
}
do {
try dataStore.events.addOrUpdate(instance: event)
} catch {
Logger.error(error)
}
tournaments.forEach { tournament in
tournament.courtCount = selectedClub?.courtCount ?? 2
tournament.startDate = startingDate
tournament.dayDuration = duration
tournament.setupFederalSettings()
}
do {
try dataStore.tournaments.addOrUpdate(contentOfs: tournaments)
} catch {
Logger.error(error)
}
dismiss()
navigation.path.append(tournaments.first!)
}
.disabled(tournaments.isEmpty)
}
} }
.toolbar { .toolbar {
if textFieldIsFocus { if textFieldIsFocus {
@ -144,11 +111,10 @@ struct EventCreationView: View {
} }
ToolbarItem(placement: .topBarTrailing) { ToolbarItem(placement: .topBarTrailing) {
BarButtonView("Ajouter une épreuve", icon: "plus.circle.fill") { ButtonValidateView {
let tournament = Tournament.newEmptyInstance() _validate()
self.tournaments.append(tournament)
} }
.popoverTip(multiTournamentsEventTip) .disabled(tournaments.isEmpty)
} }
} }
.navigationTitle("Nouvel événement") .navigationTitle("Nouvel événement")
@ -162,24 +128,63 @@ struct EventCreationView: View {
} }
} }
private func _validate() {
let event = Event(creator: Store.main.userId, name: eventName)
event.club = selectedClub?.id
tournaments.forEach { tournament in
tournament.event = event.id
}
do {
try dataStore.events.addOrUpdate(instance: event)
} catch {
Logger.error(error)
}
tournaments.forEach { tournament in
tournament.courtCount = selectedClub?.courtCount ?? 2
tournament.startDate = startingDate
tournament.dayDuration = duration
tournament.setupFederalSettings()
}
do {
try dataStore.tournaments.addOrUpdate(contentOfs: tournaments)
} catch {
Logger.error(error)
}
dismiss()
navigation.path.append(tournaments.first!)
}
@ViewBuilder @ViewBuilder
private var approvedTournamentEditorView: some View { private var approvedTournamentEditorView: some View {
ForEach(tournaments) { tournament in ForEach(tournaments.indices, id: \.self) { index in
let tournament = tournaments[index]
Section { Section {
TournamentConfigurationView(tournament: tournament) TournamentConfigurationView(tournament: tournament)
} footer: { } header: {
if tournaments.count > 1 { if tournaments.count > 1 {
FooterButtonView("effacer") { HStack {
tournaments.removeAll(where: { $0 == tournament }) Spacer()
FooterButtonView("effacer") {
tournaments.removeAll(where: { $0 == tournament })
}
.textCase(nil)
}
}
} footer: {
if index == tournaments.count - 1 {
HStack {
Spacer()
FooterButtonView("Ajouter une \((tournaments.count + 1).ordinalFormatted()) épreuve") {
let tournament = Tournament.newEmptyInstance()
self.tournaments.append(tournament)
}
.popoverTip(multiTournamentsEventTip)
} }
} }
}
}
Section {
RowButtonView("Ajouter une \((tournaments.count + 1).ordinalFormatted()) épreuve") {
let tournament = Tournament.newEmptyInstance()
self.tournaments.append(tournament)
} }
} }
} }

@ -10,26 +10,30 @@ import SwiftUI
struct MatchListView: View { struct MatchListView: View {
@EnvironmentObject var dataStore: DataStore @EnvironmentObject var dataStore: DataStore
let section: String let section: String
let matches: [Match] let matches: [Match]?
var matchViewStyle: MatchViewStyle = .standardStyle var matchViewStyle: MatchViewStyle = .standardStyle
@State var isExpanded: Bool = true @State var isExpanded: Bool = true
@ViewBuilder @ViewBuilder
var body: some View { var body: some View {
if matches.isEmpty == false { Section {
Section { DisclosureGroup(isExpanded: $isExpanded) {
DisclosureGroup(isExpanded: $isExpanded) { if let matches {
ForEach(matches) { match in ForEach(matches) { match in
MatchRowView(match: match, matchViewStyle: matchViewStyle) MatchRowView(match: match, matchViewStyle: matchViewStyle)
.listRowInsets(EdgeInsets(top: 0, leading: -2, bottom: 0, trailing: 8)) .listRowInsets(EdgeInsets(top: 0, leading: -2, bottom: 0, trailing: 8))
} }
} label: { }
LabeledContent { } label: {
Text(matches.count.formatted() + " match" + matches.count.pluralSuffix) LabeledContent {
} label: { if matches == nil {
Text(section.firstCapitalized) ProgressView()
} else {
Text(matches!.count.formatted() + " match" + matches!.count.pluralSuffix)
} }
} label: {
Text(section.firstCapitalized)
} }
} }
} }

@ -17,6 +17,10 @@ struct GroupStageView: View {
@State private var confirmResetMatch: Bool = false @State private var confirmResetMatch: Bool = false
let playedMatches: [Match] let playedMatches: [Match]
@State private var runningMatches: [Match]?
@State private var readyMatches: [Match]?
@State private var availableToStart: [Match]?
init(groupStage: GroupStage) { init(groupStage: GroupStage) {
self.groupStage = groupStage self.groupStage = groupStage
self.playedMatches = groupStage.playedMatches() self.playedMatches = groupStage.playedMatches()
@ -44,12 +48,16 @@ struct GroupStageView: View {
} }
.headerProminence(.increased) .headerProminence(.increased)
let runningMatches = groupStage.runningMatches(playedMatches: playedMatches)
MatchListView(section: "disponible", matches: groupStage.availableToStart(playedMatches: playedMatches, in: runningMatches))
MatchListView(section: "en cours", matches: runningMatches) MatchListView(section: "en cours", matches: runningMatches)
MatchListView(section: "à lancer", matches: groupStage.readyMatches(playedMatches: playedMatches)) MatchListView(section: "prêt à démarrer", matches: availableToStart)
MatchListView(section: "à lancer", matches: self.readyMatches)
MatchListView(section: "terminés", matches: groupStage.finishedMatches(playedMatches: playedMatches), isExpanded: false) MatchListView(section: "terminés", matches: groupStage.finishedMatches(playedMatches: playedMatches), isExpanded: false)
} }
.task {
self.runningMatches = await groupStage.asyncRunningMatches(playedMatches: playedMatches)
self.readyMatches = await groupStage.readyMatches(playedMatches: playedMatches)
self.availableToStart = await groupStage.availableToStart(playedMatches: playedMatches, in: runningMatches ?? [])
}
.toolbar { .toolbar {
ToolbarItem(placement: .topBarTrailing) { ToolbarItem(placement: .topBarTrailing) {
_groupStageMenuView() _groupStageMenuView()

@ -76,24 +76,30 @@ struct GroupStagesView: View {
return allDestinations return allDestinations
} }
@State private var runningMatches: [Match]?
@State private var readyMatches: [Match]?
@State private var availableToStart: [Match]?
var body: some View { var body: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
GenericDestinationPickerView(selectedDestination: $selectedDestination, destinations: allDestinations(), nilDestinationIsValid: true) GenericDestinationPickerView(selectedDestination: $selectedDestination, destinations: allDestinations(), nilDestinationIsValid: true)
switch selectedDestination { switch selectedDestination {
case .all: case .all:
let runningMatches = tournament.runningMatches(allMatches)
let availableToStart = tournament.availableToStart(allMatches, in: runningMatches)
let readyMatches = tournament.readyMatches(allMatches)
let finishedMatches = tournament.finishedMatches(allMatches) let finishedMatches = tournament.finishedMatches(allMatches)
List { List {
MatchListView(section: "en cours", matches: runningMatches, matchViewStyle: .standardStyle, isExpanded: false) MatchListView(section: "en cours", matches: runningMatches, matchViewStyle: .standardStyle, isExpanded: false)
MatchListView(section: "disponible", matches: availableToStart, matchViewStyle: .standardStyle, isExpanded: false) MatchListView(section: "prêt à démarrer", matches: availableToStart, matchViewStyle: .standardStyle, isExpanded: false)
MatchListView(section: "à lancer", matches: readyMatches, matchViewStyle: .standardStyle, isExpanded: false) MatchListView(section: "à lancer", matches: readyMatches, matchViewStyle: .standardStyle, isExpanded: false)
MatchListView(section: "terminés", matches: finishedMatches, matchViewStyle: .standardStyle, isExpanded: false) MatchListView(section: "terminés", matches: finishedMatches, matchViewStyle: .standardStyle, isExpanded: false)
} }
.task {
runningMatches = await tournament.asyncRunningMatches(allMatches)
availableToStart = await tournament.availableToStart(allMatches, in: runningMatches ?? [])
readyMatches = await tournament.readyMatches(allMatches)
}
.overlay { .overlay {
if availableToStart.isEmpty && runningMatches.isEmpty && readyMatches.isEmpty && finishedMatches.isEmpty { if availableToStart?.isEmpty == true && runningMatches?.isEmpty == true && readyMatches?.isEmpty == true && finishedMatches.isEmpty == true {
ContentUnavailableView("Aucun match à afficher", systemImage: "tennisball") ContentUnavailableView("Aucun match à afficher", systemImage: "tennisball")
} }
} }

@ -22,8 +22,11 @@ struct MatchSetupView: View {
@ViewBuilder @ViewBuilder
func _teamView(inTeamPosition teamPosition: TeamPosition) -> some View { func _teamView(inTeamPosition teamPosition: TeamPosition) -> some View {
let team = match.team(teamPosition) let scores = match.teamScores
let teamScore = match.teamScore(ofTeam: team) let team = scores.isEmpty ? nil : match.team(teamPosition)
let teamScore = (team != nil) ? scores.first(where: { $0.teamRegistration == team!.id }) : nil
let walkOutSpot = teamScore?.walkOut == 1
if let team, teamScore?.walkOut == nil { if let team, teamScore?.walkOut == nil {
VStack(alignment: .leading, spacing: 0) { VStack(alignment: .leading, spacing: 0) {
if let teamScore, teamScore.luckyLoser != nil { if let teamScore, teamScore.luckyLoser != nil {
@ -67,7 +70,6 @@ struct MatchSetupView: View {
.strikethrough() .strikethrough()
} }
HStack { HStack {
let walkOutSpot = match.isWalkOutSpot(teamPosition)
let luckyLosers = walkOutSpot ? match.luckyLosers() : [] let luckyLosers = walkOutSpot ? match.luckyLosers() : []
TeamPickerView(luckyLosers: luckyLosers, teamPicked: { team in TeamPickerView(luckyLosers: luckyLosers, teamPicked: { team in
print(team.pasteData()) print(team.pasteData())

@ -44,32 +44,6 @@ struct MatchSummaryView: View {
} }
var body: some View { var body: some View {
matchSummaryView
// .contextMenu {
// ForEach(match.teamScores) { entrant in
// if let team = entrant.team, team.orderedPlayers.count > 2 {
// NavigationLink {
// PlayerPickerView(match: match, team: team)
// } label: {
// if let teamTitle = team.entrant?.brand?.title {
// Text(teamTitle).foregroundStyle(.secondary)
// } else {
// let index = match.orderedEntrants.firstIndex(where: { $0 == entrant }) ?? 0
// Text("Équipe \(index + 1)")
// }
// if match.players(from: team).isEmpty {
// Text("Choisir la paire")
// } else {
// Text("Modifier la paire")
// }
// }
// }
// }
// }
}
@ViewBuilder
var matchSummaryView: some View {
VStack(alignment: .leading) { VStack(alignment: .leading) {
if matchViewStyle != .plainStyle { if matchViewStyle != .plainStyle {
if matchViewStyle == .feedStyle, let tournament = match.currentTournament() { if matchViewStyle == .feedStyle, let tournament = match.currentTournament() {

@ -25,6 +25,7 @@ struct ActivityView: View {
var runningTournaments: [FederalTournamentHolder] { var runningTournaments: [FederalTournamentHolder] {
dataStore.tournaments.filter({ $0.endDate == nil }) dataStore.tournaments.filter({ $0.endDate == nil })
.filter({ federalDataViewModel.isTournamentValidForFilters($0) }) .filter({ federalDataViewModel.isTournamentValidForFilters($0) })
.sorted(using: SortDescriptor(\.startDate))
} }
var endedTournaments: [Tournament] { var endedTournaments: [Tournament] {
@ -32,16 +33,16 @@ struct ActivityView: View {
.filter({ federalDataViewModel.isTournamentValidForFilters($0) }) .filter({ federalDataViewModel.isTournamentValidForFilters($0) })
.sorted(using: SortDescriptor(\.startDate, order: .reverse)) .sorted(using: SortDescriptor(\.startDate, order: .reverse))
} }
//
func _activityStatus() -> String? { // func _activityStatus() -> String? {
let tournaments = tournaments // let tournaments = tournaments
if tournaments.isEmpty && federalDataViewModel.areFiltersEnabled() == false { // if tournaments.isEmpty && federalDataViewModel.areFiltersEnabled() == false {
return nil // return nil
} else { // } else {
let count = tournaments.map { $0.tournaments.count }.reduce(0,+) // let count = tournaments.map { $0.tournaments.count }.reduce(0,+)
return "\(count) tournoi" + count.pluralSuffix // return "\(count) tournoi" + count.pluralSuffix
} // }
} // }
var tournaments: [FederalTournamentHolder] { var tournaments: [FederalTournamentHolder] {
switch navigation.agendaDestination! { switch navigation.agendaDestination! {
@ -136,19 +137,10 @@ struct ActivityView: View {
} }
.toolbar { .toolbar {
if presentToolbar { if presentToolbar {
let _activityStatus = _activityStatus() //let _activityStatus = _activityStatus()
if federalDataViewModel.areFiltersEnabled() || _activityStatus != nil { if federalDataViewModel.areFiltersEnabled() {
ToolbarItem(placement: .status) { ToolbarItem(placement: .status) {
VStack(spacing: -2) { Text(federalDataViewModel.filterStatus())
if federalDataViewModel.areFiltersEnabled() {
Text(federalDataViewModel.filterStatus())
}
if let _activityStatus {
Text(_activityStatus)
.foregroundStyle(.secondary)
}
}
.font(.footnote)
} }
} }

@ -20,8 +20,8 @@ struct EventListView: View {
let groupedTournamentsByDate = Dictionary(grouping: navigation.agendaDestination == .tenup ? federalDataViewModel.filteredFederalTournaments : tournaments) { $0.startDate.startOfMonth } let groupedTournamentsByDate = Dictionary(grouping: navigation.agendaDestination == .tenup ? federalDataViewModel.filteredFederalTournaments : tournaments) { $0.startDate.startOfMonth }
switch viewStyle { switch viewStyle {
case .list: case .list:
ForEach(groupedTournamentsByDate.keys.sorted(by: <), id: \.self) { section in ForEach(Array(groupedTournamentsByDate.keys), id: \.self) { section in
if let _tournaments = groupedTournamentsByDate[section]?.sorted(by: \.startDate) { if let _tournaments = groupedTournamentsByDate[section] {
Section { Section {
_listView(_tournaments) _listView(_tournaments)
} header: { } header: {
@ -37,7 +37,7 @@ struct EventListView: View {
} }
case .calendar: case .calendar:
ForEach(_nextMonths(), id: \.self) { section in ForEach(_nextMonths(), id: \.self) { section in
let _tournaments = groupedTournamentsByDate[section]?.sorted(by: \.startDate) ?? [] let _tournaments = groupedTournamentsByDate[section] ?? []
Section { Section {
CalendarView(date: section, tournaments: _tournaments).id(federalDataViewModel.id) CalendarView(date: section, tournaments: _tournaments).id(federalDataViewModel.id)
} header: { } header: {

@ -48,7 +48,7 @@ struct MainView: View {
dataStore.matches.filter({ $0.confirmed && $0.startDate != nil && $0.endDate == nil && $0.courtIndex != nil }) dataStore.matches.filter({ $0.confirmed && $0.startDate != nil && $0.endDate == nil && $0.courtIndex != nil })
} }
var badgeText: Text? = Store.main.userName() == nil ? Text("!").font(.headline) : nil var badgeText: Text? = Store.main.userId == nil ? Text("!").font(.headline) : nil
var body: some View { var body: some View {
TabView(selection: selectedTabHandler) { TabView(selection: selectedTabHandler) {

@ -17,7 +17,6 @@ struct OngoingView: View {
var matches: [Match] { var matches: [Match] {
let sorting = sortByField ? fieldSorting : defaultSorting let sorting = sortByField ? fieldSorting : defaultSorting
let now = Date()
return dataStore.matches.filter({ $0.confirmed && $0.startDate != nil && $0.endDate == nil && $0.courtIndex != nil }).sorted(using: sorting, order: .ascending) return dataStore.matches.filter({ $0.confirmed && $0.startDate != nil && $0.endDate == nil && $0.courtIndex != nil }).sorted(using: sorting, order: .ascending)
} }

@ -42,19 +42,7 @@ struct RoundSettingsView: View {
// } // }
Section { Section {
RowButtonView("Retirer toutes les têtes de séries", role: .destructive) { RowButtonView("Retirer toutes les têtes de séries", role: .destructive) {
tournament.unsortedTeams().forEach({ team in await _removeAllSeeds()
tournament.resetTeamScores(in: team.bracketPosition)
team.bracketPosition = nil
})
do {
try dataStore.teamRegistrations.addOrUpdate(contentOfs: tournament.unsortedTeams())
} catch {
Logger.error(error)
}
tournament.allRounds().forEach({ round in
round.enableRound()
})
self.isEditingTournamentSeed.wrappedValue = true
} }
} }
@ -94,6 +82,30 @@ struct RoundSettingsView: View {
} }
} }
} }
private func _removeAllSeeds() async {
tournament.unsortedTeams().forEach({ team in
team.bracketPosition = nil
})
let ts = tournament.allRoundMatches().flatMap { match in
match.teamScores
}
do {
try DataStore.shared.teamScores.delete(contentOfs: ts)
} catch {
Logger.error(error)
}
do {
try dataStore.teamRegistrations.addOrUpdate(contentOfs: tournament.unsortedTeams())
} catch {
Logger.error(error)
}
tournament.allRounds().forEach({ round in
round.enableRound()
})
self.isEditingTournamentSeed.wrappedValue = true
}
} }
#Preview { #Preview {

@ -13,6 +13,27 @@ struct RoundView: View {
@Environment(Tournament.self) var tournament: Tournament @Environment(Tournament.self) var tournament: Tournament
@EnvironmentObject var dataStore: DataStore @EnvironmentObject var dataStore: DataStore
@State private var selectedSeedGroup: SeedInterval? @State private var selectedSeedGroup: SeedInterval?
@State private var spaceLeft: [Match] = []
@State private var seedSpaceLeft: [Match] = []
@State private var availableSeedGroup: SeedInterval?
private func _getAvailableSeedGroup() async {
availableSeedGroup = tournament.seedGroupAvailable(atRoundIndex: round.index)
}
private func _getSpaceLeft() async {
spaceLeft.removeAll()
seedSpaceLeft.removeAll()
round.displayableMatches().forEach({
let count = $0.teamScores.count
if count == 0 {
seedSpaceLeft.append($0)
} else if count == 1 {
spaceLeft.append($0)
}
})
}
var showVisualDrawView: Binding<Bool> { Binding( var showVisualDrawView: Binding<Bool> { Binding(
get: { selectedSeedGroup != nil }, get: { selectedSeedGroup != nil },
set: { set: {
@ -26,14 +47,13 @@ struct RoundView: View {
var body: some View { var body: some View {
List { List {
let loserRounds = round.loserRounds()
let availableSeeds = tournament.availableSeeds()
let availableQualifiedTeams = tournament.availableQualifiedTeams()
let displayableMatches = round.displayableMatches().sorted(by: \.index) let displayableMatches = round.displayableMatches().sorted(by: \.index)
let spaceLeft = displayableMatches.filter({ $0.hasSpaceLeft() }) let loserRounds = round.loserRounds()
let seedSpaceLeft = displayableMatches.filter({ $0.isEmpty() }) if displayableMatches.isEmpty {
if isEditingTournamentSeed.wrappedValue == false { Section {
ContentUnavailableView("Aucun match dans cette manche", systemImage: "tennisball")
}
} else if isEditingTournamentSeed.wrappedValue == false {
//(where: { $0.isDisabled() == false || isEditingTournamentSeed.wrappedValue }) //(where: { $0.isDisabled() == false || isEditingTournamentSeed.wrappedValue })
if loserRounds.isEmpty == false { if loserRounds.isEmpty == false {
let correspondingLoserRoundTitle = round.correspondingLoserRoundTitle() let correspondingLoserRoundTitle = round.correspondingLoserRoundTitle()
@ -48,14 +68,19 @@ struct RoundView: View {
} }
} }
} else { } else {
if let availableSeedGroup = tournament.seedGroupAvailable(atRoundIndex: round.index) { let availableSeeds = tournament.availableSeeds()
let availableQualifiedTeams = tournament.availableQualifiedTeams()
if availableSeeds.isEmpty == false, let availableSeedGroup {
Section { Section {
RowButtonView("Placer \(availableSeedGroup.localizedLabel())" + ((availableSeedGroup.isFixed() == false) ? " au hasard" : "")) { RowButtonView("Placer \(availableSeedGroup.localizedLabel())" + ((availableSeedGroup.isFixed() == false) ? " au hasard" : "")) {
tournament.setSeeds(inRoundIndex: round.index, inSeedGroup: availableSeedGroup) tournament.setSeeds(inRoundIndex: round.index, inSeedGroup: availableSeedGroup)
_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()
await _getAvailableSeedGroup()
} }
} footer: { } footer: {
if availableSeedGroup.isFixed() == false { if availableSeedGroup.isFixed() == false {
@ -74,86 +99,95 @@ struct RoundView: View {
} }
} }
if availableQualifiedTeams.isEmpty == false {
if availableQualifiedTeams.isEmpty == false && spaceLeft.isEmpty == false { if spaceLeft.isEmpty == false {
Section { Section {
DisclosureGroup { DisclosureGroup {
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 { 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)
} }
_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()
} }
} }
} label: {
TeamRowView(team: team, displayCallDate: false)
} }
} label: {
TeamRowView(team: team, displayCallDate: false)
} }
} label: {
Text("Qualifié\(availableQualifiedTeams.count.pluralSuffix) à placer").badge(availableQualifiedTeams.count)
} }
} label: { } header: {
Text("Qualifié\(availableQualifiedTeams.count.pluralSuffix) à placer").badge(availableQualifiedTeams.count) Text("Tirage au sort visuel d'un qualifié").font(.subheadline)
} }
} header: {
Text("Tirage au sort visuel d'un qualifié").font(.subheadline)
} }
} }
if availableSeeds.isEmpty == false && seedSpaceLeft.isEmpty == false { if availableSeeds.isEmpty == false {
Section { if seedSpaceLeft.isEmpty == false {
DisclosureGroup { Section {
ForEach(availableSeeds) { team in DisclosureGroup {
NavigationLink { ForEach(availableSeeds) { team in
SpinDrawView(drawees: [team], segments: seedSpaceLeft) { results in NavigationLink {
Task { SpinDrawView(drawees: [team], segments: seedSpaceLeft) { results in
results.forEach { drawResult in Task {
team.setSeedPosition(inSpot: seedSpaceLeft[drawResult.drawIndex], slot: nil, opposingSeeding: false) results.forEach { drawResult in
} team.setSeedPosition(inSpot: seedSpaceLeft[drawResult.drawIndex], slot: nil, opposingSeeding: false)
_save() }
if availableSeeds.isEmpty && tournament.availableQualifiedTeams().isEmpty { await _save()
self.isEditingTournamentSeed.wrappedValue = false if tournament.availableSeeds().isEmpty && tournament.availableQualifiedTeams().isEmpty {
self.isEditingTournamentSeed.wrappedValue = false
}
await _getSpaceLeft()
await _getAvailableSeedGroup()
} }
} }
} label: {
TeamRowView(team: team, displayCallDate: false)
} }
} label: {
TeamRowView(team: team, displayCallDate: false)
} }
} label: {
Text("Tête\(availableSeeds.count.pluralSuffix) de série à placer").badge(availableSeeds.count)
} }
} label: { } header: {
Text("Tête\(availableSeeds.count.pluralSuffix) de série à placer").badge(availableSeeds.count) Text("Tirage au sort visuel d'une tête de série").font(.subheadline)
} }
} header: { } else if spaceLeft.isEmpty == false {
Text("Tirage au sort visuel d'une tête de série").font(.subheadline) Section {
} DisclosureGroup {
} else if availableSeeds.isEmpty == false && spaceLeft.isEmpty == false { ForEach(availableSeeds) { team in
Section { NavigationLink {
DisclosureGroup { SpinDrawView(drawees: [team], segments: spaceLeft) { results in
ForEach(availableSeeds) { team in Task {
NavigationLink { results.forEach { drawResult in
SpinDrawView(drawees: [team], segments: spaceLeft) { results in team.setSeedPosition(inSpot: spaceLeft[drawResult.drawIndex], slot: nil, opposingSeeding: false)
Task { }
results.forEach { drawResult in await _save()
team.setSeedPosition(inSpot: spaceLeft[drawResult.drawIndex], slot: nil, opposingSeeding: false) if tournament.availableSeeds().isEmpty && tournament.availableQualifiedTeams().isEmpty {
} self.isEditingTournamentSeed.wrappedValue = false
_save() }
if availableSeeds.isEmpty && tournament.availableQualifiedTeams().isEmpty { await _getSpaceLeft()
self.isEditingTournamentSeed.wrappedValue = false await _getAvailableSeedGroup()
} }
} }
} label: {
TeamRowView(team: team, displayCallDate: false)
} }
} label: {
TeamRowView(team: team, displayCallDate: false)
} }
} label: {
Text("Tête\(availableSeeds.count.pluralSuffix) de série à placer").badge(availableSeeds.count)
} }
} label: { } header: {
Text("Tête\(availableSeeds.count.pluralSuffix) de série à placer").badge(availableSeeds.count) Text("Tirage au sort visuel d'une tête de série").font(.subheadline)
} }
} header: {
Text("Tirage au sort visuel d'une tête de série").font(.subheadline)
} }
} }
} }
@ -164,19 +198,30 @@ struct RoundView: View {
HStack { HStack {
Text(round.roundTitle(.wide)) Text(round.roundTitle(.wide))
if round.index > 0 { if round.index > 0 {
Text(match.matchTitle(.short)) Text(match.matchTitle(.short, inMatches: displayableMatches))
} else { } else {
let tournamentTeamCount = tournament.teamCount let tournamentTeamCount = tournament.teamCount
if let seedIntervalPointRange = loserRounds.first?.seedInterval()?.pointsRange(tournamentLevel: tournament.tournamentLevel, teamsCount: tournamentTeamCount) { if let seedIntervalPointRange = round.seedInterval()?.pointsRange(tournamentLevel: tournament.tournamentLevel, teamsCount: tournamentTeamCount) {
Spacer() Spacer()
Text(seedIntervalPointRange) Text(seedIntervalPointRange)
.font(.caption) .font(.caption)
} }
} }
#if DEBUG
Spacer()
Text(match.teamScores.count.formatted())
#endif
} }
} }
} }
} }
.onAppear {
Task {
await _prepareRound()
}
}
.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)
@ -187,7 +232,7 @@ struct RoundView: View {
draws.forEach { drawResult in draws.forEach { drawResult in
seeds[drawResult.drawee].setSeedPosition(inSpot: availableSeedSpot[drawResult.drawIndex], slot: nil, opposingSeeding: false) seeds[drawResult.drawee].setSeedPosition(inSpot: availableSeedSpot[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
} }
@ -201,7 +246,9 @@ struct RoundView: View {
ToolbarItem(placement: .topBarTrailing) { ToolbarItem(placement: .topBarTrailing) {
Button(isEditingTournamentSeed.wrappedValue == true ? "Valider" : "Modifier") { Button(isEditingTournamentSeed.wrappedValue == true ? "Valider" : "Modifier") {
if isEditingTournamentSeed.wrappedValue { if isEditingTournamentSeed.wrappedValue {
_save() Task {
await _save()
}
} }
isEditingTournamentSeed.wrappedValue.toggle() isEditingTournamentSeed.wrappedValue.toggle()
} }
@ -209,7 +256,7 @@ struct RoundView: View {
} }
} }
private func _save() { private func _save() async {
do { do {
try dataStore.teamRegistrations.addOrUpdate(contentOfs: tournament.unsortedTeams()) try dataStore.teamRegistrations.addOrUpdate(contentOfs: tournament.unsortedTeams())
} catch { } catch {
@ -221,7 +268,7 @@ struct RoundView: View {
rounds.forEach { round in rounds.forEach { round in
let matches = round.playedMatches() let matches = round.playedMatches()
matches.forEach { match in matches.forEach { match in
match.name = Match.setServerTitle(upperRound: round, matchIndex: match.indexInRound()) match.name = Match.setServerTitle(upperRound: round, matchIndex: match.indexInRound(in: matches))
} }
} }
let allRoundMatches = tournament.allRoundMatches() let allRoundMatches = tournament.allRoundMatches()
@ -231,6 +278,15 @@ struct RoundView: View {
Logger.error(error) Logger.error(error)
} }
} }
private func _prepareRound() async {
Task {
await _getSpaceLeft()
}
Task {
await _getAvailableSeedGroup()
}
}
} }
#Preview { #Preview {

@ -33,7 +33,7 @@ struct RoundsView: View {
RoundSettingsView() RoundSettingsView()
.navigationTitle("Réglages") .navigationTitle("Réglages")
case .some(let selectedRound): case .some(let selectedRound):
RoundView(round: selectedRound) RoundView(round: selectedRound).id(selectedRound.id)
.navigationTitle(selectedRound.roundTitle()) .navigationTitle(selectedRound.roundTitle())
} }
} }

@ -13,10 +13,6 @@ struct TeamHeaderView: View {
var tournament: Tournament? var tournament: Tournament?
var body: some View { var body: some View {
_teamHeaderView(team, teamIndex: teamIndex)
}
private func _teamHeaderView(_ team: TeamRegistration, teamIndex: Int?) -> some View {
HStack(spacing: 16.0) { HStack(spacing: 16.0) {
if let teamIndex { if let teamIndex {
VStack(alignment: .leading, spacing: 0) { VStack(alignment: .leading, spacing: 0) {

@ -27,6 +27,7 @@ struct FileImportView: View {
@State private var selectedOptions: Set<TeamImportStrategy> = Set() @State private var selectedOptions: Set<TeamImportStrategy> = Set()
@State private var fileProvider: FileImportManager.FileProvider = .frenchFederation @State private var fileProvider: FileImportManager.FileProvider = .frenchFederation
@State private var validationInProgress: Bool = false
private var filteredTeams: [FileImportManager.TeamHolder] { private var filteredTeams: [FileImportManager.TeamHolder] {
return teams.filter { $0.tournamentCategory == tournament.tournamentCategory }.sorted(by: \.weight) return teams.filter { $0.tournamentCategory == tournament.tournamentCategory }.sorted(by: \.weight)
@ -104,6 +105,16 @@ struct FileImportView: View {
} }
} }
if validationInProgress {
Section {
LabeledContent {
ProgressView()
} label: {
Text("Mise à jour des équipes")
}
}
}
if let errorMessage { if let errorMessage {
Section { Section {
Text(errorMessage) Text(errorMessage)
@ -146,7 +157,7 @@ struct FileImportView: View {
Section { Section {
ContentUnavailableView("Aucune équipe détectée", systemImage: "person.2.slash") ContentUnavailableView("Aucune équipe détectée", systemImage: "person.2.slash")
} }
} else if didImport { } else if didImport && validationInProgress == false {
let _filteredTeams = filteredTeams let _filteredTeams = filteredTeams
let previousTeams = tournament.sortedTeams() let previousTeams = tournament.sortedTeams()
@ -222,38 +233,40 @@ struct FileImportView: View {
ToolbarItem(placement: .topBarTrailing) { ToolbarItem(placement: .topBarTrailing) {
ButtonValidateView { ButtonValidateView {
// if false { //selectedOptions.contains(.deleteBeforeImport) _validate()
// try? dataStore.teamRegistrations.delete(contentOfs: tournament.unsortedTeams())
// }
if true { //selectedOptions.contains(.notFoundAreWalkOut)
let previousTeams = filteredTeams.compactMap({ $0.previousTeam })
let unfound = Set(tournament.unsortedTeams()).subtracting(Set(previousTeams))
unfound.forEach { team in
team.resetPositions()
team.wildCardBracket = false
team.wildCardGroupStage = false
team.walkOut = true
}
do {
try dataStore.teamRegistrations.addOrUpdate(contentOfs: unfound)
} catch {
Logger.error(error)
}
}
tournament.importTeams(filteredTeams)
dismiss()
} }
.disabled(teams.isEmpty) .disabled(teams.isEmpty)
} }
} }
.interactiveDismissDisabled(validationInProgress)
.disabled(validationInProgress)
}
private func _validate() {
validationInProgress = true
Task {
let previousTeams = filteredTeams.compactMap({ $0.previousTeam })
let unfound = Set(tournament.unsortedTeams()).subtracting(Set(previousTeams))
unfound.forEach { team in
team.resetPositions()
team.wildCardBracket = false
team.wildCardGroupStage = false
team.walkOut = true
}
do {
try dataStore.teamRegistrations.addOrUpdate(contentOfs: unfound)
} catch {
Logger.error(error)
}
tournament.importTeams(filteredTeams)
dismiss()
}
} }
func _startImport(fileContent: String) async throws { private func _startImport(fileContent: String) async throws {
await MainActor.run { await MainActor.run {
errorMessage = nil errorMessage = nil
teams.removeAll() teams.removeAll()

@ -11,20 +11,16 @@ struct InscriptionInfoView: View {
@EnvironmentObject var dataStore: DataStore @EnvironmentObject var dataStore: DataStore
@Environment(Tournament.self) var tournament @Environment(Tournament.self) var tournament
var players : [PlayerRegistration] { tournament.unsortedPlayers() } @State private var players : [PlayerRegistration] = []
var selectedTeams : [TeamRegistration] { tournament.selectedSortedTeams() } @State private var selectedTeams : [TeamRegistration] = []
@State private var callDateIssue : [TeamRegistration] = []
var callDateIssue : [TeamRegistration] { @State private var waitingList : [TeamRegistration] = []
selectedTeams.filter { $0.callDate != nil && tournament.isStartDateIsDifferentThanCallDate($0) } @State private var duplicates : [PlayerRegistration] = []
} @State private var problematicPlayers : [PlayerRegistration] = []
@State private var inadequatePlayers : [PlayerRegistration] = []
var waitingList : [TeamRegistration] { tournament.waitingListTeams(in: selectedTeams) } @State private var playersWithoutValidLicense : [PlayerRegistration] = []
var duplicates : [PlayerRegistration] { tournament.duplicates(in: players) } @State private var entriesFromBeachPadel : [TeamRegistration] = []
var problematicPlayers : [PlayerRegistration] { players.filter({ $0.sex == nil }) } @State private var playersMissing : [TeamRegistration] = []
var inadequatePlayers : [PlayerRegistration] { tournament.inadequatePlayers(in: players) }
var playersWithoutValidLicense : [PlayerRegistration] { tournament.playersWithoutValidLicense(in: players) }
var entriesFromBeachPadel : [TeamRegistration] { tournament.unsortedTeams().filter({ $0.isImported() }) }
var playersMissing : [TeamRegistration] { selectedTeams.filter({ $0.unsortedPlayers().count < 2 }) }
var body: some View { var body: some View {
List { List {
@ -196,10 +192,28 @@ struct InscriptionInfoView: View {
.listRowView(color: .pink) .listRowView(color: .pink)
} }
} }
.task {
await _getIssues()
}
.navigationTitle("Synthèse") .navigationTitle("Synthèse")
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.visible, for: .navigationBar) .toolbarBackground(.visible, for: .navigationBar)
} }
private func _getIssues() async {
Task {
players = tournament.unsortedPlayers()
selectedTeams = tournament.selectedSortedTeams()
callDateIssue = selectedTeams.filter { $0.callDate != nil && tournament.isStartDateIsDifferentThanCallDate($0) }
waitingList = tournament.waitingListTeams(in: selectedTeams)
duplicates = tournament.duplicates(in: players)
problematicPlayers = players.filter({ $0.sex == nil })
inadequatePlayers = tournament.inadequatePlayers(in: players)
playersWithoutValidLicense = tournament.playersWithoutValidLicense(in: players)
entriesFromBeachPadel = tournament.unsortedTeams().filter({ $0.isImported() })
playersMissing = selectedTeams.filter({ $0.unsortedPlayers().count < 2 })
}
}
} }
#Preview { #Preview {

@ -9,12 +9,22 @@ import SwiftUI
import TipKit import TipKit
import LeStorage import LeStorage
let slideToDeleteTip = SlideToDeleteTip()
let inscriptionManagerWomanRankTip = InscriptionManagerWomanRankTip()
let fileTip = InscriptionManagerFileInputTip()
let pasteTip = InscriptionManagerPasteInputTip()
let searchTip = InscriptionManagerSearchInputTip()
let createTip = InscriptionManagerCreateInputTip()
let rankUpdateTip = InscriptionManagerRankUpdateTip()
let padelBeachExportTip = PadelBeachExportTip()
let padelBeachImportTip = PadelBeachImportTip()
struct InscriptionManagerView: View { struct InscriptionManagerView: View {
@EnvironmentObject var dataStore: DataStore @EnvironmentObject var dataStore: DataStore
@EnvironmentObject var networkMonitor: NetworkMonitor @EnvironmentObject var networkMonitor: NetworkMonitor
@FetchRequest( @FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \ImportedPlayer.rank, ascending: true)], sortDescriptors: [],
animation: .default) animation: .default)
private var fetchPlayers: FetchedResults<ImportedPlayer> private var fetchPlayers: FetchedResults<ImportedPlayer>
@ -41,6 +51,13 @@ struct InscriptionManagerView: View {
@State private var contactType: ContactType? = nil @State private var contactType: ContactType? = nil
@State private var sentError: ContactManagerError? = nil @State private var sentError: ContactManagerError? = nil
@State private var showSubscriptionView: Bool = false @State private var showSubscriptionView: Bool = false
@State private var registrationIssues: Int? = nil
@State private var sortedTeams: [TeamRegistration] = []
@State private var unfilteredTeams: [TeamRegistration] = []
@State private var walkoutTeams: [TeamRegistration] = []
@State private var unsortedTeamsWithoutWO: [TeamRegistration] = []
@State private var unsortedPlayers: [PlayerRegistration] = []
@State private var teamPaste: URL?
var messageSentFailed: Binding<Bool> { var messageSentFailed: Binding<Bool> {
Binding { Binding {
@ -83,19 +100,8 @@ struct InscriptionManagerView: View {
} }
} }
let slideToDeleteTip = SlideToDeleteTip()
let inscriptionManagerWomanRankTip = InscriptionManagerWomanRankTip()
let fileTip = InscriptionManagerFileInputTip()
let pasteTip = InscriptionManagerPasteInputTip()
let searchTip = InscriptionManagerSearchInputTip()
let createTip = InscriptionManagerCreateInputTip()
let rankUpdateTip = InscriptionManagerRankUpdateTip()
let padelBeachExportTip = PadelBeachExportTip()
let padelBeachImportTip = PadelBeachImportTip()
let categoryOption: PlayerFilterOption let categoryOption: PlayerFilterOption
let filterable: Bool let filterable: Bool
let dates = Set(SourceFileManager.shared.allFilesSortedByDate(true).map({ $0.dateFromPath })).sorted().reversed()
init(tournament: Tournament) { init(tournament: Tournament) {
self.tournament = tournament self.tournament = tournament
@ -110,6 +116,16 @@ struct InscriptionManagerView: View {
} }
} }
private func _clearScreen() {
teamPaste = nil
unsortedPlayers.removeAll()
unfilteredTeams.removeAll()
walkoutTeams.removeAll()
unsortedTeamsWithoutWO.removeAll()
sortedTeams.removeAll()
registrationIssues = nil
}
// Function to create a simple hash from a list of IDs // Function to create a simple hash from a list of IDs
private func _simpleHash(ids: [String]) -> Int { private func _simpleHash(ids: [String]) -> Int {
// Combine the hash values of each string // Combine the hash values of each string
@ -121,7 +137,43 @@ struct InscriptionManagerView: View {
return _simpleHash(ids: ids1) != _simpleHash(ids: ids2) return _simpleHash(ids: ids1) != _simpleHash(ids: ids2)
} }
private func _setHash() async {
let start = Date()
defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
print("func _setHash", duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
let selectedSortedTeams = tournament.selectedSortedTeams()
if self.teamsHash == nil, selectedSortedTeams.isEmpty == false {
self.teamsHash = _simpleHash(ids: selectedSortedTeams.map { $0.id })
}
}
private func _handleHashDiff() async {
let newHash = _simpleHash(ids: tournament.selectedSortedTeams().map { $0.id })
if let teamsHash, newHash != teamsHash {
self.teamsHash = newHash
if self.tournament.shouldVerifyBracket == false || self.tournament.shouldVerifyGroupStage == false {
self.tournament.shouldVerifyBracket = true
self.tournament.shouldVerifyGroupStage = true
let waitingList = self.tournament.waitingListTeams(in: self.tournament.selectedSortedTeams())
waitingList.forEach { team in
if team.bracketPosition != nil || team.groupStagePosition != nil {
team.resetPositions()
}
}
do {
try dataStore.teamRegistrations.addOrUpdate(contentOfs: waitingList)
try dataStore.tournaments.addOrUpdate(instance: tournament)
} catch {
Logger.error(error)
}
}
}
}
var body: some View { var body: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
@ -130,38 +182,16 @@ struct InscriptionManagerView: View {
_buildingTeamView() _buildingTeamView()
} else if tournament.unsortedTeams().isEmpty { } else if tournament.unsortedTeams().isEmpty {
_inscriptionTipsView() _inscriptionTipsView()
} else {
_teamRegisteredView()
} }
_teamRegisteredView()
} }
.onAppear { .onAppear {
let selectedSortedTeams = tournament.selectedSortedTeams() _getTeams()
if self.teamsHash == nil, selectedSortedTeams.isEmpty == false {
self.teamsHash = _simpleHash(ids: selectedSortedTeams.map { $0.id })
}
} }
.onDisappear { .onDisappear {
let newHash = _simpleHash(ids: tournament.selectedSortedTeams().map { $0.id }) Task {
if let teamsHash, newHash != teamsHash { await _handleHashDiff()
self.teamsHash = newHash
if self.tournament.shouldVerifyBracket == false || self.tournament.shouldVerifyGroupStage == false {
self.tournament.shouldVerifyBracket = true
self.tournament.shouldVerifyGroupStage = true
let waitingList = self.tournament.waitingListTeams(in: self.tournament.selectedSortedTeams())
waitingList.forEach { team in
if team.bracketPosition != nil || team.groupStagePosition != nil {
team.resetPositions()
}
}
do {
try dataStore.teamRegistrations.addOrUpdate(contentOfs: waitingList)
try dataStore.tournaments.addOrUpdate(instance: tournament)
} catch {
Logger.error(error)
}
}
} }
} }
.alert("Un problème est survenu", isPresented: messageSentFailed) { .alert("Un problème est survenu", isPresented: messageSentFailed) {
@ -258,10 +288,14 @@ struct InscriptionManagerView: View {
.tint(.master) .tint(.master)
} }
.onChange(of: tournament.prioritizeClubMembers) { .onChange(of: tournament.prioritizeClubMembers) {
_clearScreen()
_save() _save()
_getTeams()
} }
.onChange(of: tournament.teamSorting) { .onChange(of: tournament.teamSorting) {
_clearScreen()
_save() _save()
_getTeams()
} }
.onChange(of: currentRankSourceDate) { .onChange(of: currentRankSourceDate) {
if let currentRankSourceDate, tournament.rankSourceDate != currentRankSourceDate { if let currentRankSourceDate, tournament.rankSourceDate != currentRankSourceDate {
@ -334,8 +368,10 @@ struct InscriptionManagerView: View {
Label("Clôturer", systemImage: "lock") Label("Clôturer", systemImage: "lock")
} }
Divider() Divider()
ShareLink(item: tournament.pasteDataForImporting().createTxtFile(self.tournament.tournamentTitle(.short))) { if let teamPaste {
Label("Exporter les paires", systemImage: "square.and.arrow.up") ShareLink(item: teamPaste) {
Label("Exporter les paires", systemImage: "square.and.arrow.up")
}
} }
Button { Button {
presentImportView = true presentImportView = true
@ -373,7 +409,28 @@ struct InscriptionManagerView: View {
createdPlayerIds.isEmpty == false || editedTeam != nil || pasteString != nil createdPlayerIds.isEmpty == false || editedTeam != nil || pasteString != nil
} }
private func _getTeams(from sortedTeams: [TeamRegistration]) -> [TeamRegistration] { private func _prepareStats() async {
let start = Date()
defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
print("func _prepareStats", duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
unsortedPlayers = tournament.unsortedPlayers()
walkoutTeams = tournament.walkoutTeams()
unsortedTeamsWithoutWO = tournament.unsortedTeamsWithoutWO()
teamPaste = tournament.pasteDataForImporting().createTxtFile(self.tournament.tournamentTitle(.short))
}
private func _prepareTeams() {
let start = Date()
defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
print("func _prepareTeams", duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
sortedTeams = tournament.sortedTeams()
var teams = sortedTeams var teams = sortedTeams
if filterMode == .walkOut { if filterMode == .walkOut {
teams = teams.filter({ $0.walkOut }) teams = teams.filter({ $0.walkOut })
@ -384,17 +441,32 @@ struct InscriptionManagerView: View {
} }
if byDecreasingOrdering { if byDecreasingOrdering {
return teams.reversed() self.unfilteredTeams = teams.reversed()
} else { } else {
return teams self.unfilteredTeams = teams
}
}
private func _getIssues() async {
let start = Date()
defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
print("func _getIssues", duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
await registrationIssues = tournament.registrationIssues()
}
private func _getTeams() {
_prepareTeams()
Task {
await _prepareStats()
await _getIssues()
await _setHash()
} }
} }
private func _teamRegisteredView() -> some View { private func _teamRegisteredView() -> some View {
List { List {
let sortedTeams = tournament.sortedTeams()
let unfilteredTeams = _getTeams(from: sortedTeams)
if presentSearch == false { if presentSearch == false {
_rankHandlerView() _rankHandlerView()
_relatedTips() _relatedTips()
@ -418,6 +490,7 @@ struct InscriptionManagerView: View {
Task { Task {
await MainActor.run() { await MainActor.run() {
fetchPlayers.nsPredicate = _pastePredicate(pasteField: searchField, mostRecentDate: SourceFileManager.shared.mostRecentDateAvailable) fetchPlayers.nsPredicate = _pastePredicate(pasteField: searchField, mostRecentDate: SourceFileManager.shared.mostRecentDateAvailable)
fetchPlayers.nsSortDescriptors = [NSSortDescriptor(keyPath: \ImportedPlayer.rank, ascending: true)]
pasteString = searchField pasteString = searchField
} }
} }
@ -474,6 +547,7 @@ struct InscriptionManagerView: View {
Task { Task {
await MainActor.run { await MainActor.run {
fetchPlayers.nsPredicate = _pastePredicate(pasteField: first, mostRecentDate: SourceFileManager.shared.mostRecentDateAvailable) fetchPlayers.nsPredicate = _pastePredicate(pasteField: first, mostRecentDate: SourceFileManager.shared.mostRecentDateAvailable)
fetchPlayers.nsSortDescriptors = [NSSortDescriptor(keyPath: \ImportedPlayer.rank, ascending: true)]
pasteString = first pasteString = first
autoSelect = true autoSelect = true
} }
@ -504,6 +578,8 @@ struct InscriptionManagerView: View {
@ViewBuilder @ViewBuilder
func rankingDateSourcePickerView(showDateInLabel: Bool) -> some View { func rankingDateSourcePickerView(showDateInLabel: Bool) -> some View {
Section { Section {
let dates = Set(SourceFileManager.shared.allFilesSortedByDate(true).map({ $0.dateFromPath })).sorted().reversed()
Picker(selection: $currentRankSourceDate) { Picker(selection: $currentRankSourceDate) {
if currentRankSourceDate == nil { if currentRankSourceDate == nil {
Text("inconnu").tag(nil as Date?) Text("inconnu").tag(nil as Date?)
@ -571,6 +647,7 @@ struct InscriptionManagerView: View {
Task { Task {
await MainActor.run { await MainActor.run {
fetchPlayers.nsPredicate = _pastePredicate(pasteField: paste, mostRecentDate: SourceFileManager.shared.mostRecentDateAvailable) fetchPlayers.nsPredicate = _pastePredicate(pasteField: paste, mostRecentDate: SourceFileManager.shared.mostRecentDateAvailable)
fetchPlayers.nsSortDescriptors = [NSSortDescriptor(keyPath: \ImportedPlayer.rank, ascending: true)]
pasteString = paste pasteString = paste
autoSelect = true autoSelect = true
} }
@ -621,9 +698,6 @@ struct InscriptionManagerView: View {
private func _informationView(count: Int) -> some View { private func _informationView(count: Int) -> some View {
Section { Section {
let walkoutTeams = tournament.walkoutTeams()
let unsortedTeamsWithoutWO = tournament.unsortedTeamsWithoutWO()
LabeledContent { LabeledContent {
Text(unsortedTeamsWithoutWO.count.formatted() + "/" + tournament.teamCount.formatted()).font(.largeTitle) Text(unsortedTeamsWithoutWO.count.formatted() + "/" + tournament.teamCount.formatted()).font(.largeTitle)
} label: { } label: {
@ -647,7 +721,11 @@ struct InscriptionManagerView: View {
.environment(tournament) .environment(tournament)
} label: { } label: {
LabeledContent { LabeledContent {
Text(tournament.registrationIssues().formatted()).font(.largeTitle) if let registrationIssues {
Text(registrationIssues.formatted()).font(.largeTitle)
} else {
ProgressView()
}
} label: { } label: {
Text("Problèmes détéctés") Text("Problèmes détéctés")
if let closedRegistrationDate = tournament.closedRegistrationDate { if let closedRegistrationDate = tournament.closedRegistrationDate {
@ -660,43 +738,43 @@ struct InscriptionManagerView: View {
@ViewBuilder @ViewBuilder
private func _relatedTips() -> some View { private func _relatedTips() -> some View {
if pasteString == nil // if pasteString == nil
&& createdPlayerIds.isEmpty // && createdPlayerIds.isEmpty
&& tournament.unsortedTeams().count >= tournament.teamCount // && tournament.unsortedTeams().count >= tournament.teamCount
&& tournament.unsortedPlayers().filter({ $0.source == .beachPadel }).isEmpty { // && tournament.unsortedPlayers().filter({ $0.source == .beachPadel }).isEmpty {
Section { // Section {
TipView(padelBeachExportTip) { action in // TipView(padelBeachExportTip) { action in
if action.id == "more-info-export" { // if action.id == "more-info-export" {
isLearningMore = true // isLearningMore = true
} // }
if action.id == "padel-beach" { // if action.id == "padel-beach" {
UIApplication.shared.open(URLs.beachPadel.url) // UIApplication.shared.open(URLs.beachPadel.url)
} // }
} // }
.tipStyle(tint: nil) // .tipStyle(tint: nil)
} // }
Section { // Section {
TipView(padelBeachImportTip) { action in // TipView(padelBeachImportTip) { action in
if action.id == "more-info-import" { // if action.id == "more-info-import" {
presentImportView = true // presentImportView = true
} // }
} // }
.tipStyle(tint: nil) // .tipStyle(tint: nil)
} // }
} // }
//
if tournament.tournamentCategory == .men && tournament.femalePlayers().isEmpty == false { if tournament.tournamentCategory == .men && unsortedPlayers.filter({ $0.isMalePlayer() == false }).isEmpty == false {
Section { Section {
TipView(inscriptionManagerWomanRankTip) TipView(inscriptionManagerWomanRankTip)
.tipStyle(tint: nil) .tipStyle(tint: nil)
} }
} }
//
Section { // Section {
TipView(slideToDeleteTip) // TipView(slideToDeleteTip)
.tipStyle(tint: nil) // .tipStyle(tint: nil)
} // }
} }
private func _searchSource() -> String? { private func _searchSource() -> String? {
@ -777,6 +855,9 @@ struct InscriptionManagerView: View {
createdPlayers.removeAll() createdPlayers.removeAll()
createdPlayerIds.removeAll() createdPlayerIds.removeAll()
pasteString = nil pasteString = nil
_clearScreen()
_getTeams()
} }
private func _updateTeam() { private func _updateTeam() {
@ -797,6 +878,8 @@ struct InscriptionManagerView: View {
createdPlayerIds.removeAll() createdPlayerIds.removeAll()
pasteString = nil pasteString = nil
self.editedTeam = nil self.editedTeam = nil
_clearScreen()
_getTeams()
} }
private func _buildingTeamView() -> some View { private func _buildingTeamView() -> some View {
@ -999,14 +1082,19 @@ struct InscriptionManagerView: View {
Toggle(isOn: .init(get: { Toggle(isOn: .init(get: {
return team.wildCardBracket return team.wildCardBracket
}, set: { value in }, set: { value in
team.resetPositions() _clearScreen()
team.wildCardGroupStage = false
team.walkOut = false Task {
team.wildCardBracket = value team.resetPositions()
do { team.wildCardGroupStage = false
try dataStore.teamRegistrations.addOrUpdate(instance: team) team.walkOut = false
} catch { team.wildCardBracket = value
Logger.error(error) do {
try dataStore.teamRegistrations.addOrUpdate(instance: team)
} catch {
Logger.error(error)
}
_getTeams()
} }
})) { })) {
Label("Wildcard Tableau", systemImage: team.wildCardBracket ? "circle.inset.filled" : "circle") Label("Wildcard Tableau", systemImage: team.wildCardBracket ? "circle.inset.filled" : "circle")
@ -1015,14 +1103,19 @@ struct InscriptionManagerView: View {
Toggle(isOn: .init(get: { Toggle(isOn: .init(get: {
return team.wildCardGroupStage return team.wildCardGroupStage
}, set: { value in }, set: { value in
team.resetPositions() _clearScreen()
team.wildCardBracket = false
team.walkOut = false Task {
team.wildCardGroupStage = value team.resetPositions()
do { team.wildCardBracket = false
try dataStore.teamRegistrations.addOrUpdate(instance: team) team.walkOut = false
} catch { team.wildCardGroupStage = value
Logger.error(error) do {
try dataStore.teamRegistrations.addOrUpdate(instance: team)
} catch {
Logger.error(error)
}
_getTeams()
} }
})) { })) {
Label("Wildcard Poule", systemImage: team.wildCardGroupStage ? "circle.inset.filled" : "circle") Label("Wildcard Poule", systemImage: team.wildCardGroupStage ? "circle.inset.filled" : "circle")
@ -1032,24 +1125,32 @@ struct InscriptionManagerView: View {
Toggle(isOn: .init(get: { Toggle(isOn: .init(get: {
return team.walkOut return team.walkOut
}, set: { value in }, set: { value in
team.resetPositions() _clearScreen()
team.wildCardBracket = false Task {
team.wildCardGroupStage = false team.resetPositions()
team.walkOut = value team.wildCardBracket = false
do { team.wildCardGroupStage = false
try dataStore.teamRegistrations.addOrUpdate(instance: team) team.walkOut = value
} catch { do {
Logger.error(error) try dataStore.teamRegistrations.addOrUpdate(instance: team)
} catch {
Logger.error(error)
}
_getTeams()
} }
})) { })) {
Label("WO", systemImage: team.walkOut ? "circle.inset.filled" : "circle") Label("WO", systemImage: team.walkOut ? "circle.inset.filled" : "circle")
} }
Divider() Divider()
Button(role: .destructive) { Button(role: .destructive) {
do { _clearScreen()
try dataStore.teamRegistrations.delete(instance: team) Task {
} catch { do {
Logger.error(error) try dataStore.teamRegistrations.delete(instance: team)
} catch {
Logger.error(error)
}
_getTeams()
} }
} label: { } label: {
LabelDelete() LabelDelete()

@ -9,6 +9,11 @@ import SwiftUI
struct TournamentBuildView: View { struct TournamentBuildView: View {
var tournament: Tournament var tournament: Tournament
@State private var bracketStatus: String?
@State private var groupStageStatus: String?
@State private var callStatus: Tournament.TournamentStatus?
@State private var scheduleStatus: Tournament.TournamentStatus?
@State private var cashierStatus: Tournament.TournamentStatus?
@ViewBuilder @ViewBuilder
var body: some View { var body: some View {
@ -24,8 +29,12 @@ struct TournamentBuildView: View {
if tournament.groupStageCount > 0 { if tournament.groupStageCount > 0 {
NavigationLink(value: Screen.groupStage) { NavigationLink(value: Screen.groupStage) {
LabeledContent { LabeledContent {
Text(tournament.groupStageStatus()) if let groupStageStatus {
.multilineTextAlignment(.trailing) Text(groupStageStatus)
.multilineTextAlignment(.trailing)
} else {
ProgressView()
}
} label: { } label: {
Text("Poules") Text("Poules")
if tournament.shouldVerifyGroupStage { if tournament.shouldVerifyGroupStage {
@ -33,13 +42,20 @@ struct TournamentBuildView: View {
} }
} }
} }
.task {
groupStageStatus = await tournament.groupStageStatus()
}
} }
if tournament.rounds().isEmpty == false { if tournament.rounds().isEmpty == false {
NavigationLink(value: Screen.round) { NavigationLink(value: Screen.round) {
LabeledContent { LabeledContent {
Text(tournament.bracketStatus()) if let bracketStatus {
.multilineTextAlignment(.trailing) Text(bracketStatus)
.multilineTextAlignment(.trailing)
} else {
ProgressView()
}
} label: { } label: {
Text("Tableau") Text("Tableau")
if tournament.shouldVerifyBracket { if tournament.shouldVerifyBracket {
@ -47,42 +63,72 @@ struct TournamentBuildView: View {
} }
} }
} }
.task {
bracketStatus = await tournament.bracketStatus()
}
} }
} }
Section { Section {
if tournament.state() != .finished { if tournament.state() != .finished {
NavigationLink(value: Screen.schedule) { NavigationLink(value: Screen.schedule) {
let tournamentStatus = tournament.scheduleStatus() let tournamentStatus = scheduleStatus
LabeledContent { LabeledContent {
Text(tournamentStatus.completion) if let tournamentStatus {
Text(tournamentStatus.completion)
} else {
ProgressView()
}
} label: { } label: {
Text("Horaires") Text("Horaires")
Text(tournamentStatus.label) if let tournamentStatus {
Text(tournamentStatus.label)
}
} }
} }
.task {
scheduleStatus = await tournament.scheduleStatus()
}
NavigationLink(value: Screen.call) { NavigationLink(value: Screen.call) {
let tournamentStatus = tournament.callStatus() let tournamentStatus = callStatus
LabeledContent { LabeledContent {
Text(tournamentStatus.completion) if let tournamentStatus {
Text(tournamentStatus.completion)
} else {
ProgressView()
}
} label: { } label: {
Text("Convocations") Text("Convocations")
Text(tournamentStatus.label) if let tournamentStatus {
Text(tournamentStatus.label)
}
} }
} }
.task {
callStatus = await tournament.callStatus()
}
} }
if tournament.state() == .running || tournament.state() == .finished { if tournament.state() == .running || tournament.state() == .finished {
NavigationLink(value: Screen.cashier) { NavigationLink(value: Screen.cashier) {
let tournamentStatus = tournament.cashierStatus() let tournamentStatus = cashierStatus
LabeledContent { LabeledContent {
Text(tournamentStatus.completion) if let tournamentStatus {
Text(tournamentStatus.completion)
} else {
ProgressView()
}
} label: { } label: {
Text("Encaissement") Text("Encaissement")
Text(tournamentStatus.label) if let tournamentStatus {
Text(tournamentStatus.label)
}
} }
} }
.task {
cashierStatus = await tournament.cashierStatus()
}
} }
} }
} }

Loading…
Cancel
Save