club_update
Razmig Sarkissian 1 year ago
parent 4b5c05dfa3
commit b7dbac40bc
  1. 12
      PadelClub.xcodeproj/project.pbxproj
  2. 2
      PadelClub/Data/Match.swift
  3. 2
      PadelClub/Data/Round.swift
  4. 32
      PadelClub/Data/TeamRegistration.swift
  5. 16
      PadelClub/Data/Tournament.swift
  6. 192
      PadelClub/Views/GroupStage/Components/GroupStageSettingsView.swift
  7. 170
      PadelClub/Views/GroupStage/GroupStageView.swift
  8. 21
      PadelClub/Views/GroupStage/GroupStagesSettingsView.swift
  9. 2
      PadelClub/Views/GroupStage/GroupStagesView.swift
  10. 11
      PadelClub/Views/GroupStage/Shared/GroupStageTeamReplacementView.swift
  11. 2
      PadelClub/Views/Team/TeamRowView.swift
  12. 69
      PadelClub/Views/Tournament/Screen/AddTeamView.swift
  13. 85
      PadelClub/Views/Tournament/Screen/BroadcastView.swift
  14. 628
      PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift
  15. 1
      PadelClub/Views/Tournament/Shared/TournamentCellView.swift

@ -137,7 +137,7 @@
FF5D30512BD94E1000F2B93D /* ImportedPlayer+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D30502BD94E1000F2B93D /* ImportedPlayer+Extensions.swift */; }; FF5D30512BD94E1000F2B93D /* ImportedPlayer+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D30502BD94E1000F2B93D /* ImportedPlayer+Extensions.swift */; };
FF5D30532BD94E2E00F2B93D /* PlayerHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D30522BD94E2E00F2B93D /* PlayerHolder.swift */; }; FF5D30532BD94E2E00F2B93D /* PlayerHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D30522BD94E2E00F2B93D /* PlayerHolder.swift */; };
FF5D30562BD95B1100F2B93D /* OngoingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D30552BD95B1100F2B93D /* OngoingView.swift */; }; FF5D30562BD95B1100F2B93D /* OngoingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D30552BD95B1100F2B93D /* OngoingView.swift */; };
FF5DA18F2BB9268800A33061 /* GroupStageSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA18E2BB9268800A33061 /* GroupStageSettingsView.swift */; }; FF5DA18F2BB9268800A33061 /* GroupStagesSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA18E2BB9268800A33061 /* GroupStagesSettingsView.swift */; };
FF5DA1932BB9279B00A33061 /* RoundSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA1922BB9279B00A33061 /* RoundSettingsView.swift */; }; FF5DA1932BB9279B00A33061 /* RoundSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA1922BB9279B00A33061 /* RoundSettingsView.swift */; };
FF5DA1952BB927E800A33061 /* GenericDestinationPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA1942BB927E800A33061 /* GenericDestinationPickerView.swift */; }; FF5DA1952BB927E800A33061 /* GenericDestinationPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA1942BB927E800A33061 /* GenericDestinationPickerView.swift */; };
FF5DA19B2BB9662200A33061 /* TournamentSeedEditing.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA19A2BB9662200A33061 /* TournamentSeedEditing.swift */; }; FF5DA19B2BB9662200A33061 /* TournamentSeedEditing.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA19A2BB9662200A33061 /* TournamentSeedEditing.swift */; };
@ -229,6 +229,7 @@
FFC91AF92BD6A09100B29808 /* FortuneWheelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC91AF82BD6A09100B29808 /* FortuneWheelView.swift */; }; FFC91AF92BD6A09100B29808 /* FortuneWheelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC91AF82BD6A09100B29808 /* FortuneWheelView.swift */; };
FFC91B012BD85C2F00B29808 /* Court.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC91B002BD85C2F00B29808 /* Court.swift */; }; FFC91B012BD85C2F00B29808 /* Court.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC91B002BD85C2F00B29808 /* Court.swift */; };
FFC91B032BD85E2400B29808 /* CourtView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC91B022BD85E2400B29808 /* CourtView.swift */; }; FFC91B032BD85E2400B29808 /* CourtView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFC91B022BD85E2400B29808 /* CourtView.swift */; };
FFCB74132C4625BB008384D0 /* GroupStageSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCB74122C4625BB008384D0 /* GroupStageSettingsView.swift */; };
FFCD16B32C3E5E590092707B /* TeamsCallingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCD16B22C3E5E590092707B /* TeamsCallingView.swift */; }; FFCD16B32C3E5E590092707B /* TeamsCallingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCD16B22C3E5E590092707B /* TeamsCallingView.swift */; };
FFCEDA4C2C2C08EA00F8C0F2 /* PlayersWithoutContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCEDA4B2C2C08EA00F8C0F2 /* PlayersWithoutContactView.swift */; }; FFCEDA4C2C2C08EA00F8C0F2 /* PlayersWithoutContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCEDA4B2C2C08EA00F8C0F2 /* PlayersWithoutContactView.swift */; };
FFCF76072C3BE9BC006C8C3D /* CloseDatePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCF76062C3BE9BC006C8C3D /* CloseDatePicker.swift */; }; FFCF76072C3BE9BC006C8C3D /* CloseDatePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFCF76062C3BE9BC006C8C3D /* CloseDatePicker.swift */; };
@ -474,7 +475,7 @@
FF5D30502BD94E1000F2B93D /* ImportedPlayer+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ImportedPlayer+Extensions.swift"; sourceTree = "<group>"; }; FF5D30502BD94E1000F2B93D /* ImportedPlayer+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ImportedPlayer+Extensions.swift"; sourceTree = "<group>"; };
FF5D30522BD94E2E00F2B93D /* PlayerHolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerHolder.swift; sourceTree = "<group>"; }; FF5D30522BD94E2E00F2B93D /* PlayerHolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerHolder.swift; sourceTree = "<group>"; };
FF5D30552BD95B1100F2B93D /* OngoingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OngoingView.swift; sourceTree = "<group>"; }; FF5D30552BD95B1100F2B93D /* OngoingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OngoingView.swift; sourceTree = "<group>"; };
FF5DA18E2BB9268800A33061 /* GroupStageSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupStageSettingsView.swift; sourceTree = "<group>"; }; FF5DA18E2BB9268800A33061 /* GroupStagesSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupStagesSettingsView.swift; sourceTree = "<group>"; };
FF5DA1922BB9279B00A33061 /* RoundSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundSettingsView.swift; sourceTree = "<group>"; }; FF5DA1922BB9279B00A33061 /* RoundSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundSettingsView.swift; sourceTree = "<group>"; };
FF5DA1942BB927E800A33061 /* GenericDestinationPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericDestinationPickerView.swift; sourceTree = "<group>"; }; FF5DA1942BB927E800A33061 /* GenericDestinationPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericDestinationPickerView.swift; sourceTree = "<group>"; };
FF5DA19A2BB9662200A33061 /* TournamentSeedEditing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentSeedEditing.swift; sourceTree = "<group>"; }; FF5DA19A2BB9662200A33061 /* TournamentSeedEditing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentSeedEditing.swift; sourceTree = "<group>"; };
@ -567,6 +568,7 @@
FFC91AF82BD6A09100B29808 /* FortuneWheelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FortuneWheelView.swift; sourceTree = "<group>"; }; FFC91AF82BD6A09100B29808 /* FortuneWheelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FortuneWheelView.swift; sourceTree = "<group>"; };
FFC91B002BD85C2F00B29808 /* Court.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Court.swift; sourceTree = "<group>"; }; FFC91B002BD85C2F00B29808 /* Court.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Court.swift; sourceTree = "<group>"; };
FFC91B022BD85E2400B29808 /* CourtView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourtView.swift; sourceTree = "<group>"; }; FFC91B022BD85E2400B29808 /* CourtView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourtView.swift; sourceTree = "<group>"; };
FFCB74122C4625BB008384D0 /* GroupStageSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupStageSettingsView.swift; sourceTree = "<group>"; };
FFCD16B22C3E5E590092707B /* TeamsCallingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamsCallingView.swift; sourceTree = "<group>"; }; FFCD16B22C3E5E590092707B /* TeamsCallingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamsCallingView.swift; sourceTree = "<group>"; };
FFCEDA4B2C2C08EA00F8C0F2 /* PlayersWithoutContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayersWithoutContactView.swift; sourceTree = "<group>"; }; FFCEDA4B2C2C08EA00F8C0F2 /* PlayersWithoutContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayersWithoutContactView.swift; sourceTree = "<group>"; };
FFCF76062C3BE9BC006C8C3D /* CloseDatePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloseDatePicker.swift; sourceTree = "<group>"; }; FFCF76062C3BE9BC006C8C3D /* CloseDatePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloseDatePicker.swift; sourceTree = "<group>"; };
@ -1166,7 +1168,7 @@
children = ( children = (
FF967CFA2BAEE13800A9A3BD /* GroupStageView.swift */, FF967CFA2BAEE13800A9A3BD /* GroupStageView.swift */,
FF967CFB2BAEE13900A9A3BD /* GroupStagesView.swift */, FF967CFB2BAEE13900A9A3BD /* GroupStagesView.swift */,
FF5DA18E2BB9268800A33061 /* GroupStageSettingsView.swift */, FF5DA18E2BB9268800A33061 /* GroupStagesSettingsView.swift */,
FF135BF82C2FCB8300C9247A /* LoserGroupStageSettingsView.swift */, FF135BF82C2FCB8300C9247A /* LoserGroupStageSettingsView.swift */,
FF9AC3932BE3625D00C2E883 /* Components */, FF9AC3932BE3625D00C2E883 /* Components */,
FF9AC3922BE3625200C2E883 /* Shared */, FF9AC3922BE3625200C2E883 /* Shared */,
@ -1211,6 +1213,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
FFBF065B2BBD2657009D6715 /* GroupStageTeamView.swift */, FFBF065B2BBD2657009D6715 /* GroupStageTeamView.swift */,
FFCB74122C4625BB008384D0 /* GroupStageSettingsView.swift */,
); );
path = Components; path = Components;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1641,6 +1644,7 @@
FF59FFB92B90EFD70061EFF9 /* ToolboxView.swift in Sources */, FF59FFB92B90EFD70061EFF9 /* ToolboxView.swift in Sources */,
FF8E1CE62C006E0200184680 /* Alphabet.swift in Sources */, FF8E1CE62C006E0200184680 /* Alphabet.swift in Sources */,
FFF8ACD92B923F3C008466FA /* String+Extensions.swift in Sources */, FFF8ACD92B923F3C008466FA /* String+Extensions.swift in Sources */,
FFCB74132C4625BB008384D0 /* GroupStageSettingsView.swift in Sources */,
FF025AE52BD0EBB800A86CF8 /* TournamentGeneralSettingsView.swift in Sources */, FF025AE52BD0EBB800A86CF8 /* TournamentGeneralSettingsView.swift in Sources */,
FFC2DCB22BBE75D40046DB9F /* LoserRoundView.swift in Sources */, FFC2DCB22BBE75D40046DB9F /* LoserRoundView.swift in Sources */,
FF90FC1D2C44FB3E009339B2 /* AddTeamView.swift in Sources */, FF90FC1D2C44FB3E009339B2 /* AddTeamView.swift in Sources */,
@ -1692,7 +1696,7 @@
FF8F26452BAE0A3400650388 /* TournamentDurationManagerView.swift in Sources */, FF8F26452BAE0A3400650388 /* TournamentDurationManagerView.swift in Sources */,
FF1DC5532BAB354A00FD8220 /* MockData.swift in Sources */, FF1DC5532BAB354A00FD8220 /* MockData.swift in Sources */,
FF967D092BAF3D4000A9A3BD /* TeamDetailView.swift in Sources */, FF967D092BAF3D4000A9A3BD /* TeamDetailView.swift in Sources */,
FF5DA18F2BB9268800A33061 /* GroupStageSettingsView.swift in Sources */, FF5DA18F2BB9268800A33061 /* GroupStagesSettingsView.swift in Sources */,
FF663FBE2BE019EC0031AE83 /* TournamentFilterView.swift in Sources */, FF663FBE2BE019EC0031AE83 /* TournamentFilterView.swift in Sources */,
FF1F4B752BFA00FC000B4573 /* HtmlGenerator.swift in Sources */, FF1F4B752BFA00FC000B4573 /* HtmlGenerator.swift in Sources */,
FF8F26382BAD523300650388 /* PadelRule.swift in Sources */, FF8F26382BAD523300650388 /* PadelRule.swift in Sources */,

@ -608,8 +608,6 @@ defer {
func canBeStarted(inMatches matches: [Match]) -> Bool { func canBeStarted(inMatches matches: [Match]) -> Bool {
let teams = teamScores let teams = teamScores
guard teams.count == 2 else { return false } guard teams.count == 2 else { return false }
guard hasEnded() == false else { return false }
guard hasStarted() == false else { return false }
return teams.compactMap({ $0.team }).allSatisfy({ $0.canPlay() && isTeamPlaying($0, inMatches: matches) == false }) return teams.compactMap({ $0.team }).allSatisfy({ $0.canPlay() && isTeamPlaying($0, inMatches: matches) == false })
} }

@ -511,7 +511,7 @@ defer {
return seedInterval.localizedLabel(displayStyle) return seedInterval.localizedLabel(displayStyle)
} }
print("Round pas trouvé", id, parent, index) print("Round pas trouvé", id, parent, index)
return "--" return "Match de classement"
} }
return RoundRule.roundName(fromRoundIndex: index, displayStyle: displayStyle) return RoundRule.roundName(fromRoundIndex: index, displayStyle: displayStyle)
} }

@ -71,6 +71,15 @@ final class TeamRegistration: ModelObject, Storable {
// MARK: - // MARK: -
func deleteTeamScores() {
let ts = self.tournamentStore.teamScores.filter({ $0.teamRegistration == id })
do {
try self.tournamentStore.teamScores.delete(contentOfs: ts)
} catch {
Logger.error(error)
}
}
override func deleteDependencies() throws { override func deleteDependencies() throws {
let unsortedPlayers = unsortedPlayers() let unsortedPlayers = unsortedPlayers()
for player in unsortedPlayers { for player in unsortedPlayers {
@ -264,18 +273,29 @@ final class TeamRegistration: ModelObject, Storable {
} }
func resetGroupeStagePosition() { func resetGroupeStagePosition() {
groupStageObject()?._matches().forEach({ $0.updateTeamScores() }) if let groupStage {
let matches = self.tournamentStore.matches.filter({ $0.groupStage == groupStage }).map { $0.id }
let teamScores = self.tournamentStore.teamScores.filter({ $0.teamRegistration == id && matches.contains($0.match) })
do {
try tournamentStore.teamScores.delete(contentOfs: teamScores)
} catch {
Logger.error(error)
}
}
//groupStageObject()?._matches().forEach({ $0.updateTeamScores() })
groupStage = nil groupStage = nil
groupStagePosition = nil groupStagePosition = nil
} }
func resetBracketPosition() { func resetBracketPosition() {
guard let bracketPosition else { return } let matches = self.tournamentStore.matches.filter({ $0.groupStage == nil }).map { $0.id }
guard let tournamentObject = tournamentObject() else { return } let teamScores = self.tournamentStore.teamScores.filter({ $0.teamRegistration == id && matches.contains($0.match) })
if let match = tournamentObject.match(for: bracketPosition) { do {
let teamScores = match.teamScores.filter({ $0.teamRegistration != self.id }) try tournamentStore.teamScores.delete(contentOfs: teamScores)
tournamentObject.resetTeamScores(in: bracketPosition, outsideOf: teamScores) } catch {
Logger.error(error)
} }
self.bracketPosition = nil self.bracketPosition = nil
} }

@ -1063,7 +1063,7 @@ defer {
} }
} }
func registrationIssues() async -> Int { func registrationIssues() -> Int {
let players : [PlayerRegistration] = unsortedPlayers() let players : [PlayerRegistration] = unsortedPlayers()
let selectedTeams : [TeamRegistration] = selectedSortedTeams() let selectedTeams : [TeamRegistration] = selectedSortedTeams()
let callDateIssue : [TeamRegistration] = selectedTeams.filter { $0.callDate != nil && isStartDateIsDifferentThanCallDate($0) } let callDateIssue : [TeamRegistration] = selectedTeams.filter { $0.callDate != nil && isStartDateIsDifferentThanCallDate($0) }
@ -1280,6 +1280,20 @@ defer {
} }
} }
func unlockRegistration() {
closedRegistrationDate = nil
let teams = unsortedTeams()
teams.forEach { team in
team.lockedWeight = nil
}
do {
try self.tournamentStore.teamRegistrations.addOrUpdate(contentOfs: teams)
} catch {
Logger.error(error)
}
}
func updateWeights() { func updateWeights() {
let teams = self.unsortedTeams() let teams = self.unsortedTeams()
teams.forEach { team in teams.forEach { team in

@ -0,0 +1,192 @@
//
// GroupStageSettingsView.swift
// PadelClub
//
// Created by Razmig Sarkissian on 16/07/2024.
//
import SwiftUI
import LeStorage
struct GroupStageSettingsView: View {
@Environment(\.dismiss) private var dismiss
@Environment(Tournament.self) private var tournament
@EnvironmentObject var dataStore: DataStore
@Bindable var groupStage: GroupStage
@State private var groupStageName: String
@State private var presentConfirmationButton: Bool = false
@State private var size: Int
@State private var courtIndex: Int
init(groupStage: GroupStage) {
_groupStage = Bindable(groupStage)
_groupStageName = .init(wrappedValue: groupStage.name ?? "")
_size = .init(wrappedValue: groupStage.size)
_courtIndex = .init(wrappedValue: groupStage._matches().first?.courtIndex ?? 0)
}
var tournamentStore: TournamentStore {
return self.tournament.tournamentStore
}
var body: some View {
Form {
Section {
TextField("Nom de la poule", text: $groupStageName)
.keyboardType(.alphabet)
.frame(maxWidth: .infinity)
.onSubmit {
groupStageName = groupStageName.trimmed
if groupStageName.isEmpty == false {
groupStage.name = groupStageName
_save()
dismiss()
}
}
} footer: {
if groupStage.name != nil {
HStack {
Spacer()
FooterButtonView("retirer le nom") {
groupStage.name = nil
groupStageName = ""
_save()
}
}
}
}
Section {
CourtPicker(title: "Terrain dédié", selection: $courtIndex, maxCourt: tournament.courtCount)
RowButtonView("Confirmer", role: .destructive) {
groupStage._matches().forEach { match in
match.setCourt(courtIndex)
}
do {
try tournamentStore.matches.addOrUpdate(contentOfs: groupStage._matches())
} catch {
Logger.error(error)
}
}
}
Section {
LabeledContent {
StepperView(count: $size, minimum: minimumSize(), maximum: maximumSize())
} label: {
Text("Taille de la poule")
}
if presentConfirmationButton {
RowButtonView("Confirmer", role: .destructive, confirmationMessage: "Tous les matchs et les équipes de cette poule seront ré-initialisés") {
let teams = groupStage.teams()
teams.forEach { team in
team.groupStagePosition = nil
team.groupStage = nil
groupStage._matches().forEach({ $0.updateTeamScores() })
}
do {
try tournamentStore.teamRegistrations.addOrUpdate(contentOfs: teams)
} catch {
Logger.error(error)
}
groupStage.size = size
groupStage.buildMatches()
tournament.shouldVerifyGroupStage = true
_save()
presentConfirmationButton = false
}
}
} footer: {
Text("Une poule ne peut pas avoir moins de 3 équipes et plus d'une équipe de différence par rapport aux autres poules.")
}
Section {
RowButtonView("Retirer tous les horaires", role: .destructive) {
groupStage._matches().forEach { match in
match.startDate = nil
match.endDate = nil
}
do {
try tournamentStore.matches.addOrUpdate(contentOfs: groupStage._matches())
} catch {
Logger.error(error)
}
}
}
Section {
RowButtonView("Retirer tout le monde", role: .destructive) {
let teams = groupStage.teams()
teams.forEach { team in
team.groupStagePosition = nil
team.groupStage = nil
groupStage._matches().forEach({ $0.updateTeamScores() })
}
do {
try tournamentStore.teamRegistrations.addOrUpdate(contentOfs: teams)
} catch {
Logger.error(error)
}
}
} footer: {
Text("Toutes les équipes seront retirées et les scores des matchs seront perdus.")
}
Section {
RowButtonView("Recommencer tous les matchs", role: .destructive) {
groupStage.buildMatches()
}
} footer: {
Text("Tous les matchs seront recronstruits, les données des matchs seront perdus.")
}
}
.onChange(of: size) {
if size != groupStage.size {
presentConfirmationButton = true
}
}
.navigationTitle("Paramètres")
.toolbarBackground(.visible, for: .navigationBar)
}
private func maximumSize() -> Int {
return 10
// if groupStage.index == 0 {
// return tournament.teamsPerGroupStage + 1
// }
//
// if let previousGroupStage = tournament.groupStages().first(where: { $0.index == groupStage.index - 1 }), previousGroupStage.size > groupStage.size {
// return tournament.teamsPerGroupStage + 1
// }
//
// return tournament.teamsPerGroupStage
}
private func minimumSize() -> Int {
return 3
// if groupStage.index == tournament.groupStageCount - 1 {
// return max(3, tournament.teamsPerGroupStage - 1)
// }
//
// if let nextGroupStage = tournament.groupStages().first(where: { $0.index == groupStage.index + 1 }), nextGroupStage.size < groupStage.size {
// return max(3, tournament.teamsPerGroupStage - 1)
// }
//
// return tournament.teamsPerGroupStage
}
private func _save() {
do {
try tournamentStore.groupStages.addOrUpdate(instance: groupStage)
} catch {
Logger.error(error)
}
}
}

@ -16,8 +16,6 @@ struct GroupStageView: View {
@Bindable var groupStage: GroupStage @Bindable var groupStage: GroupStage
@State private var confirmGroupStageStart: Bool = false @State private var confirmGroupStageStart: Bool = false
@State private var sortingMode: GroupStageSortingMode = .auto @State private var sortingMode: GroupStageSortingMode = .auto
@State private var confirmRemoveAll: Bool = false
@State private var confirmResetMatch: Bool = false
let playedMatches: [Match] let playedMatches: [Match]
init(groupStage: GroupStage) { init(groupStage: GroupStage) {
@ -233,166 +231,20 @@ struct GroupStageView: View {
} }
private func _groupStageMenuView() -> some View { private func _groupStageMenuView() -> some View {
Menu { NavigationLink {
NavigationLink { GroupStageSettingsView(groupStage: groupStage)
GroupStageNameEditionView(groupStage: groupStage) .environment(tournament)
.environment(tournament)
} label: {
Label("Modifier le nom et la taille", systemImage: "pencil")
}
Button("Retirer tout le monde", role: .destructive) {
confirmRemoveAll = true
}
Button("Recommencer tous les matchs", role: .destructive) {
confirmResetMatch = true
}
} label: { } label: {
LabelOptions() Label {
} Text("Modifier")
.confirmationDialog("Êtes-vous sûr de vouloir faire cela ?", isPresented: $confirmRemoveAll, titleVisibility: .visible) { } icon: {
Button("Oui") { Image(systemName: "gear.circle")
let teams = groupStage.teams() .resizable()
teams.forEach { team in .scaledToFit()
team.groupStagePosition = nil .frame(height: 28)
team.groupStage = nil
groupStage._matches().forEach({ $0.updateTeamScores() })
}
do {
try tournamentStore.teamRegistrations.addOrUpdate(contentOfs: teams)
} catch {
Logger.error(error)
}
}
}
.confirmationDialog("Êtes-vous sûr de vouloir faire cela ?", isPresented: $confirmResetMatch, titleVisibility: .visible) {
Button("Oui") {
groupStage.buildMatches()
}
}
}
private func _save() {
do {
try tournamentStore.groupStages.addOrUpdate(instance: groupStage)
} catch {
Logger.error(error)
}
}
}
struct GroupStageNameEditionView: View {
@Environment(\.dismiss) private var dismiss
@Environment(Tournament.self) private var tournament
@EnvironmentObject var dataStore: DataStore
@Bindable var groupStage: GroupStage
@State private var groupStageName: String
@State private var presentConfirmationButton: Bool = false
@State private var size: Int
init(groupStage: GroupStage) {
_groupStage = Bindable(groupStage)
_groupStageName = .init(wrappedValue: groupStage.name ?? "")
_size = .init(wrappedValue: groupStage.size)
}
var tournamentStore: TournamentStore {
return self.tournament.tournamentStore
}
var body: some View {
Form {
Section {
TextField("Nom de la poule", text: $groupStageName)
.keyboardType(.alphabet)
.frame(maxWidth: .infinity)
.onSubmit {
groupStageName = groupStageName.trimmed
if groupStageName.isEmpty == false {
groupStage.name = groupStageName
_save()
dismiss()
}
}
} footer: {
if groupStage.name != nil {
HStack {
Spacer()
FooterButtonView("retirer le nom") {
groupStage.name = nil
groupStageName = ""
_save()
}
}
}
} }
.labelStyle(.titleOnly)
Section {
LabeledContent {
StepperView(count: $size, minimum: minimumSize(), maximum: maximumSize())
} label: {
Text("Taille de la poule")
}
if presentConfirmationButton {
RowButtonView("Confirmer", role: .destructive, confirmationMessage: "Tous les matchs et les équipes de cette poule seront ré-initialisés") {
let teams = groupStage.teams()
teams.forEach { team in
team.groupStagePosition = nil
team.groupStage = nil
groupStage._matches().forEach({ $0.updateTeamScores() })
}
do {
try tournamentStore.teamRegistrations.addOrUpdate(contentOfs: teams)
} catch {
Logger.error(error)
}
groupStage.size = size
groupStage.buildMatches()
tournament.shouldVerifyGroupStage = true
_save()
presentConfirmationButton = false
}
}
} footer: {
Text("Vous ne pouvez réduire la taille qu'à partir de la dernière poule et augmentez la taille qu'à partir de la première poule. Une poule ne peut pas avoir moins de 3 équipes et plus d'une équipe de différence par rapport aux autres poules.")
}
} }
.onChange(of: size) {
presentConfirmationButton = true
}
.navigationTitle(groupStage.groupStageTitle())
.toolbarBackground(.visible, for: .navigationBar)
}
private func maximumSize() -> Int {
return 10
// if groupStage.index == 0 {
// return tournament.teamsPerGroupStage + 1
// }
//
// if let previousGroupStage = tournament.groupStages().first(where: { $0.index == groupStage.index - 1 }), previousGroupStage.size > groupStage.size {
// return tournament.teamsPerGroupStage + 1
// }
//
// return tournament.teamsPerGroupStage
}
private func minimumSize() -> Int {
return 3
// if groupStage.index == tournament.groupStageCount - 1 {
// return max(3, tournament.teamsPerGroupStage - 1)
// }
//
// if let nextGroupStage = tournament.groupStages().first(where: { $0.index == groupStage.index + 1 }), nextGroupStage.size < groupStage.size {
// return max(3, tournament.teamsPerGroupStage - 1)
// }
//
// return tournament.teamsPerGroupStage
} }
private func _save() { private func _save() {

@ -1,5 +1,5 @@
// //
// GroupStageSettingsView.swift // GroupStagesSettingsView.swift
// PadelClub // PadelClub
// //
// Created by Razmig Sarkissian on 31/03/2024. // Created by Razmig Sarkissian on 31/03/2024.
@ -8,7 +8,7 @@
import SwiftUI import SwiftUI
import LeStorage import LeStorage
struct GroupStageSettingsView: View { struct GroupStagesSettingsView: View {
@EnvironmentObject var dataStore: DataStore @EnvironmentObject var dataStore: DataStore
@Environment(Tournament.self) var tournament: Tournament @Environment(Tournament.self) var tournament: Tournament
@ -110,6 +110,23 @@ struct GroupStageSettingsView: View {
// Text("Match de perdant de poules") // Text("Match de perdant de poules")
// } // }
Section {
RowButtonView("Retirer tous les horaires", role: .destructive) {
let matches = tournament.groupStages().flatMap({ $0._matches() })
tournament.groupStages().flatMap({ $0._matches() }).forEach { match in
match.startDate = nil
match.endDate = nil
}
do {
try tournamentStore.matches.addOrUpdate(contentOfs: matches)
} catch {
Logger.error(error)
}
}
}
if tournament.unsortedTeams().filter({ $0.groupStagePosition != nil }).isEmpty == false { if tournament.unsortedTeams().filter({ $0.groupStagePosition != nil }).isEmpty == false {
Section { Section {
menuBuildAllGroupStages menuBuildAllGroupStages

@ -111,7 +111,7 @@ struct GroupStagesView: View {
case .groupStage(let groupStage): case .groupStage(let groupStage):
GroupStageView(groupStage: groupStage).id(groupStage.id) GroupStageView(groupStage: groupStage).id(groupStage.id)
case nil: case nil:
GroupStageSettingsView() GroupStagesSettingsView()
.navigationTitle("Réglages") .navigationTitle("Réglages")
} }
} }

@ -134,11 +134,11 @@ struct GroupStageTeamReplacementView: View {
@ViewBuilder @ViewBuilder
var body: some View { var body: some View {
HStack { HStack {
TeamRangeSideView(team: teamRange.left, playerWeight: -playerWeight) TeamRangeSideView(team: teamRange.left, playerWeight: -playerWeight, leftLimit: true)
Spacer() Spacer()
Image(systemName: "arrowshape.forward.fill") Image(systemName: "arrowshape.forward.fill")
Spacer() Spacer()
TeamRangeSideView(team: teamRange.right, playerWeight: -playerWeight) TeamRangeSideView(team: teamRange.right, playerWeight: -playerWeight, leftLimit: false)
} }
} }
} }
@ -146,13 +146,18 @@ struct GroupStageTeamReplacementView: View {
struct TeamRangeSideView: View { struct TeamRangeSideView: View {
let team: TeamRegistration? let team: TeamRegistration?
let playerWeight: Int let playerWeight: Int
let leftLimit: Bool
@ViewBuilder @ViewBuilder
var body: some View { var body: some View {
if let team, team.weight + playerWeight > 0 { if let team, team.weight + playerWeight > 0 {
Text((team.weight + playerWeight).formatted()).font(.largeTitle) Text((team.weight + playerWeight).formatted()).font(.largeTitle)
} else { } else {
Text("Aucune limite") if leftLimit {
Text("1").font(.largeTitle)
} else {
Text("Non classé").font(.largeTitle)
}
} }
} }
} }

@ -32,7 +32,7 @@ struct TeamRowView: View {
.font(.caption) .font(.caption)
} else { } else {
Text("Pas encore convoquée") Text("Pas encore convoquée")
.foregroundStyle(.logoYellow) .foregroundStyle(.logoRed)
.italic() .italic()
.font(.caption) .font(.caption)
} }

@ -32,7 +32,6 @@ struct AddTeamView: View {
@State private var pasteString: String? @State private var pasteString: String?
@State private var selectionSearchField: String? @State private var selectionSearchField: String?
@State private var autoSelect: Bool = false @State private var autoSelect: Bool = false
@State private var teamsHash: Int?
@State private var presentationCount: Int = 0 @State private var presentationCount: Int = 0
@State private var confirmDuplicate: Bool = false @State private var confirmDuplicate: Bool = false
@ -40,7 +39,7 @@ struct AddTeamView: View {
return self.tournament.tournamentStore return self.tournament.tournamentStore
} }
init(tournament: Tournament, pasteString: String? = nil, editedTeam: TeamRegistration?) { init(tournament: Tournament, pasteString: String? = nil, editedTeam: TeamRegistration? = nil) {
self.tournament = tournament self.tournament = tournament
_editedTeam = .init(wrappedValue: editedTeam) _editedTeam = .init(wrappedValue: editedTeam)
if let team = editedTeam { if let team = editedTeam {
@ -64,57 +63,6 @@ struct AddTeamView: View {
} }
} }
// Function to create a simple hash from a list of IDs
private func _simpleHash(ids: [String]) -> Int {
// Combine the hash values of each string
return ids.reduce(0) { $0 ^ $1.hashValue }
}
// Function to check if two lists of IDs produce different hashes
private func _areDifferent(ids1: [String], ids2: [String]) -> Bool {
return _simpleHash(ids: ids1) != _simpleHash(ids: ids2)
}
private func _setHash() async {
#if DEBUG_TIME //DEBUGING TIME
let start = Date()
defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
print("func _setHash", duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
#endif
let selectedSortedTeams = tournament.selectedSortedTeams()
if self.teamsHash == nil, selectedSortedTeams.isEmpty == false {
self.teamsHash = _simpleHash(ids: selectedSortedTeams.map { $0.id })
}
}
private func _handleHashDiff() async {
let selectedSortedTeams = tournament.selectedSortedTeams()
let newHash = _simpleHash(ids: selectedSortedTeams.map { $0.id })
if (self.teamsHash != nil && newHash != teamsHash!) || (self.teamsHash == nil && selectedSortedTeams.isEmpty == false) {
self.teamsHash = newHash
if self.tournament.shouldVerifyBracket == false || self.tournament.shouldVerifyGroupStage == false {
self.tournament.shouldVerifyBracket = true
self.tournament.shouldVerifyGroupStage = true
let waitingList = self.tournament.waitingListTeams(in: selectedSortedTeams, includingWalkOuts: true)
waitingList.forEach { team in
if team.bracketPosition != nil || team.groupStagePosition != nil {
team.resetPositions()
}
}
do {
try tournamentStore.teamRegistrations.addOrUpdate(contentOfs: waitingList)
try dataStore.tournaments.addOrUpdate(instance: tournament)
} catch {
Logger.error(error)
}
}
}
}
var body: some View { var body: some View {
_buildingTeamView() _buildingTeamView()
.navigationBarBackButtonHidden(true) .navigationBarBackButtonHidden(true)
@ -198,12 +146,6 @@ struct AddTeamView: View {
tournament.unsortedPlayers() tournament.unsortedPlayers()
} }
private func _getTeams() {
Task {
await _setHash()
}
}
@ViewBuilder @ViewBuilder
private func _managementView() -> some View { private func _managementView() -> some View {
Section { Section {
@ -494,15 +436,6 @@ struct AddTeamView: View {
_managementView() _managementView()
} }
} }
.onAppear {
_getTeams()
}
.onDisappear {
Task {
await _handleHashDiff()
}
}
.headerProminence(.increased) .headerProminence(.increased)
.onReceive(fetchPlayers.publisher.count()) { _ in // <-- here .onReceive(fetchPlayers.publisher.count()) { _ in // <-- here
if let pasteString, count == 2, autoSelect == true { if let pasteString, count == 2, autoSelect == true {

@ -83,50 +83,60 @@ struct BroadcastView: View {
} }
Section { Section {
let links : [PageLink] = [.teams, .summons, .groupStages, .matches, .rankings, .broadcast, .clubBroadcast] TipView(tournamentTVBroadcastTip)
Picker(selection: $pageLink) { .tipStyle(tint: nil)
ForEach(links) { pageLink in }
Text(pageLink.localizedLabel()).tag(pageLink)
if let url = tournament.shareURL(.clubBroadcast) {
Section {
Link(destination: url) {
Text(url.absoluteString)
}
.contextMenu {
Button("Copier") {
let pasteboard = UIPasteboard.general
pasteboard.string = url.absoluteString
}
} }
} label: { } header: {
Text("Choisir la page à partager") Text("Lien pour la diffusion TV")
} footer: {
Text("Lien à mettre sur une smart tv ou ordinateur dans le club house par exemple ! Disponible même si le tournoi est privée.")
} }
.pickerStyle(.menu)
actionForURL(title: "Partager la page '" + pageLink.localizedLabel() + "'", url: tournament.shareURL(pageLink))
} header: {
Text("Lien du tournoi à partager")
} }
Section { Section {
let club = tournament.club() Toggle(isOn: $tournament.isPrivate) {
actionForURL(title: (club == nil) ? "Aucun club indiqué pour ce tournoi" : club!.clubTitle(), description: "Page du club", url: club?.shareURL()) Text("Tournoi privé")
actionForURL(title: "Padel Club", url: URLs.main.url) }
} header: { } footer: {
Text("Autres liens") let footerString = "Le tournoi sera masqué sur le site [Padel Club](\(URLs.main.rawValue))"
Text(.init(footerString))
} }
if tournament.isPrivate == false { if tournament.isPrivate == false {
Section {
TipView(tournamentTVBroadcastTip)
.tipStyle(tint: nil)
}
if let url = tournament.shareURL(.clubBroadcast) { Section {
Section { let links : [PageLink] = [.teams, .summons, .groupStages, .matches, .rankings, .broadcast, .clubBroadcast]
Link(destination: url) { Picker(selection: $pageLink) {
Text(url.absoluteString) ForEach(links) { pageLink in
} Text(pageLink.localizedLabel()).tag(pageLink)
.contextMenu {
Button("Copier") {
let pasteboard = UIPasteboard.general
pasteboard.string = url.absoluteString
}
} }
} header: { } label: {
Text("Lien pour la diffusion TV") Text("Choisir la page à partager")
} footer: {
Text("Lien à mettre sur une smart tv ou ordinateur dans le club house par exemple !")
} }
.pickerStyle(.menu)
actionForURL(title: "Partager la page '" + pageLink.localizedLabel() + "'", url: tournament.shareURL(pageLink))
} header: {
Text("Lien du tournoi à partager")
}
Section {
let club = tournament.club()
actionForURL(title: (club == nil) ? "Aucun club indiqué pour ce tournoi" : club!.clubTitle(), description: "Page du club", url: club?.shareURL())
actionForURL(title: "Padel Club", url: URLs.main.url)
} header: {
Text("Autres liens")
} }
@ -286,15 +296,6 @@ struct BroadcastView: View {
} }
//todo waitinglist & info //todo waitinglist & info
Section {
Toggle(isOn: $tournament.isPrivate) {
Text("Tournoi privé")
}
} footer: {
let footerString = "Le tournoi sera masqué sur le site [Padel Club](\(URLs.main.rawValue))"
Text(.init(footerString))
}
} }
} }
.headerProminence(.increased) .headerProminence(.increased)

@ -9,15 +9,15 @@ import SwiftUI
import TipKit import TipKit
import LeStorage import LeStorage
let slideToDeleteTip = SlideToDeleteTip() //let slideToDeleteTip = SlideToDeleteTip()
let inscriptionManagerWomanRankTip = InscriptionManagerWomanRankTip() let inscriptionManagerWomanRankTip = InscriptionManagerWomanRankTip()
let fileTip = InscriptionManagerFileInputTip() //let fileTip = InscriptionManagerFileInputTip()
let pasteTip = InscriptionManagerPasteInputTip() //let pasteTip = InscriptionManagerPasteInputTip()
let searchTip = InscriptionManagerSearchInputTip() //let searchTip = InscriptionManagerSearchInputTip()
let createTip = InscriptionManagerCreateInputTip() //let createTip = InscriptionManagerCreateInputTip()
let rankUpdateTip = InscriptionManagerRankUpdateTip() let rankUpdateTip = InscriptionManagerRankUpdateTip()
let padelBeachExportTip = PadelBeachExportTip() //let padelBeachExportTip = PadelBeachExportTip()
let padelBeachImportTip = PadelBeachImportTip() //let padelBeachImportTip = PadelBeachImportTip()
struct InscriptionManagerView: View { struct InscriptionManagerView: View {
@ -26,11 +26,6 @@ struct InscriptionManagerView: View {
@EnvironmentObject var networkMonitor: NetworkMonitor @EnvironmentObject var networkMonitor: NetworkMonitor
@Environment(\.dismiss) var dismiss @Environment(\.dismiss) var dismiss
@FetchRequest(
sortDescriptors: [],
animation: .default)
private var fetchPlayers: FetchedResults<ImportedPlayer>
var tournament: Tournament var tournament: Tournament
var cancelShouldDismiss: Bool = false var cancelShouldDismiss: Bool = false
@ -52,14 +47,11 @@ struct InscriptionManagerView: View {
@State private var contactType: ContactType? = nil @State private var contactType: ContactType? = nil
@State private var sentError: ContactManagerError? = nil @State private var sentError: ContactManagerError? = nil
@State private var showSubscriptionView: Bool = false @State private var showSubscriptionView: Bool = false
@State private var registrationIssues: Int? = nil
@State private var sortedTeams: [TeamRegistration] = []
@State private var walkoutTeams: [TeamRegistration] = []
@State private var unsortedTeamsWithoutWO: [TeamRegistration] = []
@State private var unsortedPlayers: [PlayerRegistration] = []
@State private var teamPaste: URL?
@State private var confirmDuplicate: Bool = false @State private var confirmDuplicate: Bool = false
@State private var presentAddTeamView: Bool = false @State private var presentAddTeamView: Bool = false
@State private var compactMode: Bool = false
@State private var pasteString: String?
var tournamentStore: TournamentStore { var tournamentStore: TournamentStore {
return self.tournament.tournamentStore return self.tournament.tournamentStore
} }
@ -95,15 +87,21 @@ struct InscriptionManagerView: View {
case all case all
case walkOut case walkOut
case waiting case waiting
case bracket
case groupStage
func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String {
switch self { switch self {
case .all: case .all:
return displayStyle == .wide ? "Équipes inscrites / souhaitées" : "Équipes inscrites" return displayStyle == .wide ? "Équipes inscrites / souhaitées" : "Paires inscrites"
case .bracket:
return displayStyle == .wide ? "En Tableau" : "Tableau"
case .groupStage:
return displayStyle == .wide ? "En Poule" : "Poule"
case .walkOut: case .walkOut:
return "Forfaits" return displayStyle == .wide ? "Forfaits" : "Forfait"
case .waiting: case .waiting:
return "Liste d'attente" return displayStyle == .wide ? "Liste d'attente" : "Attente"
} }
} }
} }
@ -113,15 +111,6 @@ struct InscriptionManagerView: View {
_currentRankSourceDate = State(wrappedValue: tournament.rankSourceDate) _currentRankSourceDate = State(wrappedValue: tournament.rankSourceDate)
} }
private func _clearScreen() {
teamPaste = nil
unsortedPlayers.removeAll()
walkoutTeams.removeAll()
unsortedTeamsWithoutWO.removeAll()
sortedTeams.removeAll()
registrationIssues = nil
}
// Function to create a simple hash from a list of IDs // Function to create a simple hash from a list of IDs
private func _simpleHash(ids: [String]) -> Int { private func _simpleHash(ids: [String]) -> Int {
// Combine the hash values of each string // Combine the hash values of each string
@ -133,7 +122,7 @@ struct InscriptionManagerView: View {
return _simpleHash(ids: ids1) != _simpleHash(ids: ids2) return _simpleHash(ids: ids1) != _simpleHash(ids: ids2)
} }
private func _setHash() async { private func _setHash() {
#if DEBUG_TIME //DEBUGING TIME #if DEBUG_TIME //DEBUGING TIME
let start = Date() let start = Date()
defer { defer {
@ -147,7 +136,7 @@ struct InscriptionManagerView: View {
} }
} }
private func _handleHashDiff() async { private func _handleHashDiff() {
let selectedSortedTeams = tournament.selectedSortedTeams() let selectedSortedTeams = tournament.selectedSortedTeams()
let newHash = _simpleHash(ids: selectedSortedTeams.map { $0.id }) let newHash = _simpleHash(ids: selectedSortedTeams.map { $0.id })
if (self.teamsHash != nil && newHash != teamsHash!) || (self.teamsHash == nil && selectedSortedTeams.isEmpty == false) { if (self.teamsHash != nil && newHash != teamsHash!) || (self.teamsHash == nil && selectedSortedTeams.isEmpty == false) {
@ -200,13 +189,10 @@ struct InscriptionManagerView: View {
} }
} }
.onAppear { .onAppear {
_getTeams() _setHash()
} }
.onDisappear { .onDisappear {
Task { _handleHashDiff()
await _handleHashDiff()
}
} }
.alert("Un problème est survenu", isPresented: messageSentFailed) { .alert("Un problème est survenu", isPresented: messageSentFailed) {
Button("OK") { Button("OK") {
@ -268,7 +254,7 @@ struct InscriptionManagerView: View {
.tint(.master) .tint(.master)
} }
.sheet(isPresented: $presentImportView, onDismiss: { .sheet(isPresented: $presentImportView, onDismiss: {
_getTeams() _setHash()
}) { }) {
NavigationStack { NavigationStack {
FileImportView() FileImportView()
@ -276,14 +262,12 @@ struct InscriptionManagerView: View {
.tint(.master) .tint(.master)
} }
.onChange(of: tournament.prioritizeClubMembers) { .onChange(of: tournament.prioritizeClubMembers) {
_clearScreen()
_save() _save()
_getTeams() _setHash()
} }
.onChange(of: tournament.teamSorting) { .onChange(of: tournament.teamSorting) {
_clearScreen()
_save() _save()
_getTeams() _setHash()
} }
.onChange(of: currentRankSourceDate) { .onChange(of: currentRankSourceDate) {
if let currentRankSourceDate, tournament.rankSourceDate != currentRankSourceDate { if let currentRankSourceDate, tournament.rankSourceDate != currentRankSourceDate {
@ -297,26 +281,36 @@ struct InscriptionManagerView: View {
.tint(.master) .tint(.master)
} }
.sheet(isPresented: $presentAddTeamView, onDismiss: { .sheet(isPresented: $presentAddTeamView, onDismiss: {
editedTeam = nil _setHash()
_getTeams()
}) { }) {
NavigationStack { NavigationStack {
AddTeamView(tournament: tournament, editedTeam: editedTeam) AddTeamView(tournament: tournament)
} }
.tint(.master) .tint(.master)
} }
.onChange(of: filterMode) { .sheet(item: $editedTeam, onDismiss: {
_prepareTeams() _setHash()
} }) { editedTeam in
.onChange(of: sortingMode) { NavigationStack {
_prepareTeams() AddTeamView(tournament: tournament, editedTeam: editedTeam)
}
.tint(.master)
} }
.onChange(of: byDecreasingOrdering) { .sheet(item: $pasteString, onDismiss: {
_prepareTeams() _setHash()
}) { pasteString in
NavigationStack {
AddTeamView(tournament: tournament, pasteString: pasteString)
}
.tint(.master)
} }
.toolbar { .toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) { ToolbarItemGroup(placement: .navigationBarTrailing) {
Menu { Menu {
Toggle(isOn: $compactMode) {
Text("Vue compact")
}
Divider()
Picker(selection: $filterMode) { Picker(selection: $filterMode) {
ForEach(FilterMode.allCases) { ForEach(FilterMode.allCases) {
Text($0.localizedLabel(.short)).tag($0) Text($0.localizedLabel(.short)).tag($0)
@ -337,6 +331,7 @@ struct InscriptionManagerView: View {
} }
} label: { } label: {
LabelFilter() LabelFilter()
.symbolVariant(filterMode == .all ? .none : .fill)
} }
Menu { Menu {
if tournament.inscriptionClosed() == false { if tournament.inscriptionClosed() == false {
@ -372,7 +367,7 @@ struct InscriptionManagerView: View {
} }
} else { } else {
Button { Button {
tournament.closedRegistrationDate = nil tournament.unlockRegistration()
_save() _save()
} label: { } label: {
Label("Ré-ouvrir", systemImage: "lock.open") Label("Ré-ouvrir", systemImage: "lock.open")
@ -392,41 +387,42 @@ struct InscriptionManagerView: View {
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
} }
private func _prepareStats() async { var walkoutTeams: [TeamRegistration] {
#if DEBUG_TIME //DEBUGING TIME tournament.walkoutTeams()
let start = Date() }
defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
print("func _prepareStats", duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
#endif
unsortedPlayers = tournament.unsortedPlayers() var unsortedTeamsWithoutWO: [TeamRegistration] {
walkoutTeams = tournament.walkoutTeams() tournament.unsortedTeamsWithoutWO()
unsortedTeamsWithoutWO = tournament.unsortedTeamsWithoutWO()
teamPaste = tournament.pasteDataForImporting().createTxtFile(self.tournament.tournamentTitle(.short))
} }
private func _prepareTeams() { var teamPaste: URL? {
#if DEBUG_TIME //DEBUGING TIME tournament.pasteDataForImporting().createTxtFile(self.tournament.tournamentTitle(.short))
let start = Date() }
defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) var unsortedPlayers: [PlayerRegistration] {
print("func _prepareTeams", duration.formatted(.units(allowed: [.seconds, .milliseconds]))) tournament.unsortedPlayers()
} }
#endif
var sortedTeams: [TeamRegistration] {
if filterMode == .waiting { if filterMode == .waiting {
sortedTeams = tournament.waitingListSortedTeams() return tournament.waitingListSortedTeams()
} else { } else {
sortedTeams = tournament.sortedTeams() return tournament.sortedTeams()
} }
} }
var filteredTeams: [TeamRegistration] { var filteredTeams: [TeamRegistration] {
var teams = sortedTeams var teams = sortedTeams
if filterMode == .walkOut { switch filterMode {
case .walkOut:
teams = teams.filter({ $0.walkOut }) teams = teams.filter({ $0.walkOut })
case .bracket:
teams = teams.filter({ $0.inRound() })
case .groupStage:
teams = teams.filter({ $0.inGroupStage() })
default:
break
} }
if sortingMode == .registrationDate { if sortingMode == .registrationDate {
@ -440,53 +436,14 @@ struct InscriptionManagerView: View {
} }
} }
private func _getIssues() async {
#if DEBUG_TIME //DEBUGING TIME
let start = Date()
defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
print("func _getIssues", duration.formatted(.units(allowed: [.seconds, .milliseconds])))
}
#endif
await registrationIssues = tournament.registrationIssues()
}
private func _getTeams() {
_prepareTeams()
Task {
await _prepareStats()
await _getIssues()
await _setHash()
}
}
private func _teamRegisteredView() -> some View { private func _teamRegisteredView() -> some View {
List { List {
_informationView()
let selectedSortedTeams = tournament.selectedSortedTeams() let selectedSortedTeams = tournament.selectedSortedTeams()
if let closedRegistrationDate = tournament.closedRegistrationDate {
Section {
CloseDatePicker(closedRegistrationDate: closedRegistrationDate)
} footer: {
Text("Toutes les équipes ayant été inscrites après la date de clôture seront en liste d'attente.")
}
if selectedSortedTeams.isEmpty {
Section {
ContentUnavailableView("Aucune équipe", systemImage: "person.2.slash", description: Text("Vous n'avez aucune équipe inscrite avant la date de clôture."))
}
}
}
if presentSearch == false { if presentSearch == false {
_informationView()
_rankHandlerView() _rankHandlerView()
_relatedTips() _relatedTips()
Section {
RowButtonView("Compléter la liste") {
presentAddTeamView = true
}
}
} }
let teams = searchField.isEmpty ? filteredTeams : filteredTeams.filter({ $0.contains(searchField.canonicalVersion) }) let teams = searchField.isEmpty ? filteredTeams : filteredTeams.filter({ $0.contains(searchField.canonicalVersion) })
@ -503,13 +460,7 @@ struct InscriptionManagerView: View {
} }
RowButtonView("Créer une équipe") { RowButtonView("Créer une équipe") {
// Task { pasteString = searchField
// await MainActor.run {
// fetchPlayers.nsPredicate = Self._pastePredicate(pasteField: searchField, mostRecentDate: SourceFileManager.shared.mostRecentDateAvailable, filterOption: _filterOption())
// fetchPlayers.nsSortDescriptors = [NSSortDescriptor(keyPath: \ImportedPlayer.rank, ascending: true)]
// pasteString = searchField
// }
// }
} }
RowButtonView("D'accord") { RowButtonView("D'accord") {
@ -518,16 +469,40 @@ struct InscriptionManagerView: View {
} }
} }
} }
ForEach(teams) { team in if compactMode {
let teamIndex = team.index(in: sortedTeams)
Section { Section {
TeamDetailView(team: team) ForEach(teams) { team in
let teamIndex = team.index(in: sortedTeams)
NavigationLink {
_teamCompactTeamEditionView(team)
.environment(tournament)
} label: {
TeamRowView(team: team)
}
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
_teamDeleteButtonView(team)
}
}
} header: { } header: {
TeamHeaderView(team: team, teamIndex: filterMode == .waiting ? nil : teamIndex, tournament: tournament, teamCount: filterMode == .waiting ? 0 : selectedSortedTeams.count) LabeledContent {
} footer: { Text(teams.count.formatted())
_teamFooterView(team) } label: {
Text("Équipe\(teams.count.pluralSuffix)")
}
} }
.headerProminence(.increased) .headerProminence(.increased)
} else {
ForEach(teams) { team in
let teamIndex = team.index(in: sortedTeams)
Section {
TeamDetailView(team: team)
} header: {
TeamHeaderView(team: team, teamIndex: filterMode == .waiting ? nil : teamIndex, tournament: tournament, teamCount: filterMode == .waiting ? 0 : selectedSortedTeams.count)
} footer: {
_teamFooterView(team)
}
.headerProminence(.increased)
}
} }
} }
.searchable(text: $searchField, isPresented: $presentSearch, prompt: Text("Chercher parmi les équipes inscrites")) .searchable(text: $searchField, isPresented: $presentSearch, prompt: Text("Chercher parmi les équipes inscrites"))
@ -535,6 +510,112 @@ struct InscriptionManagerView: View {
.autocorrectionDisabled() .autocorrectionDisabled()
} }
func _teamCompactTeamEditionView(_ team: TeamRegistration) -> some View {
List {
Section {
TeamDetailView(team: team)
} footer: {
FooterButtonView("Copier dans le presse-papier") {
let pasteboard = UIPasteboard.general
pasteboard.string = team.playersPasteData()
}
}
Section {
NavigationLink {
EditingTeamView(team: team)
.environment(tournament)
} label: {
Text("Éditer une donnée de l'équipe")
}
NavigationLink {
GroupStageTeamReplacementView(team: team)
.environment(tournament)
} label: {
Text("Chercher à remplacer")
}
RowButtonView("Modifier la composition de l'équipe") {
editedTeam = team
}
}
Section {
Toggle(isOn: .init(get: {
return team.wildCardBracket
}, set: { value in
team.resetPositions()
team.wildCardGroupStage = false
team.walkOut = false
team.wildCardBracket = value
do {
try tournamentStore.teamRegistrations.addOrUpdate(instance: team)
} catch {
Logger.error(error)
}
_setHash()
})) {
Text("Wildcard Tableau")
}
Toggle(isOn: .init(get: {
return team.wildCardGroupStage
}, set: { value in
team.resetPositions()
team.wildCardBracket = false
team.walkOut = false
team.wildCardGroupStage = value
do {
try tournamentStore.teamRegistrations.addOrUpdate(instance: team)
} catch {
Logger.error(error)
}
_setHash()
})) {
Text("Wildcard Poule")
}
}
Section {
Toggle(isOn: .init(get: {
return team.walkOut
}, set: { value in
team.resetPositions()
team.wildCardBracket = false
team.wildCardGroupStage = false
team.walkOut = value
do {
try tournamentStore.teamRegistrations.addOrUpdate(instance: team)
} catch {
Logger.error(error)
}
_setHash()
})) {
Text("Forfait")
}
RowButtonView("Effacer l'équipe", role: .destructive, systemImage: "trash") {
team.deleteTeamScores()
do {
try tournamentStore.teamRegistrations.delete(instance: team)
} catch {
Logger.error(error)
}
_setHash()
}
}
}
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
MenuWarningView(tournament: tournament, teams: [team], contactType: $contactType)
}
}
.toolbarBackground(.visible, for: .navigationBar)
.navigationTitle("Édition de l'équipe")
.navigationBarTitleDisplayMode(.inline)
}
@ViewBuilder @ViewBuilder
func rankingDateSourcePickerView(showDateInLabel: Bool) -> some View { func rankingDateSourcePickerView(showDateInLabel: Bool) -> some View {
Section { Section {
@ -597,6 +678,10 @@ struct InscriptionManagerView: View {
switch filterMode { switch filterMode {
case .all: case .all:
return unsortedTeamsWithoutWO.count.formatted() + " / " + tournament.teamCount.formatted() return unsortedTeamsWithoutWO.count.formatted() + " / " + tournament.teamCount.formatted()
case .bracket:
return tournament.selectedSortedTeams().filter({ $0.inRound() }).count.formatted()
case .groupStage:
return tournament.selectedSortedTeams().filter({ $0.inGroupStage() }).count.formatted()
case .walkOut: case .walkOut:
let wo = walkoutTeams.count.formatted() let wo = walkoutTeams.count.formatted()
return wo return wo
@ -609,30 +694,104 @@ struct InscriptionManagerView: View {
@ViewBuilder @ViewBuilder
private func _informationView() -> some View { private func _informationView() -> some View {
Section { Section {
ForEach(FilterMode.allCases) { filterMode in HStack {
LabeledContent { VStack(alignment: .leading, spacing: 0) {
Text(_teamCountForFilterMode(filterMode: filterMode)) Text("Inscriptions").font(.caption)
Text(unsortedTeamsWithoutWO.count.formatted()).font(.largeTitle)
}
.frame(maxWidth: .infinity)
.contentShape(Rectangle())
.onTapGesture {
self.filterMode = .all
}
VStack(alignment: .leading, spacing: 0) {
Text("Paires souhaitées").font(.caption)
Text(tournament.teamCount.formatted()).font(.largeTitle)
}
.frame(maxWidth: .infinity)
Button {
presentAddTeamView = true
} label: { } label: {
Text(filterMode.localizedLabel()) Label {
Text("Ajouter une équipe")
} icon: {
Image(systemName: "person.2.fill")
.resizable()
.scaledToFit()
.frame(height:44)
}
.labelStyle(.iconOnly)
.overlay(alignment: .bottomTrailing) {
Image(systemName: "plus.circle.fill")
.foregroundColor(.master)
.background (
Color(.systemBackground)
.clipShape(.circle)
)
}
}
.buttonBorderShape(.roundedRectangle)
.buttonStyle(.borderedProminent)
.frame(maxWidth: .infinity)
}
.fixedSize(horizontal: false, vertical: false)
.padding(.horizontal, -8)
HStack {
ForEach([FilterMode.waiting, FilterMode.walkOut, FilterMode.groupStage, FilterMode.bracket]) { filterMode in
Button {
if self.filterMode == filterMode {
self.filterMode = .all
} else {
self.filterMode = filterMode
}
} label: {
VStack(alignment: .leading, spacing: 0) {
Text(filterMode.localizedLabel(.short)).font(.caption)
Text(_teamCountForFilterMode(filterMode: filterMode)).font(.largeTitle)
}
.frame(maxWidth: .infinity)
.contentShape(Rectangle())
}
.buttonBorderShape(.roundedRectangle)
.buttonStyle(.borderedProminent)
.tint(self.filterMode == filterMode ? .master : .beige)
} }
} }
.foregroundStyle(.primary)
.fixedSize(horizontal: false, vertical: false)
NavigationLink { NavigationLink {
InscriptionInfoView() InscriptionInfoView()
.environment(tournament) .environment(tournament)
} label: { } label: {
LabeledContent { LabeledContent {
if let registrationIssues { Text(tournament.registrationIssues().formatted())
Text(registrationIssues.formatted())
} else {
ProgressView()
}
} label: { } label: {
Text("Problèmes détéctés") Text("Problèmes détéctés")
} }
} }
if let closedRegistrationDate = tournament.closedRegistrationDate {
CloseDatePicker(closedRegistrationDate: closedRegistrationDate)
}
} header: { } header: {
Text("Statut des inscriptions") HStack {
Spacer()
FooterButtonView(compactMode ? "passer en affichage détaillée" : "passer en affichage compact") {
compactMode.toggle()
}
Spacer()
}
.textCase(nil)
} footer: {
if tournament.closedRegistrationDate != nil {
Text("Toutes les équipes ayant été inscrites après la date de clôture seront en liste d'attente.")
}
} }
} }
// //
@ -821,119 +980,110 @@ struct InscriptionManagerView: View {
Text(formattedRegistrationDate) Text(formattedRegistrationDate)
} }
Spacer() Spacer()
_teamMenuOptionView(team) Menu {
_teamMenuOptionView(team)
} label: {
LabelOptions().labelStyle(.titleOnly)
}
} }
} }
private func _teamMenuOptionView(_ team: TeamRegistration) -> some View {
Menu {
Section {
NavigationLink {
GroupStageTeamReplacementView(team: team)
} label: {
Text("Chercher à remplacer")
}
MenuWarningView(tournament: tournament, teams: [team], contactType: $contactType) @ViewBuilder
//Divider() private func _teamMenuOptionView(_ team: TeamRegistration) -> some View {
Button("Copier") { Section {
let pasteboard = UIPasteboard.general NavigationLink {
pasteboard.string = team.playersPasteData() GroupStageTeamReplacementView(team: team)
} } label: {
//Divider() Text("Chercher à remplacer")
Button("Changer les joueurs") { }
editedTeam = team
presentAddTeamView = true
}
Divider()
NavigationLink {
EditingTeamView(team: team)
.environment(tournament)
} label: {
Text("Éditer une donnée de l'équipe")
}
Divider()
Toggle(isOn: .init(get: {
return team.wildCardBracket
}, set: { value in
_clearScreen()
Task { MenuWarningView(tournament: tournament, teams: [team], contactType: $contactType)
team.resetPositions() //Divider()
team.wildCardGroupStage = false Button("Copier") {
team.walkOut = false let pasteboard = UIPasteboard.general
team.wildCardBracket = value pasteboard.string = team.playersPasteData()
do { }
try tournamentStore.teamRegistrations.addOrUpdate(instance: team) //Divider()
} catch { Button("Changer les joueurs") {
Logger.error(error) editedTeam = team
} }
_getTeams() Divider()
} NavigationLink {
})) { EditingTeamView(team: team)
Label("Wildcard Tableau", systemImage: team.wildCardBracket ? "circle.inset.filled" : "circle") .environment(tournament)
} label: {
Text("Éditer une donnée de l'équipe")
}
Divider()
Toggle(isOn: .init(get: {
return team.wildCardBracket
}, set: { value in
team.resetPositions()
team.wildCardGroupStage = false
team.walkOut = false
team.wildCardBracket = value
do {
try tournamentStore.teamRegistrations.addOrUpdate(instance: team)
} catch {
Logger.error(error)
} }
_setHash()
})) {
Label("Wildcard Tableau", systemImage: team.wildCardBracket ? "circle.inset.filled" : "circle")
}
Toggle(isOn: .init(get: { Toggle(isOn: .init(get: {
return team.wildCardGroupStage return team.wildCardGroupStage
}, set: { value in }, set: { value in
_clearScreen() team.resetPositions()
team.wildCardBracket = false
Task { team.walkOut = false
team.resetPositions() team.wildCardGroupStage = value
team.wildCardBracket = false do {
team.walkOut = false try tournamentStore.teamRegistrations.addOrUpdate(instance: team)
team.wildCardGroupStage = value } catch {
do { Logger.error(error)
try tournamentStore.teamRegistrations.addOrUpdate(instance: team)
} catch {
Logger.error(error)
}
_getTeams()
}
})) {
Label("Wildcard Poule", systemImage: team.wildCardGroupStage ? "circle.inset.filled" : "circle")
} }
_setHash()
})) {
Label("Wildcard Poule", systemImage: team.wildCardGroupStage ? "circle.inset.filled" : "circle")
}
Divider() Divider()
Toggle(isOn: .init(get: { Toggle(isOn: .init(get: {
return team.walkOut return team.walkOut
}, set: { value in }, set: { value in
_clearScreen() team.resetPositions()
Task { team.wildCardBracket = false
team.resetPositions() team.wildCardGroupStage = false
team.wildCardBracket = false team.walkOut = value
team.wildCardGroupStage = false do {
team.walkOut = value try tournamentStore.teamRegistrations.addOrUpdate(instance: team)
do { } catch {
try tournamentStore.teamRegistrations.addOrUpdate(instance: team) Logger.error(error)
} catch {
Logger.error(error)
}
_getTeams()
}
})) {
Label("WO", systemImage: team.walkOut ? "circle.inset.filled" : "circle")
}
Divider()
Button(role: .destructive) {
_clearScreen()
Task {
do {
try tournamentStore.teamRegistrations.delete(instance: team)
} catch {
Logger.error(error)
}
_getTeams()
}
} label: {
LabelDelete()
} }
_setHash()
})) {
Label("WO", systemImage: team.walkOut ? "circle.inset.filled" : "circle")
}
Divider()
_teamDeleteButtonView(team)
// } header: { // } header: {
// Text(team.teamLabel(.short)) // Text(team.teamLabel(.short))
}
}
private func _teamDeleteButtonView(_ team: TeamRegistration) -> some View {
Button(role: .destructive) {
team.deleteTeamScores()
do {
try tournamentStore.teamRegistrations.delete(instance: team)
} catch {
Logger.error(error)
} }
_setHash()
} label: { } label: {
LabelOptions().labelStyle(.titleOnly) LabelDelete()
} }
} }

@ -99,6 +99,7 @@ struct TournamentCellView: View {
.resizable() .resizable()
.scaledToFit() .scaledToFit()
.frame(height: 28) .frame(height: 28)
.accessibilityLabel("importer ou ouvrir")
.tint(existingTournament != nil ? Color.green : nil) .tint(existingTournament != nil ? Color.green : nil)
} }
} }

Loading…
Cancel
Save