diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 0da430b..238f768 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -108,7 +108,6 @@ FF0EC5772BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-10-2022.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC54A2BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-10-2022.csv */; }; FF11627A2BCF8109000C4809 /* CallMessageCustomizationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1162792BCF8109000C4809 /* CallMessageCustomizationView.swift */; }; FF11627D2BCF941A000C4809 /* CashierSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF11627C2BCF941A000C4809 /* CashierSettingsView.swift */; }; - FF11627F2BCF9432000C4809 /* PlayerListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF11627E2BCF9432000C4809 /* PlayerListView.swift */; }; FF1162812BCF945C000C4809 /* TournamentCashierView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1162802BCF945C000C4809 /* TournamentCashierView.swift */; }; FF1162832BCFBE4E000C4809 /* EditablePlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1162822BCFBE4E000C4809 /* EditablePlayerView.swift */; }; FF1162852BD00279000C4809 /* PlayerDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1162842BD00279000C4809 /* PlayerDetailView.swift */; }; @@ -229,6 +228,9 @@ FFBF065C2BBD2657009D6715 /* GroupStageTeamView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF065B2BBD2657009D6715 /* GroupStageTeamView.swift */; }; FFBF065E2BBD8040009D6715 /* MatchListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF065D2BBD8040009D6715 /* MatchListView.swift */; }; FFBF06602BBD9F6D009D6715 /* NavigationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF065F2BBD9F6D009D6715 /* NavigationViewModel.swift */; }; + FFBF41822BF73EB3001B24CB /* EventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF41812BF73EB3001B24CB /* EventView.swift */; }; + FFBF41842BF75ED7001B24CB /* EventTournamentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF41832BF75ED7001B24CB /* EventTournamentsView.swift */; }; + FFBF41862BF75FDA001B24CB /* EventSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF41852BF75FDA001B24CB /* EventSettingsView.swift */; }; FFC1E1042BAC28C6008D6F59 /* ClubSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1032BAC28C6008D6F59 /* ClubSearchView.swift */; }; FFC1E1082BAC29FC008D6F59 /* LocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1072BAC29FC008D6F59 /* LocationManager.swift */; }; FFC1E10A2BAC2A77008D6F59 /* NetworkFederalService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1092BAC2A77008D6F59 /* NetworkFederalService.swift */; }; @@ -408,7 +410,6 @@ FF0EC54C2BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-12-2022.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT-PADEL-MESSIEURS-12-2022.csv"; sourceTree = ""; }; FF1162792BCF8109000C4809 /* CallMessageCustomizationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallMessageCustomizationView.swift; sourceTree = ""; }; FF11627C2BCF941A000C4809 /* CashierSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CashierSettingsView.swift; sourceTree = ""; }; - FF11627E2BCF9432000C4809 /* PlayerListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerListView.swift; sourceTree = ""; }; FF1162802BCF945C000C4809 /* TournamentCashierView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentCashierView.swift; sourceTree = ""; }; FF1162822BCFBE4E000C4809 /* EditablePlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditablePlayerView.swift; sourceTree = ""; }; FF1162842BD00279000C4809 /* PlayerDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerDetailView.swift; sourceTree = ""; }; @@ -529,6 +530,9 @@ FFBF065B2BBD2657009D6715 /* GroupStageTeamView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupStageTeamView.swift; sourceTree = ""; }; FFBF065D2BBD8040009D6715 /* MatchListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchListView.swift; sourceTree = ""; }; FFBF065F2BBD9F6D009D6715 /* NavigationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationViewModel.swift; sourceTree = ""; }; + FFBF41812BF73EB3001B24CB /* EventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventView.swift; sourceTree = ""; }; + FFBF41832BF75ED7001B24CB /* EventTournamentsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventTournamentsView.swift; sourceTree = ""; }; + FFBF41852BF75FDA001B24CB /* EventSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventSettingsView.swift; sourceTree = ""; }; FFC1E1032BAC28C6008D6F59 /* ClubSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClubSearchView.swift; sourceTree = ""; }; FFC1E1072BAC29FC008D6F59 /* LocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationManager.swift; sourceTree = ""; }; FFC1E1092BAC2A77008D6F59 /* NetworkFederalService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkFederalService.swift; sourceTree = ""; }; @@ -722,6 +726,7 @@ FF9267FD2BCE94520080F940 /* Calling */, FFF964512BC2628600EEF017 /* Planning */, FF11627B2BCF937F000C4809 /* Cashier */, + FFBF41802BF73EA2001B24CB /* Event */, FF3F74F72B919F96004CFE0E /* Tournament */, C4A47D882B7BBB5000ADC637 /* Subscription */, C4A47D852B7BA33F00ADC637 /* User */, @@ -868,7 +873,6 @@ children = ( FF9267F92BCE78EB0080F940 /* CashierDetailView.swift */, FF9267F72BCE78C70080F940 /* CashierView.swift */, - FF11627E2BCF9432000C4809 /* PlayerListView.swift */, FF11627C2BCF941A000C4809 /* CashierSettingsView.swift */, ); path = Cashier; @@ -1147,6 +1151,17 @@ path = Components; sourceTree = ""; }; + FFBF41802BF73EA2001B24CB /* Event */ = { + isa = PBXGroup; + children = ( + FFBF41812BF73EB3001B24CB /* EventView.swift */, + FFBF41832BF75ED7001B24CB /* EventTournamentsView.swift */, + FFBF41852BF75FDA001B24CB /* EventSettingsView.swift */, + ); + name = Event; + path = Cashier/Event; + sourceTree = ""; + }; FFC83D4B2BB807C200750834 /* Round */ = { isa = PBXGroup; children = ( @@ -1518,7 +1533,6 @@ FF967CEA2BAEC70100A9A3BD /* GroupStage.swift in Sources */, FF1162812BCF945C000C4809 /* TournamentCashierView.swift in Sources */, C4A47D902B7BBBEC00ADC637 /* StoreManager.swift in Sources */, - FF11627F2BCF9432000C4809 /* PlayerListView.swift in Sources */, FF4AB6BB2B9256D50002987F /* SearchViewModel.swift in Sources */, FF967CF32BAECC0B00A9A3BD /* PlayerRegistration.swift in Sources */, FF4AB6BF2B92577A0002987F /* ImportedPlayerView.swift in Sources */, @@ -1588,6 +1602,7 @@ FFEF7F4E2BDE69130033D0F0 /* MenuWarningView.swift in Sources */, FF967D0B2BAF3D4C00A9A3BD /* TeamPickerView.swift in Sources */, FFA6D7872BB0B7A2003A31F3 /* CloudConvert.swift in Sources */, + FFBF41842BF75ED7001B24CB /* EventTournamentsView.swift in Sources */, FF1DC55B2BAB80C400FD8220 /* DisplayContext.swift in Sources */, FF9268072BCE94D90080F940 /* TournamentCallView.swift in Sources */, FFC2DCB42BBE9ECD0046DB9F /* LoserRoundsView.swift in Sources */, @@ -1631,6 +1646,7 @@ FFB9C8712BBADDE200A0EF4F /* Selectable.swift in Sources */, FFCFC0122BBC3E1A00B82851 /* PointView.swift in Sources */, FF1CBC232BB53E590036DAAB /* ClubHolder.swift in Sources */, + FFBF41862BF75FDA001B24CB /* EventSettingsView.swift in Sources */, FF5D0D782BB42C5B005CB568 /* InscriptionInfoView.swift in Sources */, FF4AB6BD2B9256E10002987F /* SelectablePlayerListView.swift in Sources */, FF8F26512BAE0BAD00650388 /* MatchFormatPickerView.swift in Sources */, @@ -1638,6 +1654,7 @@ FF5D0D872BB48AFD005CB568 /* NumberFormatter+Extensions.swift in Sources */, FFCFC0182BBC5A6800B82851 /* SetLabelView.swift in Sources */, FFF9645B2BC2D53B00EEF017 /* GroupStageScheduleEditorView.swift in Sources */, + FFBF41822BF73EB3001B24CB /* EventView.swift in Sources */, C4A47DA62B83948E00ADC637 /* LoginView.swift in Sources */, FFC91B012BD85C2F00B29808 /* Court.swift in Sources */, FF967CF82BAEDF0000A9A3BD /* Labels.swift in Sources */, diff --git a/PadelClub/Data/Event.swift b/PadelClub/Data/Event.swift index 44c89e4..0c347d1 100644 --- a/PadelClub/Data/Event.swift +++ b/PadelClub/Data/Event.swift @@ -37,6 +37,26 @@ class Event: ModelObject, Storable { // self.roundFormat = roundFormat // self.loserRoundFormat = loserRoundFormat } + + func eventCourtCount() -> Int { + tournaments.map { $0.courtCount }.max() ?? 2 + } + + func eventStartDate() -> Date { + tournaments.map { $0.startDate }.min() ?? Date() + } + + func eventDayDuration() -> Int { + tournaments.map { $0.dayDuration }.max() ?? 1 + } + + func eventTitle() -> String { + if let name, name.isEmpty == false { + return name + } else { + return "Événement" + } + } func clubObject() -> Club? { guard let club else { return nil } @@ -51,6 +71,12 @@ class Event: ModelObject, Storable { tournaments.first(where: { $0.isSameBuild(build) }) } + func tournamentsCourtsUsed() -> [DateInterval] { + tournaments.flatMap({ tournament in + tournament.getPlayedMatchDateIntervals(in: self) + }) + } + var courtsUnavailability: [DateInterval] { Store.main.filter(isIncluded: { $0.event == id }) } diff --git a/PadelClub/Data/MatchScheduler.swift b/PadelClub/Data/MatchScheduler.swift index 28dacee..b8162e0 100644 --- a/PadelClub/Data/MatchScheduler.swift +++ b/PadelClub/Data/MatchScheduler.swift @@ -68,7 +68,8 @@ class MatchScheduler : ModelObject, Storable { } var courtsUnavailability: [DateInterval]? { - tournamentObject()?.eventObject()?.courtsUnavailability + guard let event = tournamentObject()?.eventObject() else { return nil } + return event.courtsUnavailability + event.tournamentsCourtsUsed() } var additionalEstimationDuration : Int { @@ -165,6 +166,7 @@ class MatchScheduler : ModelObject, Storable { var teamsPerRotation = [Int: [String]]() var freeCourtPerRotation = [Int: [Int]]() var groupLastRotation = [Int: Int]() + let courtsUnavailability = courtsUnavailability while slots.count < flattenedMatches.count { teamsPerRotation[rotationIndex] = [] @@ -192,7 +194,7 @@ class MatchScheduler : ModelObject, Storable { let timeIntervalToAdd = (Double(rotationIndex)) * Double(estimatedDuration) * 60 let rotationStartDate: Date = startingDate.addingTimeInterval(timeIntervalToAdd) - let courtsUnavailable = courtsUnavailable(startDate: rotationStartDate, duration: match.matchFormat.getEstimatedDuration(additionalEstimationDuration)) + let courtsUnavailable = courtsUnavailable(startDate: rotationStartDate, duration: match.matchFormat.getEstimatedDuration(additionalEstimationDuration), courtsUnavailability: courtsUnavailability) if courtIndex >= numberOfCourtsAvailablePerRotation - courtsUnavailable { return false } else { @@ -344,6 +346,7 @@ class MatchScheduler : ModelObject, Storable { var _startDate: Date? var rotationIndex = 0 var availableMatchs = flattenedMatches.filter({ $0.startDate == nil }) + let courtsUnavailability = courtsUnavailability flattenedMatches.filter { $0.startDate != nil }.sorted(by: \.startDate!).forEach { match in if _startDate == nil { @@ -414,7 +417,7 @@ class MatchScheduler : ModelObject, Storable { } } - dispatchCourts(availableCourts: numberOfCourtsAvailablePerRotation, courts: courts, availableMatchs: &availableMatchs, slots: &slots, rotationIndex: rotationIndex, rotationStartDate: rotationStartDate, freeCourtPerRotation: &freeCourtPerRotation) + dispatchCourts(availableCourts: numberOfCourtsAvailablePerRotation, courts: courts, availableMatchs: &availableMatchs, slots: &slots, rotationIndex: rotationIndex, rotationStartDate: rotationStartDate, freeCourtPerRotation: &freeCourtPerRotation, courtsUnavailability: courtsUnavailability) rotationIndex += 1 } @@ -434,7 +437,7 @@ class MatchScheduler : ModelObject, Storable { return MatchDispatcher(timedMatches: slots, freeCourtPerRotation: freeCourtPerRotation, rotationCount: rotationIndex) } - func dispatchCourts(availableCourts: Int, courts: [Int], availableMatchs: inout [Match], slots: inout [TimeMatch], rotationIndex: Int, rotationStartDate: Date, freeCourtPerRotation: inout [Int: [Int]]) { + func dispatchCourts(availableCourts: Int, courts: [Int], availableMatchs: inout [Match], slots: inout [TimeMatch], rotationIndex: Int, rotationStartDate: Date, freeCourtPerRotation: inout [Int: [Int]], courtsUnavailability: [DateInterval]?) { var matchPerRound = [Int: Int]() var minimumTargetedEndDate: Date = rotationStartDate print("dispatchCourts", courts.sorted(), rotationStartDate, rotationIndex) @@ -442,7 +445,7 @@ class MatchScheduler : ModelObject, Storable { print("trying to find a match for \(courtIndex) in \(rotationIndex)") if let first = availableMatchs.first(where: { match in let roundObject = match.roundObject! - let courtsUnavailable = courtsUnavailable(startDate: rotationStartDate, duration: match.matchFormat.getEstimatedDuration(additionalEstimationDuration)) + let courtsUnavailable = courtsUnavailable(startDate: rotationStartDate, duration: match.matchFormat.getEstimatedDuration(additionalEstimationDuration), courtsUnavailability: courtsUnavailability) print("courtsUnavailable \(courtsUnavailable)") if courtIndex >= availableCourts - courtsUnavailable { return false @@ -519,7 +522,7 @@ class MatchScheduler : ModelObject, Storable { }.sorted(by: \.1).map { $0.0 } } - dispatchCourts(availableCourts: availableCourts, courts: freeCourts, availableMatchs: &availableMatchs, slots: &slots, rotationIndex: rotationIndex, rotationStartDate: minimumTargetedEndDate, freeCourtPerRotation: &freeCourtPerRotation) + dispatchCourts(availableCourts: availableCourts, courts: freeCourts, availableMatchs: &availableMatchs, slots: &slots, rotationIndex: rotationIndex, rotationStartDate: minimumTargetedEndDate, freeCourtPerRotation: &freeCourtPerRotation, courtsUnavailability: courtsUnavailability) } } @@ -610,18 +613,18 @@ class MatchScheduler : ModelObject, Storable { } - func courtsUnavailable(startDate: Date, duration: Int) -> Int { + func courtsUnavailable(startDate: Date, duration: Int, courtsUnavailability: [DateInterval]?) -> Int { let endDate = startDate.addingTimeInterval(Double(duration) * 60) guard let courtsUnavailability else { return 0 } let groupedBy = Dictionary(grouping: courtsUnavailability, by: { $0.courtIndex }) let courts = groupedBy.keys return courts.filter { - courtUnavailable(courtIndex: $0, from: startDate, to: endDate) + courtUnavailable(courtIndex: $0, from: startDate, to: endDate, source: courtsUnavailability) }.count } - func courtUnavailable(courtIndex: Int, from startDate: Date, to endDate: Date) -> Bool { - guard let courtLockedSchedule = courtsUnavailability?.filter({ $0.courtIndex == courtIndex }) else { return true } + func courtUnavailable(courtIndex: Int, from startDate: Date, to endDate: Date, source: [DateInterval]) -> Bool { + let courtLockedSchedule = source.filter({ $0.courtIndex == courtIndex }) return courtLockedSchedule.anySatisfy({ dateInterval in dateInterval.isDateInside(startDate) || dateInterval.isDateInside(endDate) }) diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index 7baf738..21c5351 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -599,6 +599,12 @@ class Tournament : ModelObject, Storable { return round } } + + func getPlayedMatchDateIntervals(in event: Event) -> [DateInterval] { + allMatches().filter { $0.courtIndex != nil && $0.startDate != nil }.map { match in + DateInterval(event: event.id, courtIndex: match.courtIndex!, startDate: match.startDate!, endDate: match.estimatedEndDate(additionalEstimationDuration)!) + } + } func allRoundMatches() -> [Match] { allRounds().flatMap { $0._matches() } diff --git a/PadelClub/Utils/Tips.swift b/PadelClub/Utils/Tips.swift index 1d8465c..78e24fb 100644 --- a/PadelClub/Utils/Tips.swift +++ b/PadelClub/Utils/Tips.swift @@ -366,6 +366,32 @@ struct TournamentTVBroadcastTip: Tip { } } +struct TournamentSelectionTip: Tip { + @Parameter + static var tournamentCount: Int? = nil + + var rules: [Rule] { + [ + // Define a rule based on the app state. + #Rule(Self.$tournamentCount) { + // Set the conditions for when the tip displays. + return ($0 ?? 1) > 1 + } + ] + } + + var title: Text { + Text("Naviguer entre les épreuves") + } + + var message: Text? { + return Text("Vous pouvez appuyer sur la barre de navigation pour accéder à une épreuve de votre événement.") + } + + var image: Image? { + Image(systemName: "filemenu.and.selection") + } +} struct TipStyleModifier: ViewModifier { @Environment(\.colorScheme) var colorScheme diff --git a/PadelClub/ViewModel/NavigationViewModel.swift b/PadelClub/ViewModel/NavigationViewModel.swift index 722f686..b4cc277 100644 --- a/PadelClub/ViewModel/NavigationViewModel.swift +++ b/PadelClub/ViewModel/NavigationViewModel.swift @@ -15,7 +15,6 @@ class NavigationViewModel { var ongoingPath = NavigationPath() var selectedTab: TabDestination? var agendaDestination: AgendaDestination? = .activity - var tournament: Tournament? var organizerTournament: Tournament? func isTournamentAlreadyOpenInOrganizer(_ tournament: Tournament) -> Bool { diff --git a/PadelClub/ViewModel/Screen.swift b/PadelClub/ViewModel/Screen.swift index 05ee93b..ec0774a 100644 --- a/PadelClub/ViewModel/Screen.swift +++ b/PadelClub/ViewModel/Screen.swift @@ -18,4 +18,5 @@ enum Screen: String, Codable { case call case rankings case broadcast + case event } diff --git a/PadelClub/Views/Cashier/Event/EventSettingsView.swift b/PadelClub/Views/Cashier/Event/EventSettingsView.swift new file mode 100644 index 0000000..97dfcd0 --- /dev/null +++ b/PadelClub/Views/Cashier/Event/EventSettingsView.swift @@ -0,0 +1,54 @@ +// +// EventSettingsView.swift +// PadelClub +// +// Created by Razmig Sarkissian on 17/05/2024. +// + +import SwiftUI +import LeStorage + +struct EventSettingsView: View { + @EnvironmentObject var dataStore: DataStore + @Bindable var event: Event + @State private var eventName: String = "" + + init(event: Event) { + self.event = event + _eventName = State(wrappedValue: event.name ?? "") + } + + var body: some View { + Form { + Section { + TextField("Nom de l'événement", text: $eventName) + .autocorrectionDisabled() + .keyboardType(.alphabet) + .onSubmit { + if eventName.trimmed.isEmpty == false { + event.name = eventName.trimmed + } else { + event.name = nil + } + _save() + } + } footer: { + if eventName.isEmpty == false { + FooterButtonView("effacer le nom") { + eventName = "" + event.name = nil + _save() + } + } + } + } + } + + private func _save() { + do { + try dataStore.events.addOrUpdate(instance: event) + } catch { + Logger.error(error) + } + } +} diff --git a/PadelClub/Views/Cashier/Event/EventTournamentsView.swift b/PadelClub/Views/Cashier/Event/EventTournamentsView.swift new file mode 100644 index 0000000..a4b4263 --- /dev/null +++ b/PadelClub/Views/Cashier/Event/EventTournamentsView.swift @@ -0,0 +1,91 @@ +// +// EventTournamentsView.swift +// PadelClub +// +// Created by Razmig Sarkissian on 17/05/2024. +// + +import SwiftUI +import LeStorage + +struct EventTournamentsView: View { + @EnvironmentObject var dataStore: DataStore + @Environment(NavigationViewModel.self) private var navigation + let event: Event + @State private var newTournament: Tournament? + + var presentTournamentCreationView: Binding { Binding( + get: { newTournament != nil }, + set: { isPresented in + if isPresented == false { + newTournament = nil + } + } + )} + + var body: some View { + let tournaments = event.tournaments + List { + ForEach(tournaments) { tournament in + TournamentCellView(tournament: tournament) + .contextMenu { + Button { + navigation.openTournamentInOrganizer(tournament) + } label: { + Label("Voir dans le gestionnaire", systemImage: "line.diagonal.arrow") + } + } + } + } + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + BarButtonView("Ajouter une épreuve", icon: "plus.circle.fill") { + let tournament = Tournament.newEmptyInstance() + newTournament = tournament + } + } + } + .headerProminence(.increased) + .toolbarBackground(.visible, for: .navigationBar) + .navigationBarTitleDisplayMode(.inline) + .navigationTitle(event.eventTitle()) + .sheet(isPresented: presentTournamentCreationView) { + if let newTournament { + NavigationStack { + List { + Section { + TournamentConfigurationView(tournament: newTournament) + } + Section { + RowButtonView("Ajouter l'épreuve") { + newTournament.event = event.id + newTournament.courtCount = event.eventCourtCount() + newTournament.startDate = event.eventStartDate() + newTournament.dayDuration = event.eventDayDuration() + newTournament.setupFederalSettings() + + do { + try dataStore.tournaments.addOrUpdate(instance: newTournament) + } catch { + Logger.error(error) + } + + self.newTournament = nil + } + } + } + .toolbarBackground(.visible, for: .navigationBar) + .navigationBarTitleDisplayMode(.inline) + .navigationTitle("Nouvelle épreuve") + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button("Annuler", role: .cancel) { + self.newTournament = nil + } + } + } + } + } + } + } +} diff --git a/PadelClub/Views/Cashier/Event/EventView.swift b/PadelClub/Views/Cashier/Event/EventView.swift new file mode 100644 index 0000000..cae371a --- /dev/null +++ b/PadelClub/Views/Cashier/Event/EventView.swift @@ -0,0 +1,76 @@ +// +// EventView.swift +// PadelClub +// +// Created by Razmig Sarkissian on 17/05/2024. +// + +import SwiftUI +import LeStorage + +enum EventDestination: Identifiable, Selectable { + case tournaments(Event) + case cashier + + var id: String { + return String(describing: self) + } + + func selectionLabel() -> String { + switch self { + case .tournaments: + return "Épreuves" + case .cashier: + return "Finance" + } + } + + func badgeValue() -> Int? { + switch self { + case .tournaments(let event): + return event.tournaments.count + case .cashier: + return nil + } + } + + func badgeValueColor() -> Color? { + return .logoBackground + } + + func badgeImage() -> Badge? { + return nil + } + +} + +struct EventView: View { + let event: Event + @State private var selectedDestination: EventDestination? + + func allDestinations() -> [EventDestination] { + [.tournaments(event), .cashier] + } + + var body: some View { + VStack(spacing: 0) { + GenericDestinationPickerView(selectedDestination: $selectedDestination, destinations: allDestinations(), nilDestinationIsValid: true) + switch selectedDestination { + case .none: + EventSettingsView(event: event) + .navigationTitle("Réglages") + case .some(let selectedEventDestination): + switch selectedEventDestination { + case .tournaments(let event): + EventTournamentsView(event: event) + case .cashier: + CashierDetailView(tournaments: event.tournaments) + } + } + } + .headerProminence(.increased) + .toolbarBackground(.visible, for: .navigationBar) + .navigationBarTitleDisplayMode(.inline) + .navigationTitle(event.eventTitle()) + } +} diff --git a/PadelClub/Views/Cashier/PlayerListView.swift b/PadelClub/Views/Cashier/PlayerListView.swift deleted file mode 100644 index 14ddd19..0000000 --- a/PadelClub/Views/Cashier/PlayerListView.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// PlayerListView.swift -// PadelClub -// -// Created by Razmig Sarkissian on 17/04/2024. -// - -import SwiftUI - -struct PlayerListView: View { - var body: some View { - Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) - } -} - -#Preview { - PlayerListView() -} diff --git a/PadelClub/Views/Event/EventCreationView.swift b/PadelClub/Views/Event/EventCreationView.swift index aaca795..e958b8f 100644 --- a/PadelClub/Views/Event/EventCreationView.swift +++ b/PadelClub/Views/Event/EventCreationView.swift @@ -64,8 +64,15 @@ struct EventCreationView: View { } TextField("Nom de l'événement", text: $eventName) - } header: { - Text("Informations générales") + .autocorrectionDisabled() + .keyboardType(.alphabet) + + LabeledContent { + Text(tournaments.count.formatted()) + } label: { + Text("Nombre d'épreuves") + } + } Section { @@ -141,15 +148,8 @@ struct EventCreationView: View { TournamentConfigurationView(tournament: tournament) } footer: { if tournaments.count > 1 { - HStack { - Spacer() - Button(role: .destructive) { - tournaments.removeAll(where: { $0 == tournament }) - } label: { - LabelDelete() - } - .textCase(nil) - .font(.caption) + FooterButtonView("effacer") { + tournaments.removeAll(where: { $0 == tournament }) } } } diff --git a/PadelClub/Views/Navigation/Agenda/ActivityView.swift b/PadelClub/Views/Navigation/Agenda/ActivityView.swift index 7ef6d10..1539b9b 100644 --- a/PadelClub/Views/Navigation/Agenda/ActivityView.swift +++ b/PadelClub/Views/Navigation/Agenda/ActivityView.swift @@ -194,8 +194,7 @@ struct ActivityView: View { } .navigationTitle(TabDestination.activity.title) .navigationDestination(for: Tournament.self) { tournament in - TournamentView() - .environment(tournament) + TournamentView(tournament: tournament) } .sheet(isPresented: $presentFilterView) { TournamentFilterView(federalDataViewModel: federalDataViewModel) diff --git a/PadelClub/Views/Navigation/MainView.swift b/PadelClub/Views/Navigation/MainView.swift index 08653e2..eabfa53 100644 --- a/PadelClub/Views/Navigation/MainView.swift +++ b/PadelClub/Views/Navigation/MainView.swift @@ -61,7 +61,6 @@ struct MainView: View { } .id(Store.main.currentUserUUID) .onChange(of: Store.main.currentUserUUID) { - navigation.tournament = nil navigation.path.removeLast(navigation.path.count) } .environmentObject(dataStore) diff --git a/PadelClub/Views/Navigation/Ongoing/OngoingView.swift b/PadelClub/Views/Navigation/Ongoing/OngoingView.swift index aec2a16..72e65be 100644 --- a/PadelClub/Views/Navigation/Ongoing/OngoingView.swift +++ b/PadelClub/Views/Navigation/Ongoing/OngoingView.swift @@ -12,17 +12,38 @@ struct OngoingView: View { @EnvironmentObject var dataStore: DataStore var matches: [Match] { - dataStore.matches.filter({ $0.isRunning() }).sorted(by: \.startDate!) + dataStore.matches.filter({ $0.startDate != nil && $0.endDate == nil }).sorted(by: \.startDate!) } var body: some View { @Bindable var navigation = navigation NavigationStack(path: $navigation.ongoingPath) { + let matches = matches List { ForEach(matches) { match in - MatchRowView(match: match, matchViewStyle: .feedStyle) + Section { + MatchRowView(match: match, matchViewStyle: .sectionedStandardStyle) + } header: { + HStack { + if let roundTitle = match.roundTitle() { + Text(roundTitle) + } + Text(match.matchTitle(.short)) + Spacer() + if let courtName = match.courtName() { + Spacer() + Text(courtName) + } + } + + } footer: { + if let tournament = match.currentTournament() { + Text(tournament.tournamentTitle()) + } + } } } + .headerProminence(.increased) .overlay { if matches.isEmpty { ContentUnavailableView("Aucun match en cours", systemImage: "figure.tennis", description: Text("Tous vos matchs en cours seront visibles ici, quelque soit le tournoi.")) @@ -30,6 +51,31 @@ struct OngoingView: View { } .navigationTitle("En cours") .toolbar(matches.isEmpty ? .hidden : .visible, for: .navigationBar) + .toolbar { + ToolbarItem(placement: .topBarLeading) { + Menu { + Button("Par terrain") { + + } + Button("Par date") { + + } + //todo + //presentFilterView.toggle() + } label: { + Image(systemName: "line.3.horizontal.decrease.circle") + .resizable() + .scaledToFit() + .frame(minHeight: 28) + } + //.symbolVariant(federalDataViewModel.areFiltersEnabled() ? .fill : .none) + } + ToolbarItem(placement: .status) { + if matches.isEmpty == false { + Text("\(matches.count) matche" + matches.count.pluralSuffix) + } + } + } } } } diff --git a/PadelClub/Views/Navigation/Organizer/OrganizedTournamentView.swift b/PadelClub/Views/Navigation/Organizer/OrganizedTournamentView.swift index 78d6dfb..707b622 100644 --- a/PadelClub/Views/Navigation/Organizer/OrganizedTournamentView.swift +++ b/PadelClub/Views/Navigation/Organizer/OrganizedTournamentView.swift @@ -12,8 +12,7 @@ struct OrganizedTournamentView: View { var body: some View { @Bindable var tournament = tournament NavigationStack(path: $tournament.navigationPath) { - TournamentView(presentationContext: .organizer) - .environment(tournament) + TournamentView(tournament: tournament, presentationContext: .organizer) // .onChange(of: navigationPath) { // tournament.navigationPath = navigationPath // } diff --git a/PadelClub/Views/Tournament/TournamentInitView.swift b/PadelClub/Views/Tournament/TournamentInitView.swift index d45b06b..78584a5 100644 --- a/PadelClub/Views/Tournament/TournamentInitView.swift +++ b/PadelClub/Views/Tournament/TournamentInitView.swift @@ -8,10 +8,24 @@ import SwiftUI struct TournamentInitView: View { - @Environment(Tournament.self) private var tournament: Tournament + var tournament: Tournament @ViewBuilder var body: some View { + + if let event = tournament.eventObject() { + let tournaments = event.tournaments + Section { + NavigationLink(value: Screen.event) { + LabeledContent { + Text(tournaments.count.formatted() + " épreuve" + tournaments.count.pluralSuffix) + } label: { + Text("Gestion de l'événement") + } + } + } + } + Section { NavigationLink(value: Screen.settings) { LabeledContent { @@ -51,6 +65,5 @@ struct TournamentInitView: View { } #Preview { - TournamentInitView() - .environment(Tournament.mock()) + TournamentInitView(tournament: Tournament.mock()) } diff --git a/PadelClub/Views/Tournament/TournamentView.swift b/PadelClub/Views/Tournament/TournamentView.swift index 58c7792..8987448 100644 --- a/PadelClub/Views/Tournament/TournamentView.swift +++ b/PadelClub/Views/Tournament/TournamentView.swift @@ -7,13 +7,25 @@ import SwiftUI import LeStorage +import TipKit struct TournamentView: View { @EnvironmentObject var dataStore: DataStore @Environment(NavigationViewModel.self) var navigation: NavigationViewModel - @Environment(Tournament.self) var tournament: Tournament + @State var tournament: Tournament var presentationContext: PresentationContext = .agenda + let tournamentSelectionTip = TournamentSelectionTip() + + var selectedTournamentId: Binding { Binding( + get: { tournament.id }, + set: { id in + if let tournamentFound: Tournament = Store.main.findById(id) { + tournament = tournamentFound + } + } + )} + var lastDataSource: String? { dataStore.appSettings.lastDataSource } @@ -72,7 +84,7 @@ struct TournamentView: View { Text("todo expliquer cet état") } case .initial: - TournamentInitView() + TournamentInitView(tournament: tournament) case .build: TournamentRunningView(tournament: tournament) @@ -108,11 +120,27 @@ struct TournamentView: View { TournamentRankView() case .broadcast: BroadcastView() + case .event: + if let event = tournament.eventObject() { + EventView(event: event) + } } } .environment(tournament) }) .navigationBarTitleDisplayMode(.inline) + .toolbarTitleMenu { + if let event = tournament.eventObject() { + Picker(selection: selectedTournamentId) { + ForEach(event.tournaments) { tournament in + Text(tournament.tournamentTitle()).tag(tournament.id as String) + } + } label: { + + } + } + + } .toolbarBackground(.visible, for: .navigationBar) .toolbar { ToolbarItem(placement: .principal) { @@ -121,6 +149,10 @@ struct TournamentView: View { Text(tournament.formattedDate()) .font(.subheadline).foregroundStyle(.secondary) } + .popoverTip(tournamentSelectionTip) + .onAppear { + TournamentSelectionTip.tournamentCount = tournament.eventObject()?.tournaments.count + } } ToolbarItem(placement: .topBarTrailing) { @@ -135,6 +167,10 @@ struct TournamentView: View { Divider() if tournament.state() == .build { + NavigationLink(value: Screen.event) { + Text("Gestion de l'événement") + } + NavigationLink(value: Screen.settings) { LabelSettings() } @@ -169,7 +205,6 @@ struct TournamentView: View { #Preview { NavigationStack { - TournamentView(presentationContext: .agenda) - .environment(Tournament.mock()) + TournamentView(tournament: Tournament.mock(), presentationContext: .agenda) } }