setup final clubs management

multistore
Razmig Sarkissian 2 years ago
parent 963d110f23
commit a61e16123c
  1. 2
      PadelClub.xcodeproj/project.pbxproj
  2. 38
      PadelClub/Data/Club.swift
  3. 2
      PadelClub/Data/Event.swift
  4. 17
      PadelClub/Data/Federal/FederalTournament.swift
  5. 6
      PadelClub/Data/Tournament.swift
  6. 9
      PadelClub/Data/User.swift
  7. 1
      PadelClub/Utils/DisplayContext.swift
  8. 6
      PadelClub/ViewModel/MatchScheduler.swift
  9. 3
      PadelClub/ViewModel/NavigationViewModel.swift
  10. 10
      PadelClub/ViewModel/TabDestination.swift
  11. 40
      PadelClub/Views/Calling/CallMessageCustomizationView.swift
  12. 104
      PadelClub/Views/Club/ClubDetailView.swift
  13. 3
      PadelClub/Views/Club/ClubRowView.swift
  14. 17
      PadelClub/Views/Club/ClubSearchView.swift
  15. 37
      PadelClub/Views/Club/ClubsView.swift
  16. 24
      PadelClub/Views/Club/CreateClubView.swift
  17. 17
      PadelClub/Views/Event/EventCreationView.swift
  18. 31
      PadelClub/Views/Navigation/Agenda/ActivityView.swift
  19. 6
      PadelClub/Views/Navigation/Agenda/CalendarView.swift
  20. 5
      PadelClub/Views/Navigation/Agenda/EventListView.swift
  21. 32
      PadelClub/Views/Navigation/MainView.swift
  22. 4
      PadelClub/Views/Navigation/Ongoing/OngoingView.swift
  23. 2
      PadelClub/Views/Navigation/Toolbox/PadelClubView.swift
  24. 5
      PadelClub/Views/Navigation/Toolbox/ToolboxView.swift
  25. 44
      PadelClub/Views/Navigation/Umpire/UmpireView.swift
  26. 2
      PadelClub/Views/Planning/PlanningSettingsView.swift
  27. 5
      PadelClub/Views/Shared/TournamentFilterView.swift
  28. 16
      PadelClub/Views/Tournament/Screen/Components/TournamentClubSettingsView.swift
  29. 24
      PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift

@ -896,7 +896,6 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
FF59FFB62B90EFBF0061EFF9 /* MainView.swift */, FF59FFB62B90EFBF0061EFF9 /* MainView.swift */,
FFD783FE2B91BA42000F62A6 /* PadelClubView.swift */,
FFD783FB2B91B919000F62A6 /* Agenda */, FFD783FB2B91B919000F62A6 /* Agenda */,
FF3F74FA2B91A04B004CFE0E /* Organizer */, FF3F74FA2B91A04B004CFE0E /* Organizer */,
FF3F74FB2B91A060004CFE0E /* Toolbox */, FF3F74FB2B91A060004CFE0E /* Toolbox */,
@ -963,6 +962,7 @@
FF025AE62BD1111000A86CF8 /* GlobalSettingsView.swift */, FF025AE62BD1111000A86CF8 /* GlobalSettingsView.swift */,
FF025AEE2BD1AE9400A86CF8 /* DurationSettingsView.swift */, FF025AEE2BD1AE9400A86CF8 /* DurationSettingsView.swift */,
FF025AF02BD1AEBD00A86CF8 /* MatchFormatStorageView.swift */, FF025AF02BD1AEBD00A86CF8 /* MatchFormatStorageView.swift */,
FFD783FE2B91BA42000F62A6 /* PadelClubView.swift */,
); );
path = Toolbox; path = Toolbox;
sourceTree = "<group>"; sourceTree = "<group>";

@ -111,4 +111,42 @@ extension Club {
guard let code else { return nil } guard let code else { return nil }
return URL(string: "https://tenup.fft.fr/club/\(code)") 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)
}
}
} }

@ -37,7 +37,7 @@ class Event: ModelObject, Storable {
// self.loserRoundFormat = loserRoundFormat // self.loserRoundFormat = loserRoundFormat
} }
var clubObject: Club? { func clubObject() -> Club? {
guard let club else { return nil } guard let club else { return nil }
return Store.main.findById(club) return Store.main.findById(club)
} }

@ -5,6 +5,7 @@
import Foundation import Foundation
import CoreLocation import CoreLocation
import LeStorage
enum DayPeriod { enum DayPeriod {
case all case all
@ -18,14 +19,22 @@ struct FederalTournament: Identifiable, Codable {
func getEvent() -> Event { func getEvent() -> Event {
var club = DataStore.shared.clubs.first(where: { $0.code == codeClub }) var club = DataStore.shared.clubs.first(where: { $0.code == codeClub })
if club == nil { if club == nil {
club = Club(name: clubLabel(), code: codeClub) club = Club.findOrCreate(name: clubLabel(), code: codeClub)
try? DataStore.shared.clubs.addOrUpdate(instance: club!) do {
try DataStore.shared.clubs.addOrUpdate(instance: club!)
} catch {
Logger.error(error)
}
} }
var event = DataStore.shared.events.first(where: { $0.tenupId == id.string }) var event = DataStore.shared.events.first(where: { $0.tenupId == id.string })
if event == nil { if event == nil {
event = Event(club: club?.id, name: libelle, tenupId: id.string) event = Event(creator: DataStore.shared.user?.id, club: club?.id, name: libelle, tenupId: id.string)
try? DataStore.shared.events.addOrUpdate(instance: event!) do {
try DataStore.shared.events.addOrUpdate(instance: event!)
} catch {
Logger.error(error)
}
} }
return event! return event!
} }

@ -290,7 +290,7 @@ class Tournament : ModelObject, Storable {
startDate <= Date() startDate <= Date()
} }
var eventObject: Event? { func eventObject() -> Event? {
guard let event else { return nil } guard let event else { return nil }
return Store.main.findById(event) return Store.main.findById(event)
} }
@ -301,7 +301,7 @@ class Tournament : ModelObject, Storable {
} }
func club() -> Club? { func club() -> Club? {
eventObject?.clubObject eventObject()?.clubObject()
} }
func locationLabel(_ displayStyle: DisplayStyle = .wide) -> String { func locationLabel(_ displayStyle: DisplayStyle = .wide) -> String {
@ -656,7 +656,7 @@ class Tournament : ModelObject, Storable {
//todo //todo
var clubName: String? { var clubName: String? {
eventObject?.clubObject?.name eventObject()?.clubObject()?.name
} }
//todo //todo

@ -57,6 +57,15 @@ class User: UserBase, Storable {
return try? federalContext.fetch(fetchRequest).first 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 { enum CodingKeys: String, CodingKey {
case _id = "id" case _id = "id"
case _username = "username" case _username = "username"

@ -10,6 +10,7 @@ import Foundation
enum DisplayContext { enum DisplayContext {
case addition case addition
case edition case edition
case lockedForEditing
} }
enum DisplayStyle { enum DisplayStyle {

@ -99,7 +99,7 @@ class MatchScheduler {
let groupStageCourtCount = tournament.groupStageCourtCount ?? 1 let groupStageCourtCount = tournament.groupStageCourtCount ?? 1
let groupStages = tournament.groupStages() let groupStages = tournament.groupStages()
let numberOfCourtsAvailablePerRotation: Int = tournament.courtCount let numberOfCourtsAvailablePerRotation: Int = tournament.courtCount
courtsUnavailability = tournament.eventObject?.courtsUnavailability courtsUnavailability = tournament.eventObject()?.courtsUnavailability
let matches = groupStages.flatMap({ $0._matches() }) let matches = groupStages.flatMap({ $0._matches() })
matches.forEach({ matches.forEach({
@ -502,7 +502,7 @@ class MatchScheduler {
} }
func updateBracketSchedule(tournament: Tournament, fromRoundId roundId: String?, fromMatchId matchId: String?, startDate: Date) { 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 upperRounds = tournament.rounds()
let allMatches = tournament.allMatches() let allMatches = tournament.allMatches()
@ -607,7 +607,7 @@ class MatchScheduler {
} }
func updateSchedule(tournament: Tournament) { func updateSchedule(tournament: Tournament) {
courtsUnavailability = tournament.eventObject?.courtsUnavailability courtsUnavailability = tournament.eventObject()?.courtsUnavailability
let lastDate = updateGroupStageSchedule(tournament: tournament) let lastDate = updateGroupStageSchedule(tournament: tournament)
updateBracketSchedule(tournament: tournament, fromRoundId: nil, fromMatchId: nil, startDate: lastDate) updateBracketSchedule(tournament: tournament, fromRoundId: nil, fromMatchId: nil, startDate: lastDate)
} }

@ -10,6 +10,9 @@ import SwiftUI
@Observable @Observable
class NavigationViewModel { class NavigationViewModel {
var path = NavigationPath() var path = NavigationPath()
var toolboxPath = NavigationPath()
var umpirePath = NavigationPath()
var ongoingPath = NavigationPath()
var selectedTab: TabDestination? var selectedTab: TabDestination?
var agendaDestination: AgendaDestination? = .activity var agendaDestination: AgendaDestination? = .activity
var tournament: Tournament? var tournament: Tournament?

@ -13,19 +13,15 @@ enum TabDestination: CaseIterable, Identifiable {
} }
case activity case activity
case eventList
case toolbox case toolbox
case tournamentOrganizer case tournamentOrganizer
case umpire case umpire
case padelClub
case ongoing case ongoing
var title: String { var title: String {
switch self { switch self {
case .activity: case .activity:
return "Activité" return "Activité"
case .eventList:
return "Journal"
case .ongoing: case .ongoing:
return "En cours" return "En cours"
case .toolbox: case .toolbox:
@ -34,8 +30,6 @@ enum TabDestination: CaseIterable, Identifiable {
return "Gestionnaire" return "Gestionnaire"
case .umpire: case .umpire:
return "Juge-Arbitre" return "Juge-Arbitre"
case .padelClub:
return "Padel Club"
} }
} }
@ -43,8 +37,6 @@ enum TabDestination: CaseIterable, Identifiable {
switch self { switch self {
case .activity: case .activity:
return "calendar.day.timeline.left" return "calendar.day.timeline.left"
case .eventList:
return "book.closed"
case .ongoing: case .ongoing:
return "figure.tennis" return "figure.tennis"
case .toolbox: case .toolbox:
@ -53,8 +45,6 @@ enum TabDestination: CaseIterable, Identifiable {
return "squares.below.rectangle" return "squares.below.rectangle"
case .umpire: case .umpire:
return "person.bust" return "person.bust"
case .padelClub:
return "shield"
} }
} }
} }

@ -6,6 +6,7 @@
// //
import SwiftUI import SwiftUI
import LeStorage
struct CallMessageCustomizationView: View { struct CallMessageCustomizationView: View {
@EnvironmentObject var dataStore: DataStore @EnvironmentObject var dataStore: DataStore
@ -70,18 +71,7 @@ struct CallMessageCustomizationView: View {
Text("Signature du message") Text("Signature du message")
} }
Section { _clubNameView()
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")
}
Section { Section {
if appSettings.callUseFullCustomMessage { if appSettings.callUseFullCustomMessage {
@ -175,6 +165,32 @@ struct CallMessageCustomizationView: View {
dataStore.updateSettings() 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 { func computedFullCustomMessage() -> String {
var text = customCallMessageBody.replacingOccurrences(of: "#titre", with: tournament.tournamentTitle()) var text = customCallMessageBody.replacingOccurrences(of: "#titre", with: tournament.tournamentTitle())
text = text.replacingOccurrences(of: "#club", with: clubName) text = text.replacingOccurrences(of: "#club", with: clubName)

@ -14,12 +14,15 @@ struct ClubDetailView: View {
@EnvironmentObject var dataStore: DataStore @EnvironmentObject var dataStore: DataStore
@FocusState var focusedField: Club.CodingKeys? @FocusState var focusedField: Club.CodingKeys?
@State private var acronymMode: Club.AcronymMode = .automatic @State private var acronymMode: Club.AcronymMode = .automatic
@State private var updateClubData: Bool = false @State private var city: String
@State private var zipCode: String
init(club: Club, displayContext: DisplayContext = .edition) { init(club: Club, displayContext: DisplayContext) {
_club = Bindable(club) _club = Bindable(club)
self.displayContext = displayContext self.displayContext = displayContext
_acronymMode = State(wrappedValue: club.shortNameMode()) _acronymMode = State(wrappedValue: club.shortNameMode())
_city = State(wrappedValue: club.city ?? "")
_zipCode = State(wrappedValue: club.zipCode ?? "")
} }
var body: some View { var body: some View {
@ -87,31 +90,51 @@ struct ClubDetailView: View {
club.acronym = "" 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 {
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
}
}
.onTapGesture {
focusedField = ._city
}
if club.code == nil || updateClubData { VStack(alignment: .leading, spacing: 0) {
Section { Text("Code Postal").foregroundStyle(.secondary).font(.caption)
NavigationLink { TextField("Code Postal", text: $zipCode)
ClubSearchView(displayContext: .edition, club: club) .fixedSize()
} label: { .focused($focusedField, equals: ._zipCode)
Label("Chercher dans la base fédérale", systemImage: "magnifyingglass") .submitLabel( displayContext == .addition ? .next : .done)
.onSubmit {
club.zipCode = zipCode
} }
} footer: {
if club.code != nil {
HStack {
Spacer()
Button("annuler", role: .cancel) {
updateClubData = false
} }
.onTapGesture {
focusedField = ._zipCode
} }
}
} footer: {
if displayContext == .lockedForEditing {
Text("Édition impossible, vous n'êtes pas le créateur de ce club.").foregroundStyle(.logoRed)
} else { } else {
Text("Vous pouvez chercher un club dans la base fédérale et importer les informations directement.") 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 { Section {
LabeledContent("Code Club") { LabeledContent("Code Club") {
Text(club.code ?? "") Text(club.code ?? "")
@ -119,14 +142,24 @@ struct ClubDetailView: View {
LabeledContent("Ville") { LabeledContent("Ville") {
Text(club.city ?? "") Text(club.city ?? "")
} }
LabeledContent("Code Postal") {
Text(club.zipCode ?? "")
}
Link(destination: federalLink) { Link(destination: federalLink) {
Text("Fiche du club sur tenup") Text("Fiche du club sur tenup")
} }
} footer: { }
HStack { }
Spacer()
Button("modifier", role: .destructive) { if displayContext == .edition {
updateClubData = true 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) .keyboardType(.alphabet)
.autocorrectionDisabled() .autocorrectionDisabled()
.defaultFocus($focusedField, ._name, priority: .automatic) .defaultFocus($focusedField, ._name, priority: .automatic)
.navigationTitle(displayContext == .edition ? club.name : "Nouveau club") .navigationTitle(displayContext == .addition ? "Nouveau club" : club.name)
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.toolbar(.visible, for: .navigationBar) .toolbar(.visible, for: .navigationBar)
.toolbarBackground(.visible, for: .navigationBar) .toolbarBackground(.visible, for: .navigationBar)
.toolbar { .toolbar {
if displayContext == .edition || displayContext == .lockedForEditing {
let isFavorite = club.isFavorite()
ToolbarItem(placement: .topBarTrailing) { ToolbarItem(placement: .topBarTrailing) {
Button { BarButtonView("Favori", icon: isFavorite ? "start" : "star.fill") {
do { do {
dataStore.user?.clubs?.removeAll(where: { $0.id == club.id }) if isFavorite {
dataStore.user?.clubs?.removeAll(where: { $0 == club.id })
} else {
dataStore.user?.clubs?.append(club.id)
}
try dataStore.userStorage.update() try dataStore.userStorage.update()
} catch { } catch {
Logger.error(error) Logger.error(error)
} }
} label: { }
LabelDelete()
} }
} }
} }
.onDisappear { .onDisappear {
if displayContext == .edition { if displayContext == .edition {
try? dataStore.clubs.addOrUpdate(instance: club) do {
try dataStore.clubs.addOrUpdate(instance: club)
} catch {
Logger.error(error)
}
} }
} }
.onAppear { .onAppear {
@ -169,5 +211,5 @@ struct ClubDetailView: View {
} }
#Preview { #Preview {
ClubDetailView(club: Club.mock()) ClubDetailView(club: Club.mock(), displayContext: .edition)
} }

@ -12,7 +12,8 @@ struct ClubRowView: View {
var body: some View { var body: some View {
LabeledContent { LabeledContent {
Image(systemName: club.isFavorite() ? "star.fill" : "star")
.foregroundStyle(club.isFavorite() ? .green : .logoRed)
} label: { } label: {
Text(club.name) Text(club.name)
Text(club.acronym) Text(club.acronym)

@ -9,6 +9,7 @@ import SwiftUI
import CoreLocation import CoreLocation
import CoreLocationUI import CoreLocationUI
import TipKit import TipKit
import LeStorage
struct ClubSearchView: View { struct ClubSearchView: View {
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
@ -81,7 +82,9 @@ struct ClubSearchView: View {
Section { Section {
ForEach(_filteredClubs()) { clubMark in ForEach(_filteredClubs()) { clubMark in
Button { Button {
let clubToEdit = club ?? Club(name: clubMark.nom) 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 { if clubToEdit.name.isEmpty {
clubToEdit.name = clubMark.nom clubToEdit.name = clubMark.nom
clubToEdit.acronym = clubToEdit.automaticShortName() clubToEdit.acronym = clubToEdit.automaticShortName()
@ -90,8 +93,18 @@ struct ClubSearchView: View {
clubToEdit.latitude = clubMark.lat clubToEdit.latitude = clubMark.lat
clubToEdit.longitude = clubMark.lng clubToEdit.longitude = clubMark.lng
clubToEdit.city = clubMark.ville clubToEdit.city = clubMark.ville
}
if displayContext == .addition { 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() dismiss()
} label: { } label: {

@ -7,6 +7,7 @@
import SwiftUI import SwiftUI
import TipKit import TipKit
import LeStorage
struct ClubsView: View { struct ClubsView: View {
@EnvironmentObject var dataStore: DataStore @EnvironmentObject var dataStore: DataStore
@ -18,15 +19,15 @@ struct ClubsView: View {
var body: some View { var body: some View {
List { List {
//
if dataStore.clubs.isEmpty == false && selection == nil { // if dataStore.clubs.isEmpty == false && selection == nil {
Section { // Section {
TipView(tip) // TipView(tip)
.tipStyle(tint: nil) // .tipStyle(tint: nil)
} // }
} // }
let clubs : [Club] = (dataStore.user?.clubsObjects(includeCreated: true)) ?? []
ForEach(dataStore.clubs) { club in ForEach(clubs) { club in
if let selection { if let selection {
Button { Button {
selection(club) selection(club)
@ -39,22 +40,22 @@ struct ClubsView: View {
.buttonStyle(.plain) .buttonStyle(.plain)
} else { } else {
NavigationLink { NavigationLink {
ClubDetailView(club: club) ClubDetailView(club: club, displayContext: club.hasBeenCreated(by: dataStore.user?.id) ? .edition : .lockedForEditing)
} label: { } label: {
ClubRowView(club: club) ClubRowView(club: club)
} }
.swipeActions(edge: .trailing, allowsFullSwipe: true) { // .swipeActions(edge: .trailing, allowsFullSwipe: true) {
Button(role: .destructive) { // Button(role: .destructive) {
try? dataStore.clubs.delete(instance: club) // try? dataStore.clubs.delete(instance: club)
} label: { // } label: {
LabelDelete() // LabelDelete()
} // }
} // }
} }
} }
} }
.overlay { .overlay {
if dataStore.clubs.isEmpty { if dataStore.user == nil || dataStore.user?.hasClubs() == true {
ContentUnavailableView { ContentUnavailableView {
Label("Aucun club", systemImage: "house.and.flag.fill") Label("Aucun club", systemImage: "house.and.flag.fill")
} description: { } description: {

@ -6,6 +6,7 @@
// //
import SwiftUI import SwiftUI
import LeStorage
struct CreateClubView: View { struct CreateClubView: View {
@Bindable var club: Club @Bindable var club: Club
@ -28,7 +29,28 @@ struct CreateClubView: View {
} }
ToolbarItem(placement: .confirmationAction) { ToolbarItem(placement: .confirmationAction) {
ButtonValidateView { 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() dismiss()
} }
.disabled(club.isValid == false) .disabled(club.isValid == false)

@ -7,6 +7,7 @@
import SwiftUI import SwiftUI
import TipKit import TipKit
import LeStorage
struct EventCreationView: View { struct EventCreationView: View {
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
@ -89,13 +90,16 @@ struct EventCreationView: View {
Section { Section {
RowButtonView("Valider") { RowButtonView("Valider") {
if tournaments.count > 1 || eventName.trimmed.isEmpty == false || selectedClub != nil { let event = Event(creator: dataStore.user?.id, name: eventName)
let event = Event(name: eventName)
event.club = selectedClub?.id event.club = selectedClub?.id
tournaments.forEach { tournament in tournaments.forEach { tournament in
tournament.event = event.id tournament.event = event.id
} }
try? dataStore.events.addOrUpdate(instance: event)
do {
try dataStore.events.addOrUpdate(instance: event)
} catch {
Logger.error(error)
} }
tournaments.forEach { tournament in tournaments.forEach { tournament in
@ -105,7 +109,12 @@ struct EventCreationView: View {
tournament.setupFederalSettings() tournament.setupFederalSettings()
} }
try? dataStore.tournaments.addOrUpdate(contentOfs: tournaments) do {
try dataStore.tournaments.addOrUpdate(contentOfs: tournaments)
} catch {
Logger.error(error)
}
dismiss() dismiss()
navigation.path.append(tournaments.first!) navigation.path.append(tournaments.first!)
} }

@ -113,42 +113,44 @@ struct ActivityView: View {
.environment(navigation) .environment(navigation)
.tint(.master) .tint(.master)
} }
.refreshable { // .refreshable {
if navigation.agendaDestination == .tenup { // if navigation.agendaDestination == .tenup {
federalDataViewModel.federalTournaments.removeAll() // federalDataViewModel.federalTournaments.removeAll()
NetworkFederalService.shared.formId = "" // NetworkFederalService.shared.formId = ""
_gatherFederalTournaments() // _gatherFederalTournaments()
} // }
} // }
.task { .task {
if navigation.agendaDestination == .tenup if navigation.agendaDestination == .tenup
&& dataStore.clubs.isEmpty == false && dataStore.user?.hasClubs() == false
&& federalDataViewModel.federalTournaments.isEmpty { && federalDataViewModel.federalTournaments.isEmpty {
_gatherFederalTournaments() _gatherFederalTournaments()
} }
} }
.onChange(of: navigation.agendaDestination) { .onChange(of: navigation.agendaDestination) {
if navigation.agendaDestination == .tenup if navigation.agendaDestination == .tenup
&& dataStore.clubs.isEmpty == false && dataStore.user?.hasClubs() == false
&& federalDataViewModel.federalTournaments.isEmpty { && federalDataViewModel.federalTournaments.isEmpty {
_gatherFederalTournaments() _gatherFederalTournaments()
} }
} }
.toolbar { .toolbar {
if presentToolbar { if presentToolbar {
let _activityStatus = _activityStatus()
if federalDataViewModel.areFiltersEnabled() || _activityStatus != nil {
ToolbarItem(placement: .status) { ToolbarItem(placement: .status) {
VStack(spacing: -2) { VStack(spacing: -2) {
if federalDataViewModel.areFiltersEnabled() { if federalDataViewModel.areFiltersEnabled() {
Text(federalDataViewModel.filterStatus()) Text(federalDataViewModel.filterStatus())
} }
if let _activityStatus = _activityStatus() { if let _activityStatus {
Text(_activityStatus) Text(_activityStatus)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
} }
} }
.font(.footnote) .font(.footnote)
} }
}
ToolbarItemGroup(placement: .topBarLeading) { ToolbarItemGroup(placement: .topBarLeading) {
Button { Button {
@ -212,7 +214,8 @@ struct ActivityView: View {
isGatheringFederalTournaments = true isGatheringFederalTournaments = true
Task { Task {
do { 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 { } catch {
self.error = error self.error = error
} }
@ -250,7 +253,7 @@ struct ActivityView: View {
RowButtonView("Créer un nouvel événement") { RowButtonView("Créer un nouvel événement") {
newTournament = Tournament.newEmptyInstance() newTournament = Tournament.newEmptyInstance()
} }
if dataStore.clubs.isEmpty { if dataStore.user == nil || dataStore.user?.hasClubs() == true {
RowButtonView("Chercher l'un de vos clubs") { RowButtonView("Chercher l'un de vos clubs") {
presentClubSearchView = true presentClubSearchView = true
} }
@ -271,7 +274,7 @@ struct ActivityView: View {
} }
private func _tenupEmptyView() -> some View { private func _tenupEmptyView() -> some View {
if dataStore.clubs.isEmpty { if dataStore.user == nil || dataStore.user?.hasClubs() == true {
ContentUnavailableView { ContentUnavailableView {
Label("Aucun tournoi", systemImage: "shield.slash") Label("Aucun tournoi", systemImage: "shield.slash")
} description: { } description: {

@ -109,14 +109,14 @@ struct CalendarView: View {
} label: { } label: {
Text(day.formatted(.dateTime.day())) Text(day.formatted(.dateTime.day()))
.fontWeight(.bold) .fontWeight(.bold)
.foregroundStyle(.white) .foregroundStyle((counts[day.dayInt] != nil ? Color.white : Color.black))
.frame(maxWidth: .infinity, minHeight: 40) .frame(maxWidth: .infinity, minHeight: 40)
.background( .background(
Circle() Circle()
.foregroundStyle( .foregroundStyle(
Date.now.startOfDay == day.startOfDay Date.now.startOfDay == day.startOfDay
? .green.opacity(counts[day.dayInt] != nil ? 0.8 : 0.3) ? (counts[day.dayInt] != nil ? Color.logoRed : Color.green)
: color.opacity(counts[day.dayInt] != nil ? 0.8 : 0.3) : (counts[day.dayInt] != nil ? Color.master : Color.beige)
) )
) )
.overlay(alignment: .bottomTrailing) { .overlay(alignment: .bottomTrailing) {

@ -51,7 +51,7 @@ struct EventListView: View {
.headerProminence(.increased) .headerProminence(.increased)
.task { .task {
if navigation.agendaDestination == .tenup if navigation.agendaDestination == .tenup
&& dataStore.clubs.isEmpty == false && dataStore.user?.hasClubs() == false
&& _tournaments.isEmpty { && _tournaments.isEmpty {
_gatherFederalTournaments(startDate: section) _gatherFederalTournaments(startDate: section)
} }
@ -64,7 +64,8 @@ struct EventListView: View {
// isGatheringFederalTournaments = true // isGatheringFederalTournaments = true
Task { Task {
do { 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 { } catch {
Logger.error(error) Logger.error(error)
// self.error = error // self.error = error

@ -20,16 +20,32 @@ struct MainView: View {
dataStore.appSettings.lastDataSource dataStore.appSettings.lastDataSource
} }
@Environment(\.managedObjectContext) private var viewContext var selectedTabHandler: Binding<TabDestination?> { Binding(
get: { navigation.selectedTab },
@FetchRequest( set: {
sortDescriptors: [], if $0 == navigation.selectedTab {
animation: .default) // switch navigation.selectedTab {
private var players: FetchedResults<ImportedPlayer> // 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 { var body: some View {
@Bindable var navigation = navigation TabView(selection: selectedTabHandler) {
TabView(selection: $navigation.selectedTab) {
ActivityView() ActivityView()
.tabItem(for: .activity) .tabItem(for: .activity)
TournamentOrganizerView() TournamentOrganizerView()

@ -8,6 +8,7 @@
import SwiftUI import SwiftUI
struct OngoingView: View { struct OngoingView: View {
@Environment(NavigationViewModel.self) private var navigation: NavigationViewModel
@EnvironmentObject var dataStore: DataStore @EnvironmentObject var dataStore: DataStore
var matches: [Match] { var matches: [Match] {
@ -15,7 +16,8 @@ struct OngoingView: View {
} }
var body: some View { var body: some View {
NavigationStack { @Bindable var navigation = navigation
NavigationStack(path: $navigation.ongoingPath) {
List { List {
ForEach(matches) { match in ForEach(matches) { match in
MatchRowView(match: match, matchViewStyle: .feedStyle) MatchRowView(match: match, matchViewStyle: .feedStyle)

@ -96,7 +96,7 @@ struct PadelClubView: View {
} }
} }
.headerProminence(.increased) .headerProminence(.increased)
.navigationTitle(TabDestination.padelClub.title) .navigationTitle("Source des données fédérales")
} }
@ViewBuilder @ViewBuilder

@ -8,8 +8,11 @@
import SwiftUI import SwiftUI
struct ToolboxView: View { struct ToolboxView: View {
@Environment(NavigationViewModel.self) private var navigation: NavigationViewModel
var body: some View { var body: some View {
NavigationStack { @Bindable var navigation = navigation
NavigationStack(path: $navigation.toolboxPath) {
List { List {
Section { Section {
NavigationLink { NavigationLink {

@ -7,8 +7,10 @@
import SwiftUI import SwiftUI
import CoreLocation import CoreLocation
import LeStorage
struct UmpireView: View { struct UmpireView: View {
@Environment(NavigationViewModel.self) private var navigation: NavigationViewModel
@EnvironmentObject var dataStore: DataStore @EnvironmentObject var dataStore: DataStore
var lastDataSource: String? { var lastDataSource: String? {
dataStore.appSettings.lastDataSource dataStore.appSettings.lastDataSource
@ -24,7 +26,8 @@ struct UmpireView: View {
} }
var body: some View { var body: some View {
NavigationStack { @Bindable var navigation = navigation
NavigationStack(path: $navigation.umpirePath) {
List { List {
PurchaseListView() PurchaseListView()
@ -57,14 +60,19 @@ struct UmpireView: View {
SelectablePlayerListView(allowSelection: 1, playerSelectionAction: { players in SelectablePlayerListView(allowSelection: 1, playerSelectionAction: { players in
if let player = players.first { if let player = players.first {
user.licenceId = player.license user.licenceId = player.license
let club = dataStore.clubs.first(where: { $0.code == player.clubCode }) ?? Club(name: player.clubName!, code: player.clubCode!) let userClub = Club.findOrCreate(name: player.clubName!, code: player.clubCode)
try? dataStore.clubs.addOrUpdate(instance: club) do {
try dataStore.clubs.addOrUpdate(instance: userClub)
if user.clubs == nil { if user.clubs == nil {
user.clubs = [club.id] user.clubs = [userClub.id]
} else { } else {
user.clubs!.insert(club.id, at: 0) user.clubs!.insert(userClub.id, at: 0)
}
try dataStore.userStorage.update()
} catch {
Logger.error(error)
} }
dataStore.setUser(user)
} }
}) })
} label: { } label: {
@ -81,25 +89,13 @@ struct UmpireView: View {
} else { } else {
Button("supprimer", role: .destructive) { Button("supprimer", role: .destructive) {
user.licenceId = nil user.licenceId = nil
dataStore.setUser(user) do {
} try dataStore.userStorage.update()
} } catch {
} Logger.error(error)
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)
} }
} }
} }
} header: {
Text("Mes clubs")
}
} }
} }
@ -108,11 +104,13 @@ struct UmpireView: View {
ClubsView() ClubsView()
} label: { } label: {
LabeledContent { LabeledContent {
Text(dataStore.clubs.count.formatted()) Text((dataStore.user?.clubs ?? []).count.formatted())
} label: { } label: {
Label("Mes clubs", systemImage: "house.and.flag.circle.fill") 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 { Section {

@ -64,7 +64,7 @@ struct PlanningSettingsView: View {
TournamentFieldsManagerView(localizedStringKey: "Terrains par poule", count: $groupStageCourtCount, max: tournament.maximumCourtsPerGroupSage()) TournamentFieldsManagerView(localizedStringKey: "Terrains par poule", count: $groupStageCourtCount, max: tournament.maximumCourtsPerGroupSage())
} }
if let event = tournament.eventObject { if let event = tournament.eventObject() {
NavigationLink { NavigationLink {
CourtAvailabilitySettingsView(event: event) CourtAvailabilitySettingsView(event: event)
.environment(tournament) .environment(tournament)

@ -27,8 +27,10 @@ struct TournamentFilterView: View {
var body: some View { var body: some View {
NavigationView { NavigationView {
Form { Form {
let clubs : [Club] = (dataStore.user?.clubsObjects()) ?? []
if clubs.filter({ $0.code != nil }).isEmpty == false {
Section { Section {
ForEach(dataStore.clubs.filter({ $0.code != nil })) { club in ForEach(clubs.filter({ $0.code != nil })) { club in
LabeledContent { LabeledContent {
Button { Button {
if selectedClubs.contains(club.code!) { if selectedClubs.contains(club.code!) {
@ -48,6 +50,7 @@ struct TournamentFilterView: View {
} header: { } header: {
Text("Clubs") Text("Clubs")
} }
}
Section { Section {
ForEach(TournamentLevel.allCases) { level in ForEach(TournamentLevel.allCases) { level in
LabeledContent { LabeledContent {

@ -16,12 +16,12 @@ struct TournamentClubSettingsView: View {
var body: some View { var body: some View {
@Bindable var tournament = tournament @Bindable var tournament = tournament
List { List {
let event = tournament.eventObject let event = tournament.eventObject()
let selectedClub = event?.clubObject let selectedClub = event?.clubObject()
Section { Section {
if let selectedClub { if let selectedClub {
NavigationLink { NavigationLink {
ClubDetailView(club: selectedClub, displayContext: .edition) ClubDetailView(club: selectedClub, displayContext: selectedClub.hasBeenCreated(by: dataStore.user?.id) ? .edition : .lockedForEditing)
} label: { } label: {
ClubRowView(club: selectedClub) ClubRowView(club: selectedClub)
} }
@ -30,11 +30,11 @@ struct TournamentClubSettingsView: View {
ClubsView() { club in ClubsView() { club in
if let event { if let event {
event.club = club.id event.club = club.id
try? dataStore.events.addOrUpdate(instance: event) do {
} else { try dataStore.events.addOrUpdate(instance: event)
let event = Event(club: club.id) } catch {
tournament.event = event.id Logger.error(error)
try? dataStore.events.addOrUpdate(instance: event) }
} }
} }
} label: { } label: {

@ -689,13 +689,13 @@ struct InscriptionManagerView: View {
Divider() Divider()
NavigationLink { NavigationLink {
ClubsView() { club in ClubsView() { club in
if let event = tournament.eventObject { if let event = tournament.eventObject() {
event.club = club.id event.club = club.id
try? dataStore.events.addOrUpdate(instance: event) do {
} else { try dataStore.events.addOrUpdate(instance: event)
let event = Event(club: club.id) } catch {
tournament.event = event.id Logger.error(error)
try? dataStore.events.addOrUpdate(instance: event) }
} }
_save() _save()
} }
@ -710,13 +710,13 @@ struct InscriptionManagerView: View {
} else { } else {
NavigationLink { NavigationLink {
ClubsView() { club in ClubsView() { club in
if let event = tournament.eventObject { if let event = tournament.eventObject() {
event.club = club.id event.club = club.id
try? dataStore.events.addOrUpdate(instance: event) do {
} else { try dataStore.events.addOrUpdate(instance: event)
let event = Event(club: club.id) } catch {
tournament.event = event.id Logger.error(error)
try? dataStore.events.addOrUpdate(instance: event) }
} }
_save() _save()
} }

Loading…
Cancel
Save