Laurent 2 years ago
commit 6996ed5e09
  1. 14
      PadelClub.xcodeproj/project.pbxproj
  2. 38
      PadelClub/Data/Club.swift
  3. 8
      PadelClub/Data/DataStore.swift
  4. 2
      PadelClub/Data/Event.swift
  5. 17
      PadelClub/Data/Federal/FederalTournament.swift
  6. 16
      PadelClub/Data/PlayerRegistration.swift
  7. 2
      PadelClub/Data/TeamRegistration.swift
  8. 14
      PadelClub/Data/Tournament.swift
  9. 25
      PadelClub/Data/User.swift
  10. 2
      PadelClub/Extensions/String+Extensions.swift
  11. 1
      PadelClub/Utils/DisplayContext.swift
  12. 12
      PadelClub/Utils/FileImportManager.swift
  13. 3
      PadelClub/Utils/LocationManager.swift
  14. 2
      PadelClub/Utils/Network/NetworkManager.swift
  15. 35
      PadelClub/Utils/SourceFileManager.swift
  16. 6
      PadelClub/ViewModel/MatchScheduler.swift
  17. 3
      PadelClub/ViewModel/NavigationViewModel.swift
  18. 0
      PadelClub/ViewModel/PresentationContext.swift
  19. 0
      PadelClub/ViewModel/Screen.swift
  20. 10
      PadelClub/ViewModel/TabDestination.swift
  21. 40
      PadelClub/Views/Calling/CallMessageCustomizationView.swift
  22. 4
      PadelClub/Views/Cashier/CashierView.swift
  23. 109
      PadelClub/Views/Club/ClubDetailView.swift
  24. 3
      PadelClub/Views/Club/ClubRowView.swift
  25. 31
      PadelClub/Views/Club/ClubSearchView.swift
  26. 37
      PadelClub/Views/Club/ClubsView.swift
  27. 24
      PadelClub/Views/Club/CreateClubView.swift
  28. 17
      PadelClub/Views/Event/EventCreationView.swift
  29. 2
      PadelClub/Views/GroupStage/Shared/GroupStageTeamReplacementView.swift
  30. 48
      PadelClub/Views/Navigation/Agenda/ActivityView.swift
  31. 6
      PadelClub/Views/Navigation/Agenda/CalendarView.swift
  32. 40
      PadelClub/Views/Navigation/Agenda/EmptyActivityView.swift
  33. 5
      PadelClub/Views/Navigation/Agenda/EventListView.swift
  34. 24
      PadelClub/Views/Navigation/Agenda/WelcomeView.swift
  35. 32
      PadelClub/Views/Navigation/MainView.swift
  36. 4
      PadelClub/Views/Navigation/Ongoing/OngoingView.swift
  37. 30
      PadelClub/Views/Navigation/Toolbox/PadelClubView.swift
  38. 5
      PadelClub/Views/Navigation/Toolbox/ToolboxView.swift
  39. 51
      PadelClub/Views/Navigation/Umpire/UmpireView.swift
  40. 2
      PadelClub/Views/Planning/PlanningSettingsView.swift
  41. 2
      PadelClub/Views/Player/Components/PlayerSexPickerView.swift
  42. 6
      PadelClub/Views/Player/PlayerDetailView.swift
  43. 5
      PadelClub/Views/Shared/TournamentFilterView.swift
  44. 2
      PadelClub/Views/Tournament/FileImportView.swift
  45. 16
      PadelClub/Views/Tournament/Screen/Components/TournamentClubSettingsView.swift
  46. 2
      PadelClub/Views/Tournament/Screen/Components/UpdateSourceRankDateView.swift
  47. 28
      PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift

@ -248,8 +248,6 @@
FFCFC01A2BBC5A8500B82851 /* MatchTypeSmallSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC0192BBC5A8500B82851 /* MatchTypeSmallSelectionView.swift */; }; FFCFC01A2BBC5A8500B82851 /* MatchTypeSmallSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC0192BBC5A8500B82851 /* MatchTypeSmallSelectionView.swift */; };
FFCFC01C2BBC5AAA00B82851 /* SetDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC01B2BBC5AAA00B82851 /* SetDescriptor.swift */; }; FFCFC01C2BBC5AAA00B82851 /* SetDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC01B2BBC5AAA00B82851 /* SetDescriptor.swift */; };
FFD783FF2B91BA42000F62A6 /* PadelClubView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFD783FE2B91BA42000F62A6 /* PadelClubView.swift */; }; FFD783FF2B91BA42000F62A6 /* PadelClubView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFD783FE2B91BA42000F62A6 /* PadelClubView.swift */; };
FFD784022B91C1B4000F62A6 /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFD784012B91C1B4000F62A6 /* WelcomeView.swift */; };
FFD784042B91C280000F62A6 /* EmptyActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFD784032B91C280000F62A6 /* EmptyActivityView.swift */; };
FFDB1C6D2BB2A02000F1E467 /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDB1C6C2BB2A02000F1E467 /* AppSettings.swift */; }; FFDB1C6D2BB2A02000F1E467 /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDB1C6C2BB2A02000F1E467 /* AppSettings.swift */; };
FFDB1C732BB2CFE900F1E467 /* MySortDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDB1C722BB2CFE900F1E467 /* MySortDescriptor.swift */; }; FFDB1C732BB2CFE900F1E467 /* MySortDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDB1C722BB2CFE900F1E467 /* MySortDescriptor.swift */; };
FFDDD40C2B93B2BB00C91A49 /* DeferredViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDDD40B2B93B2BB00C91A49 /* DeferredViewModifier.swift */; }; FFDDD40C2B93B2BB00C91A49 /* DeferredViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDDD40B2B93B2BB00C91A49 /* DeferredViewModifier.swift */; };
@ -546,8 +544,6 @@
FFCFC01B2BBC5AAA00B82851 /* SetDescriptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetDescriptor.swift; sourceTree = "<group>"; }; FFCFC01B2BBC5AAA00B82851 /* SetDescriptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetDescriptor.swift; sourceTree = "<group>"; };
FFD783FE2B91BA42000F62A6 /* PadelClubView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PadelClubView.swift; sourceTree = "<group>"; }; FFD783FE2B91BA42000F62A6 /* PadelClubView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PadelClubView.swift; sourceTree = "<group>"; };
FFD784002B91BF79000F62A6 /* Launch Screen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = "Launch Screen.storyboard"; sourceTree = "<group>"; }; FFD784002B91BF79000F62A6 /* Launch Screen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = "Launch Screen.storyboard"; sourceTree = "<group>"; };
FFD784012B91C1B4000F62A6 /* WelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeView.swift; sourceTree = "<group>"; };
FFD784032B91C280000F62A6 /* EmptyActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyActivityView.swift; sourceTree = "<group>"; };
FFDB1C6C2BB2A02000F1E467 /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = "<group>"; }; FFDB1C6C2BB2A02000F1E467 /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = "<group>"; };
FFDB1C722BB2CFE900F1E467 /* MySortDescriptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MySortDescriptor.swift; sourceTree = "<group>"; }; FFDB1C722BB2CFE900F1E467 /* MySortDescriptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MySortDescriptor.swift; sourceTree = "<group>"; };
FFDDD40B2B93B2BB00C91A49 /* DeferredViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeferredViewModifier.swift; sourceTree = "<group>"; }; FFDDD40B2B93B2BB00C91A49 /* DeferredViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeferredViewModifier.swift; sourceTree = "<group>"; };
@ -900,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 */,
@ -936,8 +931,6 @@
FF3F74F92B91A018004CFE0E /* Screen */ = { FF3F74F92B91A018004CFE0E /* Screen */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
FF6EC8FD2B94792300EA7F5A /* Screen.swift */,
FF6EC8FF2B94794700EA7F5A /* PresentationContext.swift */,
FF70916D2B9108C600AB08DA /* InscriptionManagerView.swift */, FF70916D2B9108C600AB08DA /* InscriptionManagerView.swift */,
FF8F26422BADFE5B00650388 /* TournamentSettingsView.swift */, FF8F26422BADFE5B00650388 /* TournamentSettingsView.swift */,
FF8F26532BAE1E4400650388 /* TableStructureView.swift */, FF8F26532BAE1E4400650388 /* TableStructureView.swift */,
@ -969,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>";
@ -984,6 +978,8 @@
FF3F74FD2B91A087004CFE0E /* ViewModel */ = { FF3F74FD2B91A087004CFE0E /* ViewModel */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
FF6EC8FD2B94792300EA7F5A /* Screen.swift */,
FF6EC8FF2B94794700EA7F5A /* PresentationContext.swift */,
FF7091652B90F0B000AB08DA /* TabDestination.swift */, FF7091652B90F0B000AB08DA /* TabDestination.swift */,
FF025AEC2BD1513700A86CF8 /* AppScreen.swift */, FF025AEC2BD1513700A86CF8 /* AppScreen.swift */,
FF3F74FE2B91A2D4004CFE0E /* AgendaDestination.swift */, FF3F74FE2B91A2D4004CFE0E /* AgendaDestination.swift */,
@ -1176,8 +1172,6 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
FF82CFC82B9132AF00B0CAF2 /* ActivityView.swift */, FF82CFC82B9132AF00B0CAF2 /* ActivityView.swift */,
FFD784032B91C280000F62A6 /* EmptyActivityView.swift */,
FFD784012B91C1B4000F62A6 /* WelcomeView.swift */,
FF59FFB22B90EFAC0061EFF9 /* EventListView.swift */, FF59FFB22B90EFAC0061EFF9 /* EventListView.swift */,
FF5D0D8A2BB4D1E3005CB568 /* CalendarView.swift */, FF5D0D8A2BB4D1E3005CB568 /* CalendarView.swift */,
); );
@ -1596,7 +1590,6 @@
FF9AC3952BE3627B00C2E883 /* GroupStageTeamReplacementView.swift in Sources */, FF9AC3952BE3627B00C2E883 /* GroupStageTeamReplacementView.swift in Sources */,
FF4C7F022BBBD7150031B6A3 /* TabItemModifier.swift in Sources */, FF4C7F022BBBD7150031B6A3 /* TabItemModifier.swift in Sources */,
FFDDD40C2B93B2BB00C91A49 /* DeferredViewModifier.swift in Sources */, FFDDD40C2B93B2BB00C91A49 /* DeferredViewModifier.swift in Sources */,
FFD784042B91C280000F62A6 /* EmptyActivityView.swift in Sources */,
FF0E0B6D2BC254C6005F00A9 /* TournamentScheduleView.swift in Sources */, FF0E0B6D2BC254C6005F00A9 /* TournamentScheduleView.swift in Sources */,
FF025AF12BD1AEBD00A86CF8 /* MatchFormatStorageView.swift in Sources */, FF025AF12BD1AEBD00A86CF8 /* MatchFormatStorageView.swift in Sources */,
FF3F74F62B919E45004CFE0E /* UmpireView.swift in Sources */, FF3F74F62B919E45004CFE0E /* UmpireView.swift in Sources */,
@ -1618,7 +1611,6 @@
FFF964532BC262B000EEF017 /* PlanningSettingsView.swift in Sources */, FFF964532BC262B000EEF017 /* PlanningSettingsView.swift in Sources */,
FFF527D62BC6DDD000FF4EF2 /* MatchScheduleEditorView.swift in Sources */, FFF527D62BC6DDD000FF4EF2 /* MatchScheduleEditorView.swift in Sources */,
FFC91AF92BD6A09100B29808 /* FortuneWheelView.swift in Sources */, FFC91AF92BD6A09100B29808 /* FortuneWheelView.swift in Sources */,
FFD784022B91C1B4000F62A6 /* WelcomeView.swift in Sources */,
FFF8ACD62B923960008466FA /* URL+Extensions.swift in Sources */, FFF8ACD62B923960008466FA /* URL+Extensions.swift in Sources */,
FF089EBD2BB0287D00F0AEC7 /* PlayerView.swift in Sources */, FF089EBD2BB0287D00F0AEC7 /* PlayerView.swift in Sources */,
FF967D032BAEF0C000A9A3BD /* MatchDetailView.swift in Sources */, FF967D032BAEF0C000A9A3BD /* MatchDetailView.swift in Sources */,

@ -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)
}
}
} }

@ -78,7 +78,7 @@ class DataStore: ObservableObject {
// store.addMigration(Migration<TournamentV2, Tournament>(version: 3)) // store.addMigration(Migration<TournamentV2, Tournament>(version: 3))
let indexed : Bool = true let indexed : Bool = true
let synchronized : Bool = false let synchronized : Bool = true
self.clubs = store.registerCollection(synchronized: synchronized, indexed: indexed) self.clubs = store.registerCollection(synchronized: synchronized, indexed: indexed)
self.courts = store.registerCollection(synchronized: synchronized, indexed: indexed) self.courts = store.registerCollection(synchronized: synchronized, indexed: indexed)
self.tournaments = store.registerCollection(synchronized: synchronized, indexed: indexed) self.tournaments = store.registerCollection(synchronized: synchronized, indexed: indexed)
@ -89,10 +89,10 @@ class DataStore: ObservableObject {
self.playerRegistrations = store.registerCollection(synchronized: synchronized, indexed: indexed) self.playerRegistrations = store.registerCollection(synchronized: synchronized, indexed: indexed)
self.rounds = store.registerCollection(synchronized: synchronized, indexed: indexed) self.rounds = store.registerCollection(synchronized: synchronized, indexed: indexed)
self.matches = store.registerCollection(synchronized: synchronized, indexed: indexed) self.matches = store.registerCollection(synchronized: synchronized, indexed: indexed)
self.monthData = store.registerCollection(synchronized: synchronized, indexed: indexed) self.monthData = store.registerCollection(synchronized: false, indexed: indexed)
self.dateIntervals = store.registerCollection(synchronized: synchronized, indexed: indexed) self.dateIntervals = store.registerCollection(synchronized: synchronized, indexed: indexed)
self.userStorage = store.registerObject(synchronized: synchronized) self.userStorage = store.registerObject(synchronized: false)
NotificationCenter.default.addObserver(self, selector: #selector(collectionWasUpdated), name: NSNotification.Name.CollectionDidLoad, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(collectionWasUpdated), name: NSNotification.Name.CollectionDidLoad, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(collectionWasUpdated), name: NSNotification.Name.CollectionDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(collectionWasUpdated), name: NSNotification.Name.CollectionDidChange, object: nil)
@ -111,7 +111,7 @@ class DataStore: ObservableObject {
if let _ = Guard.main.currentPlan { if let _ = Guard.main.currentPlan {
return .creation return .creation
} }
if let user = self.user, user.club != nil { if let user = self.user, user.clubs != nil {
if user.umpireCode != nil { if user.umpireCode != nil {
return .creation return .creation
} else { } else {

@ -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!
} }

@ -31,7 +31,7 @@ class PlayerRegistration: ModelObject, Storable {
var email: String? var email: String?
var birthdate: String? var birthdate: String?
var weight: Int = 0 var computedRank: Int = 0
var source: PlayerDataSource? var source: PlayerDataSource?
var hasArrived: Bool = false var hasArrived: Bool = false
@ -169,8 +169,8 @@ class PlayerRegistration: ModelObject, Storable {
func rankLabel(_ displayStyle: DisplayStyle = .wide) -> String { func rankLabel(_ displayStyle: DisplayStyle = .wide) -> String {
if let rank, rank > 0 { if let rank, rank > 0 {
if rank != weight { if rank != computedRank {
return weight.formatted() + " (" + rank.formatted() + ")" return computedRank.formatted() + " (" + rank.formatted() + ")"
} else { } else {
return rank.formatted() return rank.formatted()
} }
@ -180,7 +180,7 @@ class PlayerRegistration: ModelObject, Storable {
} }
func getRank() -> Int { func getRank() -> Int {
weight computedRank
} }
@MainActor @MainActor
@ -238,13 +238,13 @@ class PlayerRegistration: ModelObject, Storable {
} }
} }
func setWeight(in tournament: Tournament) { func setComputedRank(in tournament: Tournament) {
let currentRank = rank ?? tournament.unrankValue(for: isMalePlayer()) ?? 100_000 let currentRank = rank ?? tournament.unrankValue(for: isMalePlayer()) ?? 100_000
switch tournament.tournamentCategory { switch tournament.tournamentCategory {
case .men: case .men:
weight = isMalePlayer() ? currentRank : currentRank + PlayerRegistration.addon(for: currentRank, manMax: tournament.maleUnrankedValue ?? 0, womanMax: tournament.femaleUnrankedValue ?? 0) computedRank = isMalePlayer() ? currentRank : currentRank + PlayerRegistration.addon(for: currentRank, manMax: tournament.maleUnrankedValue ?? 0, womanMax: tournament.femaleUnrankedValue ?? 0)
default: default:
weight = currentRank computedRank = currentRank
} }
} }
@ -279,7 +279,7 @@ class PlayerRegistration: ModelObject, Storable {
case _birthdate = "birthdate" case _birthdate = "birthdate"
case _phoneNumber = "phoneNumber" case _phoneNumber = "phoneNumber"
case _email = "email" case _email = "email"
case _weight = "weight" case _computedRank = "computedRank"
case _source = "source" case _source = "source"
case _hasArrived = "hasArrived" case _hasArrived = "hasArrived"

@ -268,7 +268,7 @@ class TeamRegistration: ModelObject, Storable {
func setWeight(from players: [PlayerRegistration], inTournamentCategory tournamentCategory: TournamentCategory) { func setWeight(from players: [PlayerRegistration], inTournamentCategory tournamentCategory: TournamentCategory) {
let significantPlayerCount = significantPlayerCount() let significantPlayerCount = significantPlayerCount()
weight = (players.prefix(significantPlayerCount).map { $0.weight } + missingPlayerType(inTournamentCategory: tournamentCategory).map { unrankValue(for: $0 == 1 ? true : false ) }).prefix(significantPlayerCount).reduce(0,+) weight = (players.prefix(significantPlayerCount).map { $0.computedRank } + missingPlayerType(inTournamentCategory: tournamentCategory).map { unrankValue(for: $0 == 1 ? true : false ) }).prefix(significantPlayerCount).reduce(0,+)
} }
func significantPlayerCount() -> Int { func significantPlayerCount() -> Int {

@ -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 {
@ -632,11 +632,11 @@ class Tournament : ModelObject, Storable {
} }
func selectedPlayers() -> [PlayerRegistration] { func selectedPlayers() -> [PlayerRegistration] {
selectedSortedTeams().flatMap { $0.unsortedPlayers() }.sorted(by: \.weight) selectedSortedTeams().flatMap { $0.unsortedPlayers() }.sorted(by: \.computedRank)
} }
func players() -> [PlayerRegistration] { func players() -> [PlayerRegistration] {
unsortedTeams().flatMap { $0.unsortedPlayers() }.sorted(by: \.weight) unsortedTeams().flatMap { $0.unsortedPlayers() }.sorted(by: \.computedRank)
} }
func femalePlayers() -> [PlayerRegistration] { func femalePlayers() -> [PlayerRegistration] {
@ -656,7 +656,7 @@ class Tournament : ModelObject, Storable {
//todo //todo
var clubName: String? { var clubName: String? {
eventObject?.clubObject?.name eventObject()?.clubObject()?.name
} }
//todo //todo
@ -670,7 +670,7 @@ class Tournament : ModelObject, Storable {
} }
return players.filter { player in return players.filter { player in
if player.rank == nil { return false } if player.rank == nil { return false }
if player.weight <= tournamentLevel.minimumPlayerRank(category: tournamentCategory, ageCategory: federalTournamentAge) { if player.computedRank <= tournamentLevel.minimumPlayerRank(category: tournamentCategory, ageCategory: federalTournamentAge) {
return true return true
} else { } else {
return false return false
@ -846,7 +846,7 @@ class Tournament : ModelObject, Storable {
let teams = self.unsortedTeams() let teams = self.unsortedTeams()
teams.forEach { team in teams.forEach { team in
let players = team.unsortedPlayers() let players = team.unsortedPlayers()
players.forEach { $0.setWeight(in: self) } players.forEach { $0.setComputedRank(in: self) }
team.setWeight(from: players, inTournamentCategory: tournamentCategory) team.setWeight(from: players, inTournamentCategory: tournamentCategory)
try? DataStore.shared.playerRegistrations.addOrUpdate(contentOfs: players) try? DataStore.shared.playerRegistrations.addOrUpdate(contentOfs: players)
} }

@ -24,7 +24,7 @@ class User: UserBase, Storable {
public var id: String = Store.randomId() public var id: String = Store.randomId()
public var username: String public var username: String
public var email: String public var email: String
var club: String? var clubs: [String]?
var umpireCode: String? var umpireCode: String?
var licenceId: String? var licenceId: String?
var firstName: String var firstName: String
@ -57,11 +57,32 @@ class User: UserBase, Storable {
return try? federalContext.fetch(fetchRequest).first return try? federalContext.fetch(fetchRequest).first
} }
func hasClubs() -> Bool {
clubs?.isEmpty == false
}
func hasFavoriteClubsAndCreatedClubs() -> Bool {
clubsObjects(includeCreated: true).isEmpty == false
}
func setUserClub(_ userClub: Club) {
if clubs == nil {
clubs = [userClub.id]
} else {
clubs!.insert(userClub.id, at: 0)
}
}
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"
case _email = "email" case _email = "email"
case _club = "club" case _clubs = "clubs"
case _umpireCode = "umpireCode" case _umpireCode = "umpireCode"
case _licenceId = "licenceId" case _licenceId = "licenceId"
case _firstName = "firstName" case _firstName = "firstName"

@ -52,9 +52,11 @@ extension String {
// Extract the first character of each sentence // Extract the first character of each sentence
let firstLetters = sentences.compactMap { sentence -> Character? in let firstLetters = sentences.compactMap { sentence -> Character? in
let trimmedSentence = sentence.trimmingCharacters(in: .whitespacesAndNewlines) let trimmedSentence = sentence.trimmingCharacters(in: .whitespacesAndNewlines)
if trimmedSentence.count > 2 {
if let firstCharacter = trimmedSentence.first { if let firstCharacter = trimmedSentence.first {
return firstCharacter return firstCharacter
} }
}
return nil return nil
} }

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

@ -71,7 +71,7 @@ class FileImportManager {
self.players = Set(players) self.players = Set(players)
self.tournamentCategory = tournamentCategory self.tournamentCategory = tournamentCategory
self.previousTeam = previousTeam self.previousTeam = previousTeam
self.weight = players.map { $0.weight }.reduce(0,+) self.weight = players.map { $0.computedRank }.reduce(0,+)
self.registrationDate = registrationDate self.registrationDate = registrationDate
} }
@ -210,9 +210,9 @@ class FileImportManager {
} }
let playerOne = PlayerRegistration(federalData: Array(resultOne[0...7]), sex: sexPlayerOne, sexUnknown: sexUnknown) let playerOne = PlayerRegistration(federalData: Array(resultOne[0...7]), sex: sexPlayerOne, sexUnknown: sexUnknown)
playerOne.setWeight(in: tournament) playerOne.setComputedRank(in: tournament)
let playerTwo = PlayerRegistration(federalData: Array(resultTwo[0...7]), sex: sexPlayerTwo, sexUnknown: sexUnknown) let playerTwo = PlayerRegistration(federalData: Array(resultTwo[0...7]), sex: sexPlayerTwo, sexUnknown: sexUnknown)
playerTwo.setWeight(in: tournament) playerTwo.setComputedRank(in: tournament)
let team = TeamHolder(players: [playerOne, playerTwo], tournamentCategory: tournamentCategory, previousTeam: tournament.findTeam([playerOne, playerTwo])) let team = TeamHolder(players: [playerOne, playerTwo], tournamentCategory: tournamentCategory, previousTeam: tournament.findTeam([playerOne, playerTwo]))
results.append(team) results.append(team)
} }
@ -256,9 +256,9 @@ class FileImportManager {
} }
let playerOne = PlayerRegistration(federalData: Array(result[0...7]), sex: sexPlayerOne, sexUnknown: sexUnknown) let playerOne = PlayerRegistration(federalData: Array(result[0...7]), sex: sexPlayerOne, sexUnknown: sexUnknown)
playerOne.setWeight(in: tournament) playerOne.setComputedRank(in: tournament)
let playerTwo = PlayerRegistration(federalData: Array(result[8...]), sex: sexPlayerTwo, sexUnknown: sexUnknown) let playerTwo = PlayerRegistration(federalData: Array(result[8...]), sex: sexPlayerTwo, sexUnknown: sexUnknown)
playerTwo.setWeight(in: tournament) playerTwo.setComputedRank(in: tournament)
let team = TeamHolder(players: [playerOne, playerTwo], tournamentCategory: tournamentCategory, previousTeam: tournament.findTeam([playerOne, playerTwo])) let team = TeamHolder(players: [playerOne, playerTwo], tournamentCategory: tournamentCategory, previousTeam: tournament.findTeam([playerOne, playerTwo]))
results.append(team) results.append(team)
@ -281,7 +281,7 @@ class FileImportManager {
let found = try? federalContext.fetch(fetchRequest) let found = try? federalContext.fetch(fetchRequest)
let registeredPlayers = found?.map({ importedPlayer in let registeredPlayers = found?.map({ importedPlayer in
let player = PlayerRegistration(importedPlayer: importedPlayer) let player = PlayerRegistration(importedPlayer: importedPlayer)
player.setWeight(in: tournament) player.setComputedRank(in: tournament)
return player return player
}) })
if let registeredPlayers, registeredPlayers.isEmpty == false { if let registeredPlayers, registeredPlayers.isEmpty == false {

@ -17,7 +17,6 @@ class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
@Published var requestStarted: Bool = false @Published var requestStarted: Bool = false
@Published var userReadableCityOrZipcode: String = "" @Published var userReadableCityOrZipcode: String = ""
@Published var lastError: Error? = nil @Published var lastError: Error? = nil
var shouldRequestLocation: Bool = false
override init() { override init() {
super.init() super.init()
@ -41,13 +40,11 @@ class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
if manager.authorizationStatus == .authorizedWhenInUse || manager.authorizationStatus == .authorizedAlways { if manager.authorizationStatus == .authorizedWhenInUse || manager.authorizationStatus == .authorizedAlways {
if requestStarted == false && shouldRequestLocation {
DispatchQueue.main.async { DispatchQueue.main.async {
self.requestLocation() self.requestLocation()
} }
} }
} }
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("locationManager didFailWithError", error) print("locationManager didFailWithError", error)

@ -22,7 +22,7 @@ class NetworkManager {
let dateString = ["CLASSEMENT-PADEL", fileName, lastDateString].joined(separator: "-") + ".csv" let dateString = ["CLASSEMENT-PADEL", fileName, lastDateString].joined(separator: "-") + ".csv"
let documentsUrl:URL = (FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first as URL?)! let documentsUrl: URL = SourceFileManager.shared.rankingSourceDirectory
let destinationFileUrl = documentsUrl.appendingPathComponent("\(dateString)") let destinationFileUrl = documentsUrl.appendingPathComponent("\(dateString)")
let fileURL = URL(string: "https://padelclub.app/static/\(dateString)") let fileURL = URL(string: "https://padelclub.app/static/\(dateString)")

@ -10,6 +10,31 @@ import Foundation
class SourceFileManager { class SourceFileManager {
static let shared = SourceFileManager() static let shared = SourceFileManager()
init() {
createDirectoryIfNeeded()
}
let rankingSourceDirectory : URL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appending(path: "rankings")
func createDirectoryIfNeeded() {
let fileManager = FileManager.default
do {
let directoryURL = rankingSourceDirectory
// Check if the directory exists
if !fileManager.fileExists(atPath: directoryURL.path) {
// Directory does not exist, create it
try fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil)
print("Directory created at: \(directoryURL)")
} else {
print("Directory already exists at: \(directoryURL)")
}
} catch {
print("Error: \(error)")
}
}
var lastDataSource: String? { var lastDataSource: String? {
DataStore.shared.appSettings.lastDataSource DataStore.shared.appSettings.lastDataSource
} }
@ -117,16 +142,14 @@ class SourceFileManager {
} }
func removeAllFilesFromServer() { func removeAllFilesFromServer() {
let docDir = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true) let allFiles = try! FileManager.default.contentsOfDirectory(at: rankingSourceDirectory, includingPropertiesForKeys: nil)
let allFiles = try! FileManager.default.contentsOfDirectory(at: docDir, includingPropertiesForKeys: nil)
allFiles.filter { $0.pathExtension == "csv" }.forEach { url in allFiles.filter { $0.pathExtension == "csv" }.forEach { url in
try? FileManager.default.removeItem(at: url) try? FileManager.default.removeItem(at: url)
} }
} }
var allFiles: [URL] { var allFiles: [URL] {
let docDir = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true) let allFiles = try! FileManager.default.contentsOfDirectory(at: rankingSourceDirectory, includingPropertiesForKeys: nil).filter({ url in
let allFiles = try! FileManager.default.contentsOfDirectory(at: docDir, includingPropertiesForKeys: nil).filter({ url in
url.pathExtension == "csv" url.pathExtension == "csv"
}) })
@ -149,8 +172,8 @@ enum SourceFile: String, CaseIterable {
case messieurs = "MESSIEURS" case messieurs = "MESSIEURS"
var filesFromServer: [URL] { var filesFromServer: [URL] {
let docDir = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true) let rankingSourceDirectory = SourceFileManager.shared.rankingSourceDirectory
let allFiles = try! FileManager.default.contentsOfDirectory(at: docDir, includingPropertiesForKeys: nil) let allFiles = try! FileManager.default.contentsOfDirectory(at: rankingSourceDirectory, includingPropertiesForKeys: nil)
return allFiles.filter{$0.pathExtension == "csv" && $0.path().contains(rawValue)} return allFiles.filter{$0.pathExtension == "csv" && $0.path().contains(rawValue)}
} }

@ -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)

@ -188,7 +188,7 @@ struct CashierView: View {
Text(teamCallDate.localizedDate()) Text(teamCallDate.localizedDate())
} }
Spacer() Spacer()
Text(player.weight.formatted()) Text(player.computedRank.formatted())
} }
} footer: { } footer: {
if tournaments.count > 1, let tournamentTitle = player.tournament()?.tournamentTitle() { if tournaments.count > 1, let tournamentTitle = player.tournament()?.tournamentTitle() {
@ -200,7 +200,7 @@ struct CashierView: View {
@ViewBuilder @ViewBuilder
private func _byPlayerRank() -> some View { private func _byPlayerRank() -> some View {
let players = teams.flatMap({ $0.players() }).sorted(using: .keyPath(\.weight)).filter({ _shouldDisplayPlayer($0) }) let players = teams.flatMap({ $0.players() }).sorted(using: .keyPath(\.computedRank)).filter({ _shouldDisplayPlayer($0) })
_byPlayer(players) _byPlayer(players)
} }

@ -6,6 +6,7 @@
// //
import SwiftUI import SwiftUI
import LeStorage
struct ClubDetailView: View { struct ClubDetailView: View {
@Bindable var club: Club @Bindable var club: Club
@ -13,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 {
@ -86,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 ?? "")
@ -121,11 +145,18 @@ struct ClubDetailView: View {
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)
} }
} }
} }
@ -134,13 +165,37 @@ 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" : "Détail du club")
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.toolbar(.visible, for: .navigationBar) .toolbar(.visible, for: .navigationBar)
.toolbarBackground(.visible, for: .navigationBar) .toolbarBackground(.visible, for: .navigationBar)
.toolbar {
if displayContext == .edition || displayContext == .lockedForEditing {
let isFavorite = club.isFavorite()
ToolbarItem(placement: .topBarTrailing) {
BarButtonView("Favori", icon: isFavorite ? "star.fill" : "star") {
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)
}
}
.tint(isFavorite ? .green : .logoRed)
}
}
}
.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 {
@ -154,5 +209,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
@ -25,6 +26,8 @@ struct ClubSearchView: View {
@State private var getForwardCityList: [CLPlacemark] = [] @State private var getForwardCityList: [CLPlacemark] = []
@State private var searchPresented: Bool = false @State private var searchPresented: Bool = false
@State private var showingSettingsAlert = false @State private var showingSettingsAlert = false
@State private var presentClubCreationView: Bool = false
var displayContext: DisplayContext = .edition var displayContext: DisplayContext = .edition
var club: Club? var club: Club?
@ -79,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()
@ -88,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: {
@ -141,7 +156,7 @@ struct ClubSearchView: View {
if searchAttempted { if searchAttempted {
Label("Aucun club trouvé", systemImage: "mappin.slash") Label("Aucun club trouvé", systemImage: "mappin.slash")
} else { } else {
Text("Recherche de club") Label("Recherche de club", systemImage: "location.circle")
} }
} description: { } description: {
Text("Padel Club peut rechercher un club autour de vous, d'une ville ou d'un code postal, facilitant ainsi la saisie d'information.") Text("Padel Club peut rechercher un club autour de vous, d'une ville ou d'un code postal, facilitant ainsi la saisie d'information.")
@ -160,12 +175,22 @@ struct ClubSearchView: View {
RowButtonView("Chercher une ville ou un code postal") { RowButtonView("Chercher une ville ou un code postal") {
searchPresented = true searchPresented = true
} }
if searchAttempted {
RowButtonView("Créer un club manuellement") {
presentClubCreationView = true
}
}
} }
} }
} else { } else {
ContentUnavailableView("recherche en cours", systemImage: "mappin.and.ellipse", description: Text("recherche des clubs autour de vous")) ContentUnavailableView("recherche en cours", systemImage: "mappin.and.ellipse", description: Text("recherche des clubs autour de vous"))
} }
} }
.sheet(isPresented: $presentClubCreationView) {
CreateClubView()
.tint(.master)
}
.alert(isPresented: $showingSettingsAlert) { .alert(isPresented: $showingSettingsAlert) {
Alert( Alert(
title: Text("Réglages"), title: Text("Réglages"),

@ -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?.hasFavoriteClubsAndCreatedClubs() == false {
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!)
} }

@ -21,7 +21,7 @@ struct GroupStageTeamReplacementView: View {
private func _getWeight() -> Int { private func _getWeight() -> Int {
guard let selectedPlayer else { return 0 } guard let selectedPlayer else { return 0 }
return team.weight - selectedPlayer.weight return team.weight - selectedPlayer.computedRank
} }
private func _searchableRange(_ teamRange: TeamRegistration.TeamRange) -> String { private func _searchableRange(_ teamRange: TeamRegistration.TeamRange) -> String {

@ -20,6 +20,7 @@ struct ActivityView: View {
@State private var isGatheringFederalTournaments: Bool = false @State private var isGatheringFederalTournaments: Bool = false
@State private var error: Error? @State private var error: Error?
@State private var uuid: UUID = UUID() @State private var uuid: UUID = UUID()
@State private var presentClubSearchView: Bool = false
var runningTournaments: [FederalTournamentHolder] { var runningTournaments: [FederalTournamentHolder] {
dataStore.tournaments.filter({ $0.endDate == nil }) dataStore.tournaments.filter({ $0.endDate == nil })
@ -112,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() == true
&& 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() == true
&& 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 {
@ -199,6 +202,14 @@ struct ActivityView: View {
.environment(navigation) .environment(navigation)
.tint(.master) .tint(.master)
} }
.sheet(isPresented: $presentClubSearchView, onDismiss: {
if dataStore.user?.hasClubs() == true {
navigation.agendaDestination = .tenup
}
}) {
ClubImportView()
.tint(.master)
}
} }
} }
} }
@ -207,7 +218,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
} }
@ -245,11 +257,17 @@ 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.user?.hasClubs() == false {
RowButtonView("Chercher l'un de vos clubs") {
presentClubSearchView = true
}
} else {
RowButtonView("Importer via Tenup") { RowButtonView("Importer via Tenup") {
navigation.agendaDestination = .tenup navigation.agendaDestination = .tenup
} }
} }
} }
}
private func _endedEmptyView() -> some View { private func _endedEmptyView() -> some View {
ContentUnavailableView { ContentUnavailableView {
@ -260,13 +278,13 @@ struct ActivityView: View {
} }
private func _tenupEmptyView() -> some View { private func _tenupEmptyView() -> some View {
if dataStore.clubs.isEmpty { if dataStore.user?.hasClubs() == false {
ContentUnavailableView { ContentUnavailableView {
Label("Aucun tournoi", systemImage: "shield.slash") Label("Aucun tournoi", systemImage: "shield.slash")
} description: { } description: {
Text("Pour voir vos tournois tenup ici, indiquez vos clubs préférés.") Text("Pour voir vos tournois tenup ici, choisissez vos clubs.")
} actions: { } actions: {
RowButtonView("Choisir mes clubs préférés") { RowButtonView("Choisir mes clubs") {
navigation.selectedTab = .umpire navigation.selectedTab = .umpire
} }
} }

@ -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) {

@ -1,40 +0,0 @@
//
// EmptyActivityView.swift
// PadelClub
//
// Created by Razmig Sarkissian on 01/03/2024.
//
import SwiftUI
struct EmptyActivityView: View {
@State private var newTournament: Tournament?
var body: some View {
NavigationStack {
List {
WelcomeView()
Section {
RowButtonView("Créer votre premier événement", action: {
newTournament = Tournament.newEmptyInstance()
})
}
Section {
RowButtonView("Importer vos tournois Tenup", action: {
})
}
}
.sheet(item: $newTournament) { tournament in
EventCreationView(tournaments: [tournament])
.tint(.master)
}
}
}
}
#Preview {
EmptyActivityView()
}

@ -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() == true
&& _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

@ -1,24 +0,0 @@
//
// WelcomeView.swift
// PadelClub
//
// Created by Razmig Sarkissian on 01/03/2024.
//
import SwiftUI
struct WelcomeView: View {
var body: some View {
Image(.padelClubLogoFondfonce)
.resizable()
.scaledToFit()
.buttonStyle(.borderedProminent)
.tint(.logoBackground)
.listRowBackground(Color.clear)
.listRowInsets(EdgeInsets(.zero))
}
}
#Preview {
WelcomeView()
}

@ -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)

@ -11,7 +11,7 @@ import SwiftData
struct PadelClubView: View { struct PadelClubView: View {
@State private var checkingFilesAttempt: Int = 0 @State private var checkingFilesAttempt: Int = 0
@State private var checkingFiles: Bool = false @State private var checkingFiles: Bool = false
@State private var importingFiles: Bool = false @AppStorage("importingFiles") var importingFiles: Bool = false
@EnvironmentObject var dataStore: DataStore @EnvironmentObject var dataStore: DataStore
@ -72,18 +72,20 @@ struct PadelClubView: View {
Text(monthData.monthKey) Text(monthData.monthKey)
} }
} }
//
// if players.isEmpty { if importingFiles {
// ContentUnavailableView { ContentUnavailableView("Importation en cours", systemImage: "server.rack", description: Text("Une importation des données fédérales publiques est en cours, veuillez patienter."))
// Label("Aucun joueur importé", systemImage: "person.slash") } else if (players.isEmpty || lastDataSource == nil) {
// } description: { ContentUnavailableView {
// Text("Padel peut importer toutes les données publique de la FFT concernant tous les compétiteurs et compétitrices.") Label("Aucun joueur importé", systemImage: "person.slash")
// } actions: { } description: {
// RowButtonView("Démarrer l'importation") { Text("Padel Club peut importer toutes les données publiques de la FFT concernant tous les compétiteurs et compétitrices.")
// _startImporting() } actions: {
// } RowButtonView("Démarrer l'importation") {
// } _startImporting()
// } }
}
}
} }
.task { .task {
await self._checkSourceFileAvailability() await self._checkSourceFileAvailability()
@ -94,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()
@ -54,15 +57,22 @@ struct UmpireView: View {
} }
} else { } else {
NavigationLink { NavigationLink {
SelectablePlayerListView(allowSelection: 1, playerSelectionAction: { players in SelectablePlayerListView(allowSelection: 1, searchField: user.firstName + " " + user.lastName, 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!) if user.clubsObjects().contains(where: { $0.code == player.clubCode }) == false {
try? dataStore.clubs.addOrUpdate(instance: club) let userClub = Club.findOrCreate(name: player.clubName!, code: player.clubCode)
user.club = club.id do {
dataStore.setUser(user) try dataStore.clubs.addOrUpdate(instance: userClub)
user.setUserClub(userClub)
try dataStore.userStorage.update()
} catch {
Logger.error(error)
}
}
} }
}) })
.id(UUID())
} label: { } label: {
Label("Ma fiche joueur", systemImage: "person.bust.circle.fill") Label("Ma fiche joueur", systemImage: "person.bust.circle.fill")
} }
@ -77,25 +87,10 @@ 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 clubId = user.club {
if let club = dataStore.clubs.findById(clubId) {
Section {
NavigationLink {
ClubDetailView(club: club, displayContext: .edition)
} label: {
ClubRowView(club: club)
}
} header: {
Text("Mon club")
} footer: {
Button("supprimer", role: .destructive) {
user.club = nil
dataStore.setUser(user)
} }
} }
} }
@ -107,11 +102,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 favoris", 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)

@ -32,7 +32,7 @@ struct PlayerSexPickerView: View {
private func _save() { private func _save() {
do { do {
player.setWeight(in: tournament) player.setComputedRank(in: tournament)
try dataStore.playerRegistrations.addOrUpdate(instance: player) try dataStore.playerRegistrations.addOrUpdate(instance: player)
if let team = player.team() { if let team = player.team() {
team.updateWeight(inTournamentCategory: tournament.tournamentCategory) team.updateWeight(inTournamentCategory: tournament.tournamentCategory)

@ -60,7 +60,7 @@ struct PlayerDetailView: View {
Text("Valeur à rajouter") Text("Valeur à rajouter")
} }
LabeledContent { LabeledContent {
TextField("Rang", value: $player.weight, format: .number) TextField("Rang", value: $player.computedRank, format: .number)
.keyboardType(.decimalPad) .keyboardType(.decimalPad)
.multilineTextAlignment(.trailing) .multilineTextAlignment(.trailing)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
@ -79,12 +79,12 @@ struct PlayerDetailView: View {
.onChange(of: player.sex) { .onChange(of: player.sex) {
_save() _save()
} }
.onChange(of: player.weight) { .onChange(of: player.computedRank) {
player.team()?.updateWeight(inTournamentCategory: tournament.tournamentCategory) player.team()?.updateWeight(inTournamentCategory: tournament.tournamentCategory)
_save() _save()
} }
.onChange(of: player.rank) { .onChange(of: player.rank) {
player.setWeight(in: tournament) player.setComputedRank(in: tournament)
player.team()?.updateWeight(inTournamentCategory: tournament.tournamentCategory) player.team()?.updateWeight(inTournamentCategory: tournament.tournamentCategory)
_save() _save()
} }

@ -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 {

@ -272,7 +272,7 @@ struct FileImportView: View {
Section { Section {
HStack { HStack {
VStack(alignment: .leading) { VStack(alignment: .leading) {
ForEach(team.players.sorted(by: \.weight)) { ForEach(team.players.sorted(by: \.computedRank)) {
Text($0.playerLabel()) Text($0.playerLabel())
} }
} }

@ -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: {

@ -40,7 +40,7 @@ struct UpdateSourceRankDateView: View {
try await tournament.updateRank(to: currentRankSourceDate) try await tournament.updateRank(to: currentRankSourceDate)
try await MainActor.run { try await MainActor.run {
tournament.unsortedPlayers().forEach { player in tournament.unsortedPlayers().forEach { player in
player.setWeight(in: tournament) player.setComputedRank(in: tournament)
} }
try dataStore.playerRegistrations.addOrUpdate(contentOfs: tournament.unsortedPlayers()) try dataStore.playerRegistrations.addOrUpdate(contentOfs: tournament.unsortedPlayers())

@ -83,7 +83,7 @@ struct InscriptionManagerView: View {
selectionSearchField = nil selectionSearchField = nil
players.forEach { player in players.forEach { player in
let newPlayer = PlayerRegistration(importedPlayer: player) let newPlayer = PlayerRegistration(importedPlayer: player)
newPlayer.setWeight(in: tournament) newPlayer.setComputedRank(in: tournament)
createdPlayers.insert(newPlayer) createdPlayers.insert(newPlayer)
createdPlayerIds.insert(newPlayer.id) createdPlayerIds.insert(newPlayer.id)
} }
@ -529,7 +529,7 @@ struct InscriptionManagerView: View {
fetchPlayers.first(where: { id == $0.license }) fetchPlayers.first(where: { id == $0.license })
}.forEach { player in }.forEach { player in
let player = PlayerRegistration(importedPlayer: player) let player = PlayerRegistration(importedPlayer: player)
player.setWeight(in: tournament) player.setComputedRank(in: tournament)
currentSelection.insert(player) currentSelection.insert(player)
} }
@ -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