diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 4cb3d70..864564a 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -153,6 +153,7 @@ FF5DA1932BB9279B00A33061 /* RoundSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA1922BB9279B00A33061 /* RoundSettingsView.swift */; }; FF5DA1952BB927E800A33061 /* GenericDestinationPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA1942BB927E800A33061 /* GenericDestinationPickerView.swift */; }; FF5DA19B2BB9662200A33061 /* TournamentSeedEditing.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA19A2BB9662200A33061 /* TournamentSeedEditing.swift */; }; + FF6087EA2BE25EF1004E1E47 /* TournamentStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6087E92BE25EF1004E1E47 /* TournamentStatusView.swift */; }; FF663FBE2BE019EC0031AE83 /* TournamentFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF663FBD2BE019EC0031AE83 /* TournamentFilterView.swift */; }; FF6EC8F72B94773200EA7F5A /* RowButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8F62B94773100EA7F5A /* RowButtonView.swift */; }; FF6EC8FB2B94788600EA7F5A /* TournamentButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8FA2B94788600EA7F5A /* TournamentButtonView.swift */; }; @@ -444,6 +445,7 @@ FF5DA1922BB9279B00A33061 /* RoundSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundSettingsView.swift; sourceTree = ""; }; FF5DA1942BB927E800A33061 /* GenericDestinationPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericDestinationPickerView.swift; sourceTree = ""; }; FF5DA19A2BB9662200A33061 /* TournamentSeedEditing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentSeedEditing.swift; sourceTree = ""; }; + FF6087E92BE25EF1004E1E47 /* TournamentStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentStatusView.swift; sourceTree = ""; }; FF663FBD2BE019EC0031AE83 /* TournamentFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentFilterView.swift; sourceTree = ""; }; FF6EC8F62B94773100EA7F5A /* RowButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RowButtonView.swift; sourceTree = ""; }; FF6EC8FA2B94788600EA7F5A /* TournamentButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentButtonView.swift; sourceTree = ""; }; @@ -1062,6 +1064,7 @@ FF025AE02BD0EB9000A86CF8 /* TournamentClubSettingsView.swift */, FF025AE22BD0EBA900A86CF8 /* TournamentMatchFormatsSettingsView.swift */, FF025AE42BD0EBB800A86CF8 /* TournamentGeneralSettingsView.swift */, + FF6087E92BE25EF1004E1E47 /* TournamentStatusView.swift */, ); path = Components; sourceTree = ""; @@ -1497,6 +1500,7 @@ FFC1E10C2BAC7FB0008D6F59 /* ClubImportView.swift in Sources */, FF3B60A32BC49BBC008C2E66 /* MatchScheduler.swift in Sources */, FF11627A2BCF8109000C4809 /* CallMessageCustomizationView.swift in Sources */, + FF6087EA2BE25EF1004E1E47 /* TournamentStatusView.swift in Sources */, FF025ADB2BD0C2D000A86CF8 /* MatchTeamDetailView.swift in Sources */, FF5DA1952BB927E800A33061 /* GenericDestinationPickerView.swift in Sources */, FFF116E12BD2A9B600A33B06 /* DateInterval.swift in Sources */, diff --git a/PadelClub/Data/GroupStage.swift b/PadelClub/Data/GroupStage.swift index d23ab3d..5db8453 100644 --- a/PadelClub/Data/GroupStage.swift +++ b/PadelClub/Data/GroupStage.swift @@ -98,7 +98,7 @@ class GroupStage: ModelObject, Storable { if hasEnded(), let tournament = tournamentObject() { do { let teams = teams(true) - for (index, team) in teams { + for (index, team) in teams.enumerated() { team.qualified = index < tournament.qualifiedPerGroupStage if team.bracketPosition != nil && team.qualified == false { team.bracketPosition = nil @@ -111,8 +111,8 @@ class GroupStage: ModelObject, Storable { } } - func scoreLabel(forGroupStagePosition groupStagePosition: Int) -> (wins: String, losses: String, setsDifference: String?, gamesDifference: String?)? { - if let scoreData = _score(forGroupStagePosition: groupStagePosition, nilIfEmpty: true) { + func scoreLabel(forGroupStagePosition groupStagePosition: Int, score: TeamGroupStageScore? = nil) -> (wins: String, losses: String, setsDifference: String?, gamesDifference: String?)? { + if let scoreData = (score ?? _score(forGroupStagePosition: groupStagePosition, nilIfEmpty: true)) { let hideSetDifference = matchFormat.setsToWin == 1 let setDifference = scoreData.setDifference.formatted(.number.sign(strategy: .always(includingZero: false))) let gameDifference = scoreData.gameDifference.formatted(.number.sign(strategy: .always(includingZero: false))) @@ -123,7 +123,7 @@ class GroupStage: ModelObject, Storable { } } - fileprivate func _score(forGroupStagePosition groupStagePosition: Int, nilIfEmpty: Bool = false) -> TeamGroupStageScore? { + func _score(forGroupStagePosition groupStagePosition: Int, nilIfEmpty: Bool = false) -> TeamGroupStageScore? { guard let team = teamAt(groupStagePosition: groupStagePosition) else { return nil } let matches = matches(forGroupStagePosition: groupStagePosition).filter({ $0.hasEnded() }) if matches.isEmpty && nilIfEmpty { return nil } @@ -225,8 +225,9 @@ class GroupStage: ModelObject, Storable { } fileprivate typealias TeamScoreAreInIncreasingOrder = (TeamGroupStageScore, TeamGroupStageScore) -> Bool - fileprivate typealias TeamGroupStageScore = (team: TeamRegistration, wins: Int, loses: Int, setDifference: Int, gameDifference: Int) - + + typealias TeamGroupStageScore = (team: TeamRegistration, wins: Int, loses: Int, setDifference: Int, gameDifference: Int) + fileprivate func _headToHead(_ teamPosition: TeamRegistration, _ otherTeam: TeamRegistration) -> Bool { let indexes = [teamPosition, otherTeam].compactMap({ $0.groupStagePosition }).sorted() let combos = Array((0.. [TeamRegistration] { + func teams(_ sortedByScore: Bool = false, scores: [TeamGroupStageScore]? = nil) -> [TeamRegistration] { if sortedByScore { - return unsortedTeams().compactMap({ _score(forGroupStagePosition: $0.groupStagePosition!) }).sorted { (lhs, rhs) in + return unsortedTeams().compactMap({ team in + scores?.first(where: { $0.team.id == team.id }) ?? _score(forGroupStagePosition: team.groupStagePosition!) + }).sorted { (lhs, rhs) in let predicates: [TeamScoreAreInIncreasingOrder] = [ { $0.wins < $1.wins }, { $0.setDifference < $1.setDifference }, diff --git a/PadelClub/Data/MockData.swift b/PadelClub/Data/MockData.swift index 11230c9..13dce73 100644 --- a/PadelClub/Data/MockData.swift +++ b/PadelClub/Data/MockData.swift @@ -45,22 +45,6 @@ extension Tournament { static func mock() -> Tournament { Tournament(groupStageSortMode: .snake, teamSorting: .inscriptionDate, federalCategory: .men, federalLevelCategory: .p100, federalAgeCategory: .senior) } - - static func newEmptyInstance() -> Tournament { - let lastDataSource: String? = DataStore.shared.appSettings.lastDataSource - var _mostRecentDateAvailable: Date? { - guard let lastDataSource else { return nil } - return URL.importDateFormatter.date(from: lastDataSource) - } - - let rankSourceDate = _mostRecentDateAvailable - let tournaments : [Tournament] = DataStore.shared.tournaments.filter { $0.endDate != nil }.sorted(by: \.startDate).reversed() - let tournamentLevel = TournamentLevel.mostUsed(inTournaments: tournaments) - let tournamentCategory = TournamentCategory.mostUsed(inTournaments: tournaments) - let federalTournamentAge = FederalTournamentAge.mostUsed(inTournaments: tournaments) - - return Tournament(groupStageSortMode: .snake, rankSourceDate: rankSourceDate, teamSorting: tournamentLevel.defaultTeamSortingType, federalCategory: tournamentCategory, federalLevelCategory: tournamentLevel, federalAgeCategory: federalTournamentAge) - } } extension Match { diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index ce5d580..5e06663 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -1218,7 +1218,22 @@ extension Tournament: TournamentBuildHolder { } extension Tournament { - + static func newEmptyInstance() -> Tournament { + let lastDataSource: String? = DataStore.shared.appSettings.lastDataSource + var _mostRecentDateAvailable: Date? { + guard let lastDataSource else { return nil } + return URL.importDateFormatter.date(from: lastDataSource) + } + + let rankSourceDate = _mostRecentDateAvailable + let tournaments : [Tournament] = DataStore.shared.tournaments.filter { $0.endDate != nil }.sorted(by: \.startDate).reversed() + let tournamentLevel = TournamentLevel.mostUsed(inTournaments: tournaments) + let tournamentCategory = TournamentCategory.mostUsed(inTournaments: tournaments) + let federalTournamentAge = FederalTournamentAge.mostUsed(inTournaments: tournaments) + + return Tournament(creator: DataStore.shared.user?.id, groupStageSortMode: .snake, rankSourceDate: rankSourceDate, teamSorting: tournamentLevel.defaultTeamSortingType, federalCategory: tournamentCategory, federalLevelCategory: tournamentLevel, federalAgeCategory: federalTournamentAge) + } + static func fake() -> Tournament { return Tournament(event: "Roland Garros", creator: "", name: "Magic P100", startDate: Date(), endDate: Date(), creationDate: Date(), isPrivate: false, groupStageFormat: .nineGames, roundFormat: nil, loserRoundFormat: nil, groupStageSortMode: .snake, groupStageCount: 4, rankSourceDate: nil, dayDuration: 2, teamCount: 24, teamSorting: .rank, federalCategory: .men, federalLevelCategory: .p100, federalAgeCategory: .a45, groupStageCourtCount: nil, seedCount: 8, closedRegistrationDate: nil, groupStageAdditionalQualified: 0, courtCount: 4, prioritizeClubMembers: false, qualifiedPerGroupStage: 2, teamsPerGroupStage: 4, entryFee: nil) } diff --git a/PadelClub/Views/Cashier/CashierView.swift b/PadelClub/Views/Cashier/CashierView.swift index 2660f02..a20c4fe 100644 --- a/PadelClub/Views/Cashier/CashierView.swift +++ b/PadelClub/Views/Cashier/CashierView.swift @@ -17,15 +17,15 @@ struct CashierView: View { @State private var sortOrder: SortOrder = .ascending @State private var searchText = "" @State private var isSearching: Bool = false - - init(event: Event) { - self.tournaments = event.tournaments - self.teams = [] - } - + init(tournament: Tournament, teams: [TeamRegistration]) { self.tournaments = [tournament] self.teams = teams + if teams.filter { $0.callDate != nil }.isEmpty { + _sortOption = .init(wrappedValue: .teamRank) + } else { + _sortOption = .init(wrappedValue: .callDate) + } } private func _sharedData() -> String { @@ -284,7 +284,9 @@ struct CashierView: View { private func _isContentUnavailable() -> Bool { switch sortOption { - case .teamRank, .callDate: + case .callDate: + return teams.filter({ $0.callDate != nil && _shouldDisplayTeam($0) }).isEmpty + case .teamRank: return teams.filter({ _shouldDisplayTeam($0) }).isEmpty default: return teams.flatMap({ $0.players() }).filter({ _shouldDisplayPlayer($0) }).isEmpty diff --git a/PadelClub/Views/GroupStage/GroupStageView.swift b/PadelClub/Views/GroupStage/GroupStageView.swift index ab74b59..8c61b51 100644 --- a/PadelClub/Views/GroupStage/GroupStageView.swift +++ b/PadelClub/Views/GroupStage/GroupStageView.swift @@ -89,12 +89,14 @@ struct GroupStageView: View { @EnvironmentObject var dataStore: DataStore let groupStage: GroupStage let sortByScore: Bool + let scores: [GroupStage.TeamGroupStageScore]? let teams: [TeamRegistration] init(groupStage: GroupStage, sortByScore: Bool) { self.groupStage = groupStage self.sortByScore = sortByScore - self.teams = groupStage.teams(sortByScore) + self.scores = sortByScore ? (0.. TeamRegistration? { @@ -128,7 +130,7 @@ struct GroupStageView: View { } } Spacer() - if let score = groupStage.scoreLabel(forGroupStagePosition: groupStagePosition) { + if let score = groupStage.scoreLabel(forGroupStagePosition: groupStagePosition, score: scores?.first(where: { $0.team.groupStagePosition == groupStagePosition })) { VStack(alignment: .trailing) { HStack(spacing: 0.0) { Text(score.wins) diff --git a/PadelClub/Views/Navigation/Agenda/EventListView.swift b/PadelClub/Views/Navigation/Agenda/EventListView.swift index c8267d3..944f01d 100644 --- a/PadelClub/Views/Navigation/Agenda/EventListView.swift +++ b/PadelClub/Views/Navigation/Agenda/EventListView.swift @@ -100,21 +100,6 @@ struct EventListView: View { NavigationLink(value: tournament) { TournamentCellView(tournament: tournament) } - .swipeActions(edge: .trailing, allowsFullSwipe: true) { - Button(role: .destructive) { - - tournament.isDeleted = true - do { - try dataStore.tournaments.addOrUpdate(instance: tournament) - } catch { - Logger.error(error) - } - -// try? dataStore.tournaments.delete(instance: tournament) - } label: { - LabelDelete() - } - } .contextMenu { Button { diff --git a/PadelClub/Views/Round/RoundSettingsView.swift b/PadelClub/Views/Round/RoundSettingsView.swift index 1b535ec..f57f499 100644 --- a/PadelClub/Views/Round/RoundSettingsView.swift +++ b/PadelClub/Views/Round/RoundSettingsView.swift @@ -14,16 +14,16 @@ struct RoundSettingsView: View { var body: some View { List { - Section { - RowButtonView("Enabled", role: .destructive) { - let allMatches = tournament._allMatchesIncludingDisabled() - allMatches.forEach({ - $0.disabled = false - $0.byeState = false - }) - try? dataStore.matches.addOrUpdate(contentOfs: allMatches) - } - } +// Section { +// RowButtonView("Enabled", role: .destructive) { +// let allMatches = tournament._allMatchesIncludingDisabled() +// allMatches.forEach({ +// $0.disabled = false +// $0.byeState = false +// }) +// try? dataStore.matches.addOrUpdate(contentOfs: allMatches) +// } +// } Section { RowButtonView("Retirer toutes les têtes de séries", role: .destructive) { tournament.unsortedTeams().forEach({ $0.bracketPosition = nil }) diff --git a/PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift b/PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift index 864caa1..56997ce 100644 --- a/PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift +++ b/PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift @@ -17,27 +17,7 @@ struct TournamentGeneralSettingsView: View { var body: some View { @Bindable var tournament = tournament - Form { - Section { - if tournament.hasEnded() == false { - if tournament.isCanceled == false { - RowButtonView("Annuler le tournoi", role: .destructive) { - tournament.isCanceled.toggle() - } - } else { - RowButtonView("Reprendre le tournoi", role: .destructive) { - tournament.isCanceled.toggle() - } - } - } - - Toggle(isOn: $tournament.isPrivate) { - Text("Tournoi privée") - } - } footer: { - Text("Le tournoi sera masqué sur le site padelclub.app") - } - + Form { Section { TournamentDatePickerView() TournamentDurationManagerView() @@ -121,9 +101,6 @@ struct TournamentGeneralSettingsView: View { ]) { _save() } - .onChange(of: [tournament.isDeleted, tournament.isCanceled, tournament.isPrivate]) { - _save() - } } private func _save() { diff --git a/PadelClub/Views/Tournament/Screen/Components/TournamentStatusView.swift b/PadelClub/Views/Tournament/Screen/Components/TournamentStatusView.swift new file mode 100644 index 0000000..31f2fce --- /dev/null +++ b/PadelClub/Views/Tournament/Screen/Components/TournamentStatusView.swift @@ -0,0 +1,66 @@ +// +// TournamentStatusView.swift +// PadelClub +// +// Created by Razmig Sarkissian on 01/05/2024. +// + +import SwiftUI +import LeStorage + +struct TournamentStatusView: View { + @Environment(Tournament.self) private var tournament: Tournament + @EnvironmentObject var dataStore: DataStore + + var body: some View { + @Bindable var tournament = tournament + Form { + Section { + RowButtonView("Supprimer le tournoi", role: .destructive) { + tournament.isDeleted.toggle() + } + } footer: { + Text("todo: expliquer ce que ca fait") + } + Section { + if tournament.hasEnded() == false { + if tournament.isCanceled == false { + RowButtonView("Annuler le tournoi", role: .destructive) { + tournament.isCanceled.toggle() + } + } else { + RowButtonView("Reprendre le tournoi", role: .destructive) { + tournament.isCanceled.toggle() + } + } + } + } footer: { + Text("todo: expliquer ce que ca fait") + } + + Section { + Toggle(isOn: $tournament.isPrivate) { + Text("Tournoi privée") + } + } footer: { + Text("Le tournoi sera masqué sur le site padelclub.app") + } + } + .toolbarBackground(.visible, for: .navigationBar) + .onChange(of: [tournament.isDeleted, tournament.isCanceled, tournament.isPrivate]) { + _save() + } + } + + private func _save() { + do { + try dataStore.tournaments.addOrUpdate(instance: tournament) + } catch { + Logger.error(error) + } + } +} + +#Preview { + TournamentStatusView() +} diff --git a/PadelClub/Views/Tournament/Screen/TournamentCashierView.swift b/PadelClub/Views/Tournament/Screen/TournamentCashierView.swift index 4b1e91f..fffdb15 100644 --- a/PadelClub/Views/Tournament/Screen/TournamentCashierView.swift +++ b/PadelClub/Views/Tournament/Screen/TournamentCashierView.swift @@ -71,6 +71,12 @@ struct TournamentCashierView: View { func allDestinations() -> [CashierDestination] { var allDestinations : [CashierDestination] = [] + let tournamentHasEnded = tournament.hasEnded() + if tournamentHasEnded { + allDestinations.append(.summary) + allDestinations.append(.all(tournament)) + } + let destinations : [CashierDestination] = tournament.groupStages().map { CashierDestination.groupStage($0) } allDestinations.append(contentsOf: destinations) tournament.rounds().forEach { round in @@ -78,8 +84,12 @@ struct TournamentCashierView: View { allDestinations.append(CashierDestination.bracket(round)) } } - allDestinations.append(.all(tournament)) - allDestinations.append(.summary) + + if tournamentHasEnded == false { + allDestinations.append(.all(tournament)) + allDestinations.append(.summary) + } + return allDestinations } diff --git a/PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift b/PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift index a984d14..3cd848e 100644 --- a/PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift +++ b/PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift @@ -8,6 +8,7 @@ import SwiftUI enum TournamentSettings: Identifiable, Selectable { + case status case general case club(Tournament) case matchFormats @@ -16,8 +17,10 @@ enum TournamentSettings: Identifiable, Selectable { func selectionLabel() -> String { switch self { + case .status: + return "Statut" case .matchFormats: - return "Formats de jeu" + return "Formats" case .general: return "Général" case .club: @@ -48,13 +51,15 @@ struct TournamentSettingsView: View { @Environment(Tournament.self) var tournament: Tournament private func destinations() -> [TournamentSettings] { - [.general, .club(tournament), .matchFormats] + [.status, .general, .club(tournament), .matchFormats] } var body: some View { VStack(spacing: 0) { GenericDestinationPickerView(selectedDestination: $selectedDestination, destinations: destinations(), nilDestinationIsValid: false) switch selectedDestination! { + case .status: + TournamentStatusView() case .matchFormats: TournamentMatchFormatsSettingsView() case .general: