From 7b2989d29bb880f313d53f34f8fb7f25a7039ee8 Mon Sep 17 00:00:00 2001 From: Raz Date: Sun, 20 Oct 2024 20:07:45 +0200 Subject: [PATCH] wip --- PadelClub.xcodeproj/project.pbxproj | 8 ++ PadelClub/Data/MatchScheduler.swift | 4 +- PadelClub/Data/PlayerRegistration.swift | 6 +- PadelClub/Data/Round.swift | 13 +- .../Views/Calling/SeedsCallingView.swift | 2 +- PadelClub/Views/Cashier/CashierView.swift | 17 ++- .../Match/Components/MatchDateView.swift | 2 +- PadelClub/Views/Match/MatchDetailView.swift | 8 +- .../Navigation/Ongoing/OngoingView.swift | 4 +- PadelClub/Views/Score/EditScoreView.swift | 4 + PadelClub/Views/Score/FollowUpMatchView.swift | 16 ++- PadelClub/Views/Team/TeamRestingView.swift | 123 ++++++++++++++++++ .../Screen/TournamentCashierView.swift | 31 ++--- .../Tournament/TournamentRunningView.swift | 21 ++- 14 files changed, 217 insertions(+), 42 deletions(-) create mode 100644 PadelClub/Views/Team/TeamRestingView.swift diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 8d78bc8..bec9fd4 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -700,6 +700,9 @@ FF70FBC82C90584900129CC2 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = FF0CA5742BDA4AE10080E843 /* PrivacyInfo.xcprivacy */; }; FF70FBC92C90584900129CC2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C425D4042B6D249E002A7B48 /* Assets.xcassets */; }; FF70FBCB2C90584900129CC2 /* LeStorage.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C49EF0372BDFF3000077B5AA /* LeStorage.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + FF7DCD392CC330270041110C /* TeamRestingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF7DCD382CC330260041110C /* TeamRestingView.swift */; }; + FF7DCD3A2CC330270041110C /* TeamRestingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF7DCD382CC330260041110C /* TeamRestingView.swift */; }; + FF7DCD3B2CC330270041110C /* TeamRestingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF7DCD382CC330260041110C /* TeamRestingView.swift */; }; FF8044AC2C8F676D00A49A52 /* TournamentSubscriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8044AB2C8F676D00A49A52 /* TournamentSubscriptionView.swift */; }; FF82CFC52B911F5B00B0CAF2 /* OrganizedTournamentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF82CFC42B911F5B00B0CAF2 /* OrganizedTournamentView.swift */; }; FF82CFC92B9132AF00B0CAF2 /* ActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF82CFC82B9132AF00B0CAF2 /* ActivityView.swift */; }; @@ -1078,6 +1081,7 @@ FF70916B2B91005400AB08DA /* TournamentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentView.swift; sourceTree = ""; }; FF70916D2B9108C600AB08DA /* InscriptionManagerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InscriptionManagerView.swift; sourceTree = ""; }; FF70FBCF2C90584900129CC2 /* PadelClub TestFlight.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "PadelClub TestFlight.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + FF7DCD382CC330260041110C /* TeamRestingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamRestingView.swift; sourceTree = ""; }; FF8044AB2C8F676D00A49A52 /* TournamentSubscriptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentSubscriptionView.swift; sourceTree = ""; }; FF82CFC42B911F5B00B0CAF2 /* OrganizedTournamentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrganizedTournamentView.swift; sourceTree = ""; }; FF82CFC82B9132AF00B0CAF2 /* ActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityView.swift; sourceTree = ""; }; @@ -1820,6 +1824,7 @@ FF089EB52BB00A3800F0AEC7 /* TeamRowView.swift */, FF1162862BD004AD000C4809 /* EditingTeamView.swift */, FF17CA562CC02FEA003C7323 /* CoachListView.swift */, + FF7DCD382CC330260041110C /* TeamRestingView.swift */, FF025AD62BD0C0FB00A86CF8 /* Components */, ); path = Team; @@ -2304,6 +2309,7 @@ FF5D30562BD95B1100F2B93D /* OngoingView.swift in Sources */, FF1DC5552BAB36DD00FD8220 /* CreateClubView.swift in Sources */, C4607A7D2C04DDE2004CB781 /* APICallsListView.swift in Sources */, + FF7DCD3B2CC330270041110C /* TeamRestingView.swift in Sources */, FFC1E10A2BAC2A77008D6F59 /* NetworkFederalService.swift in Sources */, FF025AEF2BD1AE9400A86CF8 /* DurationSettingsView.swift in Sources */, FF025AED2BD1513700A86CF8 /* AppScreen.swift in Sources */, @@ -2577,6 +2583,7 @@ FF4CBF802C996C0600151637 /* OngoingView.swift in Sources */, FF4CBF812C996C0600151637 /* CreateClubView.swift in Sources */, FF4CBF822C996C0600151637 /* APICallsListView.swift in Sources */, + FF7DCD392CC330270041110C /* TeamRestingView.swift in Sources */, FF4CBF832C996C0600151637 /* NetworkFederalService.swift in Sources */, FF4CBF842C996C0600151637 /* DurationSettingsView.swift in Sources */, FF4CBF852C996C0600151637 /* AppScreen.swift in Sources */, @@ -2829,6 +2836,7 @@ FF70FAFF2C90584900129CC2 /* OngoingView.swift in Sources */, FF70FB002C90584900129CC2 /* CreateClubView.swift in Sources */, FF70FB012C90584900129CC2 /* APICallsListView.swift in Sources */, + FF7DCD3A2CC330270041110C /* TeamRestingView.swift in Sources */, FF70FB022C90584900129CC2 /* NetworkFederalService.swift in Sources */, FF70FB032C90584900129CC2 /* DurationSettingsView.swift in Sources */, FF70FB042C90584900129CC2 /* AppScreen.swift in Sources */, diff --git a/PadelClub/Data/MatchScheduler.swift b/PadelClub/Data/MatchScheduler.swift index 21f0936..38e8f87 100644 --- a/PadelClub/Data/MatchScheduler.swift +++ b/PadelClub/Data/MatchScheduler.swift @@ -660,7 +660,7 @@ final class MatchScheduler : ModelObject, Storable { @discardableResult func updateBracketSchedule(tournament: Tournament, fromRoundId roundId: String?, fromMatchId matchId: String?, startDate: Date) -> Bool { let upperRounds: [Round] = tournament.rounds() - let allMatches: [Match] = tournament.allMatches() + let allMatches: [Match] = tournament.allMatches().filter({ $0.hasEnded() == false }) var rounds = [Round]() @@ -681,7 +681,7 @@ final class MatchScheduler : ModelObject, Storable { } let flattenedMatches = rounds.flatMap { round in - round._matches().filter({ $0.disabled == false }).sorted(by: \.index) + round._matches().filter({ $0.disabled == false && $0.hasEnded() == false }).sorted(by: \.index) } flattenedMatches.forEach({ diff --git a/PadelClub/Data/PlayerRegistration.swift b/PadelClub/Data/PlayerRegistration.swift index f894a4f..0047976 100644 --- a/PadelClub/Data/PlayerRegistration.swift +++ b/PadelClub/Data/PlayerRegistration.swift @@ -155,12 +155,12 @@ final class PlayerRegistration: ModelObject, Storable { } func contains(_ searchField: String) -> Bool { - firstName.localizedCaseInsensitiveContains(searchField) || lastName.localizedCaseInsensitiveContains(searchField) + firstName.canonicalVersion.localizedCaseInsensitiveContains(searchField.canonicalVersion) || lastName.canonicalVersion.localizedCaseInsensitiveContains(searchField.canonicalVersion) } func isSameAs(_ player: PlayerRegistration) -> Bool { - firstName.trimmedMultiline.localizedCaseInsensitiveCompare(player.firstName.trimmedMultiline) == .orderedSame && - lastName.trimmedMultiline.localizedCaseInsensitiveCompare(player.lastName.trimmedMultiline) == .orderedSame + firstName.trimmedMultiline.canonicalVersion.localizedCaseInsensitiveCompare(player.firstName.trimmedMultiline.canonicalVersion) == .orderedSame && + lastName.trimmedMultiline.canonicalVersion.localizedCaseInsensitiveCompare(player.lastName.trimmedMultiline.canonicalVersion) == .orderedSame } func tournament() -> Tournament? { diff --git a/PadelClub/Data/Round.swift b/PadelClub/Data/Round.swift index 0604917..e29e884 100644 --- a/PadelClub/Data/Round.swift +++ b/PadelClub/Data/Round.swift @@ -145,12 +145,21 @@ final class Round: ModelObject, Storable { let initialMatchIndex = RoundRule.matchIndex(fromRoundIndex: index) let numberOfMatches = RoundRule.numberOfMatches(forRoundIndex: index) return self.tournamentStore.teamRegistrations.filter { - $0.tournament == tournament - && $0.bracketPosition != nil + $0.bracketPosition != nil && ($0.bracketPosition! / 2) >= initialMatchIndex && ($0.bracketPosition! / 2) < initialMatchIndex + numberOfMatches } } + + func teamsOrSeeds() -> [TeamRegistration] { + let seeds = seeds() + if seeds.isEmpty { + return playedMatches().flatMap({ $0.teams() }) + } else { + return seeds + } + } + func losers() -> [TeamRegistration] { let teamIds: [String] = self._matches().compactMap { $0.losingTeamId } diff --git a/PadelClub/Views/Calling/SeedsCallingView.swift b/PadelClub/Views/Calling/SeedsCallingView.swift index e7c015d..c1b899e 100644 --- a/PadelClub/Views/Calling/SeedsCallingView.swift +++ b/PadelClub/Views/Calling/SeedsCallingView.swift @@ -28,7 +28,7 @@ struct SeedsCallingView: View { PlayersWithoutContactView(players: tournament.seededTeams().flatMap({ $0.unsortedPlayers() }).sorted(by: \.computedRank)) ForEach(tournamentRounds) { round in - let seeds = round.seeds() + let seeds = round.teamsOrSeeds() let callSeeds = seeds.filter({ tournament.isStartDateIsDifferentThanCallDate($0) == false }) if seeds.isEmpty == false { Section { diff --git a/PadelClub/Views/Cashier/CashierView.swift b/PadelClub/Views/Cashier/CashierView.swift index 34cf916..9189735 100644 --- a/PadelClub/Views/Cashier/CashierView.swift +++ b/PadelClub/Views/Cashier/CashierView.swift @@ -56,7 +56,7 @@ extension Array { class CashierViewModel: ObservableObject { let id: UUID = UUID() @Published var sortOption: SortOption = .callDate - @Published var filterOption: FilterOption = .all + @Published var filterOption: FilterOption = .didNotPay @Published var presenceFilterOption: PresenceFilterOption = .all @Published var sortOrder: SortOrder = .ascending @Published var searchText: String = "" @@ -306,8 +306,7 @@ struct CashierView: View { case .alphabeticalLastName, .alphabeticalFirstName, .playerRank, .age: PlayerCashierView(players: filteredPlayers, displayTournamentTitle: tournaments.count > 1, editingOptions: _editingOptions()) case .callDate: - let _teams = teams.filter({ $0.callDate != nil }) - TeamCallDateView(teams: _teams, displayTournamentTitle: tournaments.count > 1, editingOptions: _editingOptions()) + TeamCallDateView(teams: teams, displayTournamentTitle: tournaments.count > 1, editingOptions: _editingOptions()) } } .onAppear { @@ -354,7 +353,7 @@ struct CashierView: View { } } footer: { if let teamCallDate = player.team()?.callDate { - Text("équipe convoqué") + Text(teamCallDate.localizedDate()) + Text("équipe convoquée ") + Text(teamCallDate.localizedDate()) } } } @@ -369,8 +368,8 @@ struct CashierView: View { var body: some View { ForEach(teams) { team in - let players = team.players().filter({ cashierViewModel._shouldDisplayPlayer($0) }) - if players.isEmpty == false { + let players = team.players() + if players.isEmpty == false, cashierViewModel._shouldDisplayTeam(team) { Section { ForEach(players) { player in EditablePlayerView(player: player, editingOptions: editingOptions) @@ -404,15 +403,15 @@ struct CashierView: View { var body: some View { let groupedTeams = Dictionary(grouping: teams) { team in - team.callDate + team.callDate ?? .distantPast } let keys = cashierViewModel.sortOrder == .ascending ? groupedTeams.keys.compactMap { $0 }.sorted() : groupedTeams.keys.compactMap { $0 }.sorted().reversed() ForEach(keys, id: \.self) { key in if let _teams = groupedTeams[key] { ForEach(_teams) { team in - let players = team.players().filter({ cashierViewModel._shouldDisplayPlayer($0) }) - if players.isEmpty == false { + let players = team.players() + if players.isEmpty == false, cashierViewModel._shouldDisplayTeam(team) { Section { ForEach(players) { player in EditablePlayerView(player: player, editingOptions: editingOptions) diff --git a/PadelClub/Views/Match/Components/MatchDateView.swift b/PadelClub/Views/Match/Components/MatchDateView.swift index ab962ed..d71d326 100644 --- a/PadelClub/Views/Match/Components/MatchDateView.swift +++ b/PadelClub/Views/Match/Components/MatchDateView.swift @@ -35,7 +35,7 @@ struct MatchDateView: View { } else { Menu { let estimatedDuration = match.getDuration() - if isReady && match.hasStarted() == false { + if isReady { Section { Button("Démarrer maintenant") { if let updatedField { diff --git a/PadelClub/Views/Match/MatchDetailView.swift b/PadelClub/Views/Match/MatchDetailView.swift index 0071351..0548811 100644 --- a/PadelClub/Views/Match/MatchDetailView.swift +++ b/PadelClub/Views/Match/MatchDetailView.swift @@ -437,9 +437,11 @@ struct MatchDetailView: View { Text("Partage sur les réseaux sociaux") } - Section { - RowButtonView("Match à suivre") { - presentFollowUpMatch = true + if match.currentTournament()?.hasEnded() == false { + Section { + RowButtonView("Match à suivre") { + presentFollowUpMatch = true + } } } } diff --git a/PadelClub/Views/Navigation/Ongoing/OngoingView.swift b/PadelClub/Views/Navigation/Ongoing/OngoingView.swift index 3c41cbb..226b052 100644 --- a/PadelClub/Views/Navigation/Ongoing/OngoingView.swift +++ b/PadelClub/Views/Navigation/Ongoing/OngoingView.swift @@ -70,8 +70,8 @@ struct OngoingView: View { .toolbar { ToolbarItem(placement: .status) { Picker(selection: $sortByField) { - Text("tri par date").tag(true) - Text("tri par terrain").tag(false) + Text("tri par date").tag(false) + Text("tri par terrain").tag(true) } label: { } diff --git a/PadelClub/Views/Score/EditScoreView.swift b/PadelClub/Views/Score/EditScoreView.swift index 6728c0d..3110e4f 100644 --- a/PadelClub/Views/Score/EditScoreView.swift +++ b/PadelClub/Views/Score/EditScoreView.swift @@ -102,6 +102,10 @@ struct EditScoreView: View { } } .listRowView(isActive: teamTwoSetupIsActive, color: colorTeamTwo, hideColorVariation: false, alignment: .trailing) + } header: { + if let roundTitle = matchDescriptor.match?.roundAndMatchTitle() { + Text(roundTitle) + } } footer: { HStack { Menu { diff --git a/PadelClub/Views/Score/FollowUpMatchView.swift b/PadelClub/Views/Score/FollowUpMatchView.swift index 994d723..5eea9a2 100644 --- a/PadelClub/Views/Score/FollowUpMatchView.swift +++ b/PadelClub/Views/Score/FollowUpMatchView.swift @@ -65,6 +65,20 @@ struct FollowUpMatchView: View { match.loser() } + var sortingModeCases: [SortingMode] { + var sortingModes = [SortingMode]() + if let winningTeam { + sortingModes.append(.winner) + } + if let losingTeam { + sortingModes.append(.loser) + } + sortingModes.append(.index) + sortingModes.append(.restingTime) + sortingModes.append(.court) + return sortingModes + } + func contentUnavailableDescriptionLabel() -> String { switch sortingMode { case .winner: @@ -155,7 +169,7 @@ struct FollowUpMatchView: View { } } header: { Picker(selection: $sortingMode) { - ForEach(SortingMode.allCases) { sortingMode in + ForEach(sortingModeCases) { sortingMode in Text(sortingMode.localizedSortingModeLabel()).tag(sortingMode) } } label: { diff --git a/PadelClub/Views/Team/TeamRestingView.swift b/PadelClub/Views/Team/TeamRestingView.swift new file mode 100644 index 0000000..d576132 --- /dev/null +++ b/PadelClub/Views/Team/TeamRestingView.swift @@ -0,0 +1,123 @@ +// +// TeamRestingView.swift +// PadelClub +// +// Created by razmig on 19/10/2024. +// + +import SwiftUI + +struct TeamRestingView: View { + @Environment(Tournament.self) var tournament + + let readyMatches: [Match] + let matchesLeft: [Match] + + @State private var sortingMode: SortingMode = .restingTime + @State private var selectedCourt: Int? + + enum SortingMode: Int, Identifiable, CaseIterable { + var id: Int { self.rawValue } + case index + case restingTime + case court + + func localizedSortingModeLabel() -> String { + switch self { + case .index: + return "Ordre" + case .court: + return "Terrain" + case .restingTime: + return "Repos" + } + } + } + + var sortingModeCases: [SortingMode] { + var sortingModes = [SortingMode]() + sortingModes.append(.index) + sortingModes.append(.restingTime) + sortingModes.append(.court) + return sortingModes + } + + func contentUnavailableDescriptionLabel() -> String { + switch sortingMode { + case .index: + return "Ce tournoi n'a aucun match prêt à démarrer" + case .restingTime: + return "Ce tournoi n'a aucun match prêt à démarrer" + case .court: + return "Ce tournoi n'a aucun match prêt à démarrer" + } + } + + var sortedMatches: [Match] { + switch sortingMode { + case .index: + return readyMatches + case .restingTime: + return readyMatches.sorted(by: \.restingTimeForSorting) + case .court: + return readyMatches.sorted(using: [.keyPath(\.courtIndexForSorting), .keyPath(\.restingTimeForSorting)], order: .ascending) + } + } + + var body: some View { + NavigationStack { + List { + Section { + Picker(selection: $selectedCourt) { + Text("Aucun").tag(nil as Int?) + ForEach(0..