diff --git a/.DS_Store b/.DS_Store
index 4854eab..8b9a045 100644
Binary files a/.DS_Store and b/.DS_Store differ
diff --git a/PadelClubData.xcodeproj/xcshareddata/xcschemes/PadelClubData.xcscheme b/PadelClubData.xcodeproj/xcshareddata/xcschemes/PadelClubData.xcscheme
new file mode 100644
index 0000000..d71c984
--- /dev/null
+++ b/PadelClubData.xcodeproj/xcshareddata/xcschemes/PadelClubData.xcscheme
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/PadelClubData/.DS_Store b/PadelClubData/.DS_Store
index ca047a2..2fa4ede 100644
Binary files a/PadelClubData/.DS_Store and b/PadelClubData/.DS_Store differ
diff --git a/PadelClubData/Business/OnlineRegistrationStatus.swift b/PadelClubData/Business.swift
similarity index 56%
rename from PadelClubData/Business/OnlineRegistrationStatus.swift
rename to PadelClubData/Business.swift
index 19f3bca..422ba55 100644
--- a/PadelClubData/Business/OnlineRegistrationStatus.swift
+++ b/PadelClubData/Business.swift
@@ -1,12 +1,36 @@
//
-// OnlineRegistrationStatus.swift
+// Business.swift
// PadelClubData
//
-// Created by Laurent Morvillier on 15/04/2025.
+// Created by Laurent Morvillier on 30/04/2025.
//
import Foundation
+public protocol SpinDrawable {
+ func segmentLabel(_ displayStyle: DisplayStyle, hideNames: Bool) -> [String]
+}
+
+public enum DayPeriod: Int, CaseIterable, Identifiable, Codable {
+
+ public var id: Int { self.rawValue }
+
+ case all
+ case weekend
+ case week
+
+ public func localizedDayPeriodLabel() -> String {
+ switch self {
+ case .all:
+ return "n'importe"
+ case .week:
+ return "la semaine"
+ case .weekend:
+ return "le week-end"
+ }
+ }
+}
+
public enum OnlineRegistrationStatus: Int {
case open = 1
case notEnabled = 2
@@ -59,3 +83,32 @@ public enum OnlineRegistrationStatus: Int {
}
}
}
+
+public enum PlayerFilterOption: Int, Hashable, CaseIterable, Identifiable {
+ case all = -1
+ case male = 1
+ case female = 0
+
+ public var id: Int { rawValue }
+
+ public func icon() -> String {
+ switch self {
+ case .all:
+ return "Tous"
+ case .male:
+ return "Homme"
+ case .female:
+ return "Femme"
+ }
+ }
+
+ public var localizedPlayerLabel: String {
+ switch self {
+ case .female:
+ return "joueuse"
+ default:
+ return "joueur"
+ }
+ }
+
+}
diff --git a/PadelClubData/Business/DayPeriod.swift b/PadelClubData/Business/DayPeriod.swift
deleted file mode 100644
index b418ff7..0000000
--- a/PadelClubData/Business/DayPeriod.swift
+++ /dev/null
@@ -1,27 +0,0 @@
-//
-// DayPeriod.swift
-// PadelClubData
-//
-// Created by Laurent Morvillier on 15/04/2025.
-//
-
-import Foundation
-
-public enum DayPeriod: Int, CaseIterable, Identifiable, Codable {
- public var id: Int { self.rawValue }
-
- case all
- case weekend
- case week
-
- public func localizedDayPeriodLabel() -> String {
- switch self {
- case .all:
- return "n'importe"
- case .week:
- return "la semaine"
- case .weekend:
- return "le week-end"
- }
- }
-}
diff --git a/PadelClubData/Business/SpinDrawable.swift b/PadelClubData/Business/SpinDrawable.swift
deleted file mode 100644
index 7888d2c..0000000
--- a/PadelClubData/Business/SpinDrawable.swift
+++ /dev/null
@@ -1,12 +0,0 @@
-//
-// SpinDrawable.swift
-// PadelClubData
-//
-// Created by Laurent Morvillier on 15/04/2025.
-//
-
-import Foundation
-
-public protocol SpinDrawable {
- func segmentLabel(_ displayStyle: DisplayStyle, hideNames: Bool) -> [String]
-}
diff --git a/PadelClubData/ContactManager.swift b/PadelClubData/ContactManager.swift
deleted file mode 100644
index 4cf957f..0000000
--- a/PadelClubData/ContactManager.swift
+++ /dev/null
@@ -1,71 +0,0 @@
-//
-// ContactManager.swift
-// Padel Tournament
-//
-// Created by Razmig Sarkissian on 19/09/2023.
-//
-
-import Foundation
-import SwiftUI
-import MessageUI
-import LeStorage
-
-public enum ContactManagerError: LocalizedError {
- case mailFailed
- case mailNotSent //no network no error
- case messageFailed
- case messageNotSent //no network no error
- case calendarAccessDenied
- case calendarEventSaveFailed
- case noCalendarAvailable
- case uncalledTeams([TeamRegistration])
-
- var localizedDescription: String {
- switch self {
- case .mailFailed:
- return "Le mail n'a pas été envoyé"
- case .mailNotSent:
- return "Le mail est dans la boîte d'envoi de l'app Mail. Vérifiez son état dans l'app Mail avant d'essayer de le renvoyer."
- case .messageFailed:
- return "Le SMS n'a pas été envoyé"
- case .messageNotSent:
- return "Le SMS n'a pas été envoyé"
- case .uncalledTeams(let array):
- let verb = array.count > 1 ? "peuvent" : "peut"
- return "Attention, \(array.count) équipe\(array.count.pluralSuffix) ne \(verb) pas être contacté par la méthode choisie"
- case .calendarAccessDenied:
- return "Padel Club n'a pas accès à votre calendrier"
- case .calendarEventSaveFailed:
- return "Padel Club n'a pas réussi à sauver ce tournoi dans votre calendrier"
- case .noCalendarAvailable:
- return "Padel Club n'a pas réussi à trouver un calendrier pour y inscrire ce tournoi"
- }
- }
-
- public static func getNetworkErrorMessage(sentError: ContactManagerError?, networkMonitorConnected: Bool) -> String {
- var errors: [String] = []
-
- if networkMonitorConnected == false {
- errors.append("L'appareil n'est pas connecté à internet.")
- }
- if let sentError {
- errors.append(sentError.localizedDescription)
- }
- return errors.joined(separator: "\n")
- }
-}
-
-public enum ContactType: Identifiable {
- case mail(date: Date?, recipients: [String]?, bccRecipients: [String]?, body: String?, subject: String?, tournamentBuild: TournamentBuild?)
- case message(date: Date?, recipients: [String]?, body: String?, tournamentBuild: TournamentBuild?)
-
- public var id: Int {
- switch self {
- case .message: return 0
- case .mail: return 1
- }
- }
-
- public static let defaultAvailablePaymentMethods: String = "Règlement possible par chèque ou espèces."
-
-}
diff --git a/PadelClubData/Data/.DS_Store b/PadelClubData/Data/.DS_Store
index 5fc72cc..652a0a0 100644
Binary files a/PadelClubData/Data/.DS_Store and b/PadelClubData/Data/.DS_Store differ
diff --git a/PadelClubData/Data/AppSettings.swift b/PadelClubData/Data/AppSettings.swift
index f2fad36..1703b03 100644
--- a/PadelClubData/Data/AppSettings.swift
+++ b/PadelClubData/Data/AppSettings.swift
@@ -10,7 +10,7 @@ import LeStorage
import SwiftUI
@Observable
-public final class AppSettings: MicroStorable {
+final public class AppSettings: MicroStorable {
public var lastDataSource: String? = nil
public var didCreateAccount: Bool = false
diff --git a/PadelClubData/Data/Club.swift b/PadelClubData/Data/Club.swift
index 2370b6a..74bd667 100644
--- a/PadelClubData/Data/Club.swift
+++ b/PadelClubData/Data/Club.swift
@@ -10,10 +10,8 @@ import SwiftUI
import LeStorage
@Observable
-public final class Club: BaseClub {
-
- static var copyServerResponse: Bool { return true }
-
+final public class Club: BaseClub {
+
public func clubTitle(_ displayStyle: DisplayStyle = .wide) -> String {
switch displayStyle {
case .wide, .title:
diff --git a/PadelClubData/Data/Court.swift b/PadelClubData/Data/Court.swift
index 437847d..a2d6c39 100644
--- a/PadelClubData/Data/Court.swift
+++ b/PadelClubData/Data/Court.swift
@@ -10,7 +10,7 @@ import SwiftUI
import LeStorage
@Observable
-public final class Court: BaseCourt {
+final public class Court: BaseCourt {
static func == (lhs: Court, rhs: Court) -> Bool {
lhs.id == rhs.id
diff --git a/PadelClubData/Data/CustomUser.swift b/PadelClubData/Data/CustomUser.swift
index a7154e6..20e9203 100644
--- a/PadelClubData/Data/CustomUser.swift
+++ b/PadelClubData/Data/CustomUser.swift
@@ -8,12 +8,48 @@
import Foundation
import LeStorage
-enum UserRight: Int, Codable {
+public enum UserRight: Int, Codable {
case none = 0
case edition = 1
case creation = 2
}
+public enum RegistrationPaymentMode: Int, Codable {
+ case disabled = 0
+ case corporate = 1
+ case noFee = 2
+ case stripe = 3
+
+ public static let stripeFixedFee = 0.25 // Fixed fee in euros
+ public static let stripePercentageFee = 0.014 // 1.4%
+
+ public func canEnableOnlinePayment() -> Bool {
+ switch self {
+ case .disabled:
+ return false
+ case .corporate:
+ return true
+ case .noFee:
+ return true
+ case .stripe:
+ return true
+ }
+ }
+
+ public func requiresStripe() -> Bool {
+ switch self {
+ case .disabled:
+ return false
+ case .corporate:
+ return true
+ case .noFee:
+ return true
+ case .stripe:
+ return true
+ }
+ }
+}
+
@Observable
public class CustomUser: BaseCustomUser, UserBase {
@@ -48,7 +84,7 @@ public class CustomUser: BaseCustomUser, UserBase {
//
// var deviceId: String?
- init(username: String, email: String, firstName: String, lastName: String, phone: String?, country: String?, loserBracketMode: LoserBracketMode = .automatic) {
+ public init(username: String, email: String, firstName: String, lastName: String, phone: String?, country: String?, loserBracketMode: LoserBracketMode = .automatic) {
super.init(username: username, email: email, firstName: firstName, lastName: lastName, phone: phone, country: country, loserBracketMode: loserBracketMode)
@@ -129,7 +165,10 @@ public class CustomUser: BaseCustomUser, UserBase {
}
}
-// enum CodingKeys: String, CodingKey {
+ public func canEnableOnlinePayment() -> Bool {
+ registrationPaymentMode.canEnableOnlinePayment()
+ }
+// public enum CodingKeys: String, CodingKey {
// case _id = "id"
// case _lastUpdate = "lastUpdate"
// case _username = "username"
diff --git a/PadelClubData/Data/DataStore.swift b/PadelClubData/Data/DataStore.swift
index 67f36f3..17229cc 100644
--- a/PadelClubData/Data/DataStore.swift
+++ b/PadelClubData/Data/DataStore.swift
@@ -39,7 +39,7 @@ public class DataStore: ObservableObject {
public fileprivate(set) var dateIntervals: SyncedCollection
public fileprivate(set) var purchases: SyncedCollection
- fileprivate var userStorage: StoredSingleton
+ public var userStorage: StoredSingleton
fileprivate var _temporaryLocalUser: OptionalStorage = OptionalStorage(fileName: "tmp_local_user.json")
public fileprivate(set) var appSettingsStorage: MicroStorage = MicroStorage(fileName: "appsettings.json")
@@ -282,8 +282,16 @@ public class DataStore: ObservableObject {
// MARK: - Convenience
+ private var _lastRunningCheckDate: Date? = nil
+ private var _cachedRunningMatches: [Match]? = nil
+
public func runningMatches() -> [Match] {
let dateNow : Date = Date()
+ if let lastCheck = _lastRunningCheckDate,
+ let cachedMatches = _cachedRunningMatches,
+ dateNow.timeIntervalSince(lastCheck) < 5 {
+ return cachedMatches
+ }
let lastTournaments = self.tournaments.filter { $0.isDeleted == false && $0.startDate <= dateNow && $0.hasEnded() == false }.sorted(by: \Tournament.startDate, order: .descending).prefix(10)
var runningMatches: [Match] = []
@@ -295,11 +303,22 @@ public class DataStore: ObservableObject {
runningMatches.append(contentsOf: matches)
}
}
- return runningMatches
+ _lastRunningCheckDate = dateNow
+ _cachedRunningMatches = runningMatches
+ return _cachedRunningMatches!
}
+ private var _lastRunningAndNextCheckDate: Date? = nil
+ private var _cachedRunningAndNextMatches: [Match]? = nil
+
public func runningAndNextMatches() -> [Match] {
let dateNow : Date = Date()
+ if let lastCheck = _lastRunningAndNextCheckDate,
+ let cachedMatches = _cachedRunningAndNextMatches,
+ dateNow.timeIntervalSince(lastCheck) < 5 {
+ return cachedMatches
+ }
+
let lastTournaments = self.tournaments.filter { $0.isDeleted == false && $0.startDate <= dateNow && $0.hasEnded() == false }.sorted(by: \Tournament.startDate, order: .descending).prefix(10)
var runningMatches: [Match] = []
@@ -310,11 +329,23 @@ public class DataStore: ObservableObject {
runningMatches.append(contentsOf: matches)
}
}
- return runningMatches
+ _lastRunningAndNextCheckDate = dateNow
+ _cachedRunningAndNextMatches = runningMatches
+ return _cachedRunningAndNextMatches!
}
+ private var _lastEndCheckDate: Date? = nil
+ private var _cachedEndMatches: [Match]? = nil
+
public func endMatches() -> [Match] {
let dateNow : Date = Date()
+
+ if let lastCheck = _lastEndCheckDate,
+ let cachedMatches = _cachedEndMatches,
+ dateNow.timeIntervalSince(lastCheck) < 5 {
+ return cachedMatches
+ }
+
let lastTournaments = self.tournaments.filter { $0.isDeleted == false && $0.startDate <= dateNow && $0.hasEnded() == false }.sorted(by: \Tournament.startDate, order: .descending).prefix(10)
var runningMatches: [Match] = []
@@ -325,7 +356,18 @@ public class DataStore: ObservableObject {
runningMatches.append(contentsOf: matches)
}
}
- return runningMatches.sorted(by: \.endDate!, order: .descending)
+
+ _lastEndCheckDate = dateNow
+ _cachedEndMatches = runningMatches.sorted(by: \.endDate!, order: .descending)
+ return _cachedEndMatches!
+ }
+
+ public func subscriptionUnitlyPayedTournaments(after date: Date) -> Int {
+ return DataStore.shared.tournaments.count(where: { tournament in
+ tournament.creationDate > date &&
+ tournament.payment == .subscriptionUnit &&
+ tournament.isCanceled == false &&
+ tournament.isDeleted == false })
}
}
diff --git a/PadelClubData/Data/DateInterval.swift b/PadelClubData/Data/DateInterval.swift
index 6352641..b7cb277 100644
--- a/PadelClubData/Data/DateInterval.swift
+++ b/PadelClubData/Data/DateInterval.swift
@@ -10,7 +10,7 @@ import SwiftUI
import LeStorage
@Observable
-public final class DateInterval: BaseDateInterval {
+final public class DateInterval: BaseDateInterval {
// static func resourceName() -> String { return "date-intervals" }
// static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
@@ -45,15 +45,15 @@ public final class DateInterval: BaseDateInterval {
startDate.. Bool {
+ public func isSingleDay() -> Bool {
Calendar.current.isDate(startDate, inSameDayAs: endDate)
}
- func isDateInside(_ date: Date) -> Bool {
+ public func isDateInside(_ date: Date) -> Bool {
date >= startDate && date <= endDate
}
- func isDateOutside(_ date: Date) -> Bool {
+ public func isDateOutside(_ date: Date) -> Bool {
date <= startDate && date <= endDate && date >= startDate && date >= endDate
}
diff --git a/PadelClubData/Data/DrawLog.swift b/PadelClubData/Data/DrawLog.swift
index e663219..4130f73 100644
--- a/PadelClubData/Data/DrawLog.swift
+++ b/PadelClubData/Data/DrawLog.swift
@@ -10,7 +10,7 @@ import SwiftUI
import LeStorage
@Observable
-public final class DrawLog: BaseDrawLog, SideStorable {
+final public class DrawLog: BaseDrawLog, SideStorable {
public func tournamentObject() -> Tournament? {
Store.main.findById(self.tournament)
@@ -70,7 +70,7 @@ public final class DrawLog: BaseDrawLog, SideStorable {
return drawMatch()?.matchTitle() ?? ""
}
- public var tournamentStore: TournamentStore? {
+ var tournamentStore: TournamentStore? {
return TournamentLibrary.shared.store(tournamentId: self.tournament)
}
@@ -84,7 +84,7 @@ public enum DrawType: Int, Codable {
case groupStage
case court
- func localizedDrawType() -> String {
+ public func localizedDrawType() -> String {
switch self {
case .seed:
return "Tête de série"
diff --git a/PadelClubData/Data/Event.swift b/PadelClubData/Data/Event.swift
index de202f5..210c5f7 100644
--- a/PadelClubData/Data/Event.swift
+++ b/PadelClubData/Data/Event.swift
@@ -10,7 +10,7 @@ import LeStorage
import SwiftUI
@Observable
-public final class Event: BaseEvent {
+final public class Event: BaseEvent {
public init(creator: String? = nil, club: String? = nil, name: String? = nil, tenupId: String? = nil) {
super.init(creator: creator, club: club, name: name, tenupId: tenupId)
diff --git a/PadelClubData/Data/Gen/BaseClub.swift b/PadelClubData/Data/Gen/BaseClub.swift
index c98952d..5262305 100644
--- a/PadelClubData/Data/Gen/BaseClub.swift
+++ b/PadelClubData/Data/Gen/BaseClub.swift
@@ -10,6 +10,7 @@ public class BaseClub: SyncedModelObject, SyncedStorable {
public static func resourceName() -> String { return "clubs" }
public static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
+ public static var copyServerResponse: Bool = true
public var id: String = Store.randomId()
public var creator: String? = nil
diff --git a/PadelClubData/Data/Gen/BaseCourt.swift b/PadelClubData/Data/Gen/BaseCourt.swift
index e799249..e4ca668 100644
--- a/PadelClubData/Data/Gen/BaseCourt.swift
+++ b/PadelClubData/Data/Gen/BaseCourt.swift
@@ -10,6 +10,7 @@ public class BaseCourt: SyncedModelObject, SyncedStorable {
public static func resourceName() -> String { return "courts" }
public static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
+ public static var copyServerResponse: Bool = false
public var id: String = Store.randomId()
public var index: Int = 0
diff --git a/PadelClubData/Data/Gen/BaseCustomUser.swift b/PadelClubData/Data/Gen/BaseCustomUser.swift
index 81f7660..5abcf9e 100644
--- a/PadelClubData/Data/Gen/BaseCustomUser.swift
+++ b/PadelClubData/Data/Gen/BaseCustomUser.swift
@@ -10,6 +10,7 @@ public class BaseCustomUser: SyncedModelObject, SyncedStorable {
public static func resourceName() -> String { return "users" }
public static func tokenExemptedMethods() -> [HTTPMethod] { return [.post] }
+ public static var copyServerResponse: Bool = false
public var id: String = Store.randomId()
public var username: String = ""
@@ -32,8 +33,16 @@ public class BaseCustomUser: SyncedModelObject, SyncedStorable {
public var groupStageMatchFormatPreference: MatchFormat? = nil
public var loserBracketMatchFormatPreference: MatchFormat? = nil
public var loserBracketMode: LoserBracketMode = .automatic
+ public var disableRankingFederalRuling: Bool = false
public var deviceId: String? = nil
public var agents: [String] = []
+ public var userRole: Int? = nil
+ public var registrationPaymentMode: RegistrationPaymentMode = RegistrationPaymentMode.disabled
+ public var umpireCustomMail: String? = nil
+ public var umpireCustomContact: String? = nil
+ public var umpireCustomPhone: String? = nil
+ public var hideUmpireMail: Bool = false
+ public var hideUmpirePhone: Bool = true
public init(
id: String = Store.randomId(),
@@ -57,8 +66,16 @@ public class BaseCustomUser: SyncedModelObject, SyncedStorable {
groupStageMatchFormatPreference: MatchFormat? = nil,
loserBracketMatchFormatPreference: MatchFormat? = nil,
loserBracketMode: LoserBracketMode = .automatic,
+ disableRankingFederalRuling: Bool = false,
deviceId: String? = nil,
- agents: [String] = []
+ agents: [String] = [],
+ userRole: Int? = nil,
+ registrationPaymentMode: RegistrationPaymentMode = RegistrationPaymentMode.disabled,
+ umpireCustomMail: String? = nil,
+ umpireCustomContact: String? = nil,
+ umpireCustomPhone: String? = nil,
+ hideUmpireMail: Bool = false,
+ hideUmpirePhone: Bool = true
) {
super.init()
self.id = id
@@ -82,8 +99,16 @@ public class BaseCustomUser: SyncedModelObject, SyncedStorable {
self.groupStageMatchFormatPreference = groupStageMatchFormatPreference
self.loserBracketMatchFormatPreference = loserBracketMatchFormatPreference
self.loserBracketMode = loserBracketMode
+ self.disableRankingFederalRuling = disableRankingFederalRuling
self.deviceId = deviceId
self.agents = agents
+ self.userRole = userRole
+ self.registrationPaymentMode = registrationPaymentMode
+ self.umpireCustomMail = umpireCustomMail
+ self.umpireCustomContact = umpireCustomContact
+ self.umpireCustomPhone = umpireCustomPhone
+ self.hideUmpireMail = hideUmpireMail
+ self.hideUmpirePhone = hideUmpirePhone
}
required public override init() {
super.init()
@@ -111,8 +136,16 @@ public class BaseCustomUser: SyncedModelObject, SyncedStorable {
case _groupStageMatchFormatPreference = "groupStageMatchFormatPreference"
case _loserBracketMatchFormatPreference = "loserBracketMatchFormatPreference"
case _loserBracketMode = "loserBracketMode"
+ case _disableRankingFederalRuling = "disableRankingFederalRuling"
case _deviceId = "deviceId"
case _agents = "agents"
+ case _userRole = "userRole"
+ case _registrationPaymentMode = "registrationPaymentMode"
+ case _umpireCustomMail = "umpireCustomMail"
+ case _umpireCustomContact = "umpireCustomContact"
+ case _umpireCustomPhone = "umpireCustomPhone"
+ case _hideUmpireMail = "hideUmpireMail"
+ case _hideUmpirePhone = "hideUmpirePhone"
}
required init(from decoder: Decoder) throws {
@@ -138,8 +171,16 @@ public class BaseCustomUser: SyncedModelObject, SyncedStorable {
self.groupStageMatchFormatPreference = try container.decodeIfPresent(MatchFormat.self, forKey: ._groupStageMatchFormatPreference) ?? nil
self.loserBracketMatchFormatPreference = try container.decodeIfPresent(MatchFormat.self, forKey: ._loserBracketMatchFormatPreference) ?? nil
self.loserBracketMode = try container.decodeIfPresent(LoserBracketMode.self, forKey: ._loserBracketMode) ?? .automatic
+ self.disableRankingFederalRuling = try container.decodeIfPresent(Bool.self, forKey: ._disableRankingFederalRuling) ?? false
self.deviceId = try container.decodeIfPresent(String.self, forKey: ._deviceId) ?? nil
self.agents = try container.decodeIfPresent([String].self, forKey: ._agents) ?? []
+ self.userRole = try container.decodeIfPresent(Int.self, forKey: ._userRole) ?? nil
+ self.registrationPaymentMode = try container.decodeIfPresent(RegistrationPaymentMode.self, forKey: ._registrationPaymentMode) ?? RegistrationPaymentMode.disabled
+ self.umpireCustomMail = try container.decodeIfPresent(String.self, forKey: ._umpireCustomMail) ?? nil
+ self.umpireCustomContact = try container.decodeIfPresent(String.self, forKey: ._umpireCustomContact) ?? nil
+ self.umpireCustomPhone = try container.decodeIfPresent(String.self, forKey: ._umpireCustomPhone) ?? nil
+ self.hideUmpireMail = try container.decodeIfPresent(Bool.self, forKey: ._hideUmpireMail) ?? false
+ self.hideUmpirePhone = try container.decodeIfPresent(Bool.self, forKey: ._hideUmpirePhone) ?? true
try super.init(from: decoder)
}
@@ -166,8 +207,16 @@ public class BaseCustomUser: SyncedModelObject, SyncedStorable {
try container.encode(self.groupStageMatchFormatPreference, forKey: ._groupStageMatchFormatPreference)
try container.encode(self.loserBracketMatchFormatPreference, forKey: ._loserBracketMatchFormatPreference)
try container.encode(self.loserBracketMode, forKey: ._loserBracketMode)
+ try container.encode(self.disableRankingFederalRuling, forKey: ._disableRankingFederalRuling)
try container.encode(self.deviceId, forKey: ._deviceId)
try container.encode(self.agents, forKey: ._agents)
+ try container.encode(self.userRole, forKey: ._userRole)
+ try container.encode(self.registrationPaymentMode, forKey: ._registrationPaymentMode)
+ try container.encode(self.umpireCustomMail, forKey: ._umpireCustomMail)
+ try container.encode(self.umpireCustomContact, forKey: ._umpireCustomContact)
+ try container.encode(self.umpireCustomPhone, forKey: ._umpireCustomPhone)
+ try container.encode(self.hideUmpireMail, forKey: ._hideUmpireMail)
+ try container.encode(self.hideUmpirePhone, forKey: ._hideUmpirePhone)
try super.encode(to: encoder)
}
@@ -194,8 +243,16 @@ public class BaseCustomUser: SyncedModelObject, SyncedStorable {
self.groupStageMatchFormatPreference = customuser.groupStageMatchFormatPreference
self.loserBracketMatchFormatPreference = customuser.loserBracketMatchFormatPreference
self.loserBracketMode = customuser.loserBracketMode
+ self.disableRankingFederalRuling = customuser.disableRankingFederalRuling
self.deviceId = customuser.deviceId
self.agents = customuser.agents
+ self.userRole = customuser.userRole
+ self.registrationPaymentMode = customuser.registrationPaymentMode
+ self.umpireCustomMail = customuser.umpireCustomMail
+ self.umpireCustomContact = customuser.umpireCustomContact
+ self.umpireCustomPhone = customuser.umpireCustomPhone
+ self.hideUmpireMail = customuser.hideUmpireMail
+ self.hideUmpirePhone = customuser.hideUmpirePhone
}
public static func relationships() -> [Relationship] {
diff --git a/PadelClubData/Data/Gen/BaseDateInterval.swift b/PadelClubData/Data/Gen/BaseDateInterval.swift
index caadaa3..79e788c 100644
--- a/PadelClubData/Data/Gen/BaseDateInterval.swift
+++ b/PadelClubData/Data/Gen/BaseDateInterval.swift
@@ -10,6 +10,7 @@ public class BaseDateInterval: SyncedModelObject, SyncedStorable {
public static func resourceName() -> String { return "date-intervals" }
public static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
+ public static var copyServerResponse: Bool = false
public var id: String = Store.randomId()
public var event: String = ""
diff --git a/PadelClubData/Data/Gen/BaseDrawLog.swift b/PadelClubData/Data/Gen/BaseDrawLog.swift
index 2b619b1..12f0a12 100644
--- a/PadelClubData/Data/Gen/BaseDrawLog.swift
+++ b/PadelClubData/Data/Gen/BaseDrawLog.swift
@@ -10,6 +10,7 @@ public class BaseDrawLog: SyncedModelObject, SyncedStorable {
public static func resourceName() -> String { return "draw-logs" }
public static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
+ public static var copyServerResponse: Bool = false
public var id: String = Store.randomId()
public var tournament: String = ""
diff --git a/PadelClubData/Data/Gen/BaseEvent.swift b/PadelClubData/Data/Gen/BaseEvent.swift
index 6b7bcc2..0d2c634 100644
--- a/PadelClubData/Data/Gen/BaseEvent.swift
+++ b/PadelClubData/Data/Gen/BaseEvent.swift
@@ -10,6 +10,7 @@ public class BaseEvent: SyncedModelObject, SyncedStorable {
public static func resourceName() -> String { return "events" }
public static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
+ public static var copyServerResponse: Bool = false
public var id: String = Store.randomId()
public var creator: String? = nil
diff --git a/PadelClubData/Data/Gen/BaseGroupStage.swift b/PadelClubData/Data/Gen/BaseGroupStage.swift
index a4f11cb..b727585 100644
--- a/PadelClubData/Data/Gen/BaseGroupStage.swift
+++ b/PadelClubData/Data/Gen/BaseGroupStage.swift
@@ -10,6 +10,7 @@ public class BaseGroupStage: SyncedModelObject, SyncedStorable {
public static func resourceName() -> String { return "group-stages" }
public static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
+ public static var copyServerResponse: Bool = false
public var id: String = Store.randomId()
public var tournament: String = ""
diff --git a/PadelClubData/Data/Gen/BaseMatch.swift b/PadelClubData/Data/Gen/BaseMatch.swift
index 22848f3..2665072 100644
--- a/PadelClubData/Data/Gen/BaseMatch.swift
+++ b/PadelClubData/Data/Gen/BaseMatch.swift
@@ -10,6 +10,7 @@ public class BaseMatch: SyncedModelObject, SyncedStorable {
public static func resourceName() -> String { return "matches" }
public static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
+ public static var copyServerResponse: Bool = false
public var id: String = Store.randomId()
public var round: String? = nil
diff --git a/PadelClubData/Data/Gen/BaseMatchScheduler.swift b/PadelClubData/Data/Gen/BaseMatchScheduler.swift
index e0af3c6..8e86c9d 100644
--- a/PadelClubData/Data/Gen/BaseMatchScheduler.swift
+++ b/PadelClubData/Data/Gen/BaseMatchScheduler.swift
@@ -10,6 +10,7 @@ public class BaseMatchScheduler: BaseModelObject, Storable {
public static func resourceName() -> String { return "match-scheduler" }
public static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
+ public static var copyServerResponse: Bool = false
public var id: String = Store.randomId()
public var tournament: String = ""
diff --git a/PadelClubData/Data/Gen/BaseMonthData.swift b/PadelClubData/Data/Gen/BaseMonthData.swift
index 38f7451..9ffe47e 100644
--- a/PadelClubData/Data/Gen/BaseMonthData.swift
+++ b/PadelClubData/Data/Gen/BaseMonthData.swift
@@ -10,6 +10,7 @@ public class BaseMonthData: BaseModelObject, Storable {
public static func resourceName() -> String { return "month-data" }
public static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
+ public static var copyServerResponse: Bool = false
public var id: String = Store.randomId()
public var monthKey: String = ""
diff --git a/PadelClubData/Data/Gen/BasePlayerRegistration.swift b/PadelClubData/Data/Gen/BasePlayerRegistration.swift
index 00cd033..9cdfcef 100644
--- a/PadelClubData/Data/Gen/BasePlayerRegistration.swift
+++ b/PadelClubData/Data/Gen/BasePlayerRegistration.swift
@@ -10,6 +10,7 @@ public class BasePlayerRegistration: SyncedModelObject, SyncedStorable {
public static func resourceName() -> String { return "player-registrations" }
public static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
+ public static var copyServerResponse: Bool = false
public var id: String = Store.randomId()
public var teamRegistration: String? = nil
@@ -33,6 +34,9 @@ public class BasePlayerRegistration: SyncedModelObject, SyncedStorable {
public var coach: Bool = false
public var captain: Bool = false
public var registeredOnline: Bool = false
+ public var timeToConfirm: Date? = nil
+ public var registrationStatus: PlayerRegistration.RegistrationStatus = PlayerRegistration.RegistrationStatus.waiting
+ public var paymentId: String? = nil
public init(
id: String = Store.randomId(),
@@ -56,7 +60,10 @@ public class BasePlayerRegistration: SyncedModelObject, SyncedStorable {
hasArrived: Bool = false,
coach: Bool = false,
captain: Bool = false,
- registeredOnline: Bool = false
+ registeredOnline: Bool = false,
+ timeToConfirm: Date? = nil,
+ registrationStatus: PlayerRegistration.RegistrationStatus = PlayerRegistration.RegistrationStatus.waiting,
+ paymentId: String? = nil
) {
super.init()
self.id = id
@@ -81,6 +88,9 @@ public class BasePlayerRegistration: SyncedModelObject, SyncedStorable {
self.coach = coach
self.captain = captain
self.registeredOnline = registeredOnline
+ self.timeToConfirm = timeToConfirm
+ self.registrationStatus = registrationStatus
+ self.paymentId = paymentId
}
required public override init() {
super.init()
@@ -109,6 +119,9 @@ public class BasePlayerRegistration: SyncedModelObject, SyncedStorable {
case _coach = "coach"
case _captain = "captain"
case _registeredOnline = "registeredOnline"
+ case _timeToConfirm = "timeToConfirm"
+ case _registrationStatus = "registrationStatus"
+ case _paymentId = "paymentId"
}
required init(from decoder: Decoder) throws {
@@ -135,6 +148,9 @@ public class BasePlayerRegistration: SyncedModelObject, SyncedStorable {
self.coach = try container.decodeIfPresent(Bool.self, forKey: ._coach) ?? false
self.captain = try container.decodeIfPresent(Bool.self, forKey: ._captain) ?? false
self.registeredOnline = try container.decodeIfPresent(Bool.self, forKey: ._registeredOnline) ?? false
+ self.timeToConfirm = try container.decodeIfPresent(Date.self, forKey: ._timeToConfirm) ?? nil
+ self.registrationStatus = try container.decodeIfPresent(PlayerRegistration.RegistrationStatus.self, forKey: ._registrationStatus) ?? PlayerRegistration.RegistrationStatus.waiting
+ self.paymentId = try container.decodeIfPresent(String.self, forKey: ._paymentId) ?? nil
try super.init(from: decoder)
}
@@ -162,6 +178,9 @@ public class BasePlayerRegistration: SyncedModelObject, SyncedStorable {
try container.encode(self.coach, forKey: ._coach)
try container.encode(self.captain, forKey: ._captain)
try container.encode(self.registeredOnline, forKey: ._registeredOnline)
+ try container.encode(self.timeToConfirm, forKey: ._timeToConfirm)
+ try container.encode(self.registrationStatus, forKey: ._registrationStatus)
+ try container.encode(self.paymentId, forKey: ._paymentId)
try super.encode(to: encoder)
}
@@ -194,6 +213,9 @@ public class BasePlayerRegistration: SyncedModelObject, SyncedStorable {
self.coach = playerregistration.coach
self.captain = playerregistration.captain
self.registeredOnline = playerregistration.registeredOnline
+ self.timeToConfirm = playerregistration.timeToConfirm
+ self.registrationStatus = playerregistration.registrationStatus
+ self.paymentId = playerregistration.paymentId
}
public static func relationships() -> [Relationship] {
diff --git a/PadelClubData/Data/Gen/BasePurchase.swift b/PadelClubData/Data/Gen/BasePurchase.swift
index cd4acf3..c1f3736 100644
--- a/PadelClubData/Data/Gen/BasePurchase.swift
+++ b/PadelClubData/Data/Gen/BasePurchase.swift
@@ -8,6 +8,7 @@ public class BasePurchase: SyncedModelObject, SyncedStorable {
public static func resourceName() -> String { return "purchases" }
public static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
+ public static var copyServerResponse: Bool = false
public var id: UInt64 = 0
public var user: String = ""
diff --git a/PadelClubData/Data/Gen/BaseRound.swift b/PadelClubData/Data/Gen/BaseRound.swift
index fd35fda..b06a950 100644
--- a/PadelClubData/Data/Gen/BaseRound.swift
+++ b/PadelClubData/Data/Gen/BaseRound.swift
@@ -10,6 +10,7 @@ public class BaseRound: SyncedModelObject, SyncedStorable {
public static func resourceName() -> String { return "rounds" }
public static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
+ public static var copyServerResponse: Bool = false
public var id: String = Store.randomId()
public var tournament: String = ""
diff --git a/PadelClubData/Data/Gen/BaseTeamRegistration.swift b/PadelClubData/Data/Gen/BaseTeamRegistration.swift
index 7517b1a..b98b267 100644
--- a/PadelClubData/Data/Gen/BaseTeamRegistration.swift
+++ b/PadelClubData/Data/Gen/BaseTeamRegistration.swift
@@ -10,6 +10,7 @@ public class BaseTeamRegistration: SyncedModelObject, SyncedStorable {
public static func resourceName() -> String { return "team-registrations" }
public static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
+ public static var copyServerResponse: Bool = false
public var id: String = Store.randomId()
public var tournament: String = ""
diff --git a/PadelClubData/Data/Gen/BaseTeamScore.swift b/PadelClubData/Data/Gen/BaseTeamScore.swift
index 6b97720..025495c 100644
--- a/PadelClubData/Data/Gen/BaseTeamScore.swift
+++ b/PadelClubData/Data/Gen/BaseTeamScore.swift
@@ -10,6 +10,7 @@ public class BaseTeamScore: SyncedModelObject, SyncedStorable {
public static func resourceName() -> String { return "team-scores" }
public static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
+ public static var copyServerResponse: Bool = false
public var id: String = Store.randomId()
public var match: String = ""
diff --git a/PadelClubData/Data/Gen/BaseTournament.swift b/PadelClubData/Data/Gen/BaseTournament.swift
index 4deebf1..68ea612 100644
--- a/PadelClubData/Data/Gen/BaseTournament.swift
+++ b/PadelClubData/Data/Gen/BaseTournament.swift
@@ -10,6 +10,7 @@ public class BaseTournament: SyncedModelObject, SyncedStorable {
public static func resourceName() -> String { return "tournaments" }
public static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
+ public static var copyServerResponse: Bool = false
public var id: String = Store.randomId()
public var event: String? = nil
@@ -22,20 +23,20 @@ public class BaseTournament: SyncedModelObject, SyncedStorable {
public var roundFormat: MatchFormat? = nil
public var loserRoundFormat: MatchFormat? = nil
public var groupStageSortMode: GroupStageOrderingMode = GroupStageOrderingMode.snake
- public var groupStageCount: Int = 0
+ public var groupStageCount: Int = 4
public var rankSourceDate: Date? = nil
- public var dayDuration: Int = 0
- public var teamCount: Int = 0
+ public var dayDuration: Int = 1
+ public var teamCount: Int = 24
public var teamSorting: TeamSortingType = TeamSortingType.inscriptionDate
public var federalCategory: TournamentCategory = TournamentCategory.men
- public var federalLevelCategory: TournamentLevel = TournamentLevel.unlisted
- public var federalAgeCategory: FederalTournamentAge = FederalTournamentAge.unlisted
+ public var federalLevelCategory: TournamentLevel = TournamentLevel.p100
+ public var federalAgeCategory: FederalTournamentAge = FederalTournamentAge.senior
public var closedRegistrationDate: Date? = nil
public var groupStageAdditionalQualified: Int = 0
public var courtCount: Int = 2
public var prioritizeClubMembers: Bool = false
- public var qualifiedPerGroupStage: Int = 0
- public var teamsPerGroupStage: Int = 0
+ public var qualifiedPerGroupStage: Int = 1
+ public var teamsPerGroupStage: Int = 4
public var entryFee: Double? = nil
public var payment: TournamentPayment? = nil
public var additionalEstimationDuration: Int = 0
@@ -70,6 +71,14 @@ public class BaseTournament: SyncedModelObject, SyncedStorable {
public var hideUmpirePhone: Bool = true
public var disableRankingFederalRuling: Bool = false
public var teamCountLimit: Bool = true
+ public var enableOnlinePayment: Bool = false
+ public var onlinePaymentIsMandatory: Bool = false
+ public var enableOnlinePaymentRefund: Bool = false
+ public var refundDateLimit: Date? = nil
+ public var stripeAccountId: String? = nil
+ public var enableTimeToConfirm: Bool = false
+ public var isCorporateTournament: Bool = false
+ public var isTemplate: Bool = false
public init(
id: String = Store.randomId(),
@@ -83,20 +92,20 @@ public class BaseTournament: SyncedModelObject, SyncedStorable {
roundFormat: MatchFormat? = nil,
loserRoundFormat: MatchFormat? = nil,
groupStageSortMode: GroupStageOrderingMode = GroupStageOrderingMode.snake,
- groupStageCount: Int = 0,
+ groupStageCount: Int = 4,
rankSourceDate: Date? = nil,
- dayDuration: Int = 0,
- teamCount: Int = 0,
+ dayDuration: Int = 1,
+ teamCount: Int = 24,
teamSorting: TeamSortingType = TeamSortingType.inscriptionDate,
federalCategory: TournamentCategory = TournamentCategory.men,
- federalLevelCategory: TournamentLevel = TournamentLevel.unlisted,
- federalAgeCategory: FederalTournamentAge = FederalTournamentAge.unlisted,
+ federalLevelCategory: TournamentLevel = TournamentLevel.p100,
+ federalAgeCategory: FederalTournamentAge = FederalTournamentAge.senior,
closedRegistrationDate: Date? = nil,
groupStageAdditionalQualified: Int = 0,
courtCount: Int = 2,
prioritizeClubMembers: Bool = false,
- qualifiedPerGroupStage: Int = 0,
- teamsPerGroupStage: Int = 0,
+ qualifiedPerGroupStage: Int = 1,
+ teamsPerGroupStage: Int = 4,
entryFee: Double? = nil,
payment: TournamentPayment? = nil,
additionalEstimationDuration: Int = 0,
@@ -130,7 +139,15 @@ public class BaseTournament: SyncedModelObject, SyncedStorable {
hideUmpireMail: Bool = false,
hideUmpirePhone: Bool = true,
disableRankingFederalRuling: Bool = false,
- teamCountLimit: Bool = true
+ teamCountLimit: Bool = true,
+ enableOnlinePayment: Bool = false,
+ onlinePaymentIsMandatory: Bool = false,
+ enableOnlinePaymentRefund: Bool = false,
+ refundDateLimit: Date? = nil,
+ stripeAccountId: String? = nil,
+ enableTimeToConfirm: Bool = false,
+ isCorporateTournament: Bool = false,
+ isTemplate: Bool = false
) {
super.init()
self.id = id
@@ -192,6 +209,14 @@ public class BaseTournament: SyncedModelObject, SyncedStorable {
self.hideUmpirePhone = hideUmpirePhone
self.disableRankingFederalRuling = disableRankingFederalRuling
self.teamCountLimit = teamCountLimit
+ self.enableOnlinePayment = enableOnlinePayment
+ self.onlinePaymentIsMandatory = onlinePaymentIsMandatory
+ self.enableOnlinePaymentRefund = enableOnlinePaymentRefund
+ self.refundDateLimit = refundDateLimit
+ self.stripeAccountId = stripeAccountId
+ self.enableTimeToConfirm = enableTimeToConfirm
+ self.isCorporateTournament = isCorporateTournament
+ self.isTemplate = isTemplate
}
required public override init() {
super.init()
@@ -259,6 +284,14 @@ public class BaseTournament: SyncedModelObject, SyncedStorable {
case _hideUmpirePhone = "hideUmpirePhone"
case _disableRankingFederalRuling = "disableRankingFederalRuling"
case _teamCountLimit = "teamCountLimit"
+ case _enableOnlinePayment = "enableOnlinePayment"
+ case _onlinePaymentIsMandatory = "onlinePaymentIsMandatory"
+ case _enableOnlinePaymentRefund = "enableOnlinePaymentRefund"
+ case _refundDateLimit = "refundDateLimit"
+ case _stripeAccountId = "stripeAccountId"
+ case _enableTimeToConfirm = "enableTimeToConfirm"
+ case _isCorporateTournament = "isCorporateTournament"
+ case _isTemplate = "isTemplate"
}
private static func _decodePayment(container: KeyedDecodingContainer) throws -> TournamentPayment? {
@@ -341,20 +374,20 @@ public class BaseTournament: SyncedModelObject, SyncedStorable {
self.roundFormat = try container.decodeIfPresent(MatchFormat.self, forKey: ._roundFormat) ?? nil
self.loserRoundFormat = try container.decodeIfPresent(MatchFormat.self, forKey: ._loserRoundFormat) ?? nil
self.groupStageSortMode = try container.decodeIfPresent(GroupStageOrderingMode.self, forKey: ._groupStageSortMode) ?? GroupStageOrderingMode.snake
- self.groupStageCount = try container.decodeIfPresent(Int.self, forKey: ._groupStageCount) ?? 0
+ self.groupStageCount = try container.decodeIfPresent(Int.self, forKey: ._groupStageCount) ?? 4
self.rankSourceDate = try container.decodeIfPresent(Date.self, forKey: ._rankSourceDate) ?? nil
- self.dayDuration = try container.decodeIfPresent(Int.self, forKey: ._dayDuration) ?? 0
- self.teamCount = try container.decodeIfPresent(Int.self, forKey: ._teamCount) ?? 0
+ self.dayDuration = try container.decodeIfPresent(Int.self, forKey: ._dayDuration) ?? 1
+ self.teamCount = try container.decodeIfPresent(Int.self, forKey: ._teamCount) ?? 24
self.teamSorting = try container.decodeIfPresent(TeamSortingType.self, forKey: ._teamSorting) ?? TeamSortingType.inscriptionDate
self.federalCategory = try container.decodeIfPresent(TournamentCategory.self, forKey: ._federalCategory) ?? TournamentCategory.men
- self.federalLevelCategory = try container.decodeIfPresent(TournamentLevel.self, forKey: ._federalLevelCategory) ?? TournamentLevel.unlisted
- self.federalAgeCategory = try container.decodeIfPresent(FederalTournamentAge.self, forKey: ._federalAgeCategory) ?? FederalTournamentAge.unlisted
+ self.federalLevelCategory = try container.decodeIfPresent(TournamentLevel.self, forKey: ._federalLevelCategory) ?? TournamentLevel.p100
+ self.federalAgeCategory = try container.decodeIfPresent(FederalTournamentAge.self, forKey: ._federalAgeCategory) ?? FederalTournamentAge.senior
self.closedRegistrationDate = try container.decodeIfPresent(Date.self, forKey: ._closedRegistrationDate) ?? nil
self.groupStageAdditionalQualified = try container.decodeIfPresent(Int.self, forKey: ._groupStageAdditionalQualified) ?? 0
self.courtCount = try container.decodeIfPresent(Int.self, forKey: ._courtCount) ?? 2
self.prioritizeClubMembers = try container.decodeIfPresent(Bool.self, forKey: ._prioritizeClubMembers) ?? false
- self.qualifiedPerGroupStage = try container.decodeIfPresent(Int.self, forKey: ._qualifiedPerGroupStage) ?? 0
- self.teamsPerGroupStage = try container.decodeIfPresent(Int.self, forKey: ._teamsPerGroupStage) ?? 0
+ self.qualifiedPerGroupStage = try container.decodeIfPresent(Int.self, forKey: ._qualifiedPerGroupStage) ?? 1
+ self.teamsPerGroupStage = try container.decodeIfPresent(Int.self, forKey: ._teamsPerGroupStage) ?? 4
self.entryFee = try container.decodeIfPresent(Double.self, forKey: ._entryFee) ?? nil
self.payment = try Self._decodePayment(container: container)
self.additionalEstimationDuration = try container.decodeIfPresent(Int.self, forKey: ._additionalEstimationDuration) ?? 0
@@ -389,6 +422,14 @@ public class BaseTournament: SyncedModelObject, SyncedStorable {
self.hideUmpirePhone = try container.decodeIfPresent(Bool.self, forKey: ._hideUmpirePhone) ?? true
self.disableRankingFederalRuling = try container.decodeIfPresent(Bool.self, forKey: ._disableRankingFederalRuling) ?? false
self.teamCountLimit = try container.decodeIfPresent(Bool.self, forKey: ._teamCountLimit) ?? true
+ self.enableOnlinePayment = try container.decodeIfPresent(Bool.self, forKey: ._enableOnlinePayment) ?? false
+ self.onlinePaymentIsMandatory = try container.decodeIfPresent(Bool.self, forKey: ._onlinePaymentIsMandatory) ?? false
+ self.enableOnlinePaymentRefund = try container.decodeIfPresent(Bool.self, forKey: ._enableOnlinePaymentRefund) ?? false
+ self.refundDateLimit = try container.decodeIfPresent(Date.self, forKey: ._refundDateLimit) ?? nil
+ self.stripeAccountId = try container.decodeIfPresent(String.self, forKey: ._stripeAccountId) ?? nil
+ self.enableTimeToConfirm = try container.decodeIfPresent(Bool.self, forKey: ._enableTimeToConfirm) ?? false
+ self.isCorporateTournament = try container.decodeIfPresent(Bool.self, forKey: ._isCorporateTournament) ?? false
+ self.isTemplate = try container.decodeIfPresent(Bool.self, forKey: ._isTemplate) ?? false
try super.init(from: decoder)
}
@@ -453,6 +494,14 @@ public class BaseTournament: SyncedModelObject, SyncedStorable {
try container.encode(self.hideUmpirePhone, forKey: ._hideUmpirePhone)
try container.encode(self.disableRankingFederalRuling, forKey: ._disableRankingFederalRuling)
try container.encode(self.teamCountLimit, forKey: ._teamCountLimit)
+ try container.encode(self.enableOnlinePayment, forKey: ._enableOnlinePayment)
+ try container.encode(self.onlinePaymentIsMandatory, forKey: ._onlinePaymentIsMandatory)
+ try container.encode(self.enableOnlinePaymentRefund, forKey: ._enableOnlinePaymentRefund)
+ try container.encode(self.refundDateLimit, forKey: ._refundDateLimit)
+ try container.encode(self.stripeAccountId, forKey: ._stripeAccountId)
+ try container.encode(self.enableTimeToConfirm, forKey: ._enableTimeToConfirm)
+ try container.encode(self.isCorporateTournament, forKey: ._isCorporateTournament)
+ try container.encode(self.isTemplate, forKey: ._isTemplate)
try super.encode(to: encoder)
}
@@ -522,6 +571,14 @@ public class BaseTournament: SyncedModelObject, SyncedStorable {
self.hideUmpirePhone = tournament.hideUmpirePhone
self.disableRankingFederalRuling = tournament.disableRankingFederalRuling
self.teamCountLimit = tournament.teamCountLimit
+ self.enableOnlinePayment = tournament.enableOnlinePayment
+ self.onlinePaymentIsMandatory = tournament.onlinePaymentIsMandatory
+ self.enableOnlinePaymentRefund = tournament.enableOnlinePaymentRefund
+ self.refundDateLimit = tournament.refundDateLimit
+ self.stripeAccountId = tournament.stripeAccountId
+ self.enableTimeToConfirm = tournament.enableTimeToConfirm
+ self.isCorporateTournament = tournament.isCorporateTournament
+ self.isTemplate = tournament.isTemplate
}
public static func relationships() -> [Relationship] {
diff --git a/PadelClubData/Data/Gen/Club.json b/PadelClubData/Data/Gen/Club.json
index 14a9fc7..cdaf5e2 100644
--- a/PadelClubData/Data/Gen/Club.json
+++ b/PadelClubData/Data/Gen/Club.json
@@ -4,6 +4,7 @@
"name": "Club",
"synchronizable": true,
"observable": true,
+ "copy_server_response": "true",
"properties": [
{
"name": "id",
diff --git a/PadelClubData/Data/Gen/CustomUser.json b/PadelClubData/Data/Gen/CustomUser.json
index ac32c3c..763d975 100644
--- a/PadelClubData/Data/Gen/CustomUser.json
+++ b/PadelClubData/Data/Gen/CustomUser.json
@@ -119,6 +119,11 @@
"type": "LoserBracketMode",
"defaultValue": ".automatic"
},
+ {
+ "name": "disableRankingFederalRuling",
+ "type": "Bool",
+ "defaultValue": "false"
+ },
{
"name": "deviceId",
"type": "String",
@@ -129,6 +134,42 @@
"name": "agents",
"type": "[String]",
"defaultValue": "[]"
+ },
+ {
+ "name": "userRole",
+ "type": "Int",
+ "optional": true,
+ "defaultValue": "nil"
+ },
+ {
+ "name": "registrationPaymentMode",
+ "type": "RegistrationPaymentMode",
+ "defaultValue": "RegistrationPaymentMode.disabled"
+ },
+ {
+ "name": "umpireCustomMail",
+ "type": "String",
+ "optional": true
+ },
+ {
+ "name": "umpireCustomContact",
+ "type": "String",
+ "optional": true
+ },
+ {
+ "name": "umpireCustomPhone",
+ "type": "String",
+ "optional": true
+ },
+ {
+ "name": "hideUmpireMail",
+ "type": "Bool",
+ "defaultValue": "false"
+ },
+ {
+ "name": "hideUmpirePhone",
+ "type": "Bool",
+ "defaultValue": "true"
}
]
}
diff --git a/PadelClubData/Data/Gen/PlayerRegistration.json b/PadelClubData/Data/Gen/PlayerRegistration.json
index 1f5eb98..72dc484 100644
--- a/PadelClubData/Data/Gen/PlayerRegistration.json
+++ b/PadelClubData/Data/Gen/PlayerRegistration.json
@@ -115,6 +115,22 @@
"name": "registeredOnline",
"type": "Bool",
"defaultValue": "false"
+ },
+ {
+ "name": "timeToConfirm",
+ "type": "Date",
+ "optional": true
+ },
+ {
+ "name": "registrationStatus",
+ "type": "PlayerRegistration.RegistrationStatus",
+ "choices": "PlayerRegistration.RegistrationStatus",
+ "defaultValue": "PlayerRegistration.RegistrationStatus.waiting"
+ },
+ {
+ "name": "paymentId",
+ "type": "String",
+ "optional": true
}
]
}
diff --git a/PadelClubData/Data/Gen/Tournament.json b/PadelClubData/Data/Gen/Tournament.json
index 1262af0..ce4f01c 100644
--- a/PadelClubData/Data/Gen/Tournament.json
+++ b/PadelClubData/Data/Gen/Tournament.json
@@ -66,7 +66,8 @@
},
{
"name": "groupStageCount",
- "type": "Int"
+ "type": "Int",
+ "defaultValue": "4"
},
{
"name": "rankSourceDate",
@@ -75,11 +76,13 @@
},
{
"name": "dayDuration",
- "type": "Int"
+ "type": "Int",
+ "defaultValue": "1"
},
{
"name": "teamCount",
- "type": "Int"
+ "type": "Int",
+ "defaultValue": "24"
},
{
"name": "teamSorting",
@@ -94,12 +97,12 @@
{
"name": "federalLevelCategory",
"type": "TournamentLevel",
- "defaultValue": "TournamentLevel.unlisted"
+ "defaultValue": "TournamentLevel.p100"
},
{
"name": "federalAgeCategory",
"type": "FederalTournamentAge",
- "defaultValue": "FederalTournamentAge.unlisted"
+ "defaultValue": "FederalTournamentAge.senior"
},
{
"name": "closedRegistrationDate",
@@ -108,7 +111,8 @@
},
{
"name": "groupStageAdditionalQualified",
- "type": "Int"
+ "type": "Int",
+ "defaultValue": "0"
},
{
"name": "courtCount",
@@ -117,15 +121,18 @@
},
{
"name": "prioritizeClubMembers",
- "type": "Bool"
+ "type": "Bool",
+ "defaultValue": "false"
},
{
"name": "qualifiedPerGroupStage",
- "type": "Int"
+ "type": "Int",
+ "defaultValue": "1"
},
{
"name": "teamsPerGroupStage",
- "type": "Int"
+ "type": "Int",
+ "defaultValue": "4"
},
{
"name": "entryFee",
@@ -255,12 +262,12 @@
{
"name": "minimumPlayerPerTeam",
"type": "Int",
- "defaultValue": 2
+ "defaultValue": "2"
},
{
"name": "maximumPlayerPerTeam",
"type": "Int",
- "defaultValue": 2
+ "defaultValue": "2"
},
{
"name": "information",
@@ -295,14 +302,52 @@
{
"name": "disableRankingFederalRuling",
"type": "Bool",
- "defaultValue": "false",
- "optional": false
+ "defaultValue": "false"
},
{
"name": "teamCountLimit",
"type": "Bool",
- "defaultValue": "true",
- "optional": false
+ "defaultValue": "true"
+ },
+ {
+ "name": "enableOnlinePayment",
+ "type": "Bool",
+ "defaultValue": "false"
+ },
+ {
+ "name": "onlinePaymentIsMandatory",
+ "type": "Bool",
+ "defaultValue": "false"
+ },
+ {
+ "name": "enableOnlinePaymentRefund",
+ "type": "Bool",
+ "defaultValue": "false"
+ },
+ {
+ "name": "refundDateLimit",
+ "type": "Date",
+ "optional": true
+ },
+ {
+ "name": "stripeAccountId",
+ "type": "String",
+ "optional": true
+ },
+ {
+ "name": "enableTimeToConfirm",
+ "type": "Bool",
+ "defaultValue": "false"
+ },
+ {
+ "name": "isCorporateTournament",
+ "type": "Bool",
+ "defaultValue": "false"
+ },
+ {
+ "name": "isTemplate",
+ "type": "Bool",
+ "defaultValue": "false"
}
]
}
diff --git a/PadelClubData/Data/Gen/generator.py b/PadelClubData/Data/Gen/generator.py
index ec8f2fe..2b8f9b8 100644
--- a/PadelClubData/Data/Gen/generator.py
+++ b/PadelClubData/Data/Gen/generator.py
@@ -24,6 +24,7 @@ class SwiftModelGenerator:
resource = self.make_resource_name(model_name)
resource_name = model_data.get("resource_name", resource)
token_exempted = model_data.get("tokenExemptedMethods", [])
+ copy_server_response = model_data.get("copy_server_response", "false")
lines = ["// Generated by SwiftModelGenerator", "// Do not modify this file manually", ""]
@@ -43,7 +44,7 @@ class SwiftModelGenerator:
lines.append("")
# Add SyncedStorable protocol requirements
- lines.extend(self._generate_protocol_requirements(resource_name, token_exempted))
+ lines.extend(self._generate_protocol_requirements(resource_name, token_exempted, copy_server_response))
lines.append("")
# Properties
@@ -371,7 +372,7 @@ class SwiftModelGenerator:
lines.append(" }")
return lines
- def _generate_protocol_requirements(self, resource_name: str, token_exempted: List[str]) -> List[str]:
+ def _generate_protocol_requirements(self, resource_name: str, token_exempted: List[str], copy_server_response: str) -> List[str]:
"""Generate the static functions required by SyncedStorable protocol."""
# Convert HTTP methods to proper format
formatted_methods = [f".{method.lower()}" for method in token_exempted]
@@ -380,6 +381,8 @@ class SwiftModelGenerator:
return [
f" public static func resourceName() -> String {{ return \"{resource_name}\" }}",
f" public static func tokenExemptedMethods() -> [HTTPMethod] {{ return [{methods_str}] }}",
+ f" public static var copyServerResponse: Bool = {copy_server_response}",
+
]
def _generate_relationships(self, model_name, properties: List[Dict[str, Any]]) -> List[str]:
diff --git a/PadelClubData/Data/GroupStage.swift b/PadelClubData/Data/GroupStage.swift
index bf655cb..67f7d18 100644
--- a/PadelClubData/Data/GroupStage.swift
+++ b/PadelClubData/Data/GroupStage.swift
@@ -11,7 +11,7 @@ internal import Algorithms
import SwiftUI
@Observable
-public final class GroupStage: BaseGroupStage, SideStorable {
+final public class GroupStage: BaseGroupStage, SideStorable {
public var matchFormat: MatchFormat {
get {
@@ -22,7 +22,7 @@ public final class GroupStage: BaseGroupStage, SideStorable {
}
}
- var tournamentStore: TournamentStore? {
+ public var tournamentStore: TournamentStore? {
return TournamentLibrary.shared.store(tournamentId: self.tournament)
}
@@ -65,11 +65,11 @@ public final class GroupStage: BaseGroupStage, SideStorable {
}
}
- var computedOrder: Int {
+ public var computedOrder: Int {
index + step * 100
}
- func isRunning() -> Bool { // at least a match has started
+ public func isRunning() -> Bool { // at least a match has started
_matches().anySatisfy({ $0.isRunning() })
}
@@ -224,7 +224,7 @@ public final class GroupStage: BaseGroupStage, SideStorable {
}
}
-// func _score(forGroupStagePosition groupStagePosition: Int, nilIfEmpty: Bool = false) -> TeamGroupStageScore? {
+// public func _score(forGroupStagePosition groupStagePosition: Int, nilIfEmpty: Bool = false) -> TeamGroupStageScore? {
// guard let team = teamAt(groupStagePosition: groupStagePosition) else { return nil }
// let matches = matches(forGroupStagePosition: groupStagePosition).filter({ $0.hasEnded() })
// if matches.isEmpty && nilIfEmpty { return nil }
@@ -250,7 +250,7 @@ public final class GroupStage: BaseGroupStage, SideStorable {
matchIndexes.append(index)
}
}
- return _matches().filter { matchIndexes.contains($0.index % matchCount) }
+ return _matches().filter { matchIndexes.contains($0.index%matchCount) }
}
public func initialStartDate(forTeam team: TeamRegistration) -> Date? {
@@ -338,16 +338,16 @@ public final class GroupStage: BaseGroupStage, SideStorable {
}
- func indexOf(_ matchIndex: Int) -> Int {
+ public func indexOf(_ matchIndex: Int) -> Int {
_matchOrder().firstIndex(of: matchIndex) ?? matchIndex
}
- func _matchUp(for matchIndex: Int) -> [Int] {
+ public func _matchUp(for matchIndex: Int) -> [Int] {
let combinations = Array((0.. String {
+ public func returnMatchesSuffix(for matchIndex: Int) -> String {
if matchCount > 0 {
let count = _matches().count
if count > matchCount * 2 {
@@ -362,7 +362,7 @@ public final class GroupStage: BaseGroupStage, SideStorable {
return ""
}
- func localizedMatchUpLabel(for matchIndex: Int) -> String {
+ public func localizedMatchUpLabel(for matchIndex: Int) -> String {
let matchUp = _matchUp(for: matchIndex)
if let index = matchUp.first, let index2 = matchUp.last {
return "#\(index + 1) vs #\(index2 + 1)" + returnMatchesSuffix(for: matchIndex)
@@ -371,11 +371,11 @@ public final class GroupStage: BaseGroupStage, SideStorable {
}
}
- var matchCount: Int {
+ public var matchCount: Int {
(size * (size - 1)) / 2
}
- func team(teamPosition team: TeamPosition, inMatchIndex matchIndex: Int) -> TeamRegistration? {
+ public func team(teamPosition team: TeamPosition, inMatchIndex matchIndex: Int) -> TeamRegistration? {
let _teams = _teams(for: matchIndex)
switch team {
case .one:
@@ -455,7 +455,7 @@ public final class GroupStage: BaseGroupStage, SideStorable {
}
- var scoreCache: [Int: TeamGroupStageScore] = [:]
+ public var scoreCache: [Int: TeamGroupStageScore] = [:]
public func computedScore(forTeam team: TeamRegistration, step: Int = 0) -> TeamGroupStageScore? {
guard let groupStagePositionAtStep = team.groupStagePositionAtStep(step) else {
@@ -528,11 +528,11 @@ public final class GroupStage: BaseGroupStage, SideStorable {
}
// Clear the cache if necessary, for example when starting a new step or when matches update
- func clearScoreCache() {
+ public func clearScoreCache() {
scoreCache.removeAll()
}
-// func teams(_ sortedByScore: Bool = false, scores: [TeamGroupStageScore]? = nil) -> [TeamRegistration] {
+// public func teams(_ sortedByScore: Bool = false, scores: [TeamGroupStageScore]? = nil) -> [TeamRegistration] {
// if sortedByScore {
// return unsortedTeams().compactMap({ team in
// scores?.first(where: { $0.team.id == team.id }) ?? _score(forGroupStagePosition: team.groupStagePositionAtStep(step)!)
@@ -622,8 +622,8 @@ public final class GroupStage: BaseGroupStage, SideStorable {
// step = try container.decodeIfPresent(Int.self, forKey: ._step) ?? 0
// }
//
-// func encode(to encoder: Encoder) throws {
-// var container = encoder.container(keyedBy: CodingKeys.self)
+// public func encode(to encoder: Encoder) throws {
+// public var container = encoder.container(keyedBy: CodingKeys.self)
//
// try container.encode(id, forKey: ._id)
// try container.encode(storeId, forKey: ._storeId)
diff --git a/PadelClubData/Data/Match.swift b/PadelClubData/Data/Match.swift
index 919ad8b..8a37997 100644
--- a/PadelClubData/Data/Match.swift
+++ b/PadelClubData/Data/Match.swift
@@ -9,7 +9,7 @@ import Foundation
import LeStorage
@Observable
-public final class Match: BaseMatch, SideStorable {
+final public class Match: BaseMatch, SideStorable {
static func == (lhs: Match, rhs: Match) -> Bool {
lhs.id == rhs.id && lhs.startDate == rhs.startDate
@@ -20,7 +20,7 @@ public final class Match: BaseMatch, SideStorable {
return upperRound.roundTitle() + " #" + (matchIndex + 1).formatted()
}
- var byeState: Bool = false
+ public var byeState: Bool = false
public init(round: String? = nil, groupStage: String? = nil, startDate: Date? = nil, endDate: Date? = nil, index: Int, format: MatchFormat? = nil, servingTeamId: String? = nil, winningTeamId: String? = nil, losingTeamId: String? = nil, name: String? = nil, disabled: Bool = false, courtIndex: Int? = nil, confirmed: Bool = false) {
@@ -88,7 +88,9 @@ defer {
#endif
if groupStage != nil {
return index
- } else if let index = (matches ?? roundObject?.playedMatches().sorted(by: \.index))?.firstIndex(where: { $0.id == id }) {
+ } else if let matches, let index = matches.firstIndex(where: { $0.id == id }) {
+ return index
+ } else if let roundObject, roundObject.isUpperBracket(), let index = roundObject.playedMatches().firstIndex(where: { $0.id == id }) {
return index
}
return RoundRule.matchIndexWithinRound(fromMatchIndex: index)
@@ -278,7 +280,7 @@ defer {
return self.tournamentStore?.matches.first(where: { $0.round == roundObjectNextRound.id && $0.index == nextIndex })
}
- func _toggleForwardMatchDisableState(_ state: Bool) {
+ public func _toggleForwardMatchDisableState(_ state: Bool) {
guard let roundObject else { return }
guard roundObject.parent != nil else { return }
guard let forwardMatch = _forwardMatch(inRound: roundObject) else { return }
@@ -320,7 +322,7 @@ defer {
// forwardMatch._toggleByeState(state)
}
- func isSeededBy(team: TeamRegistration) -> Bool {
+ public func isSeededBy(team: TeamRegistration) -> Bool {
isSeededBy(team: team, inTeamPosition: .one) || isSeededBy(team: team, inTeamPosition: .two)
}
@@ -337,36 +339,28 @@ defer {
public func _toggleMatchDisableState(_ state: Bool, forward: Bool = false, single: Bool = false) {
//if disabled == state { return }
+ let tournamentStore = self.tournamentStore
let currentState = disabled
disabled = state
if disabled != currentState {
- do {
- try self.tournamentStore?.teamScores.delete(contentOfs: teamScores)
- } catch {
- Logger.error(error)
- }
+ tournamentStore?.teamScores.delete(contentOfs: teamScores)
}
if state == true, state != currentState {
let teams = teams()
for team in teams {
if isSeededBy(team: team) {
team.bracketPosition = nil
- self.tournamentStore?.teamRegistrations.addOrUpdate(instance: team)
+ tournamentStore?.teamRegistrations.addOrUpdate(instance: team)
}
}
}
//byeState = false
-
if state != currentState {
- roundObject?._cachedSeedInterval = nil
+ roundObject?.invalidateCache()
name = nil
- do {
- try self.tournamentStore?.matches.addOrUpdate(instance: self)
- } catch {
- Logger.error(error)
- }
+ tournamentStore?.matches.addOrUpdate(instance: self)
}
if single == false {
@@ -413,15 +407,15 @@ defer {
[roundTitle(displayStyle), matchTitle(displayStyle)].compactMap({ $0 }).joined(separator: " ")
}
- func topPreviousRoundMatchIndex() -> Int {
+ public func topPreviousRoundMatchIndex() -> Int {
return index * 2 + 1
}
- func bottomPreviousRoundMatchIndex() -> Int {
+ public func bottomPreviousRoundMatchIndex() -> Int {
return (index + 1) * 2
}
- func topPreviousRoundMatch() -> Match? {
+ public func topPreviousRoundMatch() -> Match? {
guard let roundObject else { return nil }
let topPreviousRoundMatchIndex = topPreviousRoundMatchIndex()
let roundObjectPreviousRoundId = roundObject.previousRound()?.id
@@ -430,7 +424,7 @@ defer {
})
}
- func bottomPreviousRoundMatch() -> Match? {
+ public func bottomPreviousRoundMatch() -> Match? {
guard let roundObject else { return nil }
let bottomPreviousRoundMatchIndex = bottomPreviousRoundMatchIndex()
let roundObjectPreviousRoundId = roundObject.previousRound()?.id
@@ -466,10 +460,10 @@ defer {
return (groupStageObject.index + 1) * 100 + groupStageObject.indexOf(index)
}
guard let roundObject else { return index }
- return (300 - (roundObject.theoryCumulativeMatchCount * 10 + roundObject.index * 22)) * 10 + indexInRound()
+ return (300 - (roundObject.theoryCumulativeMatchCount * 10 + roundObject.index * 22)) * 10 + RoundRule.matchIndexWithinRound(fromMatchIndex: index)
}
- func previousMatches() -> [Match] {
+ public func previousMatches() -> [Match] {
guard let roundObject else { return [] }
guard let tournamentStore = self.tournamentStore else { return [] }
@@ -613,7 +607,7 @@ defer {
}
}
- func createTeamScores() -> [TeamScore] {
+ public func createTeamScores() -> [TeamScore] {
let teamOne = team(.one)
let teamTwo = team(.two)
let teams = [teamOne, teamTwo].compactMap({ $0 }).map { TeamScore(match: id, team: $0) }
@@ -688,16 +682,16 @@ defer {
return currentTournament()?.courtCount ?? 1
}
- func courtIsAvailable(_ courtIndex: Int, in runningMatches: [Match]) -> Bool {
+ public func courtIsAvailable(_ courtIndex: Int, in runningMatches: [Match]) -> Bool {
let courtUsed = currentTournament()?.courtUsed(runningMatches: runningMatches) ?? []
return courtUsed.contains(courtIndex) == false
}
- func courtIsPreferred(_ courtIndex: Int) -> Bool {
+ public func courtIsPreferred(_ courtIndex: Int) -> Bool {
return false
}
- func allCourts() -> [Int] {
+ public func allCourts() -> [Int] {
let availableCourts = Array(0.. [TeamRegistration] {
+ public func walkoutTeam() -> [TeamRegistration] {
//walkout 0 means real walkout, walkout 1 means lucky loser situation
return scores().filter({ $0.walkOut == 0 }).compactMap { $0.team }
}
@@ -779,7 +773,7 @@ defer {
return groupStageObject?.tournamentObject() ?? roundObject?.tournamentObject()
}
- func tournamentId() -> String? {
+ public func tournamentId() -> String? {
return groupStageObject?.tournament ?? roundObject?.tournament
}
@@ -807,7 +801,7 @@ defer {
// return [roundProjectedTeam(.one), roundProjectedTeam(.two)].compactMap { $0 }
}
- func scoreDifference(_ teamPosition: Int, atStep step: Int) -> (set: Int, game: Int)? {
+ public func scoreDifference(_ teamPosition: Int, atStep step: Int) -> (set: Int, game: Int)? {
guard let teamScoreTeam = teamScore(.one), let teamScoreOtherTeam = teamScore(.two) else { return nil }
var reverseValue = 1
if teamPosition == team(.two)?.groupStagePositionAtStep(step) {
@@ -842,12 +836,12 @@ defer {
}
}
- func groupStageProjectedTeam(_ team: TeamPosition) -> TeamRegistration? {
+ public func groupStageProjectedTeam(_ team: TeamPosition) -> TeamRegistration? {
guard let groupStageObject else { return nil }
return groupStageObject.team(teamPosition: team, inMatchIndex: index)
}
- func roundProjectedTeam(_ team: TeamPosition) -> TeamRegistration? {
+ public func roundProjectedTeam(_ team: TeamPosition) -> TeamRegistration? {
guard let roundObject else { return nil }
let previousRound = roundObject.previousRound()
return roundObject.roundProjectedTeam(team, inMatch: self, previousRound: previousRound)
@@ -995,7 +989,7 @@ defer {
public typealias CourtIndexAndDate = (courtIndex: Int, startDate: Date)
- func nextCourtsAvailable(availableCourts: [Int], runningMatches: [Match]) -> [CourtIndexAndDate] {
+ public func nextCourtsAvailable(availableCourts: [Int], runningMatches: [Match]) -> [CourtIndexAndDate] {
guard let tournament = currentTournament() else { return [] }
let startDate = Date().withoutSeconds()
if runningMatches.isEmpty {
diff --git a/PadelClubData/Data/MatchScheduler.swift b/PadelClubData/Data/MatchScheduler.swift
index a1f6cb7..e2b2466 100644
--- a/PadelClubData/Data/MatchScheduler.swift
+++ b/PadelClubData/Data/MatchScheduler.swift
@@ -10,7 +10,7 @@ import LeStorage
import SwiftUI
@Observable
-public final class MatchScheduler: BaseMatchScheduler, SideStorable {
+final public class MatchScheduler: BaseMatchScheduler, SideStorable {
//
// init(tournament: String,
// timeDifferenceLimit: Int = 5,
@@ -63,7 +63,7 @@ public final class MatchScheduler: BaseMatchScheduler, SideStorable {
// TournamentStore.instance(tournamentId: self.tournament)
}
- func tournamentObject() -> Tournament? {
+ public func tournamentObject() -> Tournament? {
return Store.main.findById(tournament)
}
@@ -148,7 +148,7 @@ public final class MatchScheduler: BaseMatchScheduler, SideStorable {
return lastDate
}
- func groupStageDispatcher(groupStages: [GroupStage], startingDate: Date) -> GroupStageMatchDispatcher {
+ public func groupStageDispatcher(groupStages: [GroupStage], startingDate: Date) -> GroupStageMatchDispatcher {
let _groupStages = groupStages
@@ -272,7 +272,7 @@ public final class MatchScheduler: BaseMatchScheduler, SideStorable {
}
- func rotationDifference(loserBracket: Bool) -> Int {
+ public func rotationDifference(loserBracket: Bool) -> Int {
if loserBracket {
return loserBracketRotationDifference
} else {
@@ -280,7 +280,7 @@ public final class MatchScheduler: BaseMatchScheduler, SideStorable {
}
}
- func roundMatchCanBePlayed(_ match: Match, roundObject: Round, slots: [TimeMatch], rotationIndex: Int, targetedStartDate: Date, minimumTargetedEndDate: inout Date) -> Bool {
+ public func roundMatchCanBePlayed(_ match: Match, roundObject: Round, slots: [TimeMatch], rotationIndex: Int, targetedStartDate: Date, minimumTargetedEndDate: inout Date) -> Bool {
print("Evaluating match: \(match.roundAndMatchTitle()) in round: \(roundObject.roundTitle()) with index: \(match.index)")
if let roundStartDate = roundObject.startDate, targetedStartDate < roundStartDate {
@@ -372,11 +372,11 @@ public final class MatchScheduler: BaseMatchScheduler, SideStorable {
}
- func getNextStartDate(fromPreviousRotationSlots slots: [TimeMatch], includeBreakTime: Bool) -> Date? {
+ public func getNextStartDate(fromPreviousRotationSlots slots: [TimeMatch], includeBreakTime: Bool) -> Date? {
slots.map { $0.estimatedEndDate(includeBreakTime: includeBreakTime) }.min()
}
- func getNextEarliestAvailableDate(from slots: [TimeMatch]) -> [(Int, Date)] {
+ public func getNextEarliestAvailableDate(from slots: [TimeMatch]) -> [(Int, Date)] {
let byCourt = Dictionary(grouping: slots, by: { $0.courtIndex })
return (byCourt.keys.flatMap { courtIndex in
let matchesByCourt = byCourt[courtIndex]?.sorted(by: \.startDate)
@@ -390,7 +390,7 @@ public final class MatchScheduler: BaseMatchScheduler, SideStorable {
)
}
- func getAvailableCourts(from matches: [Match]) -> [(Int, Date)] {
+ public func getAvailableCourts(from matches: [Match]) -> [(Int, Date)] {
let validMatches = matches.filter({ $0.courtIndex != nil && $0.startDate != nil })
let byCourt = Dictionary(grouping: validMatches, by: { $0.courtIndex! })
return (byCourt.keys.flatMap { court in
@@ -405,7 +405,7 @@ public final class MatchScheduler: BaseMatchScheduler, SideStorable {
)
}
- func roundDispatcher(flattenedMatches: [Match], dispatcherStartDate: Date, initialCourts: [Int]?) -> MatchDispatcher {
+ public func roundDispatcher(flattenedMatches: [Match], dispatcherStartDate: Date, initialCourts: [Int]?) -> MatchDispatcher {
var slots = [TimeMatch]()
var _startDate: Date?
var rotationIndex = 0
@@ -551,7 +551,7 @@ public final class MatchScheduler: BaseMatchScheduler, SideStorable {
return MatchDispatcher(timedMatches: organizedSlots, freeCourtPerRotation: freeCourtPerRotation, rotationCount: rotationIndex, issueFound: issueFound)
}
- func dispatchCourts(courts: [Int], availableMatchs: inout [Match], slots: inout [TimeMatch], rotationIndex: Int, rotationStartDate: Date, freeCourtPerRotation: inout [Int: [Int]], courtsUnavailability: [DateInterval]?) -> Date {
+ public func dispatchCourts(courts: [Int], availableMatchs: inout [Match], slots: inout [TimeMatch], rotationIndex: Int, rotationStartDate: Date, freeCourtPerRotation: inout [Int: [Int]], courtsUnavailability: [DateInterval]?) -> Date {
var matchPerRound = [String: Int]()
var minimumTargetedEndDate = rotationStartDate
@@ -833,7 +833,7 @@ struct GroupStageTimeMatch {
let groupIndex: Int
}
-struct TimeMatch {
+public struct TimeMatch {
let matchID: String
let rotationIndex: Int
var courtIndex: Int
@@ -841,7 +841,7 @@ struct TimeMatch {
var durationLeft: Int //in minutes
var minimumBreakTime: Int //in minutes
- func estimatedEndDate(includeBreakTime: Bool) -> Date {
+ public func estimatedEndDate(includeBreakTime: Bool) -> Date {
let minutesToAdd = Double(durationLeft + (includeBreakTime ? minimumBreakTime : 0))
return startDate.addingTimeInterval(minutesToAdd * 60.0)
}
@@ -851,14 +851,14 @@ struct TimeMatch {
}
}
-struct GroupStageMatchDispatcher {
+public struct GroupStageMatchDispatcher {
let timedMatches: [GroupStageTimeMatch]
let freeCourtPerRotation: [Int: [Int]]
let rotationCount: Int
let groupLastRotation: [Int: Int]
}
-struct MatchDispatcher {
+public struct MatchDispatcher {
let timedMatches: [TimeMatch]
let freeCourtPerRotation: [Int: [Int]]
let rotationCount: Int
@@ -866,7 +866,7 @@ struct MatchDispatcher {
}
extension Match {
- func teamIds() -> [String] {
+ public func teamIds() -> [String] {
return teams().map { $0.id }
}
@@ -878,7 +878,7 @@ extension Match {
matchUp().contains(id)
}
- func matchUp() -> [String] {
+ public func matchUp() -> [String] {
guard let groupStageObject else {
return []
}
diff --git a/PadelClubData/Data/MonthData.swift b/PadelClubData/Data/MonthData.swift
index 55cca06..c12e235 100644
--- a/PadelClubData/Data/MonthData.swift
+++ b/PadelClubData/Data/MonthData.swift
@@ -10,7 +10,7 @@ import SwiftUI
import LeStorage
@Observable
-public final class MonthData: BaseMonthData {
+final public class MonthData: BaseMonthData {
public init(monthKey: String) {
super.init()
diff --git a/PadelClubData/Data/PlayerRegistration.swift b/PadelClubData/Data/PlayerRegistration.swift
index 23ef454..5451fef 100644
--- a/PadelClubData/Data/PlayerRegistration.swift
+++ b/PadelClubData/Data/PlayerRegistration.swift
@@ -9,7 +9,7 @@ import Foundation
import LeStorage
@Observable
-public final class PlayerRegistration: BasePlayerRegistration, SideStorable {
+final public class PlayerRegistration: BasePlayerRegistration, SideStorable {
public func localizedSourceLabel() -> String {
switch source {
@@ -26,7 +26,7 @@ public final class PlayerRegistration: BasePlayerRegistration, SideStorable {
}
}
- public init(teamRegistration: String? = nil, firstName: String, lastName: String, licenceId: String? = nil, rank: Int? = nil, paymentType: PlayerPaymentType? = nil, sex: PlayerSexType? = nil, tournamentPlayed: Int? = nil, points: Double? = nil, clubName: String? = nil, ligueName: String? = nil, assimilation: String? = nil, phoneNumber: String? = nil, email: String? = nil, birthdate: String? = nil, computedRank: Int = 0, source: PlayerRegistration.PlayerDataSource? = nil, hasArrived: Bool = false) {
+ public init(teamRegistration: String? = nil, firstName: String, lastName: String, licenceId: String? = nil, rank: Int? = nil, paymentType: PlayerPaymentType? = nil, sex: PlayerSexType? = nil, tournamentPlayed: Int? = nil, points: Double? = nil, clubName: String? = nil, ligueName: String? = nil, assimilation: String? = nil, phoneNumber: String? = nil, email: String? = nil, birthdate: String? = nil, computedRank: Int = 0, source: PlayerRegistration.PlayerDataSource? = nil, hasArrived: Bool = false, coach: Bool = false, captain: Bool = false, registeredOnline: Bool = false, timeToConfirm: Date? = nil, registrationStatus: PlayerRegistration.RegistrationStatus = PlayerRegistration.RegistrationStatus.waiting, paymentId: String? = nil) {
super.init()
self.teamRegistration = teamRegistration
self.firstName = firstName
@@ -46,6 +46,12 @@ public final class PlayerRegistration: BasePlayerRegistration, SideStorable {
self.computedRank = computedRank
self.source = source
self.hasArrived = hasArrived
+ self.coach = coach
+ self.captain = captain
+ self.registeredOnline = registeredOnline
+ self.timeToConfirm = timeToConfirm
+ self.registrationStatus = registrationStatus
+ self.paymentId = paymentId
}
required init(from decoder: any Decoder) throws {
@@ -89,7 +95,7 @@ public final class PlayerRegistration: BasePlayerRegistration, SideStorable {
public func pasteData(_ exportFormat: ExportFormat = .rawText) -> String {
switch exportFormat {
case .rawText:
- return [firstName.capitalized, lastName.capitalized, licenceId].compactMap({ $0 }).joined(separator: exportFormat.separator())
+ return [firstName.capitalized, lastName.capitalized, licenceId?.computedLicense].compactMap({ $0 }).joined(separator: exportFormat.separator())
case .csv:
return [lastName.uppercased() + " " + firstName.capitalized].joined(separator: exportFormat.separator())
}
@@ -187,7 +193,7 @@ public final class PlayerRegistration: BasePlayerRegistration, SideStorable {
return "non classé" + (isMalePlayer() ? "" : "e")
}
}
-
+
public func setComputedRank(in tournament: Tournament) {
let currentRank = rank ?? tournament.unrankValue(for: isMalePlayer()) ?? 90_000
switch tournament.tournamentCategory {
@@ -212,11 +218,45 @@ public final class PlayerRegistration: BasePlayerRegistration, SideStorable {
}
}
+ public func hasPaidOnline() -> Bool {
+ registrationStatus == .confirmed && paymentId != nil && paymentType == .creditCard
+ }
+
+ public func hasConfirmed() -> Bool {
+ registrationStatus == .confirmed
+ }
+
+ public func confirmRegistration() {
+ registrationStatus = .confirmed
+ }
+
public enum PlayerDataSource: Int, Codable {
case frenchFederation = 0
case beachPadel = 1
}
+ public enum RegistrationStatus: Int, Codable, CaseIterable, Identifiable {
+ case waiting = 0
+ case pending = 1
+ case confirmed = 2
+ case canceled = 3
+
+ public var id: Int { self.rawValue }
+
+ public func localizedRegistrationStatus() -> String {
+ switch self {
+ case .waiting:
+ return "En attente"
+ case .pending:
+ return "En cours"
+ case .confirmed:
+ return "Confirmé"
+ case .canceled:
+ return "Annulé"
+ }
+ }
+ }
+
public static func addon(for playerRank: Int, manMax: Int, womanMax: Int) -> Int {
switch playerRank {
case 0: return 0
diff --git a/PadelClubData/Data/Purchase.swift b/PadelClubData/Data/Purchase.swift
index 89c069d..fd0e6c1 100644
--- a/PadelClubData/Data/Purchase.swift
+++ b/PadelClubData/Data/Purchase.swift
@@ -56,7 +56,7 @@ public class Purchase: BasePurchase {
// case expirationDate
// }
- func isValid() -> Bool {
+ public func isValid() -> Bool {
guard self.revocationDate == nil else {
return false
}
@@ -66,7 +66,7 @@ public class Purchase: BasePurchase {
return expiration > Date()
}
-// func encode(to encoder: Encoder) throws {
+// public func encode(to encoder: Encoder) throws {
// var container = encoder.container(keyedBy: CodingKeys.self)
//
// try container.encode(self.id, forKey: .id)
diff --git a/PadelClubData/Data/Round.swift b/PadelClubData/Data/Round.swift
index fb24760..12c0ccf 100644
--- a/PadelClubData/Data/Round.swift
+++ b/PadelClubData/Data/Round.swift
@@ -10,10 +10,12 @@ import LeStorage
import SwiftUI
@Observable
-public final class Round: BaseRound, SideStorable {
-
- var _cachedSeedInterval: SeedInterval?
+final public class Round: BaseRound, SideStorable {
+ private var _cachedSeedInterval: SeedInterval?
+ private var _cachedLoserRounds: [Round]?
+ private var _cachedLoserRoundsAndChildren: [Round]?
+
public init(tournament: String, index: Int, parent: String? = nil, matchFormat: MatchFormat? = nil, startDate: Date? = nil, groupStageLoserBracket: Bool = false, loserBracketMode: LoserBracketMode = .automatic) {
super.init(tournament: tournament, index: index, parent: parent, format: matchFormat, startDate: startDate, groupStageLoserBracket: groupStageLoserBracket, loserBracketMode: loserBracketMode)
@@ -45,7 +47,16 @@ public final class Round: BaseRound, SideStorable {
public func tournamentObject() -> Tournament? {
return Store.main.findById(tournament)
}
-
+
+ public func _unsortedMatches(includeDisabled: Bool) -> [Match] {
+ guard let tournamentStore = self.tournamentStore else { return [] }
+ if includeDisabled {
+ return tournamentStore.matches.filter { $0.round == self.id }
+ } else {
+ return tournamentStore.matches.filter { $0.round == self.id && $0.disabled == false }
+ }
+ }
+
public func _matches() -> [Match] {
guard let tournamentStore = self.tournamentStore else { return [] }
return tournamentStore.matches.filter { $0.round == self.id }.sorted(by: \.index)
@@ -78,7 +89,20 @@ public final class Round: BaseRound, SideStorable {
return enabledMatches().anySatisfy({ $0.hasEnded() == false }) == false
}
}
+
+ public func upperMatches(upperRound: Round, match: Match) -> [Match] {
+ let matchIndex = match.index
+ let indexInRound = RoundRule.matchIndexWithinRound(fromMatchIndex: matchIndex)
+ return [upperRound.getMatch(atMatchIndexInRound: indexInRound * 2), upperRound.getMatch(atMatchIndexInRound: indexInRound * 2 + 1)].compactMap({ $0 })
+ }
+ public func previousMatches(previousRound: Round, match: Match) -> [Match] {
+ guard let tournamentStore = self.tournamentStore else { return [] }
+ return tournamentStore.matches.filter {
+ $0.round == previousRound.id && ($0.index == match.topPreviousRoundMatchIndex() || $0.index == match.bottomPreviousRoundMatchIndex())
+ }
+ }
+
public func upperMatches(ofMatch match: Match) -> [Match] {
if parent != nil, previousRound() == nil, let parentRound {
let matchIndex = match.index
@@ -164,12 +188,12 @@ public final class Round: BaseRound, SideStorable {
public func losers() -> [TeamRegistration] {
- let teamIds: [String] = self._matches().compactMap { $0.losingTeamId }
+ let teamIds: [String] = self._unsortedMatches(includeDisabled: false).compactMap { $0.losingTeamId }
return teamIds.compactMap { self.tournamentStore?.teamRegistrations.findById($0) }
}
public func winners() -> [TeamRegistration] {
- let teamIds: [String] = self._matches().compactMap { $0.winningTeamId }
+ let teamIds: [String] = self._unsortedMatches(includeDisabled: false).compactMap { $0.winningTeamId }
return teamIds.compactMap { self.tournamentStore?.teamRegistrations.findById($0) }
}
@@ -302,10 +326,10 @@ defer {
public func enabledMatches() -> [Match] {
guard let tournamentStore = self.tournamentStore else { return [] }
- return tournamentStore.matches.filter { $0.round == self.id && $0.disabled == false }.sorted(by: \.index)
+ return tournamentStore.matches.filter { $0.disabled == false && $0.round == self.id }.sorted(by: \.index)
}
-// func displayableMatches() -> [Match] {
+// public func displayableMatches() -> [Match] {
//#if _DEBUG_TIME //DEBUGING TIME
// let start = Date()
// defer {
@@ -353,17 +377,21 @@ defer {
public func loserRounds(forRoundIndex roundIndex: Int, loserRoundsAndChildren: [Round]) -> [Round] {
return loserRoundsAndChildren.filter({ $0.index == roundIndex }).sorted(by: \.theoryCumulativeMatchCount)
}
+
+ public func isEnabled() -> Bool {
+ return _unsortedMatches(includeDisabled: false).isEmpty == false
+ }
public func isDisabled() -> Bool {
- return _matches().allSatisfy({ $0.disabled })
+ return _unsortedMatches(includeDisabled: true).allSatisfy({ $0.disabled })
}
public func isRankDisabled() -> Bool {
- return _matches().allSatisfy({ $0.disabled && $0.teamScores.isEmpty })
+ return _unsortedMatches(includeDisabled: true).allSatisfy({ $0.disabled && $0.teamScores.isEmpty })
}
- func resetFromRoundAllMatchesStartDate() {
- _matches().forEach({
+ public func resetFromRoundAllMatchesStartDate() {
+ _unsortedMatches(includeDisabled: false).forEach({
$0.startDate = nil
})
loserRoundsAndChildren().forEach { round in
@@ -372,8 +400,8 @@ defer {
nextRound()?.resetFromRoundAllMatchesStartDate()
}
- func resetFromRoundAllMatchesStartDate(from match: Match) {
- let matches = _matches()
+ public func resetFromRoundAllMatchesStartDate(from match: Match) {
+ let matches = _unsortedMatches(includeDisabled: false)
if let index = matches.firstIndex(where: { $0.id == match.id }) {
matches[index...].forEach { match in
match.startDate = nil
@@ -386,10 +414,36 @@ defer {
}
public func getActiveLoserRound() -> Round? {
- let rounds = loserRounds().filter({ $0.isDisabled() == false }).sorted(by: \.index).reversed()
- return rounds.first(where: { $0.hasStarted() && $0.hasEnded() == false }) ?? rounds.first
+ // Get all loser rounds once
+ let allLoserRounds = loserRounds()
+ var lowestIndexRound: Round? = nil
+ let currentRoundMatchCount = RoundRule.numberOfMatches(forRoundIndex: index)
+ let roundCount = RoundRule.numberOfRounds(forTeams: currentRoundMatchCount)
+
+ for currentIndex in 0..100 rounds
+ // Find non-disabled round with current index
+ let roundAtIndex = allLoserRounds.first(where: { $0.index == currentIndex && $0.isEnabled() })
+
+ // No round at this index, we've checked all available rounds
+ if roundAtIndex == nil {
+ break
+ }
+
+ // Save the first non-disabled round we find (should be index 0)
+ if lowestIndexRound == nil {
+ lowestIndexRound = roundAtIndex
+ }
+
+ // If this round is active, return it immediately
+ if roundAtIndex!.hasStarted() && !roundAtIndex!.hasEnded() {
+ return roundAtIndex
+ }
+ }
+
+ // If no active round found, return the one with lowest index
+ return lowestIndexRound
}
-
+
public func enableRound() {
_toggleRound(disable: false)
}
@@ -399,7 +453,7 @@ defer {
}
private func _toggleRound(disable: Bool) {
- let _matches = _matches()
+ let _matches = _unsortedMatches(includeDisabled: true)
_matches.forEach { match in
match.disabled = disable
match.resetMatch()
@@ -414,7 +468,7 @@ defer {
}
public var cumulativeMatchCount: Int {
- var totalMatches = playedMatches().count
+ var totalMatches = _unsortedMatches(includeDisabled: false).count
if let parentRound {
totalMatches += parentRound.cumulativeMatchCount
}
@@ -443,11 +497,12 @@ defer {
}
public func disabledMatches() -> [Match] {
- return _matches().filter({ $0.disabled })
+ guard let tournamentStore = self.tournamentStore else { return [] }
+ return tournamentStore.matches.filter { $0.round == self.id && $0.disabled == true }
}
public func allLoserRoundMatches() -> [Match] {
- loserRoundsAndChildren().flatMap({ $0._matches() })
+ loserRoundsAndChildren().flatMap({ $0._unsortedMatches(includeDisabled: false) })
}
public var theoryCumulativeMatchCount: Int {
@@ -496,7 +551,7 @@ defer {
return seedInterval.localizedLabel(displayStyle)
}
- func hasNextRound() -> Bool {
+ public func hasNextRound() -> Bool {
return nextRound()?.isRankDisabled() == false
}
@@ -520,7 +575,7 @@ defer {
let start = Date()
defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
- print("func seedInterval(initialMode)", initialMode, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
+ print("func seedInterval(initialMode)", id, index, initialMode, _cachedSeedInterval, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
#endif
@@ -560,15 +615,19 @@ defer {
if let previousRound = previousRound() {
if (previousRound.enabledMatches().isEmpty == false || initialMode) {
- return previousRound.seedInterval(initialMode: initialMode)?.chunks()?.first
+ _cachedSeedInterval = previousRound.seedInterval(initialMode: initialMode)?.chunks()?.first
+ return _cachedSeedInterval
} else {
- return previousRound.seedInterval(initialMode: initialMode)
+ _cachedSeedInterval = previousRound.seedInterval(initialMode: initialMode)
+ return _cachedSeedInterval
}
} else if let parentRound {
if parentRound.isUpperBracket() {
- return parentRound.seedInterval(initialMode: initialMode)
+ _cachedSeedInterval = parentRound.seedInterval(initialMode: initialMode)
+ return _cachedSeedInterval
}
- return parentRound.seedInterval(initialMode: initialMode)?.chunks()?.last
+ _cachedSeedInterval = parentRound.seedInterval(initialMode: initialMode)?.chunks()?.last
+ return _cachedSeedInterval
}
return nil
@@ -599,18 +658,24 @@ defer {
tournamentObject?.updateTournamentState()
}
- public func roundStatus() -> String {
- let hasEnded = hasEnded()
- if hasStarted() && hasEnded == false {
+ public func roundStatus(playedMatches: [Match]) -> String {
+ let hasEnded = playedMatches.anySatisfy({ $0.hasEnded() == false }) == false
+ let hasStarted = playedMatches.anySatisfy({ $0.hasStarted() })
+ if hasStarted && hasEnded == false {
return "en cours"
} else if hasEnded {
return "terminée"
+ } else if let tournamentObject = tournamentObject(), tournamentObject.groupStagesAreOver() == false {
+ return "en attente"
} else {
return "à démarrer"
}
}
public func loserRounds() -> [Round] {
+ if let _cachedLoserRounds {
+ return _cachedLoserRounds
+ }
guard let tournamentStore = self.tournamentStore else { return [] }
#if _DEBUG_TIME //DEBUGING TIME
let start = Date()
@@ -620,12 +685,57 @@ defer {
}
#endif
- return tournamentStore.rounds.filter( { $0.parent == id }).sorted(by: \.index).reversed()
+ // Filter first to reduce sorting work
+ let filteredRounds = tournamentStore.rounds.filter { $0.parent == id }
+
+ // Return empty array early if no rounds match
+ if filteredRounds.isEmpty {
+ return []
+ }
+
+ // Sort directly in descending order to avoid the separate reversed() call
+ _cachedLoserRounds = filteredRounds.sorted { $0.index > $1.index }
+ return _cachedLoserRounds!
}
public func loserRoundsAndChildren() -> [Round] {
- let loserRounds = loserRounds()
- return loserRounds + loserRounds.flatMap({ $0.loserRoundsAndChildren() })
+ #if _DEBUG_TIME //DEBUGING TIME
+ let start = Date()
+ defer {
+ let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
+ print("func loserRoundsAndChildren: ", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
+ }
+ #endif
+
+ // Return cached result if available
+ if let cached = _cachedLoserRoundsAndChildren {
+ return cached
+ }
+
+ // Calculate result if cache is invalid or unavailable
+ let direct = loserRounds()
+
+ // Return quickly if there are no direct loser rounds
+ if direct.isEmpty {
+ // Update cache with empty result
+ _cachedLoserRoundsAndChildren = []
+ return []
+ }
+
+ // Pre-allocate capacity to avoid reallocations (estimate based on typical tournament structure)
+ var allRounds = direct
+ let estimatedChildrenCount = direct.count * 2 // Rough estimate
+ allRounds.reserveCapacity(estimatedChildrenCount)
+
+ // Collect all children rounds in one pass
+ for round in direct {
+ allRounds.append(contentsOf: round.loserRoundsAndChildren())
+ }
+
+ // Store result in cache
+ _cachedLoserRoundsAndChildren = allRounds
+
+ return allRounds
}
public func isUpperBracket() -> Bool {
@@ -637,43 +747,106 @@ defer {
}
public func deleteLoserBracket() {
- let loserRounds = loserRounds()
- self.tournamentStore?.rounds.delete(contentOfs: loserRounds)
+#if DEBUG //DEBUGING TIME
+ let start = Date()
+ defer {
+ let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
+ print("func deleteLoserBracket: ", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
+ }
+#endif
+ self.tournamentStore?.rounds.delete(contentOfs: self.loserRounds())
+ self.invalidateCache()
}
public func buildLoserBracket() {
+#if DEBUG //DEBUGING TIME
+ let start = Date()
+ defer {
+ let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
+ print("func buildLoserBracket: ", id, duration.formatted(.units(allowed: [.seconds, .milliseconds])))
+ }
+#endif
guard loserRounds().isEmpty else { return }
+ self.invalidateCache()
let currentRoundMatchCount = RoundRule.numberOfMatches(forRoundIndex: index)
guard currentRoundMatchCount > 1 else { return }
+ guard let tournamentStore else { return }
let roundCount = RoundRule.numberOfRounds(forTeams: currentRoundMatchCount)
-
- var loserBracketMatchFormat = tournamentObject()?.loserBracketMatchFormat
+ let loserBracketMatchFormat = tournamentObject()?.loserBracketMatchFormat
// if let parentRound {
// loserBracketMatchFormat = tournamentObject()?.loserBracketSmartMatchFormat(parentRound.index)
// }
-
+
+ var titles = [String: String]()
+
let rounds = (0.. 0 {
+ match.disabled = true
+ if upperRound.isUpperBracket(), prmc == 1 {
+ match.byeState = true
+ }
+ } else {
+ match.disabled = false
+ }
+ }
+ }
+ tournamentStore?.matches.addOrUpdate(contentOfs: m)
+
+ loserRounds().forEach { loserRound in
+ loserRound.disableUnplayedLoserBracketMatches()
+ }
+ }
+
public var parentRound: Round? {
guard let parent = parent else { return nil }
return self.tournamentStore?.rounds.findById(parent)
@@ -704,12 +877,18 @@ defer {
}
public func updateMatchFormatOfAllMatches(_ updatedMatchFormat: MatchFormat) {
- let playedMatches = _matches()
+ let playedMatches = _unsortedMatches(includeDisabled: true)
playedMatches.forEach { match in
match.matchFormat = updatedMatchFormat
}
self.tournamentStore?.matches.addOrUpdate(contentOfs: playedMatches)
}
+
+ public func invalidateCache() {
+ _cachedLoserRounds = nil
+ _cachedSeedInterval = nil
+ _cachedLoserRoundsAndChildren = nil
+ }
public override func deleteDependencies(shouldBeSynchronized: Bool) {
let matches = self._matches()
@@ -726,6 +905,7 @@ defer {
self.tournamentStore?.rounds.deleteDependencies(loserRounds, shouldBeSynchronized: shouldBeSynchronized)
}
+
// enum CodingKeys: String, CodingKey {
// case _id = "id"
@@ -754,7 +934,7 @@ defer {
// loserBracketMode = try container.decodeIfPresent(LoserBracketMode.self, forKey: ._loserBracketMode) ?? .automatic
// }
//
-// func encode(to encoder: Encoder) throws {
+// public func encode(to encoder: Encoder) throws {
// var container = encoder.container(keyedBy: CodingKeys.self)
//
// try container.encode(id, forKey: ._id)
@@ -773,7 +953,7 @@ defer {
func insertOnServer() {
self.tournamentStore?.rounds.writeChangeAndInsertOnServer(instance: self)
- for match in self._matches() {
+ for match in self._unsortedMatches(includeDisabled: true) {
match.insertOnServer()
}
}
diff --git a/PadelClubData/Data/TeamRegistration.swift b/PadelClubData/Data/TeamRegistration.swift
index 7632ef0..32cfc11 100644
--- a/PadelClubData/Data/TeamRegistration.swift
+++ b/PadelClubData/Data/TeamRegistration.swift
@@ -10,7 +10,7 @@ import LeStorage
import SwiftUI
@Observable
-public final class TeamRegistration: BaseTeamRegistration, SideStorable {
+final public class TeamRegistration: BaseTeamRegistration, SideStorable {
// static func resourceName() -> String { "team-registrations" }
// static func tokenExemptedMethods() -> [HTTPMethod] { return [] }
@@ -79,6 +79,20 @@ public final class TeamRegistration: BaseTeamRegistration, SideStorable {
players().anySatisfy({ $0.registeredOnline })
}
+ public func hasPaidOnline() -> Bool {
+ players().anySatisfy({ $0.hasPaidOnline() })
+ }
+
+ public func hasConfirmed() -> Bool {
+ players().allSatisfy({ $0.hasConfirmed() })
+ }
+
+ public func confirmRegistration() {
+ let players = players()
+ players.forEach({ $0.confirmRegistration() })
+ tournamentStore?.playerRegistrations.addOrUpdate(contentOfs: players)
+ }
+
public func unrankedOrUnknown() -> Bool {
players().anySatisfy({ $0.source == nil })
}
@@ -129,7 +143,7 @@ public final class TeamRegistration: BaseTeamRegistration, SideStorable {
}
self.tournamentStore?.teamScores.deleteDependencies(teamScores, shouldBeSynchronized: shouldBeSynchronized)
}
-
+
public func hasArrived(isHere: Bool = false) {
let unsortedPlayers = unsortedPlayers()
unsortedPlayers.forEach({ $0.hasArrived = !isHere })
@@ -231,26 +245,26 @@ public final class TeamRegistration: BaseTeamRegistration, SideStorable {
return currentMatch() != nil
}
- func currentMatch() -> Match? {
+ public func currentMatch() -> Match? {
return teamScores().compactMap { $0.matchObject() }.first(where: { $0.isRunning() })
}
- func teamScores() -> [TeamScore] {
+ public func teamScores() -> [TeamScore] {
guard let tournamentStore = self.tournamentStore else { return [] }
return tournamentStore.teamScores.filter({ $0.teamRegistration == id })
}
- func wins() -> [Match] {
+ public func wins() -> [Match] {
guard let tournamentStore = self.tournamentStore else { return [] }
return tournamentStore.matches.filter({ $0.winningTeamId == id })
}
- func loses() -> [Match] {
+ public func loses() -> [Match] {
guard let tournamentStore = self.tournamentStore else { return [] }
return tournamentStore.matches.filter({ $0.losingTeamId == id })
}
- func matches() -> [Match] {
+ public func matches() -> [Match] {
guard let tournamentStore = self.tournamentStore else { return [] }
return tournamentStore.matches.filter({ $0.losingTeamId == id || $0.winningTeamId == id })
}
@@ -264,7 +278,7 @@ public final class TeamRegistration: BaseTeamRegistration, SideStorable {
players().map { $0.canonicalName }.joined(separator: " ")
}
- func hasMemberOfClub(_ codeClubOrClubName: String?) -> Bool {
+ public func hasMemberOfClub(_ codeClubOrClubName: String?) -> Bool {
guard let codeClubOrClubName else { return true }
return unsortedPlayers().anySatisfy({
$0.clubName?.contains(codeClubOrClubName) == true
@@ -523,16 +537,25 @@ public final class TeamRegistration: BaseTeamRegistration, SideStorable {
}
}
- func coaches() -> [PlayerRegistration] {
+ public func coaches() -> [PlayerRegistration] {
guard let store = self.tournamentStore else { return [] }
return store.playerRegistrations.filter { $0.coach }
}
- func significantPlayerCount() -> Int {
+ public func setWeight(
+ from players: [PlayerRegistration],
+ inTournamentCategory tournamentCategory: TournamentCategory
+ ) {
+ let significantPlayerCount = significantPlayerCount()
+ let sortedPlayers = players.sorted(by: \.computedRank, order: .ascending)
+ weight = (sortedPlayers.prefix(significantPlayerCount).map { $0.computedRank } + missingPlayerType(inTournamentCategory: tournamentCategory).map { unrankValue(for: $0 == 1 ? true : false ) }).prefix(significantPlayerCount).reduce(0,+)
+ }
+
+ public func significantPlayerCount() -> Int {
return tournamentObject()?.significantPlayerCount() ?? 2
}
- func missingPlayerType(inTournamentCategory tournamentCategory: TournamentCategory) -> [Int] {
+ public func missingPlayerType(inTournamentCategory tournamentCategory: TournamentCategory) -> [Int] {
let players = unsortedPlayers()
if players.count >= 2 { return [] }
let s = players.compactMap { $0.sex?.rawValue }
@@ -545,7 +568,7 @@ public final class TeamRegistration: BaseTeamRegistration, SideStorable {
return missing
}
- func unrankValue(for malePlayer: Bool) -> Int {
+ public func unrankValue(for malePlayer: Bool) -> Int {
return tournamentObject()?.unrankValue(for: malePlayer) ?? 90_000
}
@@ -691,19 +714,6 @@ public final class TeamRegistration: BaseTeamRegistration, SideStorable {
playerRegistration.insertOnServer()
}
}
-
- // MARK: - Refacto
-
- public func setWeight(
- from players: [PlayerRegistration],
- inTournamentCategory tournamentCategory: TournamentCategory
- ) {
-
- let significantPlayerCount = significantPlayerCount()
- let sortedPlayers = players.sorted(by: \.computedRank, order: .ascending)
- weight = (sortedPlayers.prefix(significantPlayerCount).map { $0.computedRank } + missingPlayerType(inTournamentCategory: tournamentCategory).map { unrankValue(for: $0 == 1 ? true : false ) }).prefix(significantPlayerCount).reduce(0,+)
- }
-
}
diff --git a/PadelClubData/Data/TeamScore.swift b/PadelClubData/Data/TeamScore.swift
index 7c0f7f5..c5fb7c7 100644
--- a/PadelClubData/Data/TeamScore.swift
+++ b/PadelClubData/Data/TeamScore.swift
@@ -9,7 +9,7 @@ import Foundation
import LeStorage
@Observable
-public final class TeamScore: BaseTeamScore, SideStorable {
+final public class TeamScore: BaseTeamScore, SideStorable {
// static func resourceName() -> String { "team-scores" }
@@ -68,7 +68,7 @@ public final class TeamScore: BaseTeamScore, SideStorable {
// MARK: - Computed dependencies
- func matchObject() -> Match? {
+ public func matchObject() -> Match? {
return self.tournamentStore?.matches.findById(self.match)
}
@@ -97,7 +97,7 @@ public final class TeamScore: BaseTeamScore, SideStorable {
// case _luckyLoser = "luckyLoser"
// }
//
-// func encode(to encoder: Encoder) throws {
+// public func encode(to encoder: Encoder) throws {
// var container = encoder.container(keyedBy: CodingKeys.self)
//
// try container.encode(id, forKey: ._id)
diff --git a/PadelClubData/Data/Tournament.swift b/PadelClubData/Data/Tournament.swift
index fb5499e..dcc9090 100644
--- a/PadelClubData/Data/Tournament.swift
+++ b/PadelClubData/Data/Tournament.swift
@@ -10,14 +10,14 @@ import LeStorage
import SwiftUI
@Observable
-public final class Tournament: BaseTournament {
+final public class Tournament: BaseTournament {
//local variable
public var refreshInProgress: Bool = false
public var lastTeamRefresh: Date?
public var refreshRanking: Bool = false
- func shouldRefreshTeams(forced: Bool) -> Bool {
+ public func shouldRefreshTeams(forced: Bool) -> Bool {
if forced {
return true
}
@@ -27,98 +27,6 @@ public final class Tournament: BaseTournament {
@ObservationIgnored
public var navigationPath: [Screen] = []
-
-// internal init(event: String? = nil, name: String? = nil, startDate: Date = Date(), endDate: Date? = nil, creationDate: Date = Date(), isPrivate: Bool = false, groupStageFormat: MatchFormat? = nil, roundFormat: MatchFormat? = nil, loserRoundFormat: MatchFormat? = nil, groupStageSortMode: GroupStageOrderingMode, groupStageCount: Int = 4, rankSourceDate: Date? = nil, dayDuration: Int = 1, teamCount: Int = 24, teamSorting: TeamSortingType? = nil, federalCategory: TournamentCategory, federalLevelCategory: TournamentLevel, federalAgeCategory: FederalTournamentAge, closedRegistrationDate: Date? = nil, groupStageAdditionalQualified: Int = 0, courtCount: Int = 2, prioritizeClubMembers: Bool = false, qualifiedPerGroupStage: Int = 1, teamsPerGroupStage: Int = 4, entryFee: Double? = nil, additionalEstimationDuration: Int = 0, isDeleted: Bool = false, publishTeams: Bool = false, publishSummons: Bool = false, publishGroupStages: Bool = false, publishBrackets: Bool = false, shouldVerifyBracket: Bool = false, shouldVerifyGroupStage: Bool = false, hideTeamsWeight: Bool = false, publishTournament: Bool = false, hidePointsEarned: Bool = false, publishRankings: Bool = false, loserBracketMode: LoserBracketMode = .automatic, initialSeedRound: Int = 0, initialSeedCount: Int = 0) {
-// super.init()
-// }
-
-
- public init(event: String? = nil, name: String? = nil, startDate: Date = Date(), endDate: Date? = nil, creationDate: Date = Date(), isPrivate: Bool = true, groupStageFormat: MatchFormat? = nil, roundFormat: MatchFormat? = nil, loserRoundFormat: MatchFormat? = nil, groupStageSortMode: GroupStageOrderingMode, groupStageCount: Int = 4, rankSourceDate: Date? = nil, dayDuration: Int = 1, teamCount: Int = 24, teamSorting: TeamSortingType? = nil, federalCategory: TournamentCategory, federalLevelCategory: TournamentLevel, federalAgeCategory: FederalTournamentAge, closedRegistrationDate: Date? = nil, groupStageAdditionalQualified: Int = 0, courtCount: Int = 2, prioritizeClubMembers: Bool = false, qualifiedPerGroupStage: Int = 1, teamsPerGroupStage: Int = 4, entryFee: Double? = nil, additionalEstimationDuration: Int = 0, isDeleted: Bool = false, publishTeams: Bool = false, publishSummons: Bool = false, publishGroupStages: Bool = false, publishBrackets: Bool = false, shouldVerifyBracket: Bool = false, shouldVerifyGroupStage: Bool = false, hideTeamsWeight: Bool = false, publishTournament: Bool = false, hidePointsEarned: Bool = false, publishRankings: Bool = false, loserBracketMode: LoserBracketMode = .automatic, initialSeedRound: Int = 0, initialSeedCount: Int = 0, enableOnlineRegistration: Bool = false, registrationDateLimit: Date? = nil, openingRegistrationDate: Date? = nil, waitingListLimit: Int? = nil, accountIsRequired: Bool = true, licenseIsRequired: Bool = true, minimumPlayerPerTeam: Int = 2, maximumPlayerPerTeam: Int = 2, information: String? = nil,
- umpireCustomMail: String? = nil,
- umpireCustomContact: String? = nil,
- umpireCustomPhone: String? = nil,
- hideUmpireMail: Bool = false,
- hideUmpirePhone: Bool = true,
- disableRankingFederalRuling: Bool = false,
- teamCountLimit: Bool = true
- ) {
- super.init()
- self.event = event
- self.name = name
- self.startDate = startDate
- self.endDate = endDate
- self.creationDate = creationDate
-#if DEBUG
- self.isPrivate = false
-#else
- self.isPrivate = isPrivate
-#endif
- self.groupStageFormat = groupStageFormat
- self.roundFormat = roundFormat
- self.loserRoundFormat = loserRoundFormat
- self.groupStageSortMode = groupStageSortMode
- self.groupStageCount = groupStageCount
- self.rankSourceDate = rankSourceDate
- self.dayDuration = dayDuration
- self.teamCount = teamCount
- self.teamSorting = teamSorting ?? federalLevelCategory.defaultTeamSortingType
- self.federalCategory = federalCategory
- self.federalLevelCategory = federalLevelCategory
- self.federalAgeCategory = federalAgeCategory
- self.closedRegistrationDate = closedRegistrationDate
- self.groupStageAdditionalQualified = groupStageAdditionalQualified
- self.courtCount = courtCount
- self.prioritizeClubMembers = prioritizeClubMembers
- self.qualifiedPerGroupStage = qualifiedPerGroupStage
- self.teamsPerGroupStage = teamsPerGroupStage
- self.entryFee = entryFee
- self.additionalEstimationDuration = additionalEstimationDuration
- self.isDeleted = isDeleted
-#if DEBUG
- self.publishTeams = true
- self.publishSummons = true
- self.publishBrackets = true
- self.publishGroupStages = true
- self.publishRankings = true
- self.publishTournament = true
-#else
- self.publishTeams = publishTeams
- self.publishSummons = publishSummons
- self.publishBrackets = publishBrackets
- self.publishGroupStages = publishGroupStages
- self.publishRankings = publishRankings
- self.publishTournament = publishTournament
-#endif
- self.shouldVerifyBracket = shouldVerifyBracket
- self.shouldVerifyGroupStage = shouldVerifyGroupStage
- self.hideTeamsWeight = hideTeamsWeight
- self.hidePointsEarned = hidePointsEarned
- self.loserBracketMode = loserBracketMode
- self.initialSeedRound = initialSeedRound
- self.initialSeedCount = initialSeedCount
- self.enableOnlineRegistration = enableOnlineRegistration
- self.registrationDateLimit = registrationDateLimit
- self.openingRegistrationDate = openingRegistrationDate
- self.waitingListLimit = waitingListLimit
-
- self.accountIsRequired = accountIsRequired
- self.licenseIsRequired = licenseIsRequired
- self.minimumPlayerPerTeam = minimumPlayerPerTeam
- self.maximumPlayerPerTeam = maximumPlayerPerTeam
- self.information = information
- self.umpireCustomMail = umpireCustomMail
- self.umpireCustomContact = umpireCustomContact
- self.disableRankingFederalRuling = disableRankingFederalRuling
- self.teamCountLimit = teamCountLimit
- }
-
- required init(from decoder: Decoder) throws {
- try super.init(from: decoder)
- }
-
- required public init() {
- super.init()
- }
public var tournamentStore: TournamentStore? {
return TournamentLibrary.shared.store(tournamentId: self.id)
@@ -155,6 +63,7 @@ public final class Tournament: BaseTournament {
}
+
// MARK: - Computed Dependencies
public func unsortedTeams() -> [TeamRegistration] {
@@ -208,7 +117,7 @@ public final class Tournament: BaseTournament {
return self.startDate
}
- func canBePublished() -> Bool {
+ public func canBePublished() -> Bool {
switch state() {
case .build, .finished, .running:
return unsortedTeams().count > 3
@@ -286,7 +195,7 @@ public final class Tournament: BaseTournament {
return URLs.main.url.appending(path: "tournament/\(id)").appending(path: pageLink.path)
}
- func courtUsed(runningMatches: [Match]) -> [Int] {
+ public func courtUsed(runningMatches: [Match]) -> [Int] {
#if _DEBUGING_TIME //DEBUGING TIME
let start = Date()
defer {
@@ -392,7 +301,7 @@ defer {
return seeds().filter { $0.isSeedable() }
}
- func lastSeedRound() -> Int {
+ public func lastSeedRound() -> Int {
if let last = seeds().filter({ $0.bracketPosition != nil }).last {
return RoundRule.roundIndex(fromMatchIndex: last.bracketPosition! / 2)
} else {
@@ -400,16 +309,16 @@ defer {
}
}
- func getRound(atRoundIndex roundIndex: Int) -> Round? {
+ public func getRound(atRoundIndex roundIndex: Int) -> Round? {
return self.tournamentStore?.rounds.first(where: { $0.index == roundIndex })
// return Store.main.filter(isIncluded: { $0.tournament == id && $0.index == roundIndex }).first
}
- func availableSeedSpot(inRoundIndex roundIndex: Int) -> [Match] {
+ public func availableSeedSpot(inRoundIndex roundIndex: Int) -> [Match] {
return getRound(atRoundIndex: roundIndex)?.playedMatches().filter { $0.isEmpty() } ?? []
}
- func availableSeedOpponentSpot(inRoundIndex roundIndex: Int) -> [Match] {
+ public func availableSeedOpponentSpot(inRoundIndex roundIndex: Int) -> [Match] {
return getRound(atRoundIndex: roundIndex)?.playedMatches().filter { $0.hasSpaceLeft() } ?? []
}
public func availableSeedGroups(includeAll: Bool = false) -> [SeedInterval] {
@@ -596,28 +505,55 @@ defer {
return groupStages.filter({ $0.hasStarted() && $0.hasEnded() == false }).sorted(by: \.index).first ?? groupStages.first
}
- func matchesWithSpace() -> [Match] {
+ public func matchesWithSpace() -> [Match] {
getActiveRound()?.playedMatches().filter({ $0.hasSpaceLeft() }) ?? []
}
public func getActiveRound(withSeeds: Bool = false) -> Round? {
let rounds: [Round] = self.rounds()
- let unfinishedRounds: [Round] = rounds.filter { $0.hasStarted() && $0.hasEnded() == false }
- let sortedRounds: [Round] = unfinishedRounds.sorted(by: \.index).reversed()
- let round = sortedRounds.first ?? rounds.last(where: { $0.hasEnded() }) ?? rounds.first
+ for round in rounds {
+ let playedMatches = round.playedMatches()
- if withSeeds {
- if round?.seeds().isEmpty == false {
+ // Optimization: If no matches have started in this round, return nil immediately
+ if !playedMatches.contains(where: { $0.hasStarted() }) {
return round
- } else {
- return nil
}
- } else {
- return round
+
+ if playedMatches.contains(where: { $0.hasStarted() && !$0.hasEnded() }) {
+ if withSeeds {
+ if !round.seeds().isEmpty {
+ return round
+ } else {
+ return nil
+ }
+ } else {
+ return round
+ }
+ }
}
+
+ return nil
}
+
+ public func getActiveRoundAndStatus() -> (Round, String)? {
+ let rounds: [Round] = self.rounds()
+
+ for round in rounds {
+ let playedMatches = round.playedMatches()
+ // Optimization: If no matches have started in this round, return nil immediately
+ if !playedMatches.contains(where: { $0.hasStarted() }) {
+ return (round, round.roundStatus(playedMatches: playedMatches))
+ }
+
+ if playedMatches.contains(where: { $0.hasStarted() && !$0.hasEnded() }) {
+ return (round, round.roundStatus(playedMatches: playedMatches))
+ }
+ }
+ return nil
+ }
+
public func getPlayedMatchDateIntervals(in event: Event) -> [DateInterval] {
let allMatches: [Match] = self.allMatches().filter { $0.courtIndex != nil && $0.startDate != nil }
return allMatches.map { match in
@@ -634,7 +570,7 @@ defer {
return tournamentStore.matches.filter { $0.disabled == false }
}
- func _allMatchesIncludingDisabled() -> [Match] {
+ public func _allMatchesIncludingDisabled() -> [Match] {
guard let tournamentStore = self.tournamentStore else { return [] }
return Array(tournamentStore.matches)
}
@@ -642,7 +578,7 @@ defer {
public func rounds() -> [Round] {
guard let tournamentStore = self.tournamentStore else { return [] }
let rounds: [Round] = tournamentStore.rounds.filter { $0.isUpperBracket() }
- return rounds.sorted(by: \.index).reversed()
+ return rounds.sorted { $0.index > $1.index }
}
public func sortedTeams(selectedSortedTeams: [TeamRegistration]) -> [TeamRegistration] {
@@ -655,6 +591,7 @@ defer {
return waitingListTeams(in: teams, includingWalkOuts: false)
}
+
public func selectedSortedTeams() -> [TeamRegistration] {
#if _DEBUG_TIME //DEBUGING TIME
let start = Date()
@@ -765,7 +702,7 @@ defer {
public func duplicates(in players: [PlayerRegistration]) -> [PlayerRegistration] {
var duplicates = [PlayerRegistration]()
Set(players.compactMap({ $0.licenceId })).forEach { licenceId in
- let found = players.filter({ $0.licenceId == licenceId })
+ let found = players.filter({ $0.licenceId?.strippedLicense == licenceId.strippedLicense })
if found.count > 1 {
duplicates.append(found.first!)
}
@@ -828,12 +765,6 @@ defer {
}
}
-
- public func getStartDate(ofSeedIndex seedIndex: Int?) -> Date? {
- guard let seedIndex else { return nil }
- return selectedSortedTeams()[safe: seedIndex]?.callDate
- }
-
public func maximumCourtsPerGroupSage() -> Int {
if teamsPerGroupStage > 1 {
return min(teamsPerGroupStage / 2, courtCount)
@@ -841,7 +772,7 @@ defer {
return max(1, courtCount)
}
}
-
+
public func isStartDateIsDifferentThanCallDate(_ team: TeamRegistration, expectedSummonDate: Date? = nil) -> Bool {
guard let summonDate = team.callDate else { return true }
let expectedSummonDate : Date? = team.expectedSummonDate() ?? expectedSummonDate
@@ -900,6 +831,10 @@ defer {
return allMatches.filter({ $0.isRunning() == false && $0.hasEnded() == false }).sorted(using: defaultSorting, order: .ascending)
}
+ public func getStartDate(ofSeedIndex seedIndex: Int?) -> Date? {
+ guard let seedIndex else { return nil }
+ return selectedSortedTeams()[safe: seedIndex]?.callDate
+ }
public static func finishedMatches(_ allMatches: [Match], limit: Int?) -> [Match] {
#if _DEBUG_TIME //DEBUGING TIME
@@ -1031,7 +966,7 @@ defer {
let groupStages = groupStages()
var baseRank = teamCount - groupStageSpots() + qualifiedPerGroupStage * groupStageCount + groupStageAdditionalQualified
- if disableRankingFederalRuling == false {
+ if disableRankingFederalRuling == false, baseRank > 0 {
baseRank += qualifiedPerGroupStage * groupStageCount + groupStageAdditionalQualified - 1
}
let alreadyPlaceTeams = Array(teams.values.flatMap({ $0 }))
@@ -1096,6 +1031,19 @@ defer {
return rankings
}
+
+ public func refreshPointsEarned(assimilationLevel: TournamentLevel? = nil) {
+ guard let tournamentStore = self.tournamentStore else { return }
+ let tournamentLevel = assimilationLevel ?? tournamentLevel
+ let unsortedTeams = unsortedTeams()
+ unsortedTeams.forEach { team in
+ if let finalRanking = team.finalRanking {
+ team.pointsEarned = isAnimation() ? nil : tournamentLevel.points(for: finalRanking - 1, count: teamCount)
+ }
+ }
+ tournamentStore.teamRegistrations.addOrUpdate(contentOfs: unsortedTeams)
+ }
+
public func lockRegistration() {
closedRegistrationDate = Date()
@@ -1119,6 +1067,17 @@ defer {
self.tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: teams)
}
+ public func updateWeights() {
+ let teams = self.unsortedTeams()
+ teams.forEach { team in
+ let players = team.unsortedPlayers()
+ players.forEach { $0.setComputedRank(in: self) }
+ team.setWeight(from: players, inTournamentCategory: tournamentCategory)
+ self.tournamentStore?.playerRegistrations.addOrUpdate(contentOfs: players)
+ }
+ self.tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: teams)
+ }
+
public func missingUnrankedValue() -> Bool {
return maleUnrankedValue == nil || femaleUnrankedValue == nil
}
@@ -1136,9 +1095,9 @@ defer {
}
}
let displayStyleCategory = hideSenior ? .short : displayStyle
- var levelCategory = [tournamentLevel.localizedLevelLabel(displayStyle), tournamentCategory.localizedLabel(displayStyle)]
+ var levelCategory = [tournamentLevel.localizedLevelLabel(displayStyle), tournamentCategory.localizedCategoryLabel(displayStyle, ageCategory: federalAgeCategory)]
if displayStyle == .short {
- levelCategory = [tournamentLevel.localizedLevelLabel(displayStyle) + tournamentCategory.localizedLabel(displayStyle)]
+ levelCategory = [tournamentLevel.localizedLevelLabel(displayStyle) + tournamentCategory.localizedCategoryLabel(displayStyle, ageCategory: federalAgeCategory)]
}
let array = levelCategory + [federalTournamentAge.localizedFederalAgeLabel(displayStyleCategory)]
let title: String = array.filter({ $0.isEmpty == false }).joined(separator: " ")
@@ -1151,10 +1110,10 @@ defer {
public func localizedTournamentType() -> String {
switch tournamentLevel {
- case .unlisted:
+ case .unlisted, .championship:
return tournamentLevel.localizedLevelLabel(.short)
default:
- return tournamentLevel.localizedLevelLabel(.short) + tournamentCategory.localizedLabel(.short)
+ return tournamentLevel.localizedLevelLabel(.short) + tournamentCategory.localizedCategoryLabel(.short, ageCategory: federalAgeCategory)
}
}
@@ -1185,6 +1144,7 @@ defer {
return groupStageCount * qualifiedPerGroupStage
}
+
public func availableQualifiedTeams() -> [TeamRegistration] {
#if _DEBUG_TIME //DEBUGING TIME
let start = Date()
@@ -1225,7 +1185,7 @@ defer {
//return qualifiedTeams().count == qualifiedFromGroupStage() + groupStageAdditionalQualified
}
- func groupStageLoserBracketAreOver() -> Bool {
+ public func groupStageLoserBracketAreOver() -> Bool {
guard let groupStageLoserBracket = groupStageLoserBracket() else {
return true
}
@@ -1337,8 +1297,8 @@ defer {
cut = TeamRegistration.TeamRange(availableSeeds.first, availableSeeds.last)
}
- if let round = getActiveRound() {
- return ([round.roundTitle(.short), round.roundStatus()].joined(separator: " ").lowercased(), description, cut)
+ if let roundAndStatus = getActiveRoundAndStatus() {
+ return ([roundAndStatus.0.roundTitle(.short), roundAndStatus.1].joined(separator: " ").lowercased(), description, cut)
} else {
return ("", description, nil)
}
@@ -1353,15 +1313,16 @@ defer {
let cut : TeamRegistration.TeamRange? = isAnimation() ? nil : TeamRegistration.TeamRange(groupStageTeams.first, groupStageTeams.last)
- let runningGroupStages = groupStages().filter({ $0.isRunning() })
if groupStagesAreOver() { return ("terminées", cut) }
+ let groupStages = groupStages()
+ let runningGroupStages = groupStages.filter({ $0.isRunning() })
if runningGroupStages.isEmpty {
let ongoingGroupStages = runningGroupStages.filter({ $0.hasStarted() && $0.hasEnded() == false })
if ongoingGroupStages.isEmpty == false {
return ("Poule" + ongoingGroupStages.count.pluralSuffix + " " + ongoingGroupStages.map { ($0.index + 1).formatted() }.joined(separator: ", ") + " en cours", cut)
}
- return (groupStages().count.formatted() + " poule" + groupStages().count.pluralSuffix, cut)
+ return (groupStages.count.formatted() + " poule" + groupStages.count.pluralSuffix, cut)
} else {
return ("Poule" + runningGroupStages.count.pluralSuffix + " " + runningGroupStages.map { ($0.index + 1).formatted() }.joined(separator: ", ") + " en cours", cut)
}
@@ -1393,6 +1354,23 @@ defer {
}
}
+ public func addEmptyTeamRegistration(_ count: Int) {
+
+ guard let tournamentStore = self.tournamentStore else { return }
+
+ let teams = (0.. Date? {
+ guard [.p500, .p1000, .p1500, .p2000].contains(tournamentLevel) else { return nil }
+
+ let daysOffset = type.daysOffset(level: tournamentLevel)
+ if let date = Calendar.current.date(byAdding: .day, value: daysOffset, to: startDate) {
+ let startOfDay = Calendar.current.startOfDay(for: date)
+ return Calendar.current.date(byAdding: type.timeOffset, to: startOfDay)
+ }
+ return nil
+ }
+ public func setupDefaultPrivateSettings(templateTournament: Tournament?) {
+#if DEBUG
+ self.isPrivate = false
+ self.publishTeams = true
+ self.publishSummons = true
+ self.publishBrackets = true
+ self.publishGroupStages = true
+ self.publishRankings = true
+ self.publishTournament = true
+#else
+ var shouldBePrivate = templateTournament?.isPrivate ?? true
+
+ if Guard.main.currentPlan == .monthlyUnlimited {
+ shouldBePrivate = false
+ } else if Guard.main.purchasedTransactions.isEmpty == false {
+ shouldBePrivate = false
+ }
+
+ self.isPrivate = shouldBePrivate
+#endif
+ }
+
+ public func setupUmpireSettings(defaultTournament: Tournament? = nil) {
+ if let defaultTournament {
+ self.umpireCustomMail = defaultTournament.umpireCustomMail
+ self.umpireCustomPhone = defaultTournament.umpireCustomPhone
+ self.umpireCustomContact = defaultTournament.umpireCustomContact
+ self.hideUmpireMail = defaultTournament.hideUmpireMail
+ self.hideUmpirePhone = defaultTournament.hideUmpirePhone
+ self.disableRankingFederalRuling = defaultTournament.disableRankingFederalRuling
+ self.loserBracketMode = defaultTournament.loserBracketMode
+ } else {
+ let user = DataStore.shared.user
+ self.umpireCustomMail = user.umpireCustomMail
+ self.umpireCustomPhone = user.umpireCustomPhone
+ self.umpireCustomContact = user.umpireCustomContact
+ self.hideUmpireMail = user.hideUmpireMail
+ self.hideUmpirePhone = user.hideUmpirePhone
+ self.disableRankingFederalRuling = user.disableRankingFederalRuling
+ self.loserBracketMode = user.loserBracketMode
+ }
+ }
+
+ public func setupRegistrationSettings(templateTournament: Tournament) {
+ self.enableOnlineRegistration = templateTournament.enableOnlineRegistration
+ self.accountIsRequired = templateTournament.accountIsRequired
+ self.licenseIsRequired = templateTournament.licenseIsRequired
+ self.minimumPlayerPerTeam = templateTournament.minimumPlayerPerTeam
+ self.maximumPlayerPerTeam = templateTournament.maximumPlayerPerTeam
+ self.waitingListLimit = templateTournament.waitingListLimit
+ self.teamCountLimit = templateTournament.teamCountLimit
+ self.enableOnlinePayment = templateTournament.enableOnlinePayment
+ self.onlinePaymentIsMandatory = templateTournament.onlinePaymentIsMandatory
+ self.enableOnlinePaymentRefund = templateTournament.enableOnlinePaymentRefund
+ self.stripeAccountId = templateTournament.stripeAccountId
+ self.enableTimeToConfirm = templateTournament.enableTimeToConfirm
+ self.isCorporateTournament = templateTournament.isCorporateTournament
+
+ if self.registrationDateLimit == nil, templateTournament.registrationDateLimit != nil {
+ self.registrationDateLimit = startDate.truncateMinutesAndSeconds()
+ }
+ self.openingRegistrationDate = templateTournament.openingRegistrationDate != nil ? creationDate.truncateMinutesAndSeconds() : nil
+ self.refundDateLimit = templateTournament.enableOnlinePaymentRefund ? startDate.truncateMinutesAndSeconds() : nil
+ }
+
public func onlineRegistrationCanBeEnabled() -> Bool {
- isAnimation() == false
+ true
+// isAnimation() == false
}
public func roundSmartMatchFormat(_ roundIndex: Int) -> MatchFormat {
@@ -1662,7 +1753,7 @@ defer {
}
}
- func isSameBuild(_ build: any TournamentBuildHolder) -> Bool {
+ public func isSameBuild(_ build: any TournamentBuildHolder) -> Bool {
tournamentLevel == build.level
&& tournamentCategory == build.category
&& federalTournamentAge == build.age
@@ -1824,7 +1915,7 @@ defer {
}
public func allLoserRoundMatches() -> [Match] {
- rounds().flatMap { $0.loserRoundsAndChildren().flatMap({ $0._matches() }) }
+ rounds().flatMap { $0.allLoserRoundMatches() }
}
public func seedsCount() -> Int {
@@ -1914,47 +2005,44 @@ defer {
}
public func addNewRound(_ roundIndex: Int) async {
- let round = Round(tournament: id, index: roundIndex, matchFormat: matchFormat)
- let matchCount = RoundRule.numberOfMatches(forRoundIndex: roundIndex)
- let matchStartIndex = RoundRule.matchIndex(fromRoundIndex: roundIndex)
- let nextRound = round.nextRound()
- var currentIndex = 0
- let matches = (0.. Bool {
- return false
+ if hasEnded() {
+ return true
+ }
+ if hasStarted() == false {
+ return false
+ }
+ if hasStarted(), self.startDate.timeIntervalSinceNow > -3600*24 {
+ return false
+ }
if tournamentStore?.store.fileCollectionsAllLoaded() == false {
return false
}
-#if _DEBUGING_TIME //DEBUGING TIME
+#if DEBUG //DEBUGING TIME
let start = Date()
defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
@@ -2057,6 +2153,10 @@ defer {
unsortedTeams().filter({ $0.hasRegisteredOnline() })
}
+ public func paidOnlineTeams() -> [TeamRegistration] {
+ unsortedTeams().filter({ $0.hasPaidOnline() })
+ }
+
public func shouldWarnOnlineRegistrationUpdates() -> Bool {
enableOnlineRegistration && onlineTeams().isEmpty == false && hasEnded() == false && hasStarted() == false
}
@@ -2064,12 +2164,17 @@ defer {
public func refreshTeamList(forced: Bool) async {
guard StoreCenter.main.isAuthenticated else { return }
guard tournamentStore?.store.fileCollectionsAllLoaded() == true else { return }
- guard shouldRefreshTeams(forced: forced), refreshInProgress == false, enableOnlineRegistration, hasEnded() == false else { return }
+ guard shouldRefreshTeams(forced: forced), refreshInProgress == false else { return }
+ if forced == false {
+ guard enableOnlineRegistration, hasEnded() == false else {
+ return
+ }
+ }
refreshInProgress = true
do {
-// try await self.tournamentStore?.playerRegistrations.loadDataFromServerIfAllowed(clear: true)
-// try await self.tournamentStore?.teamScores.loadDataFromServerIfAllowed(clear: true)
-// try await self.tournamentStore?.teamRegistrations.loadDataFromServerIfAllowed(clear: true)
+ try await self.tournamentStore?.playerRegistrations.loadDataFromServerIfAllowed(clear: true)
+ //try await self.tournamentStore?.teamScores.loadDataFromServerIfAllowed(clear: true)
+ try await self.tournamentStore?.teamRegistrations.loadDataFromServerIfAllowed(clear: true)
refreshInProgress = false
lastTeamRefresh = Date()
} catch {
@@ -2079,6 +2184,11 @@ defer {
}
}
+ public func mailSubject() -> String {
+ let subject = [tournamentTitle(hideSenior: true), formattedDate(.short), clubName].compactMap({ $0 }).joined(separator: " | ")
+ return subject
+ }
+
// MARK: -
func insertOnServer() throws {
@@ -2104,42 +2214,12 @@ defer {
}
-
// MARK: - Payments & Crypto
public enum PaymentError: Error {
case cantPayTournament
}
- // MARK: - Refacto
-
-
- public var tournamentCategory: TournamentCategory {
- get {
- federalCategory
- }
- set {
- if federalCategory != newValue {
- federalCategory = newValue
- updateWeights()
- } else {
- federalCategory = newValue
- }
- }
- }
-
- func updateWeights() {
- let teams = self.unsortedTeams()
- teams.forEach { team in
- let players = team.unsortedPlayers()
- players.forEach { $0.setComputedRank(in: self) }
- team.setWeight(from: players, inTournamentCategory: tournamentCategory)
- self.tournamentStore?.playerRegistrations.addOrUpdate(contentOfs: players)
- }
- self.tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: teams)
- }
-
-
}
extension Bool {
@@ -2151,7 +2231,7 @@ extension Bool {
return Int.random(in: (5...9))
}
}
- static func decodeInt(_ int: Int) -> Bool {
+ public static func decodeInt(_ int: Int) -> Bool {
switch int {
case (0...4):
return true
@@ -2197,6 +2277,17 @@ extension Bool {
// }
//}
+public extension Tournament {
+
+ static func getTemplateTournament() -> Tournament? {
+ return DataStore.shared.tournaments.filter { $0.isTemplate && $0.isDeleted == false }.sorted(by: \.startDate, order: .descending).first
+ }
+
+ static func fake() -> Tournament {
+ return Tournament(event: "Roland Garros", name: "Magic P100", startDate: Date(), endDate: Date(), creationDate: Date(), isPrivate: false, groupStageFormat: .nineGames, roundFormat: nil, loserRoundFormat: nil, groupStageSortMode: .snake, groupStageCount: 4, rankSourceDate: nil, dayDuration: 2, teamCount: 24, teamSorting: .rank, federalCategory: .men, federalLevelCategory: .p100, federalAgeCategory: .a45, closedRegistrationDate: nil, groupStageAdditionalQualified: 0, courtCount: 4, prioritizeClubMembers: false, qualifiedPerGroupStage: 2, teamsPerGroupStage: 4, entryFee: nil)
+ }
+
+}
/// Warning: if the enum has more than 10 cases, the payment algo is broken
public enum TournamentPayment: Int, CaseIterable {
diff --git a/PadelClubData/Data/TournamentLibrary.swift b/PadelClubData/Data/TournamentLibrary.swift
index 062083f..50f2618 100644
--- a/PadelClubData/Data/TournamentLibrary.swift
+++ b/PadelClubData/Data/TournamentLibrary.swift
@@ -8,7 +8,7 @@
import Foundation
import LeStorage
-class TournamentLibrary {
+public class TournamentLibrary {
static let shared: TournamentLibrary = TournamentLibrary()
diff --git a/PadelClubData/Extensions/Array+Extensions.swift b/PadelClubData/Extensions/Array+Extensions.swift
index 8669783..0c5a1f0 100644
--- a/PadelClubData/Extensions/Array+Extensions.swift
+++ b/PadelClubData/Extensions/Array+Extensions.swift
@@ -30,8 +30,19 @@ public extension Array {
}
}
+public extension Array where Element: Equatable {
+
+ /// Remove first collection element that is equal to the given `object` or `element`:
+ mutating func remove(elements: [Element]) {
+ elements.forEach {
+ if let index = firstIndex(of: $0) {
+ remove(at: index)
+ }
+ }
+ }
+}
-extension Array where Element: CustomStringConvertible {
+public extension Array where Element: CustomStringConvertible {
func customJoined(separator: String, lastSeparator: String) -> String {
switch count {
case 0:
@@ -48,7 +59,8 @@ extension Array where Element: CustomStringConvertible {
}
}
-extension Dictionary where Key == Int, Value == [String] {
+
+public extension Dictionary where Key == Int, Value == [String] {
mutating func setOrAppend(_ element: String?, at key: Int) {
// Check if the element is nil; do nothing if it is
guard let element = element else {
diff --git a/PadelClubData/Extensions/Color+Extensions.swift b/PadelClubData/Extensions/Color+Extensions.swift
new file mode 100644
index 0000000..324efcd
--- /dev/null
+++ b/PadelClubData/Extensions/Color+Extensions.swift
@@ -0,0 +1,69 @@
+//
+// Color+Extensions.swift
+// PadelClub
+//
+// Created by Razmig Sarkissian on 27/03/2024.
+//
+
+import SwiftUI
+import UIKit
+
+public extension Color {
+
+ func variation(withHueOffset hueOffset: Double = 0, saturationFactor: Double = 0.4, brightnessFactor: Double = 0.8, opacity: Double = 0.5) -> Color {
+ var hue: CGFloat = 0
+ var saturation: CGFloat = 0
+ var brightness: CGFloat = 0
+ var alpha: CGFloat = 0
+
+ UIColor(self).getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha)
+
+ // Apply adjustments
+ hue += CGFloat(hueOffset)
+ saturation *= CGFloat(saturationFactor)
+ brightness *= CGFloat(brightnessFactor)
+ alpha *= CGFloat(opacity)
+
+ // Clamp values
+ hue = max(0, min(hue, 1))
+ saturation = max(0, min(saturation, 1))
+ brightness = max(0, min(brightness, 1))
+ alpha = max(0, min(alpha, 1))
+
+ return Color(hue: Double(hue), saturation: Double(saturation), brightness: Double(brightness), opacity: Double(alpha))
+ }
+
+}
+
+public extension UIColor {
+ /// Initialises NSColor from a hexadecimal string. Color is clear if string is invalid.
+ /// - Parameter fromHex: supported formats are "#RGB", "#RGBA", "#RRGGBB", "#RRGGBBAA", with or without the # character
+ convenience init(fromHex:String) {
+ var r = 0, g = 0, b = 0, a = 255
+ let offset = fromHex.hasPrefix("#") ? 1 : 0
+ let ch = fromHex.map{$0}
+ switch(ch.count - offset) {
+ case 8:
+ a = 16 * (ch[offset+6].hexDigitValue ?? 0) + (ch[offset+7].hexDigitValue ?? 0)
+ fallthrough
+ case 6:
+ r = 16 * (ch[offset+0].hexDigitValue ?? 0) + (ch[offset+1].hexDigitValue ?? 0)
+ g = 16 * (ch[offset+2].hexDigitValue ?? 0) + (ch[offset+3].hexDigitValue ?? 0)
+ b = 16 * (ch[offset+4].hexDigitValue ?? 0) + (ch[offset+5].hexDigitValue ?? 0)
+ break
+ case 4:
+ a = 16 * (ch[offset+3].hexDigitValue ?? 0) + (ch[offset+3].hexDigitValue ?? 0)
+ fallthrough
+ case 3: // Three digit #0D3 is the same as six digit #00DD33
+ r = 16 * (ch[offset+0].hexDigitValue ?? 0) + (ch[offset+0].hexDigitValue ?? 0)
+ g = 16 * (ch[offset+1].hexDigitValue ?? 0) + (ch[offset+1].hexDigitValue ?? 0)
+ b = 16 * (ch[offset+2].hexDigitValue ?? 0) + (ch[offset+2].hexDigitValue ?? 0)
+ break
+ default:
+ a = 0
+ break
+ }
+ self.init(red: CGFloat(r)/255, green: CGFloat(g)/255, blue: CGFloat(b)/255, alpha: CGFloat(a)/255)
+
+ }
+}
diff --git a/PadelClubData/Extensions/Date+Extensions.swift b/PadelClubData/Extensions/Date+Extensions.swift
index c991acd..286b8c5 100644
--- a/PadelClubData/Extensions/Date+Extensions.swift
+++ b/PadelClubData/Extensions/Date+Extensions.swift
@@ -6,6 +6,7 @@
//
import Foundation
+
public enum TimeOfDay {
case morning
case noon
@@ -56,6 +57,10 @@ public extension Date {
formatted(.dateTime.weekday().day(.twoDigits).month().year())
}
+ var dateFormatted: String {
+ formatted(.dateTime.day(.twoDigits).month(.twoDigits).year(.twoDigits))
+ }
+
var monthYearFormatted: String {
formatted(.dateTime.month(.wide).year(.defaultDigits))
}
@@ -110,7 +115,7 @@ public extension Date {
}
static var firstDayOfWeek = Calendar.current.firstWeekday
- static var capitalizedFirstLettersOfWeekdays: [String] {
+ static var capitalizedFirstLettersOfWeekdays: [String] = {
let calendar = Calendar.current
// let weekdays = calendar.shortWeekdaySymbols
@@ -129,9 +134,9 @@ public extension Date {
}
}
return weekdays.map { $0.capitalized }
- }
+ }()
- static var fullMonthNames: [String] {
+ static var fullMonthNames: [String] = {
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale.current
@@ -140,7 +145,7 @@ public extension Date {
let date = Calendar.current.date(from: DateComponents(year: 2000, month: month, day: 1))
return date.map { dateFormatter.string(from: $0) }
}
- }
+ }()
var startOfMonth: Date {
Calendar.current.dateInterval(of: .month, for: self)!.start
diff --git a/PadelClubData/Utils/MySortDescriptor.swift b/PadelClubData/Extensions/MySortDescriptor.swift
similarity index 100%
rename from PadelClubData/Utils/MySortDescriptor.swift
rename to PadelClubData/Extensions/MySortDescriptor.swift
diff --git a/PadelClubData/Extensions/NumberFormatter+Extensions.swift b/PadelClubData/Extensions/NumberFormatter+Extensions.swift
index 1711cf9..68b231b 100644
--- a/PadelClubData/Extensions/NumberFormatter+Extensions.swift
+++ b/PadelClubData/Extensions/NumberFormatter+Extensions.swift
@@ -8,13 +8,13 @@
import Foundation
public extension NumberFormatter {
- static var ordinal: NumberFormatter {
+ static var ordinal: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .ordinal
return formatter
- }
+ }()
- static var standard: NumberFormatter {
+ static var standard: NumberFormatter = {
return NumberFormatter()
- }
+ }()
}
diff --git a/PadelClubData/Extensions/Sequence+Extensions.swift b/PadelClubData/Extensions/Sequence+Extensions.swift
index 70ef0c1..db0603b 100644
--- a/PadelClubData/Extensions/Sequence+Extensions.swift
+++ b/PadelClubData/Extensions/Sequence+Extensions.swift
@@ -29,7 +29,6 @@ public extension Sequence {
}
public extension Sequence {
-
func concurrentForEach(
_ operation: @escaping (Element) async throws -> Void
) async throws {
diff --git a/PadelClubData/Extensions/String+Crypto.swift b/PadelClubData/Extensions/String+Crypto.swift
index 903b588..28d6662 100644
--- a/PadelClubData/Extensions/String+Crypto.swift
+++ b/PadelClubData/Extensions/String+Crypto.swift
@@ -8,14 +8,14 @@
import Foundation
import CryptoKit
-enum CryptoError: Error {
+public enum CryptoError: Error {
case invalidUTF8
case cantConvertUTF8
case invalidBase64String
case nilSeal
}
-extension Data {
+public extension Data {
func encrypt(pass: String) throws -> Data {
let key = try self._createSymmetricKey(fromString: pass)
@@ -44,3 +44,4 @@ extension Data {
}
}
+
diff --git a/PadelClubData/Extensions/String+Extensions.swift b/PadelClubData/Extensions/String+Extensions.swift
index c8ea2a6..97986d2 100644
--- a/PadelClubData/Extensions/String+Extensions.swift
+++ b/PadelClubData/Extensions/String+Extensions.swift
@@ -163,8 +163,50 @@ public extension String {
}
func licencesFound() -> [String] {
- let matches = self.matches(of: /[1-9][0-9]{5,7}/)
- return matches.map { String(self[$0.range]) }
+ // First try to find licenses with format: 5-8 digits followed by optional letter
+ let precisePattern = /[1-9][0-9]{5,7}[ ]?[A-Za-z]?/
+ let preciseMatches = self.matches(of: precisePattern)
+ let preciseResults = preciseMatches.map { String(self[$0.range]).trimmingCharacters(in: .whitespaces) }
+
+ // If we find potential licenses with the precise pattern
+ if !preciseResults.isEmpty {
+ // Filter to only include those with trailing letters
+ let licensesWithLetters = preciseResults.filter {
+ let lastChar = $0.last
+ return lastChar != nil && lastChar!.isLetter
+ }
+
+ print("🎫 Found \(preciseResults.count) potential licenses, filtering to \(licensesWithLetters.count) with trailing letters")
+
+ // If we have licenses with letters, validate them
+ if !licensesWithLetters.isEmpty {
+ let validLicenses = licensesWithLetters.filter { $0.isLicenseNumber }
+
+ // If we have valid licenses, return the numeric part of each
+ if !validLicenses.isEmpty {
+ let numericLicenses = validLicenses.map { license -> String in
+ // Extract just the numeric part (all characters except the last letter)
+ if let lastChar = license.last, lastChar.isLetter {
+ return String(license.dropLast())
+ }
+ return license
+ }
+
+ if numericLicenses.isEmpty == false {
+ print("🎫 Found valid licenses: \(validLicenses), returning numeric parts: \(numericLicenses)")
+ return numericLicenses
+ }
+ }
+ }
+ }
+
+ // Fallback to just number pattern if we didn't find good matches
+ let numberPattern = /[1-9][0-9]{5,7}/
+ let numberMatches = self.matches(of: numberPattern)
+ let numberResults = numberMatches.map { String(self[$0.range]) }
+
+ print("🎫 Falling back to number-only pattern, found: \(numberResults)")
+ return numberResults
}
}
@@ -261,7 +303,7 @@ public extension String {
/// Formats the birthdate string into "DD/MM/YYYY".
/// - Returns: A formatted birthdate string, or the original string if parsing fails.
- public func formattedAsBirthdate() -> String {
+ func formattedAsBirthdate() -> String {
if let parsedDate = self.parseAsBirthdate() {
let outputFormatter = DateFormatter()
outputFormatter.dateFormat = "dd/MM/yyyy" // Desired output format
diff --git a/PadelClubData/Extensions/Tournament+Extensions.swift b/PadelClubData/Extensions/Tournament+Extensions.swift
deleted file mode 100644
index 907bc6a..0000000
--- a/PadelClubData/Extensions/Tournament+Extensions.swift
+++ /dev/null
@@ -1,16 +0,0 @@
-//
-// Tournament+Extensions.swift
-// PadelClubData
-//
-// Created by Laurent Morvillier on 17/04/2025.
-//
-
-import Foundation
-
-extension Tournament {
-
- public static func fake() -> Tournament {
- return Tournament(event: "Roland Garros", name: "Magic P100", startDate: Date(), endDate: Date(), creationDate: Date(), isPrivate: false, groupStageFormat: .nineGames, roundFormat: nil, loserRoundFormat: nil, groupStageSortMode: .snake, groupStageCount: 4, rankSourceDate: nil, dayDuration: 2, teamCount: 24, teamSorting: .rank, federalCategory: .men, federalLevelCategory: .p100, federalAgeCategory: .a45, closedRegistrationDate: nil, groupStageAdditionalQualified: 0, courtCount: 4, prioritizeClubMembers: false, qualifiedPerGroupStage: 2, teamsPerGroupStage: 4, entryFee: nil)
- }
-
-}
diff --git a/PadelClubData/Extensions/URL+Extensions.swift b/PadelClubData/Extensions/URL+Extensions.swift
index 6afb0c2..4d33dbe 100644
--- a/PadelClubData/Extensions/URL+Extensions.swift
+++ b/PadelClubData/Extensions/URL+Extensions.swift
@@ -9,13 +9,13 @@ import Foundation
public extension URL {
- public static var savedDateFormatter: DateFormatter = {
+ static var savedDateFormatter: DateFormatter = {
let df = DateFormatter()
df.dateFormat = "DD/MM/yyyy"
return df
}()
- public static var importDateFormatter: DateFormatter = {
+ static var importDateFormatter: DateFormatter = {
let df = DateFormatter()
df.dateFormat = "MM-yyyy"
return df
@@ -89,7 +89,7 @@ public extension URL {
return nil
}
- public func fftImportingMaleUnrankValue() -> Int? {
+ func fftImportingMaleUnrankValue() -> Int? {
// Read the contents of the file
guard let fileContents = try? String(contentsOfFile: path(), encoding: .utf8) else {
return nil
@@ -107,7 +107,7 @@ public extension URL {
return nil
}
- public func fileModelIdentifier() -> String? {
+ func fileModelIdentifier() -> String? {
// Read the contents of the file
guard let fileContents = try? String(contentsOfFile: path(), encoding: .utf8) else {
return nil
@@ -125,7 +125,7 @@ public extension URL {
return nil
}
- public func fftImportingUncomplete() -> Int? {
+ func fftImportingUncomplete() -> Int? {
// Read the contents of the file
guard let fileContents = try? String(contentsOfFile: path(), encoding: .utf8) else {
return nil
diff --git a/PadelClubData/Subscriptions/Guard.swift b/PadelClubData/Subscriptions/Guard.swift
index 1e13d4b..06b0aed 100644
--- a/PadelClubData/Subscriptions/Guard.swift
+++ b/PadelClubData/Subscriptions/Guard.swift
@@ -8,6 +8,7 @@
import Foundation
import StoreKit
import LeStorage
+import Combine
@available(iOS 15, *)
@objc public class Guard: NSObject {
@@ -20,7 +21,7 @@ import LeStorage
var updateListenerTask: Task? = nil
- fileprivate let _freeTournaments: Int = 3
+ public let freeTournaments: Int = 3
override init() {
@@ -34,6 +35,7 @@ import LeStorage
} catch {
Logger.error(error)
}
+ Logger.log("plan = \(String(describing: currentBestPurchase?.productId))")
}
NotificationCenter.default.addObserver(self, selector: #selector(collectionDidLoad), name: NSNotification.Name.CollectionDidLoad, object: nil)
@@ -263,8 +265,8 @@ import LeStorage
return TournamentPayment.unlimited
case .fivePerMonth:
if let purchaseDate = self.currentBestPurchase?.purchaseDate {
- let tournaments = DataStore.shared.tournaments.filter { $0.creationDate > purchaseDate && $0.payment == .subscriptionUnit && $0.isCanceled == false }
- if tournaments.count < StoreItem.five {
+ let count = DataStore.shared.subscriptionUnitlyPayedTournaments(after: purchaseDate)
+ if count < StoreItem.five {
return TournamentPayment.subscriptionUnit
}
}
@@ -277,7 +279,7 @@ import LeStorage
fileprivate func _paymentWithoutSubscription() -> TournamentPayment? {
let freelyPayed: Int = DataStore.shared.tournaments.filter { $0.payment == .free && $0.isCanceled == false }.count
- if freelyPayed < self._freeTournaments {
+ if freelyPayed < self.freeTournaments {
return TournamentPayment.free
}
let tournamentCreditCount: Int = self._purchasedTournamentCount()
@@ -291,12 +293,14 @@ import LeStorage
public var remainingTournaments: Int {
let unitlyPayed = DataStore.shared.tournaments.filter { $0.payment == TournamentPayment.unit }.count
let tournamentCreditCount = self._purchasedTournamentCount()
-// let notDeletedTournamentCount = DataStore.shared.tournaments.filter { $0.isDeleted == false }.count
-
-// Logger.log("unitlyPayed = \(unitlyPayed), purchased = \(tournamentCreditCount) ")
return tournamentCreditCount - unitlyPayed
}
+ public var remainingFreeTournaments: Int {
+ let freelyPayed = DataStore.shared.tournaments.filter { $0.payment == TournamentPayment.free }.count
+ return self.freeTournaments - freelyPayed
+ }
+
func disconnect() {
let purchases = DataStore.shared.purchases
purchases.reset()
@@ -305,20 +309,21 @@ import LeStorage
}
public struct PurchaseRow: Identifiable {
-
public var id: UInt64
-
public var name: String
public var item: StoreItem
public var quantity: Int?
+ public var expirationDate: Date?
+ public var remainingCount: Int? = nil
- public init(id: UInt64, name: String, item: StoreItem, quantity: Int? = nil) {
+ public init(id: UInt64, name: String, item: StoreItem, quantity: Int? = nil, expirationDate: Date? = nil, remainingCount: Int? = nil) {
self.id = id
self.name = name
self.item = item
self.quantity = quantity
+ self.expirationDate = expirationDate
+ self.remainingCount = remainingCount
}
-
}
fileprivate extension StoreKit.Transaction {
diff --git a/PadelClubData/Subscriptions/StoreItem.swift b/PadelClubData/Subscriptions/StoreItem.swift
index 233a721..868d28c 100644
--- a/PadelClubData/Subscriptions/StoreItem.swift
+++ b/PadelClubData/Subscriptions/StoreItem.swift
@@ -13,9 +13,9 @@ public enum StoreItem: String, Identifiable, CaseIterable {
case unit = "app.padelclub.tournament.unit"
#if DEBUG
- static let five: Int = 2
+ public static let five: Int = 2
#else
- static let five: Int = 5
+ public static let five: Int = 5
#endif
public var id: String { return self.rawValue }
diff --git a/PadelClubData/Utils/ContactManager.swift b/PadelClubData/Utils/ContactManager.swift
new file mode 100644
index 0000000..2422b4c
--- /dev/null
+++ b/PadelClubData/Utils/ContactManager.swift
@@ -0,0 +1,141 @@
+//
+// ContactManager.swift
+// Padel Tournament
+//
+// Created by Razmig Sarkissian on 19/09/2023.
+//
+
+import Foundation
+import SwiftUI
+import MessageUI
+import LeStorage
+
+public enum ContactManagerError: LocalizedError {
+ case mailFailed
+ case mailNotSent //no network no error
+ case messageFailed
+ case messageNotSent //no network no error
+ case calendarAccessDenied
+ case calendarEventSaveFailed
+ case noCalendarAvailable
+ case uncalledTeams([TeamRegistration])
+
+ public var localizedDescription: String {
+ switch self {
+ case .mailFailed:
+ return "Le mail n'a pas été envoyé"
+ case .mailNotSent:
+ return "Le mail est dans la boîte d'envoi de l'app Mail. Vérifiez son état dans l'app Mail avant d'essayer de le renvoyer."
+ case .messageFailed:
+ return "Le SMS n'a pas été envoyé"
+ case .messageNotSent:
+ return "Le SMS n'a pas été envoyé"
+ case .uncalledTeams(let array):
+ let verb = array.count > 1 ? "peuvent" : "peut"
+ return "Attention, \(array.count) équipe\(array.count.pluralSuffix) ne \(verb) pas être contacté par la méthode choisie"
+ case .calendarAccessDenied:
+ return "Padel Club n'a pas accès à votre calendrier"
+ case .calendarEventSaveFailed:
+ return "Padel Club n'a pas réussi à sauver ce tournoi dans votre calendrier"
+ case .noCalendarAvailable:
+ return "Padel Club n'a pas réussi à trouver un calendrier pour y inscrire ce tournoi"
+ }
+ }
+
+ public static func getNetworkErrorMessage(sentError: ContactManagerError?, networkMonitorConnected: Bool) -> String {
+ var errors: [String] = []
+
+ if networkMonitorConnected == false {
+ errors.append("L'appareil n'est pas connecté à internet.")
+ }
+ if let sentError {
+ errors.append(sentError.localizedDescription)
+ }
+ return errors.joined(separator: "\n")
+ }
+}
+
+public enum ContactType: Identifiable {
+ case mail(date: Date?, recipients: [String]?, bccRecipients: [String]?, body: String?, subject: String?, tournamentBuild: TournamentBuild?)
+ case message(date: Date?, recipients: [String]?, body: String?, tournamentBuild: TournamentBuild?)
+
+ public var id: Int {
+ switch self {
+ case .message: return 0
+ case .mail: return 1
+ }
+ }
+}
+
+public extension ContactType {
+ static let defaultCustomMessage: String =
+"""
+Il est conseillé de vous présenter 10 minutes avant de jouer.\n\nMerci de me confirmer votre présence avec votre nom et de prévenir votre partenaire.
+"""
+ static let defaultAvailablePaymentMethods: String = "Règlement possible par chèque ou espèces."
+
+ static func callingCustomMessage(source: String? = nil, tournament: Tournament?, startDate: Date?, roundLabel: String) -> String {
+ let tournamentCustomMessage = source ?? DataStore.shared.user.summonsMessageBody ?? defaultCustomMessage
+ let clubName = tournament?.clubName ?? ""
+
+ var text = tournamentCustomMessage
+ let date = startDate ?? tournament?.startDate ?? Date()
+
+ if let tournament {
+ text = text.replacingOccurrences(of: "#titre", with: tournament.tournamentTitle(.title, hideSenior: true))
+ text = text.replacingOccurrences(of: "#prix", with: tournament.entryFeeMessage)
+ }
+
+ text = text.replacingOccurrences(of: "#club", with: clubName)
+ text = text.replacingOccurrences(of: "#manche", with: roundLabel.lowercased())
+ text = text.replacingOccurrences(of: "#jour", with: "\(date.formatted(Date.FormatStyle().weekday(.wide).day().month(.wide)))")
+ text = text.replacingOccurrences(of: "#horaire", with: "\(date.formatted(Date.FormatStyle().hour().minute()))")
+
+ let signature = DataStore.shared.user.getSummonsMessageSignature() ?? DataStore.shared.user.defaultSignature(tournament)
+
+ text = text.replacingOccurrences(of: "#signature", with: signature)
+ return text
+ }
+
+ static func callingMessage(tournament: Tournament?, startDate: Date?, roundLabel: String, matchFormat: MatchFormat?, reSummon: Bool = false) -> String {
+
+ let useFullCustomMessage = DataStore.shared.user.summonsUseFullCustomMessage
+
+ if useFullCustomMessage {
+ return callingCustomMessage(tournament: tournament, startDate: startDate, roundLabel: roundLabel)
+ }
+
+ let date = startDate ?? tournament?.startDate ?? Date()
+
+ let clubName = tournament?.clubName ?? ""
+ let message = DataStore.shared.user.summonsMessageBody ?? defaultCustomMessage
+ let signature = DataStore.shared.user.getSummonsMessageSignature() ?? DataStore.shared.user.defaultSignature(tournament)
+
+ let localizedCalled = "convoqué" + (tournament?.tournamentCategory == .women ? "e" : "") + "s"
+
+ var entryFeeMessage: String? {
+ (DataStore.shared.user.summonsDisplayEntryFee) ? tournament?.entryFeeMessage : nil
+ }
+
+ var linkMessage: String? {
+ if let tournament, tournament.isPrivate == false, let shareLink = tournament.shareURL(.matches)?.absoluteString {
+ return "Vous pourrez suivre tous les résultats de ce tournoi sur le site :\n\n".appending(shareLink)
+ } else {
+ return nil
+ }
+ }
+
+ var computedMessage: String {
+ [entryFeeMessage, message, linkMessage].compacted().map { $0.trimmedMultiline }.joined(separator: "\n\n")
+ }
+
+ let intro = reSummon ? "Suite à des forfaits, vous êtes finalement" : "Vous êtes"
+
+ if let tournament {
+ return "Bonjour,\n\n\(intro) \(localizedCalled) pour jouer en \(roundLabel.lowercased()) du \(tournament.tournamentTitle(.title, hideSenior: true)) au \(clubName) le \(date.formatted(Date.FormatStyle().weekday(.wide).day().month(.wide))) à \(date.formatted(Date.FormatStyle().hour().minute())).\n\n" + computedMessage + "\n\n\(signature)"
+ } else {
+ return "Bonjour,\n\n\(intro) \(localizedCalled) \(roundLabel) au \(clubName) le \(date.formatted(Date.FormatStyle().weekday(.wide).day().month(.wide))) à \(date.formatted(Date.FormatStyle().hour().minute())).\n\nMerci de confirmer en répondant à ce message et de prévenir votre partenaire !\n\n\(signature)"
+ }
+ }
+}
+
diff --git a/PadelClubData/Business/DisplayContext.swift b/PadelClubData/Utils/DisplayContext.swift
similarity index 99%
rename from PadelClubData/Business/DisplayContext.swift
rename to PadelClubData/Utils/DisplayContext.swift
index becb38a..03ad2aa 100644
--- a/PadelClubData/Business/DisplayContext.swift
+++ b/PadelClubData/Utils/DisplayContext.swift
@@ -27,6 +27,7 @@ public enum SummoningDisplayContext {
}
public struct DeviceHelper {
+
public static func isBigScreen() -> Bool {
switch UIDevice.current.userInterfaceIdiom {
case .pad: // iPads
diff --git a/PadelClubData/Business/ExportFormat.swift b/PadelClubData/Utils/ExportFormat.swift
similarity index 86%
rename from PadelClubData/Business/ExportFormat.swift
rename to PadelClubData/Utils/ExportFormat.swift
index e60460a..bea2a1d 100644
--- a/PadelClubData/Business/ExportFormat.swift
+++ b/PadelClubData/Utils/ExportFormat.swift
@@ -13,7 +13,7 @@ public enum ExportFormat: Int, Identifiable, CaseIterable {
case rawText
case csv
- var suffix: String {
+ public var suffix: String {
switch self {
case .rawText:
return "txt"
@@ -31,7 +31,7 @@ public enum ExportFormat: Int, Identifiable, CaseIterable {
}
}
- func newLineSeparator(_ count: Int = 1) -> String {
+ public func newLineSeparator(_ count: Int = 1) -> String {
return Array(repeating: "\n", count: count).joined()
}
}
diff --git a/PadelClubData/Utils/NetworkManager.swift b/PadelClubData/Utils/NetworkManager.swift
index 0f32b96..f353427 100644
--- a/PadelClubData/Utils/NetworkManager.swift
+++ b/PadelClubData/Utils/NetworkManager.swift
@@ -7,10 +7,10 @@
import Foundation
-class NetworkManager {
- static let shared: NetworkManager = NetworkManager()
+public class NetworkManager {
+ public static let shared: NetworkManager = NetworkManager()
- func removeRankingData(lastDateString: String, fileName: String) {
+ public func removeRankingData(lastDateString: String, fileName: String) {
let dateString = ["CLASSEMENT-PADEL", fileName, lastDateString].joined(separator: "-") + ".csv"
let documentsUrl:URL = (FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first as URL?)!
@@ -35,7 +35,7 @@ class NetworkManager {
// }
// }
//
- func formatDateForHTTPHeader(_ date: Date) -> String {
+ public func formatDateForHTTPHeader(_ date: Date) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss 'GMT'"
formatter.locale = Locale(identifier: "en_US_POSIX")
@@ -45,7 +45,7 @@ class NetworkManager {
}
@discardableResult
- func downloadRankingData(lastDateString: String, fileName: String) async throws -> Int? {
+ public func downloadRankingData(lastDateString: String, fileName: String) async throws -> Int? {
let dateString = ["CLASSEMENT-PADEL", fileName, lastDateString].joined(separator: "-") + ".csv"
@@ -93,7 +93,7 @@ class NetworkManager {
return nil
}
- func checkFileCreationDate(filePath: String) throws -> Date? {
+ public func checkFileCreationDate(filePath: String) throws -> Date? {
let fileManager = FileManager.default
let attributes = try fileManager.attributesOfItem(atPath: filePath)
return attributes[.creationDate] as? Date
diff --git a/PadelClubData/Utils/NetworkManagerError.swift b/PadelClubData/Utils/NetworkManagerError.swift
index 970af60..eb91408 100644
--- a/PadelClubData/Utils/NetworkManagerError.swift
+++ b/PadelClubData/Utils/NetworkManagerError.swift
@@ -7,7 +7,7 @@
import Foundation
-enum NetworkManagerError: LocalizedError {
+public enum NetworkManagerError: LocalizedError {
case maintenance
case fileNotYetAvailable
case mailFailed
@@ -17,7 +17,7 @@ enum NetworkManagerError: LocalizedError {
case fileNotModified
case fileNotDownloaded(Int)
- var errorDescription: String? {
+ public var errorDescription: String? {
switch self {
case .maintenance:
return "Le site de la FFT est en maintenance"
diff --git a/PadelClubData/Utils/PListReader.swift b/PadelClubData/Utils/PListReader.swift
index 1e5af5a..e6095fb 100644
--- a/PadelClubData/Utils/PListReader.swift
+++ b/PadelClubData/Utils/PListReader.swift
@@ -25,7 +25,7 @@ public class PListReader {
print("Failed to read plist file at path: \(plistPath)")
}
} else {
- print("Plist file '\(plist)' not found in bundle")
+ print("Plist file 'Data.plist' not found in bundle")
}
return nil
diff --git a/PadelClubData/Patcher.swift b/PadelClubData/Utils/Patcher.swift
similarity index 98%
rename from PadelClubData/Patcher.swift
rename to PadelClubData/Utils/Patcher.swift
index db2efe8..babf700 100644
--- a/PadelClubData/Patcher.swift
+++ b/PadelClubData/Utils/Patcher.swift
@@ -11,7 +11,7 @@ import LeStorage
public enum ManualPatch: String {
case disconnect
- var id: String {
+ public var id: String {
return "padelclub.app.manual.patch.\(self.rawValue)"
}
}
@@ -63,7 +63,7 @@ enum Patch: String, CaseIterable {
public class AutomaticPatcher {
- static func applyAllWhenApplicable() {
+ public static func applyAllWhenApplicable() {
for patch in Patch.allCases {
self.patchIfPossible(patch)
}
diff --git a/PadelClubData/SourceFileManager.swift b/PadelClubData/Utils/SourceFileManager.swift
similarity index 96%
rename from PadelClubData/SourceFileManager.swift
rename to PadelClubData/Utils/SourceFileManager.swift
index 227629e..7cda9ce 100644
--- a/PadelClubData/SourceFileManager.swift
+++ b/PadelClubData/Utils/SourceFileManager.swift
@@ -11,7 +11,7 @@ import LeStorage
public class SourceFileManager {
public static let shared = SourceFileManager()
- init() {
+ public init() {
createDirectoryIfNeeded(directoryURL: rankingSourceDirectory)
#if targetEnvironment(simulator)
createDirectoryIfNeeded(directoryURL: anonymousSourceDirectory)
@@ -21,7 +21,7 @@ public class SourceFileManager {
public let rankingSourceDirectory : URL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appending(path: "rankings")
public let anonymousSourceDirectory : URL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appending(path: "anonymous")
- func createDirectoryIfNeeded(directoryURL: URL) {
+ public func createDirectoryIfNeeded(directoryURL: URL) {
let fileManager = FileManager.default
do {
// Check if the directory exists
@@ -37,7 +37,7 @@ public class SourceFileManager {
}
}
- var lastDataSource: String? {
+ public var lastDataSource: String? {
DataStore.shared.appSettings.lastDataSource
}
@@ -54,14 +54,14 @@ public class SourceFileManager {
// }
}
- func _removeAllData(fromDate current: Date) {
+ public func _removeAllData(fromDate current: Date) {
let lastStringDate = URL.importDateFormatter.string(from: current)
let files = ["MESSIEURS", "MESSIEURS-2", "MESSIEURS-3", "MESSIEURS-4", "DAMES"]
files.forEach { fileName in
NetworkManager.shared.removeRankingData(lastDateString: lastStringDate, fileName: fileName)
}
}
-
+
actor SourceFileDownloadTracker {
var _downloadedFileStatus : Int? = nil
@@ -134,7 +134,7 @@ public class SourceFileManager {
}
}
- func monthsBetweenDates(startDateString: String, endDateString: String) -> [String] {
+ public func monthsBetweenDates(startDateString: String, endDateString: String) -> [String] {
let dateFormatter = URL.importDateFormatter
guard let startDate = dateFormatter.date(from: startDateString),
@@ -207,7 +207,7 @@ public class SourceFileManager {
return allFiles(isManPlayer)
}
- static func isDateAfterUrlImportDate(date: Date, dateString: String) -> Bool {
+ public static func isDateAfterUrlImportDate(date: Date, dateString: String) -> Bool {
guard let importDate = URL.importDateFormatter.date(from: dateString) else {
return false
}
diff --git a/PadelClubData/URLs.swift b/PadelClubData/Utils/URLs.swift
similarity index 99%
rename from PadelClubData/URLs.swift
rename to PadelClubData/Utils/URLs.swift
index e4c1185..67531ba 100644
--- a/PadelClubData/URLs.swift
+++ b/PadelClubData/Utils/URLs.swift
@@ -71,7 +71,7 @@ public enum PageLink: String, Identifiable, CaseIterable {
rawValue
}
- var path: String {
+ public var path: String {
switch self {
case .matches:
return ""
diff --git a/PadelClubData/Business/MatchDescriptor.swift b/PadelClubData/ViewModel/MatchDescriptor.swift
similarity index 99%
rename from PadelClubData/Business/MatchDescriptor.swift
rename to PadelClubData/ViewModel/MatchDescriptor.swift
index 6a48240..a93de28 100644
--- a/PadelClubData/Business/MatchDescriptor.swift
+++ b/PadelClubData/ViewModel/MatchDescriptor.swift
@@ -10,7 +10,6 @@ import SwiftUI
import Combine
public class MatchDescriptor: ObservableObject {
-
@Published public var matchFormat: MatchFormat
@Published public var setDescriptors: [SetDescriptor]
public var court: Int = 1
@@ -22,29 +21,6 @@ public class MatchDescriptor: ObservableObject {
public let colorTeamOne: Color = .teal
public let colorTeamTwo: Color = .indigo
- public init(match: Match? = nil) {
- self.match = match
- if let groupStage = match?.groupStageObject {
- self.matchFormat = groupStage.matchFormat
- self.setDescriptors = [SetDescriptor(setFormat: groupStage.matchFormat.setFormat)]
- } else {
- let format = match?.matchFormat ?? match?.currentTournament()?.matchFormat ?? .defaultFormatForMatchType(.groupStage)
- self.matchFormat = format
- self.setDescriptors = [SetDescriptor(setFormat: format.setFormat)]
- }
- let teamOne = match?.team(.one)
- let teamTwo = match?.team(.two)
- self.teamLabelOne = teamOne?.teamLabel(.wide, twoLines: true) ?? ""
- self.teamLabelTwo = teamTwo?.teamLabel(.wide, twoLines: true) ?? ""
-
- if let match, let scoresTeamOne = match.teamScore(ofTeam: teamOne)?.score, let scoresTeamTwo = match.teamScore(ofTeam: teamTwo)?.score {
-
- self.setDescriptors = combineArraysIntoTuples(scoresTeamOne.components(separatedBy: ","), scoresTeamTwo.components(separatedBy: ",")).map({ (a:String?, b:String?) in
- SetDescriptor(valueTeamOne: a != nil ? Int(a!) : nil, valueTeamTwo: b != nil ? Int(b!) : nil, setFormat: match.matchFormat.setFormat)
- })
- }
- }
-
public var teamOneSetupIsActive: Bool {
if hasEnded && showSetInputView == false && showTieBreakInputView == false {
return false
@@ -95,6 +71,29 @@ public class MatchDescriptor: ObservableObject {
return setDescriptors.anySatisfy({ $0.showTieBreakInputView })
}
+ public init(match: Match? = nil) {
+ self.match = match
+ if let groupStage = match?.groupStageObject {
+ self.matchFormat = groupStage.matchFormat
+ self.setDescriptors = [SetDescriptor(setFormat: groupStage.matchFormat.setFormat)]
+ } else {
+ let format = match?.matchFormat ?? match?.currentTournament()?.matchFormat ?? .defaultFormatForMatchType(.groupStage)
+ self.matchFormat = format
+ self.setDescriptors = [SetDescriptor(setFormat: format.setFormat)]
+ }
+ let teamOne = match?.team(.one)
+ let teamTwo = match?.team(.two)
+ self.teamLabelOne = teamOne?.teamLabel(.wide, twoLines: true) ?? ""
+ self.teamLabelTwo = teamTwo?.teamLabel(.wide, twoLines: true) ?? ""
+
+ if let match, let scoresTeamOne = match.teamScore(ofTeam: teamOne)?.score, let scoresTeamTwo = match.teamScore(ofTeam: teamTwo)?.score {
+
+ self.setDescriptors = combineArraysIntoTuples(scoresTeamOne.components(separatedBy: ","), scoresTeamTwo.components(separatedBy: ",")).map({ (a:String?, b:String?) in
+ SetDescriptor(valueTeamOne: a != nil ? Int(a!) : nil, valueTeamTwo: b != nil ? Int(b!) : nil, setFormat: match.matchFormat.setFormat)
+ })
+ }
+ }
+
public var teamOneScores: [String] {
setDescriptors.compactMap { $0.getValue(teamPosition: .one) }
}
diff --git a/PadelClubData/Business/MatchSpot.swift b/PadelClubData/ViewModel/MatchSpot.swift
similarity index 100%
rename from PadelClubData/Business/MatchSpot.swift
rename to PadelClubData/ViewModel/MatchSpot.swift
diff --git a/PadelClubData/Business/PadelRule.swift b/PadelClubData/ViewModel/PadelRule.swift
similarity index 95%
rename from PadelClubData/Business/PadelRule.swift
rename to PadelClubData/ViewModel/PadelRule.swift
index 7ee1bd1..0dd3e1a 100644
--- a/PadelClubData/Business/PadelRule.swift
+++ b/PadelClubData/ViewModel/PadelRule.swift
@@ -34,7 +34,6 @@ public protocol TournamentBuildHolder: Identifiable {
}
public struct TournamentBuild: TournamentBuildHolder, Hashable, Codable, Identifiable {
-
public var uniqueId: String = Store.randomId()
public var id: String { uniqueId }
public let category: TournamentCategory
@@ -55,8 +54,8 @@ public struct TournamentBuild: TournamentBuildHolder, Hashable, Codable, Identif
computedLabel(displayStyle)
}
- var identifier: String {
- level.localizedLevelLabel()+":"+category.localizedLabel()+":"+age.localizedFederalAgeLabel()
+ public var identifier: String {
+ level.localizedLevelLabel()+":"+category.localizedCategoryLabel(ageCategory: age)+":"+age.localizedFederalAgeLabel()
}
func computedLabel(_ displayStyle: DisplayStyle = .wide) -> String {
@@ -64,12 +63,12 @@ public struct TournamentBuild: TournamentBuildHolder, Hashable, Codable, Identif
return localizedLabel(displayStyle) + " " + localizedAge(displayStyle)
}
- func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String {
- level.localizedLevelLabel(displayStyle) + " " + category.localizedLabel(displayStyle)
+ public func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String {
+ level.localizedLevelLabel(displayStyle) + " " + category.localizedCategoryLabel(displayStyle, ageCategory: age)
}
- func localizedTitle(_ displayStyle: DisplayStyle = .wide) -> String {
- level.localizedLevelLabel(displayStyle) + " " + category.localizedLabel(displayStyle)
+ public func localizedTitle(_ displayStyle: DisplayStyle = .wide) -> String {
+ level.localizedLevelLabel(displayStyle) + " " + category.localizedCategoryLabel(displayStyle, ageCategory: age)
}
func localizedAge(_ displayStyle: DisplayStyle = .wide) -> String {
@@ -77,7 +76,7 @@ public struct TournamentBuild: TournamentBuildHolder, Hashable, Codable, Identif
}
}
-public extension TournamentBuild {
+extension TournamentBuild {
init?(category: String, level: String, age: FederalTournamentAge = .senior) {
guard let levelFound = TournamentLevel.allCases.first(where: { $0.localizedLevelLabel() == level }) else { return nil }
@@ -104,7 +103,6 @@ public enum FederalTournamentType: String, Hashable, Codable, CaseIterable, Iden
case championnatParPaire = "L"
public var id: String { self.rawValue }
-
public func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String {
switch self {
case .tournoi:
@@ -266,13 +264,13 @@ public enum FederalTournamentAge: Int, Hashable, Codable, CaseIterable, Identifi
case .unlisted:
return displayStyle == .title ? "Aucune" : ""
case .a11_12:
- return "11/12 ans"
+ return "U12"
case .a13_14:
- return "13/14 ans"
+ return "U14"
case .a15_16:
- return "15/16 ans"
+ return "U16"
case .a17_18:
- return "17/18 ans"
+ return "U18"
case .senior:
return displayStyle == .short ? "" : "Senior"
case .a45:
@@ -307,6 +305,27 @@ public enum FederalTournamentAge: Int, Hashable, Codable, CaseIterable, Identifi
return age >= 55
}
}
+
+ public func isChildCategory() -> Bool {
+ switch self {
+ case .unlisted:
+ return false
+ case .a11_12:
+ return true
+ case .a13_14:
+ return true
+ case .a15_16:
+ return true
+ case .a17_18:
+ return true
+ case .senior:
+ return false
+ case .a45:
+ return false
+ case .a55:
+ return false
+ }
+ }
}
public enum TournamentLevel: Int, Hashable, Codable, CaseIterable, Identifiable {
@@ -325,9 +344,9 @@ public enum TournamentLevel: Int, Hashable, Codable, CaseIterable, Identifiable
self.init(rawValue: value)
}
- public static var assimilationAllCases: [TournamentLevel] {
+ public static var assimilationAllCases: [TournamentLevel] = {
return [.p25, .p100, .p250, .p500, .p1000, .p1500, .p2000]
- }
+ }()
public var entryFee: Double? {
switch self {
@@ -404,6 +423,15 @@ public enum TournamentLevel: Int, Hashable, Codable, CaseIterable, Identifiable
}
}
+ public func haveDeadlines() -> Bool {
+ switch self {
+ case .p500, .p1000, .p1500, .p2000:
+ return true
+ default:
+ return false
+ }
+ }
+
public func minimumPlayerRank(category: TournamentCategory, ageCategory: FederalTournamentAge) -> Int {
switch self {
case .p25:
@@ -504,6 +532,10 @@ public enum TournamentLevel: Int, Hashable, Codable, CaseIterable, Identifiable
public var defaultTeamSortingType: TeamSortingType {
switch self {
+ case .championship:
+ return .inscriptionDate
+ case .unlisted:
+ return .inscriptionDate
case .p25, .p100, .p250:
return .inscriptionDate
default:
@@ -555,7 +587,7 @@ public enum TournamentLevel: Int, Hashable, Codable, CaseIterable, Identifiable
public var coachingIsAuthorized: Bool {
switch self {
- case .p500, .p1000, .p1500, .p2000:
+ case .p500, .p1000, .p1500, .p2000, .championship:
return true
default:
return false
@@ -564,6 +596,7 @@ public enum TournamentLevel: Int, Hashable, Codable, CaseIterable, Identifiable
public func points(for rank: Int, count: Int) -> Int {
if self == .unlisted { return 0 }
+ if self == .championship { return 0 }
let points = points(for: count)
if rank >= points.count {
return points.last!
@@ -913,26 +946,46 @@ public enum TournamentCategory: Int, Hashable, Codable, CaseIterable, Identifiab
}
}
- public func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String {
+ public func localizedCategoryLabel(_ displayStyle: DisplayStyle = .wide, ageCategory: FederalTournamentAge? = nil) -> String {
switch self {
case .unlisted:
return displayStyle == .title ? "Aucune" : ""
case .men:
switch displayStyle {
case .title:
+ if ageCategory?.isChildCategory() == true {
+ return "Garçons"
+ }
return "Hommes"
case .wide:
+ if ageCategory?.isChildCategory() == true {
+ return "Garçons"
+ }
return "Hommes"
case .short:
+ if ageCategory?.isChildCategory() == true {
+ return "G"
+ }
+
return "H"
}
case .women:
switch displayStyle {
case .title:
+ if ageCategory?.isChildCategory() == true {
+ return "Filles"
+ }
return "Dames"
case .wide:
+ if ageCategory?.isChildCategory() == true {
+ return "Filles"
+ }
return "Dames"
case .short:
+ if ageCategory?.isChildCategory() == true {
+ return "F"
+ }
+
return "D"
}
case .mix:
@@ -959,35 +1012,6 @@ public enum TournamentCategory: Int, Hashable, Codable, CaseIterable, Identifiab
}
}
-public enum PlayerFilterOption: Int, Hashable, CaseIterable, Identifiable {
- case all = -1
- case male = 1
- case female = 0
-
- public var id: Int { rawValue }
-
- public func icon() -> String {
- switch self {
- case .all:
- return "Tous"
- case .male:
- return "Homme"
- case .female:
- return "Femme"
- }
- }
-
- public var localizedPlayerLabel: String {
- switch self {
- case .female:
- return "joueuse"
- default:
- return "joueur"
- }
- }
-
-}
-
public enum GroupStageOrderingMode: Int, Hashable, Codable, CaseIterable, Identifiable {
case random
@@ -1000,7 +1024,6 @@ public enum GroupStageOrderingMode: Int, Hashable, Codable, CaseIterable, Identi
}
public var id: Int { self.rawValue }
-
public func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String {
switch self {
case .random:
@@ -1305,9 +1328,9 @@ public enum MatchFormat: Int, Hashable, Codable, CaseIterable, Identifiable {
}
}
- public static var allCases: [MatchFormat] {
+ public static var allCases: [MatchFormat] = {
[.twoSets, .twoSetsDecisivePoint, .twoSetsSuperTie, .twoSetsDecisivePointSuperTie, .twoSetsOfFourGames, .twoSetsOfFourGamesDecisivePoint, .nineGames, .nineGamesDecisivePoint, .superTie, .megaTie, .twoSetsOfSuperTie, .singleSet, .singleSetDecisivePoint, .singleSetOfFourGames, .singleSetOfFourGamesDecisivePoint]
- }
+ }()
public func winner(scoreTeamOne: Int, scoreTeamTwo: Int) -> TeamPosition {
scoreTeamOne >= scoreTeamTwo ? .one : .two
@@ -1384,7 +1407,7 @@ public enum MatchFormat: Int, Hashable, Codable, CaseIterable, Identifiable {
}
}
- var estimatedTimeWithBreak: Int {
+ public var estimatedTimeWithBreak: Int {
estimatedDuration + breakTime.breakTime
}
@@ -1472,6 +1495,7 @@ public enum MatchFormat: Int, Hashable, Codable, CaseIterable, Identifiable {
public var format: String {
shortFormat + (isFederal ? "" : " (non officiel)")
}
+
public var shortFormat: String {
switch self {
case .twoSets:
@@ -1585,7 +1609,6 @@ public enum Format: Int, Hashable, Codable {
return "tie-break en 15"
}
}
-
public var isTiebreak: Bool {
switch self {
case .normal:
@@ -1736,20 +1759,20 @@ public enum RoundRule {
}
}
- static func teamsInFirstRound(forTeams teams: Int) -> Int {
+ public static func teamsInFirstRound(forTeams teams: Int) -> Int {
Int(pow(2.0, ceil(log2(Double(teams)))))
}
- static func numberOfMatches(forTeams teams: Int) -> Int {
+ public static func numberOfMatches(forTeams teams: Int) -> Int {
teamsInFirstRound(forTeams: teams) - 1
}
- static func numberOfRounds(forTeams teams: Int) -> Int {
+ public static func numberOfRounds(forTeams teams: Int) -> Int {
if teams == 0 { return 0 }
return Int(log2(Double(teamsInFirstRound(forTeams: teams))))
}
- static func matchIndex(fromRoundIndex roundIndex: Int) -> Int {
+ public static func matchIndex(fromRoundIndex roundIndex: Int) -> Int {
guard roundIndex >= 0 else {
return -1 // Invalid round index
}
@@ -1757,7 +1780,7 @@ public enum RoundRule {
return (1 << roundIndex) - 1
}
- static func matchIndex(fromBracketPosition: Int) -> Int {
+ public static func matchIndex(fromBracketPosition: Int) -> Int {
roundIndex(fromMatchIndex: fromBracketPosition / 2) + fromBracketPosition%2
}
diff --git a/PadelClubData/Business/Screen.swift b/PadelClubData/ViewModel/Screen.swift
similarity index 100%
rename from PadelClubData/Business/Screen.swift
rename to PadelClubData/ViewModel/Screen.swift
diff --git a/PadelClubData/Business/SeedInterval.swift b/PadelClubData/ViewModel/SeedInterval.swift
similarity index 99%
rename from PadelClubData/Business/SeedInterval.swift
rename to PadelClubData/ViewModel/SeedInterval.swift
index e9e7b0d..a5be1ce 100644
--- a/PadelClubData/Business/SeedInterval.swift
+++ b/PadelClubData/ViewModel/SeedInterval.swift
@@ -8,7 +8,6 @@
import Foundation
public struct SeedInterval: Hashable, Comparable {
-
public let first: Int
public let last: Int
diff --git a/PadelClubData/Selectable.swift b/PadelClubData/ViewModel/Selectable.swift
similarity index 95%
rename from PadelClubData/Selectable.swift
rename to PadelClubData/ViewModel/Selectable.swift
index 19d1158..24dd1c0 100644
--- a/PadelClubData/Selectable.swift
+++ b/PadelClubData/ViewModel/Selectable.swift
@@ -9,7 +9,7 @@ import Foundation
import SwiftUI
import TipKit
-protocol Selectable {
+public protocol Selectable {
func selectionLabel(index: Int) -> String
func badgeValue() -> Int?
func badgeImage() -> Badge?
@@ -19,7 +19,7 @@ protocol Selectable {
func associatedTip() -> (any Tip)?
}
-extension Selectable {
+public extension Selectable {
func associatedTip() -> (any Tip)? {
return nil
}
@@ -71,7 +71,7 @@ struct SelectionTipViewModifier: ViewModifier {
}
}
-extension View {
+public extension View {
func selectableTipViewModifier(selectable: Selectable, action: @escaping () -> Void) -> some View {
modifier(SelectionTipViewModifier(selectable: selectable, action: action))
}
diff --git a/PadelClubData/Utils/SetDescriptor.swift b/PadelClubData/ViewModel/SetDescriptor.swift
similarity index 92%
rename from PadelClubData/Utils/SetDescriptor.swift
rename to PadelClubData/ViewModel/SetDescriptor.swift
index b105ed7..756420a 100644
--- a/PadelClubData/Utils/SetDescriptor.swift
+++ b/PadelClubData/ViewModel/SetDescriptor.swift
@@ -17,11 +17,11 @@ public struct SetDescriptor: Identifiable, Equatable {
public var showSetInputView: Bool = true
public var showTieBreakInputView: Bool = false
- public var isTeamOneSet: Bool {
+ public var isTeamOneSet: Bool {
return valueTeamOne != nil || tieBreakValueTeamOne != nil
}
- public var hasEnded: Bool {
+ public var hasEnded: Bool {
if let valueTeamTwo, let valueTeamOne {
return setFormat.hasEnded(teamOne: valueTeamOne, teamTwo: valueTeamTwo)
} else {
@@ -29,7 +29,7 @@ public struct SetDescriptor: Identifiable, Equatable {
}
}
- public var winner: TeamPosition? {
+ public var winner: TeamPosition? {
if let valueTeamTwo, let valueTeamOne, valueTeamOne != valueTeamTwo {
return setFormat.winner(teamOne: valueTeamOne, teamTwo: valueTeamTwo)
} else {
@@ -37,7 +37,7 @@ public struct SetDescriptor: Identifiable, Equatable {
}
}
- public var shouldTieBreak: Bool {
+ public var shouldTieBreak: Bool {
setFormat.shouldTiebreak(scoreTeamOne: valueTeamOne ?? 0, scoreTeamTwo: valueTeamTwo ?? 0)
}