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. 6
      PadelClub/Extensions/String+Extensions.swift
  11. 1
      PadelClub/Utils/DisplayContext.swift
  12. 12
      PadelClub/Utils/FileImportManager.swift
  13. 7
      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. 44
      PadelClub/Views/Calling/CallMessageCustomizationView.swift
  22. 4
      PadelClub/Views/Cashier/CashierView.swift
  23. 119
      PadelClub/Views/Club/ClubDetailView.swift
  24. 3
      PadelClub/Views/Club/ClubRowView.swift
  25. 45
      PadelClub/Views/Club/ClubSearchView.swift
  26. 37
      PadelClub/Views/Club/ClubsView.swift
  27. 24
      PadelClub/Views/Club/CreateClubView.swift
  28. 25
      PadelClub/Views/Event/EventCreationView.swift
  29. 2
      PadelClub/Views/GroupStage/Shared/GroupStageTeamReplacementView.swift
  30. 70
      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. 32
      PadelClub/Views/Navigation/Toolbox/PadelClubView.swift
  38. 7
      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. 33
      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 */; };
FFCFC01C2BBC5AAA00B82851 /* SetDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCFC01B2BBC5AAA00B82851 /* SetDescriptor.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 */; };
FFDB1C732BB2CFE900F1E467 /* MySortDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFDB1C722BB2CFE900F1E467 /* MySortDescriptor.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>"; };
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>"; };
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>"; };
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>"; };
@ -900,7 +896,6 @@
isa = PBXGroup;
children = (
FF59FFB62B90EFBF0061EFF9 /* MainView.swift */,
FFD783FE2B91BA42000F62A6 /* PadelClubView.swift */,
FFD783FB2B91B919000F62A6 /* Agenda */,
FF3F74FA2B91A04B004CFE0E /* Organizer */,
FF3F74FB2B91A060004CFE0E /* Toolbox */,
@ -936,8 +931,6 @@
FF3F74F92B91A018004CFE0E /* Screen */ = {
isa = PBXGroup;
children = (
FF6EC8FD2B94792300EA7F5A /* Screen.swift */,
FF6EC8FF2B94794700EA7F5A /* PresentationContext.swift */,
FF70916D2B9108C600AB08DA /* InscriptionManagerView.swift */,
FF8F26422BADFE5B00650388 /* TournamentSettingsView.swift */,
FF8F26532BAE1E4400650388 /* TableStructureView.swift */,
@ -969,6 +962,7 @@
FF025AE62BD1111000A86CF8 /* GlobalSettingsView.swift */,
FF025AEE2BD1AE9400A86CF8 /* DurationSettingsView.swift */,
FF025AF02BD1AEBD00A86CF8 /* MatchFormatStorageView.swift */,
FFD783FE2B91BA42000F62A6 /* PadelClubView.swift */,
);
path = Toolbox;
sourceTree = "<group>";
@ -984,6 +978,8 @@
FF3F74FD2B91A087004CFE0E /* ViewModel */ = {
isa = PBXGroup;
children = (
FF6EC8FD2B94792300EA7F5A /* Screen.swift */,
FF6EC8FF2B94794700EA7F5A /* PresentationContext.swift */,
FF7091652B90F0B000AB08DA /* TabDestination.swift */,
FF025AEC2BD1513700A86CF8 /* AppScreen.swift */,
FF3F74FE2B91A2D4004CFE0E /* AgendaDestination.swift */,
@ -1176,8 +1172,6 @@
isa = PBXGroup;
children = (
FF82CFC82B9132AF00B0CAF2 /* ActivityView.swift */,
FFD784032B91C280000F62A6 /* EmptyActivityView.swift */,
FFD784012B91C1B4000F62A6 /* WelcomeView.swift */,
FF59FFB22B90EFAC0061EFF9 /* EventListView.swift */,
FF5D0D8A2BB4D1E3005CB568 /* CalendarView.swift */,
);
@ -1596,7 +1590,6 @@
FF9AC3952BE3627B00C2E883 /* GroupStageTeamReplacementView.swift in Sources */,
FF4C7F022BBBD7150031B6A3 /* TabItemModifier.swift in Sources */,
FFDDD40C2B93B2BB00C91A49 /* DeferredViewModifier.swift in Sources */,
FFD784042B91C280000F62A6 /* EmptyActivityView.swift in Sources */,
FF0E0B6D2BC254C6005F00A9 /* TournamentScheduleView.swift in Sources */,
FF025AF12BD1AEBD00A86CF8 /* MatchFormatStorageView.swift in Sources */,
FF3F74F62B919E45004CFE0E /* UmpireView.swift in Sources */,
@ -1618,7 +1611,6 @@
FFF964532BC262B000EEF017 /* PlanningSettingsView.swift in Sources */,
FFF527D62BC6DDD000FF4EF2 /* MatchScheduleEditorView.swift in Sources */,
FFC91AF92BD6A09100B29808 /* FortuneWheelView.swift in Sources */,
FFD784022B91C1B4000F62A6 /* WelcomeView.swift in Sources */,
FFF8ACD62B923960008466FA /* URL+Extensions.swift in Sources */,
FF089EBD2BB0287D00F0AEC7 /* PlayerView.swift in Sources */,
FF967D032BAEF0C000A9A3BD /* MatchDetailView.swift in Sources */,

@ -111,4 +111,42 @@ extension Club {
guard let code else { return nil }
return URL(string: "https://tenup.fft.fr/club/\(code)")
}
func update(fromClub club: Club) {
self.acronym = club.acronym
self.name = club.name
self.phone = club.phone
self.code = club.code
self.address = club.address
self.city = club.city
self.zipCode = club.zipCode
self.latitude = club.latitude
self.longitude = club.longitude
}
func hasBeenCreated(by creatorId: String?) -> Bool {
guard let creatorId else { return false }
guard let creator else { return false }
return creatorId == creator
}
func isFavorite() -> Bool {
DataStore.shared.user?.clubs?.contains(where: { $0 == id }) == true
}
static func findOrCreate(name: String, code: String?, city: String? = nil, zipCode: String? = nil) -> Club {
/*
identify a club : code, name, ??
*/
let clubs: [Club] = Store.main.filter(isIncluded: { (code == nil && $0.name == name && $0.city == city && $0.zipCode == zipCode) || $0.code == code })
if clubs.isEmpty == false {
return clubs.first!
} else {
return Club(creator: DataStore.shared.user?.id, name: name, code: code)
}
}
}

@ -78,7 +78,7 @@ class DataStore: ObservableObject {
// store.addMigration(Migration<TournamentV2, Tournament>(version: 3))
let indexed : Bool = true
let synchronized : Bool = false
let synchronized : Bool = true
self.clubs = store.registerCollection(synchronized: synchronized, indexed: indexed)
self.courts = 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.rounds = 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.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.CollectionDidChange, object: nil)
@ -111,7 +111,7 @@ class DataStore: ObservableObject {
if let _ = Guard.main.currentPlan {
return .creation
}
if let user = self.user, user.club != nil {
if let user = self.user, user.clubs != nil {
if user.umpireCode != nil {
return .creation
} else {

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

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

@ -31,7 +31,7 @@ class PlayerRegistration: ModelObject, Storable {
var email: String?
var birthdate: String?
var weight: Int = 0
var computedRank: Int = 0
var source: PlayerDataSource?
var hasArrived: Bool = false
@ -169,8 +169,8 @@ class PlayerRegistration: ModelObject, Storable {
func rankLabel(_ displayStyle: DisplayStyle = .wide) -> String {
if let rank, rank > 0 {
if rank != weight {
return weight.formatted() + " (" + rank.formatted() + ")"
if rank != computedRank {
return computedRank.formatted() + " (" + rank.formatted() + ")"
} else {
return rank.formatted()
}
@ -180,7 +180,7 @@ class PlayerRegistration: ModelObject, Storable {
}
func getRank() -> Int {
weight
computedRank
}
@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
switch tournament.tournamentCategory {
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:
weight = currentRank
computedRank = currentRank
}
}
@ -279,7 +279,7 @@ class PlayerRegistration: ModelObject, Storable {
case _birthdate = "birthdate"
case _phoneNumber = "phoneNumber"
case _email = "email"
case _weight = "weight"
case _computedRank = "computedRank"
case _source = "source"
case _hasArrived = "hasArrived"

@ -268,7 +268,7 @@ class TeamRegistration: ModelObject, Storable {
func setWeight(from players: [PlayerRegistration], inTournamentCategory tournamentCategory: TournamentCategory) {
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 {

@ -290,7 +290,7 @@ class Tournament : ModelObject, Storable {
startDate <= Date()
}
var eventObject: Event? {
func eventObject() -> Event? {
guard let event else { return nil }
return Store.main.findById(event)
}
@ -301,7 +301,7 @@ class Tournament : ModelObject, Storable {
}
func club() -> Club? {
eventObject?.clubObject
eventObject()?.clubObject()
}
func locationLabel(_ displayStyle: DisplayStyle = .wide) -> String {
@ -632,11 +632,11 @@ class Tournament : ModelObject, Storable {
}
func selectedPlayers() -> [PlayerRegistration] {
selectedSortedTeams().flatMap { $0.unsortedPlayers() }.sorted(by: \.weight)
selectedSortedTeams().flatMap { $0.unsortedPlayers() }.sorted(by: \.computedRank)
}
func players() -> [PlayerRegistration] {
unsortedTeams().flatMap { $0.unsortedPlayers() }.sorted(by: \.weight)
unsortedTeams().flatMap { $0.unsortedPlayers() }.sorted(by: \.computedRank)
}
func femalePlayers() -> [PlayerRegistration] {
@ -656,7 +656,7 @@ class Tournament : ModelObject, Storable {
//todo
var clubName: String? {
eventObject?.clubObject?.name
eventObject()?.clubObject()?.name
}
//todo
@ -670,7 +670,7 @@ class Tournament : ModelObject, Storable {
}
return players.filter { player in
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
} else {
return false
@ -846,7 +846,7 @@ class Tournament : ModelObject, Storable {
let teams = self.unsortedTeams()
teams.forEach { team in
let players = team.unsortedPlayers()
players.forEach { $0.setWeight(in: self) }
players.forEach { $0.setComputedRank(in: self) }
team.setWeight(from: players, inTournamentCategory: tournamentCategory)
try? DataStore.shared.playerRegistrations.addOrUpdate(contentOfs: players)
}

@ -24,7 +24,7 @@ class User: UserBase, Storable {
public var id: String = Store.randomId()
public var username: String
public var email: String
var club: String?
var clubs: [String]?
var umpireCode: String?
var licenceId: String?
var firstName: String
@ -57,11 +57,32 @@ class User: UserBase, Storable {
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 {
case _id = "id"
case _username = "username"
case _email = "email"
case _club = "club"
case _clubs = "clubs"
case _umpireCode = "umpireCode"
case _licenceId = "licenceId"
case _firstName = "firstName"

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

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

@ -71,7 +71,7 @@ class FileImportManager {
self.players = Set(players)
self.tournamentCategory = tournamentCategory
self.previousTeam = previousTeam
self.weight = players.map { $0.weight }.reduce(0,+)
self.weight = players.map { $0.computedRank }.reduce(0,+)
self.registrationDate = registrationDate
}
@ -210,9 +210,9 @@ class FileImportManager {
}
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)
playerTwo.setWeight(in: tournament)
playerTwo.setComputedRank(in: tournament)
let team = TeamHolder(players: [playerOne, playerTwo], tournamentCategory: tournamentCategory, previousTeam: tournament.findTeam([playerOne, playerTwo]))
results.append(team)
}
@ -256,9 +256,9 @@ class FileImportManager {
}
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)
playerTwo.setWeight(in: tournament)
playerTwo.setComputedRank(in: tournament)
let team = TeamHolder(players: [playerOne, playerTwo], tournamentCategory: tournamentCategory, previousTeam: tournament.findTeam([playerOne, playerTwo]))
results.append(team)
@ -281,7 +281,7 @@ class FileImportManager {
let found = try? federalContext.fetch(fetchRequest)
let registeredPlayers = found?.map({ importedPlayer in
let player = PlayerRegistration(importedPlayer: importedPlayer)
player.setWeight(in: tournament)
player.setComputedRank(in: tournament)
return player
})
if let registeredPlayers, registeredPlayers.isEmpty == false {

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

@ -22,7 +22,7 @@ class NetworkManager {
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 fileURL = URL(string: "https://padelclub.app/static/\(dateString)")

@ -9,7 +9,32 @@ import Foundation
class 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? {
DataStore.shared.appSettings.lastDataSource
}
@ -117,16 +142,14 @@ class SourceFileManager {
}
func removeAllFilesFromServer() {
let docDir = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let allFiles = try! FileManager.default.contentsOfDirectory(at: docDir, includingPropertiesForKeys: nil)
let allFiles = try! FileManager.default.contentsOfDirectory(at: rankingSourceDirectory, includingPropertiesForKeys: nil)
allFiles.filter { $0.pathExtension == "csv" }.forEach { url in
try? FileManager.default.removeItem(at: 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: docDir, includingPropertiesForKeys: nil).filter({ url in
let allFiles = try! FileManager.default.contentsOfDirectory(at: rankingSourceDirectory, includingPropertiesForKeys: nil).filter({ url in
url.pathExtension == "csv"
})
@ -149,8 +172,8 @@ enum SourceFile: String, CaseIterable {
case messieurs = "MESSIEURS"
var filesFromServer: [URL] {
let docDir = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let allFiles = try! FileManager.default.contentsOfDirectory(at: docDir, includingPropertiesForKeys: nil)
let rankingSourceDirectory = SourceFileManager.shared.rankingSourceDirectory
let allFiles = try! FileManager.default.contentsOfDirectory(at: rankingSourceDirectory, includingPropertiesForKeys: nil)
return allFiles.filter{$0.pathExtension == "csv" && $0.path().contains(rawValue)}
}

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

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

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

@ -6,6 +6,7 @@
//
import SwiftUI
import LeStorage
struct CallMessageCustomizationView: View {
@EnvironmentObject var dataStore: DataStore
@ -69,20 +70,9 @@ struct CallMessageCustomizationView: View {
} header: {
Text("Signature du message")
}
Section {
TextField("Nom du club", text: $customClubName)
.autocorrectionDisabled()
.onSubmit {
if let eventClub = tournament.eventObject?.clubObject {
eventClub.name = customClubName
try? dataStore.clubs.addOrUpdate(instance: eventClub)
}
}
} header: {
Text("Nom du club")
}
_clubNameView()
Section {
if appSettings.callUseFullCustomMessage {
Text(self.computedFullCustomMessage())
@ -174,6 +164,32 @@ struct CallMessageCustomizationView: View {
private func _save() {
dataStore.updateSettings()
}
@ViewBuilder
private func _clubNameView() -> some View {
if let eventClub = tournament.eventObject()?.clubObject() {
let hasBeenCreated: Bool = eventClub.hasBeenCreated(by: dataStore.user?.id)
Section {
TextField("Nom du club", text: $customClubName)
.autocorrectionDisabled()
.onSubmit {
eventClub.name = customClubName
do {
try dataStore.clubs.addOrUpdate(instance: eventClub)
} catch {
Logger.error(error)
}
}
.disabled(hasBeenCreated == false)
} header: {
Text("Nom du club")
} footer: {
if hasBeenCreated == false {
Text("Édition impossible, vous n'êtes pas le créateur de ce club.").foregroundStyle(.logoRed)
}
}
}
}
func computedFullCustomMessage() -> String {
var text = customCallMessageBody.replacingOccurrences(of: "#titre", with: tournament.tournamentTitle())

@ -188,7 +188,7 @@ struct CashierView: View {
Text(teamCallDate.localizedDate())
}
Spacer()
Text(player.weight.formatted())
Text(player.computedRank.formatted())
}
} footer: {
if tournaments.count > 1, let tournamentTitle = player.tournament()?.tournamentTitle() {
@ -200,7 +200,7 @@ struct CashierView: View {
@ViewBuilder
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)
}

@ -6,6 +6,7 @@
//
import SwiftUI
import LeStorage
struct ClubDetailView: View {
@Bindable var club: Club
@ -13,12 +14,15 @@ struct ClubDetailView: View {
@EnvironmentObject var dataStore: DataStore
@FocusState var focusedField: Club.CodingKeys?
@State private var acronymMode: Club.AcronymMode = .automatic
@State private var updateClubData: Bool = false
init(club: Club, displayContext: DisplayContext = .edition) {
@State private var city: String
@State private var zipCode: String
init(club: Club, displayContext: DisplayContext) {
_club = Bindable(club)
self.displayContext = displayContext
_acronymMode = State(wrappedValue: club.shortNameMode())
_city = State(wrappedValue: club.city ?? "")
_zipCode = State(wrappedValue: club.zipCode ?? "")
}
var body: some View {
@ -86,31 +90,51 @@ struct ClubDetailView: View {
club.acronym = ""
}
}
} footer: {
Text("Vous pouvez personaliser le nom court ou laisser celui généré par défaut. Le nom court est utile au niveau des liens de diffusions.")
}
if club.code == nil || updateClubData {
Section {
NavigationLink {
ClubSearchView(displayContext: .edition, club: club)
} label: {
Label("Chercher dans la base fédérale", systemImage: "magnifyingglass")
if club.code == nil {
VStack(alignment: .leading, spacing: 0) {
Text("Ville").foregroundStyle(.secondary).font(.caption)
TextField("Ville", text: $city)
.fixedSize()
.focused($focusedField, equals: ._city)
.submitLabel( displayContext == .addition ? .next : .done)
.onSubmit {
if displayContext == .addition {
focusedField = ._zipCode
}
club.city = city
}
}
.onTapGesture {
focusedField = ._city
}
} footer: {
if club.code != nil {
HStack {
Spacer()
Button("annuler", role: .cancel) {
updateClubData = false
VStack(alignment: .leading, spacing: 0) {
Text("Code Postal").foregroundStyle(.secondary).font(.caption)
TextField("Code Postal", text: $zipCode)
.fixedSize()
.focused($focusedField, equals: ._zipCode)
.submitLabel( displayContext == .addition ? .next : .done)
.onSubmit {
club.zipCode = zipCode
}
}
} else {
Text("Vous pouvez chercher un club dans la base fédérale et importer les informations directement.")
}
.onTapGesture {
focusedField = ._zipCode
}
}
} else if let federalLink = club.federalLink() {
} footer: {
if displayContext == .lockedForEditing {
Text("Édition impossible, vous n'êtes pas le créateur de ce club.").foregroundStyle(.logoRed)
} else {
Text("Vous pouvez personaliser le nom court ou laisser celui généré par défaut.")
}
}
.disabled(displayContext == .lockedForEditing)
if let federalLink = club.federalLink() {
Section {
LabeledContent("Code Club") {
Text(club.code ?? "")
@ -121,11 +145,18 @@ struct ClubDetailView: View {
Link(destination: federalLink) {
Text("Fiche du club sur tenup")
}
} footer: {
HStack {
Spacer()
Button("modifier", role: .destructive) {
updateClubData = true
}
}
if displayContext == .edition {
Section {
RowButtonView("Supprimer ce club", role: .destructive) {
do {
try dataStore.clubs.deleteById(club.id)
dataStore.user?.clubs?.removeAll(where: { $0 == club.id })
try dataStore.userStorage.update()
} catch {
Logger.error(error)
}
}
}
@ -134,13 +165,37 @@ struct ClubDetailView: View {
.keyboardType(.alphabet)
.autocorrectionDisabled()
.defaultFocus($focusedField, ._name, priority: .automatic)
.navigationTitle(displayContext == .edition ? club.name : "Nouveau club")
.navigationTitle(displayContext == .addition ? "Nouveau club" : "Détail du club")
.navigationBarTitleDisplayMode(.inline)
.toolbar(.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 {
if displayContext == .edition {
try? dataStore.clubs.addOrUpdate(instance: club)
do {
try dataStore.clubs.addOrUpdate(instance: club)
} catch {
Logger.error(error)
}
}
}
.onAppear {
@ -154,5 +209,5 @@ struct ClubDetailView: View {
}
#Preview {
ClubDetailView(club: Club.mock())
ClubDetailView(club: Club.mock(), displayContext: .edition)
}

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

@ -9,6 +9,7 @@ import SwiftUI
import CoreLocation
import CoreLocationUI
import TipKit
import LeStorage
struct ClubSearchView: View {
@Environment(\.dismiss) private var dismiss
@ -25,6 +26,8 @@ struct ClubSearchView: View {
@State private var getForwardCityList: [CLPlacemark] = []
@State private var searchPresented: Bool = false
@State private var showingSettingsAlert = false
@State private var presentClubCreationView: Bool = false
var displayContext: DisplayContext = .edition
var club: Club?
@ -79,17 +82,29 @@ struct ClubSearchView: View {
Section {
ForEach(_filteredClubs()) { clubMark in
Button {
let clubToEdit = club ?? Club(name: clubMark.nom)
if clubToEdit.name.isEmpty {
clubToEdit.name = clubMark.nom
clubToEdit.acronym = clubToEdit.automaticShortName()
let clubToEdit = club ?? Club.findOrCreate(name: clubMark.nom, code: clubMark.clubID)
if clubToEdit.creator == dataStore.user?.id && dataStore.user?.id != nil {
if clubToEdit.name.isEmpty {
clubToEdit.name = clubMark.nom
clubToEdit.acronym = clubToEdit.automaticShortName()
}
clubToEdit.code = clubMark.clubID
clubToEdit.latitude = clubMark.lat
clubToEdit.longitude = clubMark.lng
clubToEdit.city = clubMark.ville
}
clubToEdit.code = clubMark.clubID
clubToEdit.latitude = clubMark.lat
clubToEdit.longitude = clubMark.lng
clubToEdit.city = clubMark.ville
if displayContext == .addition {
try? dataStore.clubs.addOrUpdate(instance: clubToEdit)
do {
try dataStore.clubs.addOrUpdate(instance: clubToEdit)
if dataStore.user?.clubs?.contains(where: { $0 == clubToEdit.id }) == false {
dataStore.user?.clubs?.append(clubToEdit.id)
try dataStore.userStorage.update()
}
} catch {
Logger.error(error)
}
}
dismiss()
} label: {
@ -141,7 +156,7 @@ struct ClubSearchView: View {
if searchAttempted {
Label("Aucun club trouvé", systemImage: "mappin.slash")
} else {
Text("Recherche de club")
Label("Recherche de club", systemImage: "location.circle")
}
} 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.")
@ -160,12 +175,22 @@ struct ClubSearchView: View {
RowButtonView("Chercher une ville ou un code postal") {
searchPresented = true
}
if searchAttempted {
RowButtonView("Créer un club manuellement") {
presentClubCreationView = true
}
}
}
}
} else {
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(
title: Text("Réglages"),

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

@ -6,6 +6,7 @@
//
import SwiftUI
import LeStorage
struct CreateClubView: View {
@Bindable var club: Club
@ -28,7 +29,28 @@ struct CreateClubView: View {
}
ToolbarItem(placement: .confirmationAction) {
ButtonValidateView {
try? dataStore.clubs.addOrUpdate(instance: club)
let existingOrCreatedClub = Club.findOrCreate(name: club.name, code: club.code, city: club.city, zipCode: club.zipCode)
//update existing club if rights ok / freshly created club with data input from user
if existingOrCreatedClub.hasBeenCreated(by: dataStore.user?.id) {
existingOrCreatedClub.update(fromClub: club)
do {
try dataStore.clubs.addOrUpdate(instance: existingOrCreatedClub)
} catch {
Logger.error(error)
}
}
//save into user
do {
if dataStore.user?.clubs?.contains(where: { $0 == existingOrCreatedClub.id }) == false {
dataStore.user?.clubs?.append(existingOrCreatedClub.id)
try dataStore.userStorage.update()
}
} catch {
Logger.error(error)
}
dismiss()
}
.disabled(club.isValid == false)

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

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

@ -20,7 +20,8 @@ struct ActivityView: View {
@State private var isGatheringFederalTournaments: Bool = false
@State private var error: Error?
@State private var uuid: UUID = UUID()
@State private var presentClubSearchView: Bool = false
var runningTournaments: [FederalTournamentHolder] {
dataStore.tournaments.filter({ $0.endDate == nil })
.filter({ federalDataViewModel.isTournamentValidForFilters($0) })
@ -112,43 +113,45 @@ struct ActivityView: View {
.environment(navigation)
.tint(.master)
}
.refreshable {
if navigation.agendaDestination == .tenup {
federalDataViewModel.federalTournaments.removeAll()
NetworkFederalService.shared.formId = ""
_gatherFederalTournaments()
}
}
// .refreshable {
// if navigation.agendaDestination == .tenup {
// federalDataViewModel.federalTournaments.removeAll()
// NetworkFederalService.shared.formId = ""
// _gatherFederalTournaments()
// }
// }
.task {
if navigation.agendaDestination == .tenup
&& dataStore.clubs.isEmpty == false
&& dataStore.user?.hasClubs() == true
&& federalDataViewModel.federalTournaments.isEmpty {
_gatherFederalTournaments()
}
}
.onChange(of: navigation.agendaDestination) {
if navigation.agendaDestination == .tenup
&& dataStore.clubs.isEmpty == false
&& dataStore.user?.hasClubs() == true
&& federalDataViewModel.federalTournaments.isEmpty {
_gatherFederalTournaments()
}
}
.toolbar {
if presentToolbar {
ToolbarItem(placement: .status) {
VStack(spacing: -2) {
if federalDataViewModel.areFiltersEnabled() {
Text(federalDataViewModel.filterStatus())
}
if let _activityStatus = _activityStatus() {
Text(_activityStatus)
.foregroundStyle(.secondary)
let _activityStatus = _activityStatus()
if federalDataViewModel.areFiltersEnabled() || _activityStatus != nil {
ToolbarItem(placement: .status) {
VStack(spacing: -2) {
if federalDataViewModel.areFiltersEnabled() {
Text(federalDataViewModel.filterStatus())
}
if let _activityStatus {
Text(_activityStatus)
.foregroundStyle(.secondary)
}
}
.font(.footnote)
}
.font(.footnote)
}
ToolbarItemGroup(placement: .topBarLeading) {
Button {
switch viewStyle {
@ -199,6 +202,14 @@ struct ActivityView: View {
.environment(navigation)
.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
Task {
do {
try await federalDataViewModel.gatherTournaments(clubs: dataStore.clubs.filter { $0.code != nil }, startDate: .now.startOfMonth)
let clubs : [Club] = (dataStore.user?.clubsObjects()) ?? []
try await federalDataViewModel.gatherTournaments(clubs: clubs.filter { $0.code != nil }, startDate: .now.startOfMonth)
} catch {
self.error = error
}
@ -245,8 +257,14 @@ struct ActivityView: View {
RowButtonView("Créer un nouvel événement") {
newTournament = Tournament.newEmptyInstance()
}
RowButtonView("Importer via Tenup") {
navigation.agendaDestination = .tenup
if dataStore.user?.hasClubs() == false {
RowButtonView("Chercher l'un de vos clubs") {
presentClubSearchView = true
}
} else {
RowButtonView("Importer via Tenup") {
navigation.agendaDestination = .tenup
}
}
}
}
@ -260,13 +278,13 @@ struct ActivityView: View {
}
private func _tenupEmptyView() -> some View {
if dataStore.clubs.isEmpty {
if dataStore.user?.hasClubs() == false {
ContentUnavailableView {
Label("Aucun tournoi", systemImage: "shield.slash")
} 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: {
RowButtonView("Choisir mes clubs préférés") {
RowButtonView("Choisir mes clubs") {
navigation.selectedTab = .umpire
}
}

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

@ -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)
.task {
if navigation.agendaDestination == .tenup
&& dataStore.clubs.isEmpty == false
&& dataStore.user?.hasClubs() == true
&& _tournaments.isEmpty {
_gatherFederalTournaments(startDate: section)
}
@ -64,7 +64,8 @@ struct EventListView: View {
// isGatheringFederalTournaments = true
Task {
do {
try await federalDataViewModel.gatherTournaments(clubs: dataStore.clubs.filter { $0.code != nil }, startDate: startDate, endDate: startDate.endOfMonth)
let clubs : [Club] = (dataStore.user?.clubsObjects()) ?? []
try await federalDataViewModel.gatherTournaments(clubs: clubs.filter { $0.code != nil }, startDate: startDate, endDate: startDate.endOfMonth)
} catch {
Logger.error(error)
// self.error = error

@ -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
}
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(
sortDescriptors: [],
animation: .default)
private var players: FetchedResults<ImportedPlayer>
var selectedTabHandler: Binding<TabDestination?> { Binding(
get: { navigation.selectedTab },
set: {
if $0 == navigation.selectedTab {
// switch navigation.selectedTab {
// case .activity:
// navigation.path.removeLast()
// case .toolbox:
// navigation.toolboxPath = NavigationPath()
// case .umpire:
// navigation.umpirePath = NavigationPath()
// case .ongoing:
// navigation.ongoingPath = NavigationPath()
// case .tournamentOrganizer:
// break
// case .none:
// break
// }
} else {
navigation.selectedTab = $0
}
}
)}
var body: some View {
@Bindable var navigation = navigation
TabView(selection: $navigation.selectedTab) {
TabView(selection: selectedTabHandler) {
ActivityView()
.tabItem(for: .activity)
TournamentOrganizerView()

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

@ -11,8 +11,8 @@ import SwiftData
struct PadelClubView: View {
@State private var checkingFilesAttempt: Int = 0
@State private var checkingFiles: Bool = false
@State private var importingFiles: Bool = false
@AppStorage("importingFiles") var importingFiles: Bool = false
@EnvironmentObject var dataStore: DataStore
var lastDataSource: String? {
@ -72,18 +72,20 @@ struct PadelClubView: View {
Text(monthData.monthKey)
}
}
//
// if players.isEmpty {
// ContentUnavailableView {
// Label("Aucun joueur importé", systemImage: "person.slash")
// } description: {
// Text("Padel peut importer toutes les données publique de la FFT concernant tous les compétiteurs et compétitrices.")
// } actions: {
// RowButtonView("Démarrer l'importation") {
// _startImporting()
// }
// }
// }
if importingFiles {
ContentUnavailableView("Importation en cours", systemImage: "server.rack", description: Text("Une importation des données fédérales publiques est en cours, veuillez patienter."))
} else if (players.isEmpty || lastDataSource == nil) {
ContentUnavailableView {
Label("Aucun joueur importé", systemImage: "person.slash")
} description: {
Text("Padel Club peut importer toutes les données publiques de la FFT concernant tous les compétiteurs et compétitrices.")
} actions: {
RowButtonView("Démarrer l'importation") {
_startImporting()
}
}
}
}
.task {
await self._checkSourceFileAvailability()
@ -94,7 +96,7 @@ struct PadelClubView: View {
}
}
.headerProminence(.increased)
.navigationTitle(TabDestination.padelClub.title)
.navigationTitle("Source des données fédérales")
}
@ViewBuilder

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

@ -7,8 +7,10 @@
import SwiftUI
import CoreLocation
import LeStorage
struct UmpireView: View {
@Environment(NavigationViewModel.self) private var navigation: NavigationViewModel
@EnvironmentObject var dataStore: DataStore
var lastDataSource: String? {
dataStore.appSettings.lastDataSource
@ -24,7 +26,8 @@ struct UmpireView: View {
}
var body: some View {
NavigationStack {
@Bindable var navigation = navigation
NavigationStack(path: $navigation.umpirePath) {
List {
PurchaseListView()
@ -54,15 +57,22 @@ struct UmpireView: View {
}
} else {
NavigationLink {
SelectablePlayerListView(allowSelection: 1, playerSelectionAction: { players in
SelectablePlayerListView(allowSelection: 1, searchField: user.firstName + " " + user.lastName, playerSelectionAction: { players in
if let player = players.first {
user.licenceId = player.license
let club = dataStore.clubs.first(where: { $0.code == player.clubCode }) ?? Club(name: player.clubName!, code: player.clubCode!)
try? dataStore.clubs.addOrUpdate(instance: club)
user.club = club.id
dataStore.setUser(user)
if user.clubsObjects().contains(where: { $0.code == player.clubCode }) == false {
let userClub = Club.findOrCreate(name: player.clubName!, code: player.clubCode)
do {
try dataStore.clubs.addOrUpdate(instance: userClub)
user.setUserClub(userClub)
try dataStore.userStorage.update()
} catch {
Logger.error(error)
}
}
}
})
.id(UUID())
} label: {
Label("Ma fiche joueur", systemImage: "person.bust.circle.fill")
}
@ -77,25 +87,10 @@ struct UmpireView: View {
} else {
Button("supprimer", role: .destructive) {
user.licenceId = nil
dataStore.setUser(user)
}
}
}
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)
do {
try dataStore.userStorage.update()
} catch {
Logger.error(error)
}
}
}
@ -107,11 +102,13 @@ struct UmpireView: View {
ClubsView()
} label: {
LabeledContent {
Text(dataStore.clubs.count.formatted())
Text((dataStore.user?.clubs ?? []).count.formatted())
} 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 {

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

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

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

@ -27,26 +27,29 @@ struct TournamentFilterView: View {
var body: some View {
NavigationView {
Form {
Section {
ForEach(dataStore.clubs.filter({ $0.code != nil })) { club in
LabeledContent {
Button {
if selectedClubs.contains(club.code!) {
selectedClubs.remove(club.code!)
} else {
selectedClubs.insert(club.code!)
let clubs : [Club] = (dataStore.user?.clubsObjects()) ?? []
if clubs.filter({ $0.code != nil }).isEmpty == false {
Section {
ForEach(clubs.filter({ $0.code != nil })) { club in
LabeledContent {
Button {
if selectedClubs.contains(club.code!) {
selectedClubs.remove(club.code!)
} else {
selectedClubs.insert(club.code!)
}
} label: {
if selectedClubs.contains(club.code!) {
Image(systemName: "checkmark.circle.fill")
}
}
} label: {
if selectedClubs.contains(club.code!) {
Image(systemName: "checkmark.circle.fill")
}
Text(club.clubTitle())
}
} label: {
Text(club.clubTitle())
}
} header: {
Text("Clubs")
}
} header: {
Text("Clubs")
}
Section {
ForEach(TournamentLevel.allCases) { level in

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

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

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

@ -83,7 +83,7 @@ struct InscriptionManagerView: View {
selectionSearchField = nil
players.forEach { player in
let newPlayer = PlayerRegistration(importedPlayer: player)
newPlayer.setWeight(in: tournament)
newPlayer.setComputedRank(in: tournament)
createdPlayers.insert(newPlayer)
createdPlayerIds.insert(newPlayer.id)
}
@ -529,7 +529,7 @@ struct InscriptionManagerView: View {
fetchPlayers.first(where: { id == $0.license })
}.forEach { player in
let player = PlayerRegistration(importedPlayer: player)
player.setWeight(in: tournament)
player.setComputedRank(in: tournament)
currentSelection.insert(player)
}
@ -689,13 +689,13 @@ struct InscriptionManagerView: View {
Divider()
NavigationLink {
ClubsView() { club in
if let event = tournament.eventObject {
if let event = tournament.eventObject() {
event.club = club.id
try? dataStore.events.addOrUpdate(instance: event)
} else {
let event = Event(club: club.id)
tournament.event = event.id
try? dataStore.events.addOrUpdate(instance: event)
do {
try dataStore.events.addOrUpdate(instance: event)
} catch {
Logger.error(error)
}
}
_save()
}
@ -710,13 +710,13 @@ struct InscriptionManagerView: View {
} else {
NavigationLink {
ClubsView() { club in
if let event = tournament.eventObject {
if let event = tournament.eventObject() {
event.club = club.id
try? dataStore.events.addOrUpdate(instance: event)
} else {
let event = Event(club: club.id)
tournament.event = event.id
try? dataStore.events.addOrUpdate(instance: event)
do {
try dataStore.events.addOrUpdate(instance: event)
} catch {
Logger.error(error)
}
}
_save()
}

Loading…
Cancel
Save