diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index fc1c4ba..2f7369e 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -136,6 +136,7 @@ FF59FFB32B90EFAC0061EFF9 /* EventListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF59FFB22B90EFAC0061EFF9 /* EventListView.swift */; }; FF59FFB72B90EFBF0061EFF9 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF59FFB62B90EFBF0061EFF9 /* MainView.swift */; }; FF59FFB92B90EFD70061EFF9 /* ToolboxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF59FFB82B90EFD70061EFF9 /* ToolboxView.swift */; }; + FF5BAF6E2BE0B3C8008B4B7E /* FederalDataViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5BAF6D2BE0B3C8008B4B7E /* FederalDataViewModel.swift */; }; FF5D0D722BB3EFA5005CB568 /* LearnMoreSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D6F2BB3EFA5005CB568 /* LearnMoreSheetView.swift */; }; FF5D0D742BB41DF8005CB568 /* Color+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D732BB41DF8005CB568 /* Color+Extensions.swift */; }; FF5D0D762BB428B2005CB568 /* ListRowViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D752BB428B2005CB568 /* ListRowViewModifier.swift */; }; @@ -151,6 +152,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 */; }; + 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 */; }; FF6EC8FE2B94792300EA7F5A /* Screen.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8FD2B94792300EA7F5A /* Screen.swift */; }; @@ -424,6 +426,7 @@ FF59FFB22B90EFAC0061EFF9 /* EventListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventListView.swift; sourceTree = ""; }; FF59FFB62B90EFBF0061EFF9 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = ""; }; FF59FFB82B90EFD70061EFF9 /* ToolboxView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolboxView.swift; sourceTree = ""; }; + FF5BAF6D2BE0B3C8008B4B7E /* FederalDataViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FederalDataViewModel.swift; sourceTree = ""; }; FF5D0D6F2BB3EFA5005CB568 /* LearnMoreSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LearnMoreSheetView.swift; sourceTree = ""; }; FF5D0D732BB41DF8005CB568 /* Color+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Extensions.swift"; sourceTree = ""; }; FF5D0D752BB428B2005CB568 /* ListRowViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRowViewModifier.swift; sourceTree = ""; }; @@ -439,6 +442,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 = ""; }; + 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 = ""; }; FF6EC8FD2B94792300EA7F5A /* Screen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Screen.swift; sourceTree = ""; }; @@ -974,6 +978,7 @@ FFCFC01B2BBC5AAA00B82851 /* SetDescriptor.swift */, FFBF065F2BBD9F6D009D6715 /* NavigationViewModel.swift */, FF3B60A22BC49BBC008C2E66 /* MatchScheduler.swift */, + FF5BAF6D2BE0B3C8008B4B7E /* FederalDataViewModel.swift */, ); path = ViewModel; sourceTree = ""; @@ -995,6 +1000,7 @@ FF4AB6BE2B92577A0002987F /* ImportedPlayerView.swift */, FF5D0D6F2BB3EFA5005CB568 /* LearnMoreSheetView.swift */, FFCFC0192BBC5A8500B82851 /* MatchTypeSmallSelectionView.swift */, + FF663FBD2BE019EC0031AE83 /* TournamentFilterView.swift */, ); path = Shared; sourceTree = ""; @@ -1495,6 +1501,7 @@ C45BAE442BCA753E002EEC8A /* Purchase.swift in Sources */, FF6EC8FE2B94792300EA7F5A /* Screen.swift in Sources */, FF967CEE2BAECBD700A9A3BD /* Round.swift in Sources */, + FF5BAF6E2BE0B3C8008B4B7E /* FederalDataViewModel.swift in Sources */, FF3F74FF2B91A2D4004CFE0E /* AgendaDestination.swift in Sources */, FF3795622B9396D0004EA093 /* PadelClubApp.xcdatamodeld in Sources */, FFCFC0162BBC5A4C00B82851 /* SetInputView.swift in Sources */, @@ -1556,6 +1563,7 @@ FF1DC5532BAB354A00FD8220 /* MockData.swift in Sources */, FF967D092BAF3D4000A9A3BD /* TeamDetailView.swift in Sources */, FF5DA18F2BB9268800A33061 /* GroupStageSettingsView.swift in Sources */, + FF663FBE2BE019EC0031AE83 /* TournamentFilterView.swift in Sources */, FF8F26382BAD523300650388 /* PadelRule.swift in Sources */, FF967CF42BAECC0B00A9A3BD /* TeamRegistration.swift in Sources */, FFF8ACDB2B923F48008466FA /* Date+Extensions.swift in Sources */, diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index eaa172f..089d4e5 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -959,7 +959,7 @@ class Tournament : ModelObject, Storable { var groupStageOrderingMode: GroupStageOrderingMode { get { - groupStageSortMode ?? .random + groupStageSortMode } set { groupStageSortMode = newValue @@ -968,7 +968,7 @@ class Tournament : ModelObject, Storable { var tournamentCategory: TournamentCategory { get { - federalCategory ?? .men + federalCategory } set { if federalCategory != newValue { @@ -982,7 +982,7 @@ class Tournament : ModelObject, Storable { var tournamentLevel: TournamentLevel { get { - federalLevelCategory ?? .p100 + federalLevelCategory } set { federalLevelCategory = newValue @@ -996,7 +996,7 @@ class Tournament : ModelObject, Storable { var federalTournamentAge: FederalTournamentAge { get { - federalAgeCategory ?? .senior + federalAgeCategory } set { federalAgeCategory = newValue @@ -1148,6 +1148,10 @@ extension Tournament: FederalTournamentHolder { } extension Tournament: TournamentBuildHolder { + func buildHolderTitle() -> String { + tournamentTitle() + } + var category: TournamentCategory { tournamentCategory } diff --git a/PadelClub/Manager/Network/NetworkFederalService.swift b/PadelClub/Manager/Network/NetworkFederalService.swift index 1a3be1b..666b049 100644 --- a/PadelClub/Manager/Network/NetworkFederalService.swift +++ b/PadelClub/Manager/Network/NetworkFederalService.swift @@ -92,7 +92,7 @@ class NetworkFederalService { } - func getClubFederalTournaments(page: Int, tournaments: [FederalTournament], club: String, codeClub: String, startDate: Date? = nil) async throws -> [FederalTournament] { + func getClubFederalTournaments(page: Int, tournaments: [FederalTournament], club: String, codeClub: String, startDate: Date? = nil, endDate: Date? = nil) async throws -> [FederalTournament] { if formId.isEmpty { do { @@ -103,8 +103,10 @@ class NetworkFederalService { } var dateComponent = "" - if let startDate { - dateComponent = "&date[start]=\(startDate.twoDigitsYearFormatted)" + if let startDate, let endDate { + dateComponent = "&date[start]=\(startDate.twoDigitsYearFormatted)&date[end]=\(endDate.endOfMonth.twoDigitsYearFormatted)" + } else if let startDate { + dateComponent = "&date[start]=\(startDate.twoDigitsYearFormatted)&date[end]=\(Calendar.current.date(byAdding: .month, value: 3, to: startDate)!.endOfMonth.twoDigitsYearFormatted)" } let parameters = """ diff --git a/PadelClub/Manager/PadelRule.swift b/PadelClub/Manager/PadelRule.swift index aeeae15..11118db 100644 --- a/PadelClub/Manager/PadelRule.swift +++ b/PadelClub/Manager/PadelRule.swift @@ -29,6 +29,7 @@ protocol TournamentBuildHolder: Identifiable { var category: TournamentCategory { get } var level: TournamentLevel { get } var age: FederalTournamentAge { get } + func buildHolderTitle() -> String } struct TournamentBuild: TournamentBuildHolder, Hashable, Codable, Identifiable { @@ -40,6 +41,10 @@ struct TournamentBuild: TournamentBuildHolder, Hashable, Codable, Identifiable { // var japFirstName: String? = nil // var japLastName: String? = nil + func buildHolderTitle() -> String { + localizedLabel() + } + var identifier: String { level.localizedLabel()+":"+category.localizedLabel()+":"+age.localizedLabel() } diff --git a/PadelClub/ViewModel/AgendaDestination.swift b/PadelClub/ViewModel/AgendaDestination.swift index 4070cc8..1531906 100644 --- a/PadelClub/ViewModel/AgendaDestination.swift +++ b/PadelClub/ViewModel/AgendaDestination.swift @@ -48,15 +48,26 @@ enum AgendaDestination: CaseIterable, Identifiable, Selectable { func badgeValue() -> Int? { switch self { case .activity: - DataStore.shared.tournaments.filter { $0.endDate == nil }.count + DataStore.shared.tournaments.filter { $0.endDate == nil && FederalDataViewModel.shared.isTournamentValidForFilters($0) }.count case .history: - DataStore.shared.tournaments.filter { $0.endDate != nil }.count + DataStore.shared.tournaments.filter { $0.endDate != nil && FederalDataViewModel.shared.isTournamentValidForFilters($0) }.count case .tenup: - nil + FederalDataViewModel.shared.filteredFederalTournaments.count } } func badgeImage() -> Badge? { - nil + switch self { + case .activity: + return nil + case .history: + return nil + case .tenup: + if FederalDataViewModel.shared.federalTournaments.isEmpty { + return .custom(systemName: "exclamationmark.circle.fill", color: .yellow) + } else { + return nil + } + } } } diff --git a/PadelClub/ViewModel/FederalDataViewModel.swift b/PadelClub/ViewModel/FederalDataViewModel.swift new file mode 100644 index 0000000..30e0fc2 --- /dev/null +++ b/PadelClub/ViewModel/FederalDataViewModel.swift @@ -0,0 +1,104 @@ +// +// FederalDataViewModel.swift +// PadelClub +// +// Created by Razmig Sarkissian on 30/04/2024. +// + +import SwiftUI +import LeStorage + +@Observable +class FederalDataViewModel { + static let shared = FederalDataViewModel() + + var federalTournaments: [FederalTournament] = [] + var levels: Set = Set() + var categories: Set = Set() + var ageCategories: Set = Set() + var selectedClubs: Set = Set() + var id: UUID = UUID() + + func filterStatus() -> String { + var labels: [String] = [] + labels.append(contentsOf: levels.map { $0.localizedLabel() }) + labels.append(contentsOf: categories.map { $0.localizedLabel() }) + labels.append(contentsOf: ageCategories.map { $0.localizedLabel() }) + let clubNames = selectedClubs.compactMap { codeClub in + let club: Club? = Store.main.filter(isIncluded: { $0.code == codeClub }).first + return club?.clubTitle(.short) + } + + labels.append(contentsOf: clubNames) + return labels.joined(separator: ", ") + } + + func selectedClub() -> Club? { + if selectedClubs.isEmpty == false { + return Store.main.filter(isIncluded: { $0.code == selectedClubs.first! }).first + } else { + return nil + } + } + + func removeFilters() { + levels.removeAll() + categories.removeAll() + ageCategories.removeAll() + selectedClubs.removeAll() + id = UUID() + } + + func areFiltersEnabled() -> Bool { + (levels.isEmpty && categories.isEmpty && ageCategories.isEmpty && selectedClubs.isEmpty) == false + } + + var filteredFederalTournaments: [FederalTournament] { + federalTournaments.filter({ tournament in + (levels.isEmpty || tournament.tournaments.anySatisfy({ levels.contains($0.level) })) + && + (categories.isEmpty || tournament.tournaments.anySatisfy({ categories.contains($0.category) })) + && + (ageCategories.isEmpty || tournament.tournaments.anySatisfy({ ageCategories.contains($0.age) })) + && + (selectedClubs.isEmpty || selectedClubs.contains(tournament.codeClub!)) + }) + } + + func isTournamentValidForFilters(_ tournament: Tournament) -> Bool { + let firstPart = (levels.isEmpty || levels.contains(tournament.level)) + && + (categories.isEmpty || categories.contains(tournament.category)) + && + (ageCategories.isEmpty || ageCategories.contains(tournament.age)) + + if let codeClub = tournament.club()?.code { + return firstPart && (selectedClubs.isEmpty || selectedClubs.contains(codeClub)) + } else { + return firstPart + } + } + + func isFederalTournamentValidForFilters(_ tournament: FederalTournament, build: any TournamentBuildHolder) -> Bool { + (levels.isEmpty || levels.contains(build.level)) + && + (categories.isEmpty || categories.contains(build.category)) + && + (ageCategories.isEmpty || ageCategories.contains(build.age)) + && + (selectedClubs.isEmpty || selectedClubs.contains(tournament.codeClub!)) + } + + func gatherTournaments(clubs: [Club], startDate: Date, endDate: Date? = nil) async throws { + try await clubs.filter { $0.code != nil }.concurrentForEach { club in + let newTournaments = try await NetworkFederalService.shared.getClubFederalTournaments(page: 0, tournaments: [], club: club.name, codeClub: club.code!, startDate: startDate, endDate: endDate) + + newTournaments.forEach { tournament in + if self.federalTournaments.contains(where: { $0.id == tournament.id }) == false { + self.federalTournaments.append(tournament) + } + } + } + } +} + diff --git a/PadelClub/Views/Club/CourtView.swift b/PadelClub/Views/Club/CourtView.swift index bbcfecf..d9617bd 100644 --- a/PadelClub/Views/Club/CourtView.swift +++ b/PadelClub/Views/Club/CourtView.swift @@ -32,7 +32,7 @@ struct CourtView: View { Section { Toggle(isOn: $court.exitAllowed) { - Text("Sortie authorisée") + Text("Sortie autorisée") } Toggle(isOn: $court.indoor) { Text("Terrain intérieur") diff --git a/PadelClub/Views/Components/GenericDestinationPickerView.swift b/PadelClub/Views/Components/GenericDestinationPickerView.swift index 93e9c6a..fd936a8 100644 --- a/PadelClub/Views/Components/GenericDestinationPickerView.swift +++ b/PadelClub/Views/Components/GenericDestinationPickerView.swift @@ -22,6 +22,7 @@ struct GenericDestinationPickerView: View { } label: { Image(systemName: "wrench.and.screwdriver") } + .foregroundStyle(selectedDestination == nil ? .white : .black) .padding() .background { Circle() @@ -36,6 +37,7 @@ struct GenericDestinationPickerView: View { selectedDestination = destination } label: { Text(destination.selectionLabel()) + .foregroundStyle(selectedDestination?.id == destination.id ? .white : .black) } .padding() .background { diff --git a/PadelClub/Views/Event/EventCreationView.swift b/PadelClub/Views/Event/EventCreationView.swift index 09fcfaa..2a07290 100644 --- a/PadelClub/Views/Event/EventCreationView.swift +++ b/PadelClub/Views/Event/EventCreationView.swift @@ -11,6 +11,7 @@ import TipKit struct EventCreationView: View { @Environment(\.dismiss) private var dismiss @EnvironmentObject var dataStore: DataStore + @Environment(FederalDataViewModel.self) private var federalDataViewModel: FederalDataViewModel @Environment(NavigationViewModel.self) private var navigation: NavigationViewModel @State private var eventType: EventType = .approvedTournament @State private var animationType: AnimationType = .upAndDown @@ -18,7 +19,7 @@ struct EventCreationView: View { @State private var duration: Int = 3 @State private var eventName: String = "" @State var tournaments: [Tournament] = [] - @State private var selectedClub: Club? + @State var selectedClub: Club? let multiTournamentsEventTip = MultiTournamentsEventTip() diff --git a/PadelClub/Views/GroupStage/GroupStageView.swift b/PadelClub/Views/GroupStage/GroupStageView.swift index 751ba58..782fb36 100644 --- a/PadelClub/Views/GroupStage/GroupStageView.swift +++ b/PadelClub/Views/GroupStage/GroupStageView.swift @@ -54,7 +54,7 @@ struct GroupStageView: View { sortingMode = .score } } else if sortingMode == .weight { - sortingMode = .weight + sortingMode = .score } else { sortingMode = .weight } diff --git a/PadelClub/Views/Match/MatchDetailView.swift b/PadelClub/Views/Match/MatchDetailView.swift index 9815eb1..72887eb 100644 --- a/PadelClub/Views/Match/MatchDetailView.swift +++ b/PadelClub/Views/Match/MatchDetailView.swift @@ -134,6 +134,12 @@ struct MatchDetailView: View { } } + if match.isReady() { + Section { + inputScoreView + } + } + let players = match.teams().flatMap { $0.players() } let unpaid = players.filter({ $0.hasPaid() == false }) @@ -343,12 +349,6 @@ struct MatchDetailView: View { @ViewBuilder var menuView: some View { - if match.isReady() { - Section { - inputScoreView - } - } - broadcastView if match.hasStarted() { diff --git a/PadelClub/Views/Navigation/Agenda/ActivityView.swift b/PadelClub/Views/Navigation/Agenda/ActivityView.swift index 0709773..90c2cee 100644 --- a/PadelClub/Views/Navigation/Agenda/ActivityView.swift +++ b/PadelClub/Views/Navigation/Agenda/ActivityView.swift @@ -10,28 +10,31 @@ import SwiftUI struct ActivityView: View { @EnvironmentObject var dataStore: DataStore @Environment(NavigationViewModel.self) private var navigation - + @State private var federalDataViewModel : FederalDataViewModel = .shared @State private var searchText: String = "" - @State private var filterEnabled: Bool = false + @State private var presentFilterView: Bool = false @State private var presentToolbar: Bool = false @State private var newTournament: Tournament? @State private var viewStyle: AgendaDestination.ViewStyle = .list - @State private var federalTournaments: [FederalTournament] = [] @State private var isGatheringFederalTournaments: Bool = false @State private var error: Error? - + @State private var uuid: UUID = UUID() + var runningTournaments: [FederalTournamentHolder] { dataStore.tournaments.filter({ $0.endDate == nil }) + .filter({ federalDataViewModel.isTournamentValidForFilters($0) }) } var endedTournaments: [Tournament] { - dataStore.tournaments.filter({ $0.endDate != nil }).sorted(using: SortDescriptor(\.startDate, order: .reverse)) + dataStore.tournaments.filter({ $0.endDate != nil }) + .filter({ federalDataViewModel.isTournamentValidForFilters($0) }) + .sorted(using: SortDescriptor(\.startDate, order: .reverse)) } func _activityStatus() -> String? { let tournaments = tournaments - if tournaments.isEmpty && filterEnabled == false { + if tournaments.isEmpty && federalDataViewModel.areFiltersEnabled() == false { return nil } else { let count = tournaments.map { $0.tournaments.count }.reduce(0,+) @@ -46,7 +49,7 @@ struct ActivityView: View { case .history: endedTournaments case .tenup: - federalTournaments + federalDataViewModel.filteredFederalTournaments } } @@ -63,9 +66,11 @@ struct ActivityView: View { case .history: EventListView(tournaments: endedTournaments, viewStyle: viewStyle) case .tenup: - EventListView(tournaments: federalTournaments, viewStyle: viewStyle) + EventListView(tournaments: federalDataViewModel.federalTournaments, viewStyle: viewStyle) + .id(uuid) } } + .environment(federalDataViewModel) .overlay { if let error, navigation.agendaDestination == .tenup { ContentUnavailableView { @@ -83,14 +88,14 @@ struct ActivityView: View { if tournaments.isEmpty && viewStyle == .list { if searchText.isEmpty == false { ContentUnavailableView.search(text: searchText) - } else if filterEnabled { + } else if federalDataViewModel.areFiltersEnabled() { ContentUnavailableView { Text("Aucun résultat") } description: { - Text("Description du filtre") + Text(federalDataViewModel.filterStatus()) } actions: { RowButtonView("supprimer le filtre") { - filterEnabled.toggle() + federalDataViewModel.removeFilters() } } } else { @@ -103,27 +108,28 @@ struct ActivityView: View { .onAppear { presentToolbar = true } .onDisappear { presentToolbar = false } .sheet(item: $newTournament) { tournament in - EventCreationView(tournaments: [tournament]) + EventCreationView(tournaments: [tournament], selectedClub: federalDataViewModel.selectedClub()) .environment(navigation) .tint(.master) } .refreshable { if navigation.agendaDestination == .tenup { - federalTournaments.removeAll() + federalDataViewModel.federalTournaments.removeAll() + NetworkFederalService.shared.formId = "" _gatherFederalTournaments() } } .task { if navigation.agendaDestination == .tenup && dataStore.clubs.isEmpty == false - && federalTournaments.isEmpty { + && federalDataViewModel.federalTournaments.isEmpty { _gatherFederalTournaments() } } .onChange(of: navigation.agendaDestination) { if navigation.agendaDestination == .tenup && dataStore.clubs.isEmpty == false - && federalTournaments.isEmpty { + && federalDataViewModel.federalTournaments.isEmpty { _gatherFederalTournaments() } } @@ -131,8 +137,8 @@ struct ActivityView: View { if presentToolbar { ToolbarItem(placement: .status) { VStack(spacing: -2) { - if filterEnabled { - Text("filtre actif") + if federalDataViewModel.areFiltersEnabled() { + Text(federalDataViewModel.filterStatus()) } if let _activityStatus = _activityStatus() { Text(_activityStatus) @@ -143,7 +149,7 @@ struct ActivityView: View { } - ToolbarItem(placement: .topBarLeading) { + ToolbarItemGroup(placement: .topBarLeading) { Button { switch viewStyle { case .list: @@ -158,17 +164,16 @@ struct ActivityView: View { .frame(minHeight: 28) } .symbolVariant(viewStyle == .calendar ? .fill : .none) - - // - // Button { - // filterEnabled.toggle() - // } label: { - // Image(systemName: "line.3.horizontal.decrease.circle") - // .resizable() - // .scaledToFit() - // .frame(minHeight: 28) - // } - // .symbolVariant(filterEnabled ? .fill : .none) + + Button { + presentFilterView.toggle() + } label: { + Image(systemName: "line.3.horizontal.decrease.circle") + .resizable() + .scaledToFit() + .frame(minHeight: 28) + } + .symbolVariant(federalDataViewModel.areFiltersEnabled() ? .fill : .none) } ToolbarItem(placement: .topBarTrailing) { @@ -189,22 +194,25 @@ struct ActivityView: View { TournamentView() .environment(tournament) } + .sheet(isPresented: $presentFilterView) { + TournamentFilterView(federalDataViewModel: federalDataViewModel) + .environment(navigation) + .tint(.master) + } } } } private func _gatherFederalTournaments() { isGatheringFederalTournaments = true - NetworkFederalService.shared.formId = "" Task { do { - try await dataStore.clubs.filter { $0.code != nil }.concurrentForEach { club in - federalTournaments += try await NetworkFederalService.shared.getClubFederalTournaments(page: 0, tournaments: [], club: club.name, codeClub: club.code!, startDate: .now.startOfMonth) - } + try await federalDataViewModel.gatherTournaments(clubs: dataStore.clubs.filter { $0.code != nil }, startDate: .now.startOfMonth) } catch { self.error = error } isGatheringFederalTournaments = false + uuid = UUID() } } @@ -269,6 +277,7 @@ struct ActivityView: View { Text("Aucun tournoi n'a pu être récupéré via tenup.") } actions: { RowButtonView("Rafraîchir") { + NetworkFederalService.shared.formId = "" _gatherFederalTournaments() } } diff --git a/PadelClub/Views/Navigation/Agenda/CalendarView.swift b/PadelClub/Views/Navigation/Agenda/CalendarView.swift index acbaa49..5a9bfda 100644 --- a/PadelClub/Views/Navigation/Agenda/CalendarView.swift +++ b/PadelClub/Views/Navigation/Agenda/CalendarView.swift @@ -13,7 +13,9 @@ import SwiftUI struct CalendarView: View { + @EnvironmentObject var dataStore: DataStore @Environment(NavigationViewModel.self) var navigation: NavigationViewModel + @Environment(FederalDataViewModel.self) var federalDataViewModel: FederalDataViewModel let date: Date let tournaments: [FederalTournamentHolder] let daysOfWeek = Date.capitalizedFirstLettersOfWeekdays @@ -42,7 +44,7 @@ struct CalendarView: View { } } .sheet(item: $newTournament) { tournament in - EventCreationView(startingDate: tournament.startDate, tournaments: [tournament]) + EventCreationView(startingDate: tournament.startDate, tournaments: [tournament], selectedClub: federalDataViewModel.selectedClub()) .environment(navigation) .tint(.master) } @@ -65,14 +67,42 @@ struct CalendarView: View { Button("Créer un tournoi") { let tournament = Tournament.newEmptyInstance() tournament.startDate = day.atNine() + if federalDataViewModel.categories.isEmpty == false { + tournament.tournamentCategory = federalDataViewModel.categories.first! + } + if federalDataViewModel.levels.isEmpty == false { + tournament.tournamentLevel = federalDataViewModel.levels.first! + } + if federalDataViewModel.ageCategories.isEmpty == false { + tournament.federalTournamentAge = federalDataViewModel.ageCategories.first! + } newTournament = tournament } + + Divider() - let tournamentsByDay = tournaments.filter { $0.startDate.dayInt == day.dayInt } + let tournamentsByDay = tournaments.filter { day.dayInt >= $0.startDate.dayInt && day.dayInt < $0.startDate.dayInt + $0.dayDuration } ForEach(tournamentsByDay, id: \.holderId) { tournamentHolder in if let tournament = tournamentHolder as? Tournament { - Button(tournament.tournamentTitle()) { - navigation.path.append(tournament) + Section { + Button(tournament.tournamentTitle()) { + navigation.path.append(tournament) + } + } header: { + Text("sur " + tournament.dayDuration.formatted() + " jour" + tournament.dayDuration.pluralSuffix) + } + } else if let tournament = tournamentHolder as? FederalTournament { + Menu { + ForEach(tournament.tournaments, id: \.id) { build in + if federalDataViewModel.isFederalTournamentValidForFilters(tournament, build: build) { + Button(build.buildHolderTitle()) { + _createOrShow(federalTournament: tournament, existingTournament: event(forTournament: tournament)?.existingBuild(build), build: build) + } + } + } + } label: { + Text(tournament.clubLabel()) + Text("sur " + tournament.dayDuration.formatted() + " jour" + tournament.dayDuration.pluralSuffix) } } } @@ -116,6 +146,34 @@ struct CalendarView: View { } counts = Dictionary(mappedItems, uniquingKeysWith: +) } + + func event(forTournament tournament: FederalTournamentHolder) -> Event? { + guard let federalTournament = tournament as? FederalTournament else { return nil } + return dataStore.events.first(where: { $0.tenupId == federalTournament.id.string }) + } + + private func _createOrShow(federalTournament: FederalTournament, existingTournament: Tournament?, build: any TournamentBuildHolder) { + if let existingTournament { + navigation.agendaDestination = .activity + navigation.path.append(existingTournament) + } else { + let event = federalTournament.getEvent() + let newTournament = Tournament.newEmptyInstance() + newTournament.event = event.id + //todo + //newTournament.umpireMail() + //newTournament.jsonData = jsonData + newTournament.tournamentLevel = build.level + newTournament.tournamentCategory = build.category + newTournament.federalTournamentAge = build.age + newTournament.dayDuration = federalTournament.dayDuration + newTournament.startDate = federalTournament.startDate.atBeginningOfDay(hourInt: 9) + newTournament.setupFederalSettings() + try? dataStore.tournaments.addOrUpdate(instance: newTournament) + navigation.path.append(newTournament) + } + } + } #Preview { diff --git a/PadelClub/Views/Navigation/Agenda/EventListView.swift b/PadelClub/Views/Navigation/Agenda/EventListView.swift index eb661cf..227cb55 100644 --- a/PadelClub/Views/Navigation/Agenda/EventListView.swift +++ b/PadelClub/Views/Navigation/Agenda/EventListView.swift @@ -9,23 +9,20 @@ import SwiftUI struct EventListView: View { @EnvironmentObject var dataStore: DataStore + @Environment(NavigationViewModel.self) var navigation: NavigationViewModel + @Environment(FederalDataViewModel.self) var federalDataViewModel: FederalDataViewModel let tournaments: [FederalTournamentHolder] let viewStyle: AgendaDestination.ViewStyle var body: some View { - let groupedTournamentsByDate = Dictionary(grouping: tournaments) { $0.startDate.startOfMonth } + let groupedTournamentsByDate = Dictionary(grouping: navigation.agendaDestination == .tenup ? federalDataViewModel.filteredFederalTournaments : tournaments) { $0.startDate.startOfMonth } switch viewStyle { case .list: ForEach(groupedTournamentsByDate.keys.sorted(by: <), id: \.self) { section in if let _tournaments = groupedTournamentsByDate[section]?.sorted(by: \.startDate) { Section { - switch viewStyle { - case .list: - _listView(_tournaments) - case .calendar: - CalendarView(date: section, tournaments: _tournaments) - } + _listView(_tournaments) } header: { HStack { Text(section.monthYearFormatted) @@ -41,7 +38,7 @@ struct EventListView: View { ForEach(_nextMonths(), id: \.self) { section in let _tournaments = groupedTournamentsByDate[section]?.sorted(by: \.startDate) ?? [] Section { - CalendarView(date: section, tournaments: _tournaments) + CalendarView(date: section, tournaments: _tournaments).id(federalDataViewModel.id) } header: { HStack { Text(section.monthYearFormatted) @@ -51,7 +48,26 @@ struct EventListView: View { } } .headerProminence(.increased) + .task { + if navigation.agendaDestination == .tenup + && dataStore.clubs.isEmpty == false + && _tournaments.isEmpty { + _gatherFederalTournaments(startDate: section) + } + } + } + } + } + + private func _gatherFederalTournaments(startDate: Date) { +// isGatheringFederalTournaments = true + Task { + do { + try await federalDataViewModel.gatherTournaments(clubs: dataStore.clubs.filter { $0.code != nil }, startDate: startDate, endDate: startDate.endOfMonth) + } catch { +// self.error = error } +// isGatheringFederalTournaments = false } } diff --git a/PadelClub/Views/Shared/TournamentFilterView.swift b/PadelClub/Views/Shared/TournamentFilterView.swift new file mode 100644 index 0000000..7ea8c1d --- /dev/null +++ b/PadelClub/Views/Shared/TournamentFilterView.swift @@ -0,0 +1,147 @@ +// +// TournamentFilterView.swift +// PadelClub +// +// Created by Razmig Sarkissian on 29/04/2024. +// + +import SwiftUI + +struct TournamentFilterView: View { + @EnvironmentObject var dataStore: DataStore + @Environment(\.dismiss) private var dismiss + @State private var levels: Set + @State private var categories: Set + @State private var ageCategories: Set + @State private var selectedClubs: Set + var federalDataViewModel: FederalDataViewModel + + init(federalDataViewModel: FederalDataViewModel) { + self.federalDataViewModel = federalDataViewModel + _levels = State(wrappedValue: federalDataViewModel.levels) + _categories = State(wrappedValue: federalDataViewModel.categories) + _ageCategories = State(wrappedValue: federalDataViewModel.ageCategories) + _selectedClubs = State(wrappedValue: federalDataViewModel.selectedClubs) + } + + var body: some View { + NavigationView { + Form { + Section { + ForEach(dataStore.clubs.filter({ $0.code != nil })) { club in + LabeledContent { + Button { + if selectedClubs.contains(club.code!) { + selectedClubs.remove(club.code!) + } else { + selectedClubs.insert(club.code!) + } + } label: { + if selectedClubs.contains(club.code!) { + Image(systemName: "checkmark.circle.fill") + } + } + } label: { + Text(club.clubTitle()) + } + } + } header: { + Text("Clubs") + } + Section { + ForEach(TournamentLevel.allCases) { level in + LabeledContent { + Button { + if levels.contains(level) { + levels.remove(level) + } else { + levels.insert(level) + } + } label: { + if levels.contains(level) { + Image(systemName: "checkmark.circle.fill") + } + } + } label: { + Text(level.localizedLabel()) + } + } + } header: { + Text("Niveaux") + } + + Section { + ForEach(TournamentCategory.allCases) { category in + LabeledContent { + Button { + if categories.contains(category) { + categories.remove(category) + } else { + categories.insert(category) + } + } label: { + if categories.contains(category) { + Image(systemName: "checkmark.circle.fill") + } + } + } label: { + Text(category.localizedLabel()) + } + } + } header: { + Text("Catégories") + } + + Section { + ForEach(FederalTournamentAge.allCases) { category in + LabeledContent { + Button { + if ageCategories.contains(category) { + ageCategories.remove(category) + } else { + ageCategories.insert(category) + } + } label: { + if ageCategories.contains(category) { + Image(systemName: "checkmark.circle.fill") + } + } + } label: { + Text(category.localizedLabel()) + } + } + } header: { + Text("Catégories d'âge") + } + } + .toolbar { + ToolbarItem(placement: .topBarLeading) { + if federalDataViewModel.areFiltersEnabled() { + Button("Tout retirer", role: .cancel) { + federalDataViewModel.removeFilters() + dismiss() + } + } + } + ToolbarItem(placement: .topBarTrailing) { + ButtonValidateView { + federalDataViewModel.levels = levels + federalDataViewModel.categories = categories + federalDataViewModel.ageCategories = ageCategories + federalDataViewModel.selectedClubs = selectedClubs + federalDataViewModel.id = UUID() + dismiss() + } + } + } + .headerProminence(.increased) + .navigationTitle("Filtres") + .navigationBarTitleDisplayMode(.inline) + .toolbarBackground(.visible, for: .navigationBar) + } + } +} + +#Preview { + TournamentFilterView(federalDataViewModel: FederalDataViewModel()) +} diff --git a/PadelClub/Views/Tournament/Screen/Components/InscriptionInfoView.swift b/PadelClub/Views/Tournament/Screen/Components/InscriptionInfoView.swift index 5c9b896..5c3ab54 100644 --- a/PadelClub/Views/Tournament/Screen/Components/InscriptionInfoView.swift +++ b/PadelClub/Views/Tournament/Screen/Components/InscriptionInfoView.swift @@ -15,7 +15,7 @@ struct InscriptionInfoView: View { var selectedTeams : [TeamRegistration] { tournament.selectedSortedTeams() } var callDateIssue : [TeamRegistration] { - selectedTeams.filter { tournament.isStartDateIsDifferentThanCallDate($0) } + selectedTeams.filter { $0.callDate != nil && tournament.isStartDateIsDifferentThanCallDate($0) } } var waitingList : [TeamRegistration] { tournament.waitingListTeams(in: selectedTeams) } diff --git a/PadelClub/Views/Tournament/Screen/TableStructureView.swift b/PadelClub/Views/Tournament/Screen/TableStructureView.swift index 1dc45d1..3faa36f 100644 --- a/PadelClub/Views/Tournament/Screen/TableStructureView.swift +++ b/PadelClub/Views/Tournament/Screen/TableStructureView.swift @@ -82,7 +82,7 @@ struct TableStructureView: View { LabeledContent { StepperView(count: $groupStageAdditionalQualified, minimum: 0, maximum: maxMoreQualified) } label: { - Text("Qualifiés supplémentaires").foregroundStyle(.secondary).font(.caption) + Text("Qualifiés supplémentaires") Text(moreQualifiedLabel) } .onChange(of: groupStageAdditionalQualified) {