From a61e16123cbcfe5da7fc9d7152bd415f2f16b554 Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Sat, 4 May 2024 11:35:12 +0200 Subject: [PATCH] setup final clubs management --- PadelClub.xcodeproj/project.pbxproj | 2 +- PadelClub/Data/Club.swift | 38 ++++++ PadelClub/Data/Event.swift | 2 +- .../Data/Federal/FederalTournament.swift | 17 ++- PadelClub/Data/Tournament.swift | 6 +- PadelClub/Data/User.swift | 9 ++ PadelClub/Utils/DisplayContext.swift | 1 + PadelClub/ViewModel/MatchScheduler.swift | 6 +- PadelClub/ViewModel/NavigationViewModel.swift | 3 + PadelClub/ViewModel/TabDestination.swift | 10 -- .../CallMessageCustomizationView.swift | 44 +++++-- PadelClub/Views/Club/ClubDetailView.swift | 124 ++++++++++++------ PadelClub/Views/Club/ClubRowView.swift | 3 +- PadelClub/Views/Club/ClubSearchView.swift | 31 +++-- PadelClub/Views/Club/ClubsView.swift | 37 +++--- PadelClub/Views/Club/CreateClubView.swift | 24 +++- PadelClub/Views/Event/EventCreationView.swift | 25 ++-- .../Navigation/Agenda/ActivityView.swift | 47 +++---- .../Navigation/Agenda/CalendarView.swift | 6 +- .../Navigation/Agenda/EventListView.swift | 5 +- PadelClub/Views/Navigation/MainView.swift | 32 +++-- .../Navigation/Ongoing/OngoingView.swift | 4 +- .../{ => Toolbox}/PadelClubView.swift | 2 +- .../Navigation/Toolbox/ToolboxView.swift | 7 +- .../Views/Navigation/Umpire/UmpireView.swift | 48 ++++--- .../Views/Planning/PlanningSettingsView.swift | 2 +- .../Views/Shared/TournamentFilterView.swift | 33 ++--- .../TournamentClubSettingsView.swift | 16 +-- .../Screen/InscriptionManagerView.swift | 24 ++-- 29 files changed, 394 insertions(+), 214 deletions(-) rename PadelClub/Views/Navigation/{ => Toolbox}/PadelClubView.swift (98%) diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 55692ae..ff3dbf5 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -896,7 +896,6 @@ isa = PBXGroup; children = ( FF59FFB62B90EFBF0061EFF9 /* MainView.swift */, - FFD783FE2B91BA42000F62A6 /* PadelClubView.swift */, FFD783FB2B91B919000F62A6 /* Agenda */, FF3F74FA2B91A04B004CFE0E /* Organizer */, FF3F74FB2B91A060004CFE0E /* Toolbox */, @@ -963,6 +962,7 @@ FF025AE62BD1111000A86CF8 /* GlobalSettingsView.swift */, FF025AEE2BD1AE9400A86CF8 /* DurationSettingsView.swift */, FF025AF02BD1AEBD00A86CF8 /* MatchFormatStorageView.swift */, + FFD783FE2B91BA42000F62A6 /* PadelClubView.swift */, ); path = Toolbox; sourceTree = ""; diff --git a/PadelClub/Data/Club.swift b/PadelClub/Data/Club.swift index 98fb664..f02328d 100644 --- a/PadelClub/Data/Club.swift +++ b/PadelClub/Data/Club.swift @@ -111,4 +111,42 @@ extension Club { guard let code else { return nil } return URL(string: "https://tenup.fft.fr/club/\(code)") } + + func update(fromClub club: Club) { + self.acronym = club.acronym + self.name = club.name + self.phone = club.phone + self.code = club.code + self.address = club.address + self.city = club.city + self.zipCode = club.zipCode + self.latitude = club.latitude + self.longitude = club.longitude + } + + func hasBeenCreated(by creatorId: String?) -> Bool { + guard let creatorId else { return false } + guard let creator else { return false } + return creatorId == creator + } + + func isFavorite() -> Bool { + DataStore.shared.user?.clubs?.contains(where: { $0 == id }) == true + } + + static func findOrCreate(name: String, code: String?, city: String? = nil, zipCode: String? = nil) -> Club { + + /* + + identify a club : code, name, ?? + + */ + let clubs: [Club] = Store.main.filter(isIncluded: { (code == nil && $0.name == name && $0.city == city && $0.zipCode == zipCode) || $0.code == code }) + + if clubs.isEmpty == false { + return clubs.first! + } else { + return Club(creator: DataStore.shared.user?.id, name: name, code: code) + } + } } diff --git a/PadelClub/Data/Event.swift b/PadelClub/Data/Event.swift index a33e05f..3ecf737 100644 --- a/PadelClub/Data/Event.swift +++ b/PadelClub/Data/Event.swift @@ -37,7 +37,7 @@ class Event: ModelObject, Storable { // self.loserRoundFormat = loserRoundFormat } - var clubObject: Club? { + func clubObject() -> Club? { guard let club else { return nil } return Store.main.findById(club) } diff --git a/PadelClub/Data/Federal/FederalTournament.swift b/PadelClub/Data/Federal/FederalTournament.swift index c9391ec..338ed0f 100644 --- a/PadelClub/Data/Federal/FederalTournament.swift +++ b/PadelClub/Data/Federal/FederalTournament.swift @@ -5,6 +5,7 @@ import Foundation import CoreLocation +import LeStorage enum DayPeriod { case all @@ -18,14 +19,22 @@ struct FederalTournament: Identifiable, Codable { func getEvent() -> Event { var club = DataStore.shared.clubs.first(where: { $0.code == codeClub }) if club == nil { - club = Club(name: clubLabel(), code: codeClub) - try? DataStore.shared.clubs.addOrUpdate(instance: club!) + club = Club.findOrCreate(name: clubLabel(), code: codeClub) + do { + try DataStore.shared.clubs.addOrUpdate(instance: club!) + } catch { + Logger.error(error) + } } var event = DataStore.shared.events.first(where: { $0.tenupId == id.string }) if event == nil { - event = Event(club: club?.id, name: libelle, tenupId: id.string) - try? DataStore.shared.events.addOrUpdate(instance: event!) + event = Event(creator: DataStore.shared.user?.id, club: club?.id, name: libelle, tenupId: id.string) + do { + try DataStore.shared.events.addOrUpdate(instance: event!) + } catch { + Logger.error(error) + } } return event! } diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index 9e70175..0ce195b 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -290,7 +290,7 @@ class Tournament : ModelObject, Storable { startDate <= Date() } - var eventObject: Event? { + func eventObject() -> Event? { guard let event else { return nil } return Store.main.findById(event) } @@ -301,7 +301,7 @@ class Tournament : ModelObject, Storable { } func club() -> Club? { - eventObject?.clubObject + eventObject()?.clubObject() } func locationLabel(_ displayStyle: DisplayStyle = .wide) -> String { @@ -656,7 +656,7 @@ class Tournament : ModelObject, Storable { //todo var clubName: String? { - eventObject?.clubObject?.name + eventObject()?.clubObject()?.name } //todo diff --git a/PadelClub/Data/User.swift b/PadelClub/Data/User.swift index b3b99dc..b2604ef 100644 --- a/PadelClub/Data/User.swift +++ b/PadelClub/Data/User.swift @@ -57,6 +57,15 @@ class User: UserBase, Storable { return try? federalContext.fetch(fetchRequest).first } + func hasClubs() -> Bool { + clubs?.isEmpty == false + } + + func clubsObjects(includeCreated: Bool = false) -> [Club] { + guard let clubs else { return [] } + return Store.main.filter(isIncluded: { (includeCreated && $0.creator == id) || clubs.contains($0.id) }) + } + enum CodingKeys: String, CodingKey { case _id = "id" case _username = "username" diff --git a/PadelClub/Utils/DisplayContext.swift b/PadelClub/Utils/DisplayContext.swift index 7ca52be..29187ed 100644 --- a/PadelClub/Utils/DisplayContext.swift +++ b/PadelClub/Utils/DisplayContext.swift @@ -10,6 +10,7 @@ import Foundation enum DisplayContext { case addition case edition + case lockedForEditing } enum DisplayStyle { diff --git a/PadelClub/ViewModel/MatchScheduler.swift b/PadelClub/ViewModel/MatchScheduler.swift index 6c4708d..85760fc 100644 --- a/PadelClub/ViewModel/MatchScheduler.swift +++ b/PadelClub/ViewModel/MatchScheduler.swift @@ -99,7 +99,7 @@ class MatchScheduler { let groupStageCourtCount = tournament.groupStageCourtCount ?? 1 let groupStages = tournament.groupStages() let numberOfCourtsAvailablePerRotation: Int = tournament.courtCount - courtsUnavailability = tournament.eventObject?.courtsUnavailability + courtsUnavailability = tournament.eventObject()?.courtsUnavailability let matches = groupStages.flatMap({ $0._matches() }) matches.forEach({ @@ -502,7 +502,7 @@ class MatchScheduler { } func updateBracketSchedule(tournament: Tournament, fromRoundId roundId: String?, fromMatchId matchId: String?, startDate: Date) { - courtsUnavailability = tournament.eventObject?.courtsUnavailability + courtsUnavailability = tournament.eventObject()?.courtsUnavailability let upperRounds = tournament.rounds() let allMatches = tournament.allMatches() @@ -607,7 +607,7 @@ class MatchScheduler { } func updateSchedule(tournament: Tournament) { - courtsUnavailability = tournament.eventObject?.courtsUnavailability + courtsUnavailability = tournament.eventObject()?.courtsUnavailability let lastDate = updateGroupStageSchedule(tournament: tournament) updateBracketSchedule(tournament: tournament, fromRoundId: nil, fromMatchId: nil, startDate: lastDate) } diff --git a/PadelClub/ViewModel/NavigationViewModel.swift b/PadelClub/ViewModel/NavigationViewModel.swift index 1a8467e..dfe589c 100644 --- a/PadelClub/ViewModel/NavigationViewModel.swift +++ b/PadelClub/ViewModel/NavigationViewModel.swift @@ -10,6 +10,9 @@ import SwiftUI @Observable class NavigationViewModel { var path = NavigationPath() + var toolboxPath = NavigationPath() + var umpirePath = NavigationPath() + var ongoingPath = NavigationPath() var selectedTab: TabDestination? var agendaDestination: AgendaDestination? = .activity var tournament: Tournament? diff --git a/PadelClub/ViewModel/TabDestination.swift b/PadelClub/ViewModel/TabDestination.swift index b773564..429b2fd 100644 --- a/PadelClub/ViewModel/TabDestination.swift +++ b/PadelClub/ViewModel/TabDestination.swift @@ -13,19 +13,15 @@ enum TabDestination: CaseIterable, Identifiable { } case activity - case eventList case toolbox case tournamentOrganizer case umpire - case padelClub case ongoing var title: String { switch self { case .activity: return "Activité" - case .eventList: - return "Journal" case .ongoing: return "En cours" case .toolbox: @@ -34,8 +30,6 @@ enum TabDestination: CaseIterable, Identifiable { return "Gestionnaire" case .umpire: return "Juge-Arbitre" - case .padelClub: - return "Padel Club" } } @@ -43,8 +37,6 @@ enum TabDestination: CaseIterable, Identifiable { switch self { case .activity: return "calendar.day.timeline.left" - case .eventList: - return "book.closed" case .ongoing: return "figure.tennis" case .toolbox: @@ -53,8 +45,6 @@ enum TabDestination: CaseIterable, Identifiable { return "squares.below.rectangle" case .umpire: return "person.bust" - case .padelClub: - return "shield" } } } diff --git a/PadelClub/Views/Calling/CallMessageCustomizationView.swift b/PadelClub/Views/Calling/CallMessageCustomizationView.swift index 3187722..d6626af 100644 --- a/PadelClub/Views/Calling/CallMessageCustomizationView.swift +++ b/PadelClub/Views/Calling/CallMessageCustomizationView.swift @@ -6,6 +6,7 @@ // import SwiftUI +import LeStorage struct CallMessageCustomizationView: View { @EnvironmentObject var dataStore: DataStore @@ -69,20 +70,9 @@ struct CallMessageCustomizationView: View { } header: { Text("Signature du message") } - - Section { - TextField("Nom du club", text: $customClubName) - .autocorrectionDisabled() - .onSubmit { - if let eventClub = tournament.eventObject?.clubObject { - eventClub.name = customClubName - try? dataStore.clubs.addOrUpdate(instance: eventClub) - } - } - } header: { - Text("Nom du club") - } - + + _clubNameView() + Section { if appSettings.callUseFullCustomMessage { Text(self.computedFullCustomMessage()) @@ -174,6 +164,32 @@ struct CallMessageCustomizationView: View { private func _save() { dataStore.updateSettings() } + + @ViewBuilder + private func _clubNameView() -> some View { + if let eventClub = tournament.eventObject()?.clubObject() { + let hasBeenCreated: Bool = eventClub.hasBeenCreated(by: dataStore.user?.id) + Section { + TextField("Nom du club", text: $customClubName) + .autocorrectionDisabled() + .onSubmit { + eventClub.name = customClubName + do { + try dataStore.clubs.addOrUpdate(instance: eventClub) + } catch { + Logger.error(error) + } + } + .disabled(hasBeenCreated == false) + } header: { + Text("Nom du club") + } footer: { + if hasBeenCreated == false { + Text("Édition impossible, vous n'êtes pas le créateur de ce club.").foregroundStyle(.logoRed) + } + } + } + } func computedFullCustomMessage() -> String { var text = customCallMessageBody.replacingOccurrences(of: "#titre", with: tournament.tournamentTitle()) diff --git a/PadelClub/Views/Club/ClubDetailView.swift b/PadelClub/Views/Club/ClubDetailView.swift index 9a0b310..acaa1aa 100644 --- a/PadelClub/Views/Club/ClubDetailView.swift +++ b/PadelClub/Views/Club/ClubDetailView.swift @@ -14,12 +14,15 @@ struct ClubDetailView: View { @EnvironmentObject var dataStore: DataStore @FocusState var focusedField: Club.CodingKeys? @State private var acronymMode: Club.AcronymMode = .automatic - @State private var updateClubData: Bool = false - - init(club: Club, displayContext: DisplayContext = .edition) { + @State private var city: String + @State private var zipCode: String + + init(club: Club, displayContext: DisplayContext) { _club = Bindable(club) self.displayContext = displayContext _acronymMode = State(wrappedValue: club.shortNameMode()) + _city = State(wrappedValue: club.city ?? "") + _zipCode = State(wrappedValue: club.zipCode ?? "") } var body: some View { @@ -87,31 +90,51 @@ struct ClubDetailView: View { club.acronym = "" } } - } footer: { - Text("Vous pouvez personaliser le nom court ou laisser celui généré par défaut. Le nom court est utile au niveau des liens de diffusions.") - } - - - if club.code == nil || updateClubData { - Section { - NavigationLink { - ClubSearchView(displayContext: .edition, club: club) - } label: { - Label("Chercher dans la base fédérale", systemImage: "magnifyingglass") + + if club.code == nil { + VStack(alignment: .leading, spacing: 0) { + Text("Ville").foregroundStyle(.secondary).font(.caption) + TextField("Ville", text: $city) + .fixedSize() + .focused($focusedField, equals: ._city) + .submitLabel( displayContext == .addition ? .next : .done) + .onSubmit { + if displayContext == .addition { + focusedField = ._zipCode + } + club.city = city + } } - } footer: { - if club.code != nil { - HStack { - Spacer() - Button("annuler", role: .cancel) { - updateClubData = false + .onTapGesture { + focusedField = ._city + } + + VStack(alignment: .leading, spacing: 0) { + Text("Code Postal").foregroundStyle(.secondary).font(.caption) + TextField("Code Postal", text: $zipCode) + .fixedSize() + .focused($focusedField, equals: ._zipCode) + .submitLabel( displayContext == .addition ? .next : .done) + .onSubmit { + club.zipCode = zipCode } - } - } else { - Text("Vous pouvez chercher un club dans la base fédérale et importer les informations directement.") } + .onTapGesture { + focusedField = ._zipCode + } + } + + } footer: { + if displayContext == .lockedForEditing { + Text("Édition impossible, vous n'êtes pas le créateur de ce club.").foregroundStyle(.logoRed) + } else { + Text("Vous pouvez personaliser le nom court ou laisser celui généré par défaut.") } - } else if let federalLink = club.federalLink() { + } + .disabled(displayContext == .lockedForEditing) + + + if let federalLink = club.federalLink() { Section { LabeledContent("Code Club") { Text(club.code ?? "") @@ -119,14 +142,24 @@ struct ClubDetailView: View { LabeledContent("Ville") { Text(club.city ?? "") } + LabeledContent("Code Postal") { + Text(club.zipCode ?? "") + } Link(destination: federalLink) { Text("Fiche du club sur tenup") } - } footer: { - HStack { - Spacer() - Button("modifier", role: .destructive) { - updateClubData = true + } + } + + if displayContext == .edition { + Section { + RowButtonView("Supprimer ce club", role: .destructive) { + do { + try dataStore.clubs.deleteById(club.id) + dataStore.user?.clubs?.removeAll(where: { $0 == club.id }) + try dataStore.userStorage.update() + } catch { + Logger.error(error) } } } @@ -135,27 +168,36 @@ struct ClubDetailView: View { .keyboardType(.alphabet) .autocorrectionDisabled() .defaultFocus($focusedField, ._name, priority: .automatic) - .navigationTitle(displayContext == .edition ? club.name : "Nouveau club") + .navigationTitle(displayContext == .addition ? "Nouveau club" : club.name) .navigationBarTitleDisplayMode(.inline) .toolbar(.visible, for: .navigationBar) .toolbarBackground(.visible, for: .navigationBar) .toolbar { - ToolbarItem(placement: .topBarTrailing) { - Button { - do { - dataStore.user?.clubs?.removeAll(where: { $0.id == club.id }) - try dataStore.userStorage.update() - } catch { - Logger.error(error) + if displayContext == .edition || displayContext == .lockedForEditing { + let isFavorite = club.isFavorite() + ToolbarItem(placement: .topBarTrailing) { + BarButtonView("Favori", icon: isFavorite ? "start" : "star.fill") { + do { + if isFavorite { + dataStore.user?.clubs?.removeAll(where: { $0 == club.id }) + } else { + dataStore.user?.clubs?.append(club.id) + } + try dataStore.userStorage.update() + } catch { + Logger.error(error) + } } - } label: { - LabelDelete() } } } .onDisappear { if displayContext == .edition { - try? dataStore.clubs.addOrUpdate(instance: club) + do { + try dataStore.clubs.addOrUpdate(instance: club) + } catch { + Logger.error(error) + } } } .onAppear { @@ -169,5 +211,5 @@ struct ClubDetailView: View { } #Preview { - ClubDetailView(club: Club.mock()) + ClubDetailView(club: Club.mock(), displayContext: .edition) } diff --git a/PadelClub/Views/Club/ClubRowView.swift b/PadelClub/Views/Club/ClubRowView.swift index a87c51b..f9d5a89 100644 --- a/PadelClub/Views/Club/ClubRowView.swift +++ b/PadelClub/Views/Club/ClubRowView.swift @@ -12,7 +12,8 @@ struct ClubRowView: View { var body: some View { LabeledContent { - + Image(systemName: club.isFavorite() ? "star.fill" : "star") + .foregroundStyle(club.isFavorite() ? .green : .logoRed) } label: { Text(club.name) Text(club.acronym) diff --git a/PadelClub/Views/Club/ClubSearchView.swift b/PadelClub/Views/Club/ClubSearchView.swift index b80e524..4fca530 100644 --- a/PadelClub/Views/Club/ClubSearchView.swift +++ b/PadelClub/Views/Club/ClubSearchView.swift @@ -9,6 +9,7 @@ import SwiftUI import CoreLocation import CoreLocationUI import TipKit +import LeStorage struct ClubSearchView: View { @Environment(\.dismiss) private var dismiss @@ -81,17 +82,29 @@ struct ClubSearchView: View { Section { ForEach(_filteredClubs()) { clubMark in Button { - let clubToEdit = club ?? Club(name: clubMark.nom) - if clubToEdit.name.isEmpty { - clubToEdit.name = clubMark.nom - clubToEdit.acronym = clubToEdit.automaticShortName() + let clubToEdit = club ?? Club.findOrCreate(name: clubMark.nom, code: clubMark.clubID) + + if clubToEdit.creator == dataStore.user?.id && dataStore.user?.id != nil { + if clubToEdit.name.isEmpty { + clubToEdit.name = clubMark.nom + clubToEdit.acronym = clubToEdit.automaticShortName() + } + clubToEdit.code = clubMark.clubID + clubToEdit.latitude = clubMark.lat + clubToEdit.longitude = clubMark.lng + clubToEdit.city = clubMark.ville } - clubToEdit.code = clubMark.clubID - clubToEdit.latitude = clubMark.lat - clubToEdit.longitude = clubMark.lng - clubToEdit.city = clubMark.ville + if displayContext == .addition { - try? dataStore.clubs.addOrUpdate(instance: clubToEdit) + do { + try dataStore.clubs.addOrUpdate(instance: clubToEdit) + if dataStore.user?.clubs?.contains(where: { $0 == clubToEdit.id }) == false { + dataStore.user?.clubs?.append(clubToEdit.id) + try dataStore.userStorage.update() + } + } catch { + Logger.error(error) + } } dismiss() } label: { diff --git a/PadelClub/Views/Club/ClubsView.swift b/PadelClub/Views/Club/ClubsView.swift index dce11a8..c5786f5 100644 --- a/PadelClub/Views/Club/ClubsView.swift +++ b/PadelClub/Views/Club/ClubsView.swift @@ -7,6 +7,7 @@ import SwiftUI import TipKit +import LeStorage struct ClubsView: View { @EnvironmentObject var dataStore: DataStore @@ -18,15 +19,15 @@ struct ClubsView: View { var body: some View { List { - - if dataStore.clubs.isEmpty == false && selection == nil { - Section { - TipView(tip) - .tipStyle(tint: nil) - } - } - - ForEach(dataStore.clubs) { club in +// +// if dataStore.clubs.isEmpty == false && selection == nil { +// Section { +// TipView(tip) +// .tipStyle(tint: nil) +// } +// } + let clubs : [Club] = (dataStore.user?.clubsObjects(includeCreated: true)) ?? [] + ForEach(clubs) { club in if let selection { Button { selection(club) @@ -39,22 +40,22 @@ struct ClubsView: View { .buttonStyle(.plain) } else { NavigationLink { - ClubDetailView(club: club) + ClubDetailView(club: club, displayContext: club.hasBeenCreated(by: dataStore.user?.id) ? .edition : .lockedForEditing) } label: { ClubRowView(club: club) } - .swipeActions(edge: .trailing, allowsFullSwipe: true) { - Button(role: .destructive) { - try? dataStore.clubs.delete(instance: club) - } label: { - LabelDelete() - } - } +// .swipeActions(edge: .trailing, allowsFullSwipe: true) { +// Button(role: .destructive) { +// try? dataStore.clubs.delete(instance: club) +// } label: { +// LabelDelete() +// } +// } } } } .overlay { - if dataStore.clubs.isEmpty { + if dataStore.user == nil || dataStore.user?.hasClubs() == true { ContentUnavailableView { Label("Aucun club", systemImage: "house.and.flag.fill") } description: { diff --git a/PadelClub/Views/Club/CreateClubView.swift b/PadelClub/Views/Club/CreateClubView.swift index 8655200..f455d3e 100644 --- a/PadelClub/Views/Club/CreateClubView.swift +++ b/PadelClub/Views/Club/CreateClubView.swift @@ -6,6 +6,7 @@ // import SwiftUI +import LeStorage struct CreateClubView: View { @Bindable var club: Club @@ -28,7 +29,28 @@ struct CreateClubView: View { } ToolbarItem(placement: .confirmationAction) { ButtonValidateView { - try? dataStore.clubs.addOrUpdate(instance: club) + + let existingOrCreatedClub = Club.findOrCreate(name: club.name, code: club.code, city: club.city, zipCode: club.zipCode) + + //update existing club if rights ok / freshly created club with data input from user + if existingOrCreatedClub.hasBeenCreated(by: dataStore.user?.id) { + existingOrCreatedClub.update(fromClub: club) + do { + try dataStore.clubs.addOrUpdate(instance: existingOrCreatedClub) + } catch { + Logger.error(error) + } + } + + //save into user + do { + if dataStore.user?.clubs?.contains(where: { $0 == existingOrCreatedClub.id }) == false { + dataStore.user?.clubs?.append(existingOrCreatedClub.id) + try dataStore.userStorage.update() + } + } catch { + Logger.error(error) + } dismiss() } .disabled(club.isValid == false) diff --git a/PadelClub/Views/Event/EventCreationView.swift b/PadelClub/Views/Event/EventCreationView.swift index 9bb2938..6689ede 100644 --- a/PadelClub/Views/Event/EventCreationView.swift +++ b/PadelClub/Views/Event/EventCreationView.swift @@ -7,6 +7,7 @@ import SwiftUI import TipKit +import LeStorage struct EventCreationView: View { @Environment(\.dismiss) private var dismiss @@ -89,15 +90,18 @@ struct EventCreationView: View { Section { RowButtonView("Valider") { - if tournaments.count > 1 || eventName.trimmed.isEmpty == false || selectedClub != nil { - let event = Event(name: eventName) - event.club = selectedClub?.id - tournaments.forEach { tournament in - tournament.event = event.id - } - try? dataStore.events.addOrUpdate(instance: event) + let event = Event(creator: dataStore.user?.id, name: eventName) + event.club = selectedClub?.id + tournaments.forEach { tournament in + tournament.event = event.id } + do { + try dataStore.events.addOrUpdate(instance: event) + } catch { + Logger.error(error) + } + tournaments.forEach { tournament in tournament.courtCount = selectedClub?.courts.count ?? 2 tournament.startDate = startingDate @@ -105,7 +109,12 @@ struct EventCreationView: View { tournament.setupFederalSettings() } - try? dataStore.tournaments.addOrUpdate(contentOfs: tournaments) + do { + try dataStore.tournaments.addOrUpdate(contentOfs: tournaments) + } catch { + Logger.error(error) + } + dismiss() navigation.path.append(tournaments.first!) } diff --git a/PadelClub/Views/Navigation/Agenda/ActivityView.swift b/PadelClub/Views/Navigation/Agenda/ActivityView.swift index 55ef80d..26cee95 100644 --- a/PadelClub/Views/Navigation/Agenda/ActivityView.swift +++ b/PadelClub/Views/Navigation/Agenda/ActivityView.swift @@ -113,43 +113,45 @@ struct ActivityView: View { .environment(navigation) .tint(.master) } - .refreshable { - if navigation.agendaDestination == .tenup { - federalDataViewModel.federalTournaments.removeAll() - NetworkFederalService.shared.formId = "" - _gatherFederalTournaments() - } - } +// .refreshable { +// if navigation.agendaDestination == .tenup { +// federalDataViewModel.federalTournaments.removeAll() +// NetworkFederalService.shared.formId = "" +// _gatherFederalTournaments() +// } +// } .task { if navigation.agendaDestination == .tenup - && dataStore.clubs.isEmpty == false + && dataStore.user?.hasClubs() == false && federalDataViewModel.federalTournaments.isEmpty { _gatherFederalTournaments() } } .onChange(of: navigation.agendaDestination) { if navigation.agendaDestination == .tenup - && dataStore.clubs.isEmpty == false + && dataStore.user?.hasClubs() == false && federalDataViewModel.federalTournaments.isEmpty { _gatherFederalTournaments() } } .toolbar { if presentToolbar { - ToolbarItem(placement: .status) { - VStack(spacing: -2) { - if federalDataViewModel.areFiltersEnabled() { - Text(federalDataViewModel.filterStatus()) - } - if let _activityStatus = _activityStatus() { - Text(_activityStatus) - .foregroundStyle(.secondary) + let _activityStatus = _activityStatus() + if federalDataViewModel.areFiltersEnabled() || _activityStatus != nil { + ToolbarItem(placement: .status) { + VStack(spacing: -2) { + if federalDataViewModel.areFiltersEnabled() { + Text(federalDataViewModel.filterStatus()) + } + if let _activityStatus { + Text(_activityStatus) + .foregroundStyle(.secondary) + } } + .font(.footnote) } - .font(.footnote) } - ToolbarItemGroup(placement: .topBarLeading) { Button { switch viewStyle { @@ -212,7 +214,8 @@ struct ActivityView: View { isGatheringFederalTournaments = true Task { do { - try await federalDataViewModel.gatherTournaments(clubs: dataStore.clubs.filter { $0.code != nil }, startDate: .now.startOfMonth) + let clubs : [Club] = (dataStore.user?.clubsObjects()) ?? [] + try await federalDataViewModel.gatherTournaments(clubs: clubs.filter { $0.code != nil }, startDate: .now.startOfMonth) } catch { self.error = error } @@ -250,7 +253,7 @@ struct ActivityView: View { RowButtonView("Créer un nouvel événement") { newTournament = Tournament.newEmptyInstance() } - if dataStore.clubs.isEmpty { + if dataStore.user == nil || dataStore.user?.hasClubs() == true { RowButtonView("Chercher l'un de vos clubs") { presentClubSearchView = true } @@ -271,7 +274,7 @@ struct ActivityView: View { } private func _tenupEmptyView() -> some View { - if dataStore.clubs.isEmpty { + if dataStore.user == nil || dataStore.user?.hasClubs() == true { ContentUnavailableView { Label("Aucun tournoi", systemImage: "shield.slash") } description: { diff --git a/PadelClub/Views/Navigation/Agenda/CalendarView.swift b/PadelClub/Views/Navigation/Agenda/CalendarView.swift index 5a9bfda..e700bf9 100644 --- a/PadelClub/Views/Navigation/Agenda/CalendarView.swift +++ b/PadelClub/Views/Navigation/Agenda/CalendarView.swift @@ -109,14 +109,14 @@ struct CalendarView: View { } label: { Text(day.formatted(.dateTime.day())) .fontWeight(.bold) - .foregroundStyle(.white) + .foregroundStyle((counts[day.dayInt] != nil ? Color.white : Color.black)) .frame(maxWidth: .infinity, minHeight: 40) .background( Circle() .foregroundStyle( Date.now.startOfDay == day.startOfDay - ? .green.opacity(counts[day.dayInt] != nil ? 0.8 : 0.3) - : color.opacity(counts[day.dayInt] != nil ? 0.8 : 0.3) + ? (counts[day.dayInt] != nil ? Color.logoRed : Color.green) + : (counts[day.dayInt] != nil ? Color.master : Color.beige) ) ) .overlay(alignment: .bottomTrailing) { diff --git a/PadelClub/Views/Navigation/Agenda/EventListView.swift b/PadelClub/Views/Navigation/Agenda/EventListView.swift index 944f01d..bc0f872 100644 --- a/PadelClub/Views/Navigation/Agenda/EventListView.swift +++ b/PadelClub/Views/Navigation/Agenda/EventListView.swift @@ -51,7 +51,7 @@ struct EventListView: View { .headerProminence(.increased) .task { if navigation.agendaDestination == .tenup - && dataStore.clubs.isEmpty == false + && dataStore.user?.hasClubs() == false && _tournaments.isEmpty { _gatherFederalTournaments(startDate: section) } @@ -64,7 +64,8 @@ struct EventListView: View { // isGatheringFederalTournaments = true Task { do { - try await federalDataViewModel.gatherTournaments(clubs: dataStore.clubs.filter { $0.code != nil }, startDate: startDate, endDate: startDate.endOfMonth) + let clubs : [Club] = (dataStore.user?.clubsObjects()) ?? [] + try await federalDataViewModel.gatherTournaments(clubs: clubs.filter { $0.code != nil }, startDate: startDate, endDate: startDate.endOfMonth) } catch { Logger.error(error) // self.error = error diff --git a/PadelClub/Views/Navigation/MainView.swift b/PadelClub/Views/Navigation/MainView.swift index 94768e7..4e0af40 100644 --- a/PadelClub/Views/Navigation/MainView.swift +++ b/PadelClub/Views/Navigation/MainView.swift @@ -20,16 +20,32 @@ struct MainView: View { dataStore.appSettings.lastDataSource } - @Environment(\.managedObjectContext) private var viewContext - - @FetchRequest( - sortDescriptors: [], - animation: .default) - private var players: FetchedResults + var selectedTabHandler: Binding { Binding( + get: { navigation.selectedTab }, + set: { + if $0 == navigation.selectedTab { +// switch navigation.selectedTab { +// case .activity: +// navigation.path.removeLast() +// case .toolbox: +// navigation.toolboxPath = NavigationPath() +// case .umpire: +// navigation.umpirePath = NavigationPath() +// case .ongoing: +// navigation.ongoingPath = NavigationPath() +// case .tournamentOrganizer: +// break +// case .none: +// break +// } + } else { + navigation.selectedTab = $0 + } + } + )} var body: some View { - @Bindable var navigation = navigation - TabView(selection: $navigation.selectedTab) { + TabView(selection: selectedTabHandler) { ActivityView() .tabItem(for: .activity) TournamentOrganizerView() diff --git a/PadelClub/Views/Navigation/Ongoing/OngoingView.swift b/PadelClub/Views/Navigation/Ongoing/OngoingView.swift index cfc9646..aec2a16 100644 --- a/PadelClub/Views/Navigation/Ongoing/OngoingView.swift +++ b/PadelClub/Views/Navigation/Ongoing/OngoingView.swift @@ -8,6 +8,7 @@ import SwiftUI struct OngoingView: View { + @Environment(NavigationViewModel.self) private var navigation: NavigationViewModel @EnvironmentObject var dataStore: DataStore var matches: [Match] { @@ -15,7 +16,8 @@ struct OngoingView: View { } var body: some View { - NavigationStack { + @Bindable var navigation = navigation + NavigationStack(path: $navigation.ongoingPath) { List { ForEach(matches) { match in MatchRowView(match: match, matchViewStyle: .feedStyle) diff --git a/PadelClub/Views/Navigation/PadelClubView.swift b/PadelClub/Views/Navigation/Toolbox/PadelClubView.swift similarity index 98% rename from PadelClub/Views/Navigation/PadelClubView.swift rename to PadelClub/Views/Navigation/Toolbox/PadelClubView.swift index 5f81c40..40d0b8f 100644 --- a/PadelClub/Views/Navigation/PadelClubView.swift +++ b/PadelClub/Views/Navigation/Toolbox/PadelClubView.swift @@ -96,7 +96,7 @@ struct PadelClubView: View { } } .headerProminence(.increased) - .navigationTitle(TabDestination.padelClub.title) + .navigationTitle("Source des données fédérales") } @ViewBuilder diff --git a/PadelClub/Views/Navigation/Toolbox/ToolboxView.swift b/PadelClub/Views/Navigation/Toolbox/ToolboxView.swift index a875a2c..3e110e0 100644 --- a/PadelClub/Views/Navigation/Toolbox/ToolboxView.swift +++ b/PadelClub/Views/Navigation/Toolbox/ToolboxView.swift @@ -8,10 +8,13 @@ import SwiftUI struct ToolboxView: View { + @Environment(NavigationViewModel.self) private var navigation: NavigationViewModel + var body: some View { - NavigationStack { + @Bindable var navigation = navigation + NavigationStack(path: $navigation.toolboxPath) { List { - Section { + Section { NavigationLink { SelectablePlayerListView() } label: { diff --git a/PadelClub/Views/Navigation/Umpire/UmpireView.swift b/PadelClub/Views/Navigation/Umpire/UmpireView.swift index 37bddc4..5fe8e34 100644 --- a/PadelClub/Views/Navigation/Umpire/UmpireView.swift +++ b/PadelClub/Views/Navigation/Umpire/UmpireView.swift @@ -7,8 +7,10 @@ import SwiftUI import CoreLocation +import LeStorage struct UmpireView: View { + @Environment(NavigationViewModel.self) private var navigation: NavigationViewModel @EnvironmentObject var dataStore: DataStore var lastDataSource: String? { dataStore.appSettings.lastDataSource @@ -24,7 +26,8 @@ struct UmpireView: View { } var body: some View { - NavigationStack { + @Bindable var navigation = navigation + NavigationStack(path: $navigation.umpirePath) { List { PurchaseListView() @@ -57,14 +60,19 @@ struct UmpireView: View { SelectablePlayerListView(allowSelection: 1, playerSelectionAction: { players in if let player = players.first { user.licenceId = player.license - let club = dataStore.clubs.first(where: { $0.code == player.clubCode }) ?? Club(name: player.clubName!, code: player.clubCode!) - try? dataStore.clubs.addOrUpdate(instance: club) - if user.clubs == nil { - user.clubs = [club.id] - } else { - user.clubs!.insert(club.id, at: 0) + let userClub = Club.findOrCreate(name: player.clubName!, code: player.clubCode) + do { + try dataStore.clubs.addOrUpdate(instance: userClub) + + if user.clubs == nil { + user.clubs = [userClub.id] + } else { + user.clubs!.insert(userClub.id, at: 0) + } + try dataStore.userStorage.update() + } catch { + Logger.error(error) } - dataStore.setUser(user) } }) } label: { @@ -81,24 +89,12 @@ struct UmpireView: View { } else { Button("supprimer", role: .destructive) { user.licenceId = nil - dataStore.setUser(user) - } - } - } - - if let clubs = user.clubs { - Section { - ForEach(clubs) { clubId in - if let club = dataStore.clubs.findById(clubId) { - NavigationLink { - ClubDetailView(club: club, displayContext: .edition) - } label: { - ClubRowView(club: club) - } + do { + try dataStore.userStorage.update() + } catch { + Logger.error(error) } } - } header: { - Text("Mes clubs") } } } @@ -108,11 +104,13 @@ struct UmpireView: View { ClubsView() } label: { LabeledContent { - Text(dataStore.clubs.count.formatted()) + Text((dataStore.user?.clubs ?? []).count.formatted()) } label: { Label("Mes clubs", systemImage: "house.and.flag.circle.fill") } } + } footer: { + Text("Il s'agit des clubs qui sont utilisés pour récupérer les tournois tenup.") } Section { diff --git a/PadelClub/Views/Planning/PlanningSettingsView.swift b/PadelClub/Views/Planning/PlanningSettingsView.swift index 1b47663..42d5218 100644 --- a/PadelClub/Views/Planning/PlanningSettingsView.swift +++ b/PadelClub/Views/Planning/PlanningSettingsView.swift @@ -64,7 +64,7 @@ struct PlanningSettingsView: View { TournamentFieldsManagerView(localizedStringKey: "Terrains par poule", count: $groupStageCourtCount, max: tournament.maximumCourtsPerGroupSage()) } - if let event = tournament.eventObject { + if let event = tournament.eventObject() { NavigationLink { CourtAvailabilitySettingsView(event: event) .environment(tournament) diff --git a/PadelClub/Views/Shared/TournamentFilterView.swift b/PadelClub/Views/Shared/TournamentFilterView.swift index 7ea8c1d..3a9a0ff 100644 --- a/PadelClub/Views/Shared/TournamentFilterView.swift +++ b/PadelClub/Views/Shared/TournamentFilterView.swift @@ -27,26 +27,29 @@ struct TournamentFilterView: View { 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!) + let clubs : [Club] = (dataStore.user?.clubsObjects()) ?? [] + if clubs.filter({ $0.code != nil }).isEmpty == false { + Section { + ForEach(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: { - if selectedClubs.contains(club.code!) { - Image(systemName: "checkmark.circle.fill") - } + Text(club.clubTitle()) } - } label: { - Text(club.clubTitle()) } + } header: { + Text("Clubs") } - } header: { - Text("Clubs") } Section { ForEach(TournamentLevel.allCases) { level in diff --git a/PadelClub/Views/Tournament/Screen/Components/TournamentClubSettingsView.swift b/PadelClub/Views/Tournament/Screen/Components/TournamentClubSettingsView.swift index 7d8b320..8169b8a 100644 --- a/PadelClub/Views/Tournament/Screen/Components/TournamentClubSettingsView.swift +++ b/PadelClub/Views/Tournament/Screen/Components/TournamentClubSettingsView.swift @@ -16,12 +16,12 @@ struct TournamentClubSettingsView: View { var body: some View { @Bindable var tournament = tournament List { - let event = tournament.eventObject - let selectedClub = event?.clubObject + let event = tournament.eventObject() + let selectedClub = event?.clubObject() Section { if let selectedClub { NavigationLink { - ClubDetailView(club: selectedClub, displayContext: .edition) + ClubDetailView(club: selectedClub, displayContext: selectedClub.hasBeenCreated(by: dataStore.user?.id) ? .edition : .lockedForEditing) } label: { ClubRowView(club: selectedClub) } @@ -30,11 +30,11 @@ struct TournamentClubSettingsView: View { ClubsView() { club in if let event { event.club = club.id - try? dataStore.events.addOrUpdate(instance: event) - } else { - let event = Event(club: club.id) - tournament.event = event.id - try? dataStore.events.addOrUpdate(instance: event) + do { + try dataStore.events.addOrUpdate(instance: event) + } catch { + Logger.error(error) + } } } } label: { diff --git a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift index d0d202e..e3a828a 100644 --- a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift +++ b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift @@ -689,13 +689,13 @@ struct InscriptionManagerView: View { Divider() NavigationLink { ClubsView() { club in - if let event = tournament.eventObject { + if let event = tournament.eventObject() { event.club = club.id - try? dataStore.events.addOrUpdate(instance: event) - } else { - let event = Event(club: club.id) - tournament.event = event.id - try? dataStore.events.addOrUpdate(instance: event) + do { + try dataStore.events.addOrUpdate(instance: event) + } catch { + Logger.error(error) + } } _save() } @@ -710,13 +710,13 @@ struct InscriptionManagerView: View { } else { NavigationLink { ClubsView() { club in - if let event = tournament.eventObject { + if let event = tournament.eventObject() { event.club = club.id - try? dataStore.events.addOrUpdate(instance: event) - } else { - let event = Event(club: club.id) - tournament.event = event.id - try? dataStore.events.addOrUpdate(instance: event) + do { + try dataStore.events.addOrUpdate(instance: event) + } catch { + Logger.error(error) + } } _save() }