online reg feature

paca_championship
Raz 12 months ago
parent 58ffb56ef5
commit 28e94cb348
  1. 16
      PadelClub.xcodeproj/project.pbxproj
  2. 17
      PadelClub/Data/PlayerRegistration.swift
  3. 24
      PadelClub/Data/TeamRegistration.swift
  4. 11
      PadelClub/Data/Tournament.swift
  5. 5
      PadelClub/Extensions/Date+Extensions.swift
  6. 8
      PadelClub/Views/Player/PlayerDetailView.swift
  7. 33
      PadelClub/Views/Shared/DateMenuView.swift
  8. 28
      PadelClub/Views/Team/EditingTeamView.swift
  9. 37
      PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift
  10. 244
      PadelClub/Views/Tournament/Screen/RegisrationSetupView.swift

@ -798,6 +798,12 @@
FFBF41822BF73EB3001B24CB /* EventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF41812BF73EB3001B24CB /* EventView.swift */; }; FFBF41822BF73EB3001B24CB /* EventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF41812BF73EB3001B24CB /* EventView.swift */; };
FFBF41842BF75ED7001B24CB /* EventTournamentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF41832BF75ED7001B24CB /* EventTournamentsView.swift */; }; FFBF41842BF75ED7001B24CB /* EventTournamentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF41832BF75ED7001B24CB /* EventTournamentsView.swift */; };
FFBF41862BF75FDA001B24CB /* EventSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF41852BF75FDA001B24CB /* EventSettingsView.swift */; }; FFBF41862BF75FDA001B24CB /* EventSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBF41852BF75FDA001B24CB /* EventSettingsView.swift */; };
FFBFC3912CEE3A0E000EBD8D /* RegisrationSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBFC3902CEE3A0E000EBD8D /* RegisrationSetupView.swift */; };
FFBFC3922CEE3A0E000EBD8D /* RegisrationSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBFC3902CEE3A0E000EBD8D /* RegisrationSetupView.swift */; };
FFBFC3932CEE3A0E000EBD8D /* RegisrationSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBFC3902CEE3A0E000EBD8D /* RegisrationSetupView.swift */; };
FFBFC3952CF05CBB000EBD8D /* DateMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBFC3942CF05CBB000EBD8D /* DateMenuView.swift */; };
FFBFC3962CF05CBB000EBD8D /* DateMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBFC3942CF05CBB000EBD8D /* DateMenuView.swift */; };
FFBFC3972CF05CBB000EBD8D /* DateMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBFC3942CF05CBB000EBD8D /* DateMenuView.swift */; };
FFC1E1042BAC28C6008D6F59 /* ClubSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1032BAC28C6008D6F59 /* ClubSearchView.swift */; }; FFC1E1042BAC28C6008D6F59 /* ClubSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1032BAC28C6008D6F59 /* ClubSearchView.swift */; };
FFC1E1082BAC29FC008D6F59 /* LocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1072BAC29FC008D6F59 /* LocationManager.swift */; }; FFC1E1082BAC29FC008D6F59 /* LocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1072BAC29FC008D6F59 /* LocationManager.swift */; };
FFC1E10A2BAC2A77008D6F59 /* NetworkFederalService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1092BAC2A77008D6F59 /* NetworkFederalService.swift */; }; FFC1E10A2BAC2A77008D6F59 /* NetworkFederalService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC1E1092BAC2A77008D6F59 /* NetworkFederalService.swift */; };
@ -1188,6 +1194,8 @@
FFBF41812BF73EB3001B24CB /* EventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventView.swift; sourceTree = "<group>"; }; FFBF41812BF73EB3001B24CB /* EventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventView.swift; sourceTree = "<group>"; };
FFBF41832BF75ED7001B24CB /* EventTournamentsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventTournamentsView.swift; sourceTree = "<group>"; }; FFBF41832BF75ED7001B24CB /* EventTournamentsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventTournamentsView.swift; sourceTree = "<group>"; };
FFBF41852BF75FDA001B24CB /* EventSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventSettingsView.swift; sourceTree = "<group>"; }; FFBF41852BF75FDA001B24CB /* EventSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventSettingsView.swift; sourceTree = "<group>"; };
FFBFC3902CEE3A0E000EBD8D /* RegisrationSetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisrationSetupView.swift; sourceTree = "<group>"; };
FFBFC3942CF05CBB000EBD8D /* DateMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateMenuView.swift; sourceTree = "<group>"; };
FFC1E1032BAC28C6008D6F59 /* ClubSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClubSearchView.swift; sourceTree = "<group>"; }; FFC1E1032BAC28C6008D6F59 /* ClubSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClubSearchView.swift; sourceTree = "<group>"; };
FFC1E1072BAC29FC008D6F59 /* LocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationManager.swift; sourceTree = "<group>"; }; FFC1E1072BAC29FC008D6F59 /* LocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationManager.swift; sourceTree = "<group>"; };
FFC1E1092BAC2A77008D6F59 /* NetworkFederalService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkFederalService.swift; sourceTree = "<group>"; }; FFC1E1092BAC2A77008D6F59 /* NetworkFederalService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkFederalService.swift; sourceTree = "<group>"; };
@ -1672,6 +1680,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
FF70916D2B9108C600AB08DA /* InscriptionManagerView.swift */, FF70916D2B9108C600AB08DA /* InscriptionManagerView.swift */,
FFBFC3902CEE3A0E000EBD8D /* RegisrationSetupView.swift */,
FF90FC1C2C44FB3E009339B2 /* AddTeamView.swift */, FF90FC1C2C44FB3E009339B2 /* AddTeamView.swift */,
FF8F26422BADFE5B00650388 /* TournamentSettingsView.swift */, FF8F26422BADFE5B00650388 /* TournamentSettingsView.swift */,
FF8F26532BAE1E4400650388 /* TableStructureView.swift */, FF8F26532BAE1E4400650388 /* TableStructureView.swift */,
@ -1772,6 +1781,7 @@
FF663FBD2BE019EC0031AE83 /* TournamentFilterView.swift */, FF663FBD2BE019EC0031AE83 /* TournamentFilterView.swift */,
FFE2D2E12C231BEE00D0C7BE /* SupportButtonView.swift */, FFE2D2E12C231BEE00D0C7BE /* SupportButtonView.swift */,
FFE103112C366E5900684FC9 /* ImagePickerView.swift */, FFE103112C366E5900684FC9 /* ImagePickerView.swift */,
FFBFC3942CF05CBB000EBD8D /* DateMenuView.swift */,
); );
path = Shared; path = Shared;
sourceTree = "<group>"; sourceTree = "<group>";
@ -2426,6 +2436,7 @@
C45BAE442BCA753E002EEC8A /* Purchase.swift in Sources */, C45BAE442BCA753E002EEC8A /* Purchase.swift in Sources */,
FF6EC8FE2B94792300EA7F5A /* Screen.swift in Sources */, FF6EC8FE2B94792300EA7F5A /* Screen.swift in Sources */,
FF967CEE2BAECBD700A9A3BD /* Round.swift in Sources */, FF967CEE2BAECBD700A9A3BD /* Round.swift in Sources */,
FFBFC3922CEE3A0E000EBD8D /* RegisrationSetupView.swift in Sources */,
FF5BAF6E2BE0B3C8008B4B7E /* FederalDataViewModel.swift in Sources */, FF5BAF6E2BE0B3C8008B4B7E /* FederalDataViewModel.swift in Sources */,
FF3F74FF2B91A2D4004CFE0E /* AgendaDestination.swift in Sources */, FF3F74FF2B91A2D4004CFE0E /* AgendaDestination.swift in Sources */,
FF3795622B9396D0004EA093 /* PadelClubApp.xcdatamodeld in Sources */, FF3795622B9396D0004EA093 /* PadelClubApp.xcdatamodeld in Sources */,
@ -2458,6 +2469,7 @@
FF6525C32C8C61B400B9498E /* LoserBracketFromGroupStageView.swift in Sources */, FF6525C32C8C61B400B9498E /* LoserBracketFromGroupStageView.swift in Sources */,
FF5D30512BD94E1000F2B93D /* ImportedPlayer+Extensions.swift in Sources */, FF5D30512BD94E1000F2B93D /* ImportedPlayer+Extensions.swift in Sources */,
FFC1E1042BAC28C6008D6F59 /* ClubSearchView.swift in Sources */, FFC1E1042BAC28C6008D6F59 /* ClubSearchView.swift in Sources */,
FFBFC3962CF05CBB000EBD8D /* DateMenuView.swift in Sources */,
FF089EBB2BB0120700F0AEC7 /* PlayerPopoverView.swift in Sources */, FF089EBB2BB0120700F0AEC7 /* PlayerPopoverView.swift in Sources */,
FF70916E2B9108C600AB08DA /* InscriptionManagerView.swift in Sources */, FF70916E2B9108C600AB08DA /* InscriptionManagerView.swift in Sources */,
FF77CE542CCCD1B200CBCBB4 /* MatchFormatPickingView.swift in Sources */, FF77CE542CCCD1B200CBCBB4 /* MatchFormatPickingView.swift in Sources */,
@ -2711,6 +2723,7 @@
FF4CBFB92C996C0600151637 /* Purchase.swift in Sources */, FF4CBFB92C996C0600151637 /* Purchase.swift in Sources */,
FF4CBFBA2C996C0600151637 /* Screen.swift in Sources */, FF4CBFBA2C996C0600151637 /* Screen.swift in Sources */,
FF4CBFBB2C996C0600151637 /* Round.swift in Sources */, FF4CBFBB2C996C0600151637 /* Round.swift in Sources */,
FFBFC3912CEE3A0E000EBD8D /* RegisrationSetupView.swift in Sources */,
FF4CBFBC2C996C0600151637 /* FederalDataViewModel.swift in Sources */, FF4CBFBC2C996C0600151637 /* FederalDataViewModel.swift in Sources */,
FF4CBFBD2C996C0600151637 /* AgendaDestination.swift in Sources */, FF4CBFBD2C996C0600151637 /* AgendaDestination.swift in Sources */,
FF4CBFBE2C996C0600151637 /* PadelClubApp.xcdatamodeld in Sources */, FF4CBFBE2C996C0600151637 /* PadelClubApp.xcdatamodeld in Sources */,
@ -2743,6 +2756,7 @@
FF4CBFD72C996C0600151637 /* LoserBracketFromGroupStageView.swift in Sources */, FF4CBFD72C996C0600151637 /* LoserBracketFromGroupStageView.swift in Sources */,
FF4CBFD82C996C0600151637 /* ImportedPlayer+Extensions.swift in Sources */, FF4CBFD82C996C0600151637 /* ImportedPlayer+Extensions.swift in Sources */,
FF4CBFD92C996C0600151637 /* ClubSearchView.swift in Sources */, FF4CBFD92C996C0600151637 /* ClubSearchView.swift in Sources */,
FFBFC3972CF05CBB000EBD8D /* DateMenuView.swift in Sources */,
FF4CBFDA2C996C0600151637 /* PlayerPopoverView.swift in Sources */, FF4CBFDA2C996C0600151637 /* PlayerPopoverView.swift in Sources */,
FF4CBFDB2C996C0600151637 /* InscriptionManagerView.swift in Sources */, FF4CBFDB2C996C0600151637 /* InscriptionManagerView.swift in Sources */,
FF77CE522CCCD1B200CBCBB4 /* MatchFormatPickingView.swift in Sources */, FF77CE522CCCD1B200CBCBB4 /* MatchFormatPickingView.swift in Sources */,
@ -2975,6 +2989,7 @@
FF70FB382C90584900129CC2 /* Purchase.swift in Sources */, FF70FB382C90584900129CC2 /* Purchase.swift in Sources */,
FF70FB392C90584900129CC2 /* Screen.swift in Sources */, FF70FB392C90584900129CC2 /* Screen.swift in Sources */,
FF70FB3A2C90584900129CC2 /* Round.swift in Sources */, FF70FB3A2C90584900129CC2 /* Round.swift in Sources */,
FFBFC3932CEE3A0E000EBD8D /* RegisrationSetupView.swift in Sources */,
FF70FB3B2C90584900129CC2 /* FederalDataViewModel.swift in Sources */, FF70FB3B2C90584900129CC2 /* FederalDataViewModel.swift in Sources */,
FF70FB3C2C90584900129CC2 /* AgendaDestination.swift in Sources */, FF70FB3C2C90584900129CC2 /* AgendaDestination.swift in Sources */,
FF70FB3D2C90584900129CC2 /* PadelClubApp.xcdatamodeld in Sources */, FF70FB3D2C90584900129CC2 /* PadelClubApp.xcdatamodeld in Sources */,
@ -3007,6 +3022,7 @@
FF70FB562C90584900129CC2 /* LoserBracketFromGroupStageView.swift in Sources */, FF70FB562C90584900129CC2 /* LoserBracketFromGroupStageView.swift in Sources */,
FF70FB572C90584900129CC2 /* ImportedPlayer+Extensions.swift in Sources */, FF70FB572C90584900129CC2 /* ImportedPlayer+Extensions.swift in Sources */,
FF70FB582C90584900129CC2 /* ClubSearchView.swift in Sources */, FF70FB582C90584900129CC2 /* ClubSearchView.swift in Sources */,
FFBFC3952CF05CBB000EBD8D /* DateMenuView.swift in Sources */,
FF70FB592C90584900129CC2 /* PlayerPopoverView.swift in Sources */, FF70FB592C90584900129CC2 /* PlayerPopoverView.swift in Sources */,
FF70FB5A2C90584900129CC2 /* InscriptionManagerView.swift in Sources */, FF70FB5A2C90584900129CC2 /* InscriptionManagerView.swift in Sources */,
FF77CE532CCCD1B200CBCBB4 /* MatchFormatPickingView.swift in Sources */, FF77CE532CCCD1B200CBCBB4 /* MatchFormatPickingView.swift in Sources */,

@ -38,6 +38,22 @@ final class PlayerRegistration: ModelObject, Storable {
var source: PlayerDataSource? var source: PlayerDataSource?
var hasArrived: Bool = false var hasArrived: Bool = false
var coach: Bool = false
var captain: Bool = false
func localizedSourceLabel() -> String {
switch source {
case .frenchFederation:
return "Via la base fédérale"
case .beachPadel:
return "Via le fichier beach-padel"
case .onlineRegistration:
return "Via un inscription en ligne"
case nil:
return "Manuellement"
}
}
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: PlayerDataSource? = nil, hasArrived: Bool = false) { 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: PlayerDataSource? = nil, hasArrived: Bool = false) {
self.teamRegistration = teamRegistration self.teamRegistration = teamRegistration
@ -391,6 +407,7 @@ final class PlayerRegistration: ModelObject, Storable {
enum PlayerDataSource: Int, Codable { enum PlayerDataSource: Int, Codable {
case frenchFederation = 0 case frenchFederation = 0
case beachPadel = 1 case beachPadel = 1
case onlineRegistration = 2
} }
enum PlayerSexType: Int, Hashable, CaseIterable, Identifiable, Codable { enum PlayerSexType: Int, Hashable, CaseIterable, Identifiable, Codable {

@ -39,6 +39,21 @@ final class TeamRegistration: ModelObject, Storable {
var finalRanking: Int? var finalRanking: Int?
var pointsEarned: Int? var pointsEarned: Int?
var unregistered: Bool = false
var unregistrationDate: Date? = nil
func hasUnregistered() -> Bool {
unregistered
}
func hasRegisteredOnline() -> Bool {
players().anySatisfy({ $0.source == .onlineRegistration })
}
func isOutOfTournament() -> Bool {
walkOut || unregistered
}
init(tournament: String, groupStage: String? = nil, registrationDate: Date? = nil, callDate: Date? = nil, bracketPosition: Int? = nil, groupStagePosition: Int? = nil, comment: String? = nil, source: String? = nil, sourceValue: String? = nil, logo: String? = nil, name: String? = nil, walkOut: Bool = false, wildCardBracket: Bool = false, wildCardGroupStage: Bool = false, weight: Int = 0, lockedWeight: Int? = nil, confirmationDate: Date? = nil, qualified: Bool = false) { init(tournament: String, groupStage: String? = nil, registrationDate: Date? = nil, callDate: Date? = nil, bracketPosition: Int? = nil, groupStagePosition: Int? = nil, comment: String? = nil, source: String? = nil, sourceValue: String? = nil, logo: String? = nil, name: String? = nil, walkOut: Bool = false, wildCardBracket: Bool = false, wildCardGroupStage: Bool = false, weight: Int = 0, lockedWeight: Int? = nil, confirmationDate: Date? = nil, qualified: Bool = false) {
self.tournament = tournament self.tournament = tournament
self.groupStage = groupStage self.groupStage = groupStage
@ -67,7 +82,7 @@ final class TeamRegistration: ModelObject, Storable {
// MARK: - Computed dependencies // MARK: - Computed dependencies
func unsortedPlayers() -> [PlayerRegistration] { func unsortedPlayers() -> [PlayerRegistration] {
return self.tournamentStore.playerRegistrations.filter { $0.teamRegistration == self.id } return self.tournamentStore.playerRegistrations.filter { $0.teamRegistration == self.id && $0.coach == false }
} }
// MARK: - // MARK: -
@ -314,6 +329,7 @@ final class TeamRegistration: ModelObject, Storable {
} }
func initialRoundColor() -> Color? { func initialRoundColor() -> Color? {
if unregistered { return Color.black }
if walkOut { return Color.logoRed } if walkOut { return Color.logoRed }
if groupStagePosition != nil { return Color.blue } if groupStagePosition != nil { return Color.blue }
if let initialRound = initialRound(), let colorHex = RoundRule.colors[safe: initialRound.index] { if let initialRound = initialRound(), let colorHex = RoundRule.colors[safe: initialRound.index] {
@ -463,7 +479,7 @@ final class TeamRegistration: ModelObject, Storable {
func players() -> [PlayerRegistration] { func players() -> [PlayerRegistration] {
self.tournamentStore.playerRegistrations.filter { $0.teamRegistration == self.id }.sorted { (lhs, rhs) in self.unsortedPlayers().sorted { (lhs, rhs) in
let predicates: [AreInIncreasingOrder] = [ let predicates: [AreInIncreasingOrder] = [
{ $0.sex?.rawValue ?? 0 < $1.sex?.rawValue ?? 0 }, { $0.sex?.rawValue ?? 0 < $1.sex?.rawValue ?? 0 },
{ $0.rank ?? 0 < $1.rank ?? 0 }, { $0.rank ?? 0 < $1.rank ?? 0 },
@ -483,6 +499,10 @@ final class TeamRegistration: ModelObject, Storable {
} }
} }
func coaches() -> [PlayerRegistration] {
tournamentStore.playerRegistrations.filter({ $0.coach })
}
func setWeight(from players: [PlayerRegistration], inTournamentCategory tournamentCategory: TournamentCategory) { func setWeight(from players: [PlayerRegistration], inTournamentCategory tournamentCategory: TournamentCategory) {
let significantPlayerCount = significantPlayerCount() let significantPlayerCount = significantPlayerCount()
weight = (players.prefix(significantPlayerCount).map { $0.computedRank } + missingPlayerType(inTournamentCategory: tournamentCategory).map { unrankValue(for: $0 == 1 ? true : false ) }).prefix(significantPlayerCount).reduce(0,+) weight = (players.prefix(significantPlayerCount).map { $0.computedRank } + missingPlayerType(inTournamentCategory: tournamentCategory).map { unrankValue(for: $0 == 1 ? true : false ) }).prefix(significantPlayerCount).reduce(0,+)

@ -60,6 +60,11 @@ final class Tournament : ModelObject, Storable {
var loserBracketMode: LoserBracketMode = .automatic var loserBracketMode: LoserBracketMode = .automatic
var initialSeedRound: Int = 0 var initialSeedRound: Int = 0
var initialSeedCount: Int = 0 var initialSeedCount: Int = 0
var enableOnlineRegistration: Bool = false
var registrationDateLimit: Date? = nil
var openingRegistrationDate: Date? = nil
var targetTeamCount: Int? = nil
var waitingListLimit: Int? = nil
@ObservationIgnored @ObservationIgnored
var navigationPath: [Screen] = [] var navigationPath: [Screen] = []
@ -851,7 +856,7 @@ defer {
} }
#endif #endif
var _sortedTeams : [TeamRegistration] = [] var _sortedTeams : [TeamRegistration] = []
var _teams = unsortedTeams().filter({ $0.walkOut == false }) var _teams = unsortedTeams().filter({ $0.isOutOfTournament() == false })
if let closedRegistrationDate { if let closedRegistrationDate {
_teams = _teams.filter({ team in _teams = _teams.filter({ team in
@ -907,7 +912,7 @@ defer {
func waitingListTeams(in teams: [TeamRegistration], includingWalkOuts: Bool) -> [TeamRegistration] { func waitingListTeams(in teams: [TeamRegistration], includingWalkOuts: Bool) -> [TeamRegistration] {
let waitingList = Set(unsortedTeams()).subtracting(teams) let waitingList = Set(unsortedTeams()).subtracting(teams)
let waitings = waitingList.filter { $0.walkOut == false }.sorted(using: _defaultSorting(), order: .ascending) let waitings = waitingList.filter { $0.isOutOfTournament() == false }.sorted(using: _defaultSorting(), order: .ascending)
let walkOuts = waitingList.filter { $0.walkOut == true }.sorted(using: _defaultSorting(), order: .ascending) let walkOuts = waitingList.filter { $0.walkOut == true }.sorted(using: _defaultSorting(), order: .ascending)
if includingWalkOuts { if includingWalkOuts {
return waitings + walkOuts return waitings + walkOuts
@ -950,7 +955,7 @@ defer {
} }
func unsortedTeamsWithoutWO() -> [TeamRegistration] { func unsortedTeamsWithoutWO() -> [TeamRegistration] {
return self.tournamentStore.teamRegistrations.filter { $0.walkOut == false } return self.tournamentStore.teamRegistrations.filter { $0.isOutOfTournament() == false }
// return Store.main.filter { $0.tournament == self.id && $0.walkOut == false } // return Store.main.filter { $0.tournament == self.id && $0.walkOut == false }
} }

@ -254,4 +254,9 @@ extension Date {
formatter.unitsStyle = .abbreviated // You can choose .abbreviated or .short formatter.unitsStyle = .abbreviated // You can choose .abbreviated or .short
return formatter return formatter
}() }()
func truncateMinutesAndSeconds() -> Date {
let calendar = Calendar.current
return calendar.date(bySetting: .minute, value: 0, of: self)!.withoutSeconds()
}
} }

@ -34,7 +34,11 @@ struct PlayerDetailView: View {
Form { Form {
Section { Section {
Toggle("Joueur sur place", isOn: $player.hasArrived) Toggle("Joueur sur place", isOn: $player.hasArrived)
Toggle("Capitaine", isOn: $player.captain)
Toggle("Coach", isOn: $player.coach)
}
Section {
LabeledContent { LabeledContent {
TextField("Nom", text: $player.lastName) TextField("Nom", text: $player.lastName)
.keyboardType(.alphabet) .keyboardType(.alphabet)
@ -66,6 +70,8 @@ struct PlayerDetailView: View {
if let birthdate = player.birthdate { if let birthdate = player.birthdate {
Text(birthdate) Text(birthdate)
} }
} footer: {
Text(player.localizedSourceLabel())
} }
Section { Section {
@ -223,7 +229,7 @@ struct PlayerDetailView: View {
} }
} }
} }
.onChange(of: player.hasArrived) { .onChange(of: [player.hasArrived, player.captain, player.coach]) {
_save() _save()
} }
.onChange(of: player.sex) { .onChange(of: player.sex) {

@ -0,0 +1,33 @@
//
// DateMenuView.swift
// PadelClub
//
// Created by razmig on 22/11/2024.
//
import SwiftUI
struct DateMenuView: View {
@Binding var date: Date
private func adjustDate(byDays days: Int) {
let calendar = Calendar.current
if let newDate = calendar.date(byAdding: .day, value: days, to: date) {
date = newDate.truncateMinutesAndSeconds()
}
}
var body: some View {
Menu {
Button("24h avant") { adjustDate(byDays: -1) }
Button("48h avant") { adjustDate(byDays: -2) }
Divider()
Button("24h après") { adjustDate(byDays: 1) }
Button("48h après") { adjustDate(byDays: 2) }
} label: {
Text("Ajuster la date")
.underline()
}
}
}

@ -94,6 +94,34 @@ struct EditingTeamView: View {
} }
} }
Toggle(isOn: .init(get: {
return team.unregistered
}, set: { value in
team.resetPositions()
team.wildCardGroupStage = false
team.walkOut = false
team.wildCardBracket = false
team.unregistered = value
if value {
team.unregistrationDate = Date()
} else {
team.unregistered = false
team.unregistrationDate = nil
team.registrationDate = Date()
}
do {
try tournamentStore.teamRegistrations.addOrUpdate(instance: team)
} catch {
Logger.error(error)
}
})) {
Text("Annulation")
if let unregisterDate = team.unregistrationDate {
Text(unregisterDate.localizedDate())
}
}
Toggle(isOn: .init(get: { Toggle(isOn: .init(get: {
return team.wildCardBracket return team.wildCardBracket
}, set: { value in }, set: { value in

@ -72,8 +72,10 @@ struct InscriptionManagerView: View {
enum FilterMode: Int, Identifiable, CaseIterable { enum FilterMode: Int, Identifiable, CaseIterable {
var id: Int { self.rawValue } var id: Int { self.rawValue }
case all case all
case registeredOnline
case walkOut case walkOut
case waiting case waiting
case unregistered
case bracket case bracket
case groupStage case groupStage
case wildcardGroupStage case wildcardGroupStage
@ -88,6 +90,8 @@ struct InscriptionManagerView: View {
return "Vous n'avez aucune wildcard en poule." return "Vous n'avez aucune wildcard en poule."
case .all: case .all:
return "Vous n'avez encore aucune équipe inscrite." return "Vous n'avez encore aucune équipe inscrite."
case .registeredOnline:
return "Aucune équipe inscrite en ligne."
case .walkOut: case .walkOut:
return "Vous n'avez aucune équipe forfait." return "Vous n'avez aucune équipe forfait."
case .waiting: case .waiting:
@ -98,6 +102,8 @@ struct InscriptionManagerView: View {
return "Vous n'avez placé aucune équipe en poule." return "Vous n'avez placé aucune équipe en poule."
case .notImported: case .notImported:
return "Vous n'avez aucune équipe non importé. Elles proviennent toutes du fichier." return "Vous n'avez aucune équipe non importé. Elles proviennent toutes du fichier."
case .unregistered:
return "Vous n'avez aucune équipe ayant annulé une inscription."
} }
} }
@ -109,6 +115,8 @@ struct InscriptionManagerView: View {
return "Aucune wildcard en poule" return "Aucune wildcard en poule"
case .all: case .all:
return "Aucune équipe inscrite" return "Aucune équipe inscrite"
case .registeredOnline:
return "Aucune équipe inscrite en ligne"
case .walkOut: case .walkOut:
return "Aucune équipe forfait" return "Aucune équipe forfait"
case .waiting: case .waiting:
@ -119,6 +127,8 @@ struct InscriptionManagerView: View {
return "Aucune équipe en poule" return "Aucune équipe en poule"
case .notImported: case .notImported:
return "Aucune équipe non importée" return "Aucune équipe non importée"
case .unregistered:
return "Aucune équipe ayant annulé"
} }
} }
@ -130,6 +140,8 @@ struct InscriptionManagerView: View {
return displayStyle == .wide ? "Wildcard Poule" : "wc poule" return displayStyle == .wide ? "Wildcard Poule" : "wc poule"
case .all: case .all:
return displayStyle == .wide ? "Équipes inscrites" : "inscris" return displayStyle == .wide ? "Équipes inscrites" : "inscris"
case .registeredOnline:
return displayStyle == .wide ? "Inscrites en ligne" : "en ligne"
case .bracket: case .bracket:
return displayStyle == .wide ? "En Tableau" : "tableau" return displayStyle == .wide ? "En Tableau" : "tableau"
case .groupStage: case .groupStage:
@ -140,10 +152,13 @@ struct InscriptionManagerView: View {
return displayStyle == .wide ? "Liste d'attente" : "attente" return displayStyle == .wide ? "Liste d'attente" : "attente"
case .notImported: case .notImported:
return "Non importées" return "Non importées"
case .unregistered:
return displayStyle == .wide ? "Retirées" : "retirées"
} }
} }
} }
init(tournament: Tournament) { init(tournament: Tournament) {
self.tournament = tournament self.tournament = tournament
_currentRankSourceDate = State(wrappedValue: tournament.rankSourceDate) _currentRankSourceDate = State(wrappedValue: tournament.rankSourceDate)
@ -229,6 +244,16 @@ struct InscriptionManagerView: View {
} }
} }
} }
.refreshable {
do {
self.tournament.tournamentStore.teamRegistrations.reset()
try await self.tournament.tournamentStore.teamRegistrations.loadDataFromServerIfAllowed()
self.tournament.tournamentStore.playerRegistrations.reset()
try await self.tournament.tournamentStore.playerRegistrations.loadDataFromServerIfAllowed()
} catch {
Logger.error(error)
}
}
.onAppear { .onAppear {
_setHash() _setHash()
} }
@ -320,6 +345,12 @@ struct InscriptionManagerView: View {
.symbolVariant(filterMode == .all ? .none : .fill) .symbolVariant(filterMode == .all ? .none : .fill)
} }
Menu { Menu {
NavigationLink {
RegisrationSetupView(tournament: tournament)
} label: {
Text("Inscription en ligne")
}
if tournament.isAnimation() == false { if tournament.isAnimation() == false {
if tournament.inscriptionClosed() == false { if tournament.inscriptionClosed() == false {
Menu { Menu {
@ -664,6 +695,12 @@ struct InscriptionManagerView: View {
case .notImported: case .notImported:
let notImported: Int = max(0, sortedTeams.filter({ $0.isImported() == false }).count) let notImported: Int = max(0, sortedTeams.filter({ $0.isImported() == false }).count)
return notImported.formatted() return notImported.formatted()
case .unregistered:
let unregistered: Int = max(0, sortedTeams.filter({ $0.hasUnregistered() }).count)
return unregistered.formatted()
case .registeredOnline:
let registeredOnline: Int = max(0, sortedTeams.filter({ $0.hasRegisteredOnline() }).count)
return registeredOnline.formatted()
} }
} }

@ -0,0 +1,244 @@
//
// RegisrationSetupView.swift
// PadelClub
//
// Created by razmig on 20/11/2024.
//
import SwiftUI
import LeStorage
struct RegisrationSetupView: View {
@EnvironmentObject var dataStore: DataStore
@Bindable var tournament: Tournament
@State private var enableOnlineRegistration: Bool
@State private var registrationDateLimit: Date
@State private var openingRegistrationDate: Date
@State private var targetTeamCount: Int
@State private var waitingListLimit: Int
@State private var registrationDateLimitEnabled: Bool
@State private var targetTeamCountEnabled: Bool
@State private var waitingListLimitEnabled: Bool
@State private var openingRegistrationDateEnabled: Bool
@State private var hasChanges: Bool = false
@Environment(\.dismiss) private var dismiss
init(tournament: Tournament) {
self.tournament = tournament
_enableOnlineRegistration = .init(wrappedValue: tournament.enableOnlineRegistration)
// Registration Date Limit
if let registrationDateLimit = tournament.registrationDateLimit {
_registrationDateLimit = .init(wrappedValue: registrationDateLimit)
_registrationDateLimitEnabled = .init(wrappedValue: true)
} else {
_registrationDateLimit = .init(wrappedValue: tournament.startDate.truncateMinutesAndSeconds())
_registrationDateLimitEnabled = .init(wrappedValue: false)
}
// Opening Registration Date
if let openingRegistrationDate = tournament.openingRegistrationDate {
_openingRegistrationDate = .init(wrappedValue: openingRegistrationDate)
_openingRegistrationDateEnabled = .init(wrappedValue: true)
} else {
_openingRegistrationDate = .init(wrappedValue: tournament.creationDate.truncateMinutesAndSeconds())
_openingRegistrationDateEnabled = .init(wrappedValue: false)
}
// Target Team Count
if let targetTeamCount = tournament.targetTeamCount {
_targetTeamCount = .init(wrappedValue: targetTeamCount)
_targetTeamCountEnabled = .init(wrappedValue: true)
} else {
_targetTeamCount = .init(wrappedValue: tournament.teamCount) // Default value
_targetTeamCountEnabled = .init(wrappedValue: false)
}
// Waiting List Limit
if let waitingListLimit = tournament.waitingListLimit {
_waitingListLimit = .init(wrappedValue: waitingListLimit)
_waitingListLimitEnabled = .init(wrappedValue: true)
} else {
_waitingListLimit = .init(wrappedValue: 0) // Default value
_waitingListLimitEnabled = .init(wrappedValue: false)
}
}
var body: some View {
List {
Section {
Toggle(isOn: $enableOnlineRegistration) {
Text("Inscription en ligne")
}
}
if enableOnlineRegistration {
Section {
Toggle(isOn: $openingRegistrationDateEnabled) {
Text("Date d'ouverture des inscriptions")
}
if openingRegistrationDateEnabled {
DatePicker(selection: $openingRegistrationDate, in: tournament.creationDate.truncateMinutesAndSeconds()...tournament.startDate.truncateMinutesAndSeconds()) {
DateMenuView(date: $openingRegistrationDate)
}
}
} header: {
Text("Date d'ouverture des inscriptions")
} footer: {
Text("Activez et définissez une date d'ouverture pour les inscriptions au tournoi.")
}
Section {
Toggle(isOn: $registrationDateLimitEnabled) {
Text("Activer une limite")
}
if registrationDateLimitEnabled {
DatePicker(selection: $registrationDateLimit, in: tournament.creationDate.truncateMinutesAndSeconds()...tournament.startDate.truncateMinutesAndSeconds()) {
DateMenuView(date: $registrationDateLimit)
}
}
} header: {
Text("Date de fermeture des inscriptions")
} footer: {
Text("Activez et définissez une date limite pour l'inscription en ligne.")
}
Section {
Toggle(isOn: $targetTeamCountEnabled) {
Text("Nombre d'équipes cible")
}
if targetTeamCountEnabled {
StepperView(count: $targetTeamCount, minimum: 4)
}
} header: {
Text("Nombre d'équipes cible")
} footer: {
Text("Activez et définissez le nombre d'équipes cible pour le tournoi.")
}
Section {
Toggle(isOn: $waitingListLimitEnabled) {
Text("Limite de la liste d'attente")
}
if waitingListLimitEnabled {
StepperView(count: $waitingListLimit, minimum: 0)
}
} header: {
Text("Limite de la liste d'attente")
} footer: {
Text("Activez et définissez une limite pour la liste d'attente des équipes.")
}
} else {
ContentUnavailableView(
"Activez les inscriptions en ligne",
systemImage: "person.2.crop.square.stack.fill",
description: Text("Permettez aux joueurs de s'inscrire eux-mêmes à ce tournoi. Les équipes inscrites apparaîtront automatiquement dans la liste de l'arbitre. L'inscription en ligne requiert un email de contact et une licence FFT.")
)
}
}
.toolbar(content: {
if hasChanges {
ToolbarItem(placement: .topBarLeading) {
Button("Annuler", role: .cancel) {
dismiss()
}
}
ToolbarItem(placement: .topBarTrailing) {
ButtonValidateView(role: .destructive) {
_save()
dismiss()
}
}
}
})
.toolbarRole(.editor)
.headerProminence(.increased)
.navigationTitle("Inscription en ligne")
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.visible, for: .navigationBar)
.navigationBarBackButtonHidden(hasChanges)
.onChange(of: enableOnlineRegistration, {
_hasChanged()
})
.onChange(of: openingRegistrationDateEnabled) {
_hasChanged()
}
.onChange(of: openingRegistrationDate) {
_hasChanged()
}
.onChange(of: registrationDateLimitEnabled) {
_hasChanged()
}
.onChange(of: registrationDateLimit) {
_hasChanged()
}
.onChange(of: targetTeamCountEnabled) {
_hasChanged()
}
.onChange(of: targetTeamCount) {
_hasChanged()
}
.onChange(of: waitingListLimitEnabled) {
_hasChanged()
}
.onChange(of: waitingListLimit) {
_hasChanged()
}
}
private func _hasChanged() {
hasChanges = true
}
private func _save() {
hasChanges = false
tournament.enableOnlineRegistration = enableOnlineRegistration
if openingRegistrationDateEnabled == false {
tournament.openingRegistrationDate = nil
} else {
tournament.openingRegistrationDate = openingRegistrationDate
}
if registrationDateLimitEnabled == false {
tournament.registrationDateLimit = nil
} else {
tournament.registrationDateLimit = registrationDateLimit
}
if targetTeamCountEnabled == false {
tournament.targetTeamCount = nil
} else {
tournament.targetTeamCount = targetTeamCount
}
if waitingListLimitEnabled == false {
tournament.waitingListLimit = nil
} else {
tournament.waitingListLimit = waitingListLimit
}
do {
try self.dataStore.tournaments.addOrUpdate(instance: tournament)
} catch {
Logger.error(error)
}
dismiss()
}
}
Loading…
Cancel
Save