sync
Laurent 8 months ago
commit 37c0ac62e9
  1. 4
      PadelClub.xcodeproj/project.pbxproj
  2. 6
      PadelClub/Data/Round.swift
  3. 53
      PadelClub/Data/TeamRegistration.swift
  4. 15
      PadelClub/Data/Tournament.swift
  5. 14
      PadelClub/Views/GroupStage/GroupStageView.swift
  6. 47
      PadelClub/Views/Match/Components/PlayerBlockView.swift
  7. 7
      PadelClub/Views/Team/Components/TeamWeightView.swift
  8. 15
      PadelClub/Views/Team/TeamPickerView.swift
  9. 25
      PadelClub/Views/Team/TeamRowView.swift
  10. 15
      PadelClub/Views/Tournament/Screen/AddTeamView.swift
  11. 15
      PadelClub/Views/Tournament/Screen/Components/TournamentStatusView.swift
  12. 38
      PadelClub/Views/Tournament/Screen/TableStructureView.swift

@ -3650,7 +3650,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.1.23; MARKETING_VERSION = 1.1.25;
PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@ -3695,7 +3695,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.1.23; MARKETING_VERSION = 1.1.25;
PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";

@ -644,9 +644,9 @@ defer {
let roundCount = RoundRule.numberOfRounds(forTeams: currentRoundMatchCount) let roundCount = RoundRule.numberOfRounds(forTeams: currentRoundMatchCount)
var loserBracketMatchFormat = tournamentObject()?.loserBracketMatchFormat var loserBracketMatchFormat = tournamentObject()?.loserBracketMatchFormat
if let parentRound { // if let parentRound {
loserBracketMatchFormat = tournamentObject()?.loserBracketSmartMatchFormat(parentRound.index) // loserBracketMatchFormat = tournamentObject()?.loserBracketSmartMatchFormat(parentRound.index)
} // }
let rounds = (0..<roundCount).map { //index 0 is the final let rounds = (0..<roundCount).map { //index 0 is the final
let round = Round(tournament: tournament, index: $0, matchFormat: loserBracketMatchFormat) let round = Round(tournament: tournament, index: $0, matchFormat: loserBracketMatchFormat)

@ -650,8 +650,7 @@ final class TeamRegistration: BaseTeamRegistration, SideStorable {
func wildcardLabel() -> String? { func wildcardLabel() -> String? {
if isWildCard() { if isWildCard() {
let wildcardLabel: String = ["wildcard", (wildCardBracket ? "tableau" : "poule")] let wildcardLabel: String = ["Wildcard", (wildCardBracket ? "Tableau" : "Poule")].joined(separator: " ")
.joined(separator: " ")
return wildcardLabel return wildcardLabel
} else { } else {
return nil return nil
@ -700,22 +699,48 @@ final class TeamRegistration: BaseTeamRegistration, SideStorable {
unsortedPlayers().count > 0 unsortedPlayers().count > 0
} }
func bracketMatchTitleAndQualifiedStatus() -> String? { func teamInitialPositionBracket() -> String? {
let v = groupStageObject()?.groupStageTitle() let round = initialMatch()?.roundAndMatchTitle()
var base = "Qualifié" if let round {
if qualified { return round
if let groupStageTitle = groupStageObject()?.groupStageTitle() { }
base = base + " (\(groupStageTitle))" return nil
}
func teamInitialPositionGroupStage() -> String? {
let groupStage = self.groupStageObject()
let group = groupStage?.groupStageTitle(.title)
var groupPositionLabel: String? = nil
if let finalPosition = groupStage?.finalPosition(ofTeam: self) {
groupPositionLabel = (finalPosition + 1).ordinalFormatted()
} else if let groupStagePosition {
groupPositionLabel = "\(groupStagePosition + 1)"
}
if let group {
if let groupPositionLabel {
return [group, "#\(groupPositionLabel)"].joined(separator: " ")
} else {
return group
} }
} }
return nil
}
let suffix = qualified ? nil : groupStageObject()?.groupStageTitle() func qualifiedStatus(hideBracketStatus: Bool = false) -> String? {
let initalMatchTitle = initialMatch()?.roundAndMatchTitle() ?? suffix let teamInitialPositionBracket = teamInitialPositionBracket()
let values = [qualified ? base : nil, initalMatchTitle].compactMap({ $0 }) let groupStageTitle = teamInitialPositionGroupStage()
if values.isEmpty {
return nil let base: String? = qualified ? "Qualifié" : nil
if let groupStageTitle, let teamInitialPositionBracket, hideBracketStatus == false {
return [base, groupStageTitle, ">", teamInitialPositionBracket].compactMap({ $0 }).joined(separator: " ")
} else if let groupStageTitle {
return [base, groupStageTitle].compactMap({ $0 }).joined(separator: " ")
} else if hideBracketStatus == false {
return teamInitialPositionBracket
} }
return values.joined(separator: " -> ")
return nil
} }
func insertOnServer() { func insertOnServer() {

@ -1612,7 +1612,7 @@ defer {
var _groupStages = [GroupStage]() var _groupStages = [GroupStage]()
for index in 0..<groupStageCount { for index in 0..<groupStageCount {
let groupStage = GroupStage(tournament: id, index: index, size: teamsPerGroupStage, format: groupStageSmartMatchFormat()) let groupStage = GroupStage(tournament: id, index: index, size: teamsPerGroupStage, format: groupStageFormat)
_groupStages.append(groupStage) _groupStages.append(groupStage)
} }
@ -1631,7 +1631,7 @@ defer {
let matchCount = RoundRule.numberOfMatches(forTeams: minimalBracketTeamCount ?? bracketTeamCount()) let matchCount = RoundRule.numberOfMatches(forTeams: minimalBracketTeamCount ?? bracketTeamCount())
let rounds = (0..<roundCount).map { //index 0 is the final let rounds = (0..<roundCount).map { //index 0 is the final
return Round(tournament: id, index: $0, matchFormat: roundSmartMatchFormat($0), loserBracketMode: loserBracketMode) return Round(tournament: id, index: $0, matchFormat: matchFormat, loserBracketMode: loserBracketMode)
} }
if rounds.isEmpty { if rounds.isEmpty {
@ -1726,10 +1726,19 @@ defer {
} }
} }
func removeWildCards() {
let wcs = unsortedTeams().filter({ $0.isWildCard() && $0.unsortedPlayers().isEmpty })
do {
try tournamentStore?.teamRegistrations.delete(contentOfs: wcs)
} catch {
Logger.error(error)
}
}
func setGroupStageTeams(randomize: Bool) { func setGroupStageTeams(randomize: Bool) {
let groupStages = groupStages() let groupStages = groupStages()
let max = groupStages.map { $0.size }.reduce(0,+) let max = groupStages.map { $0.size }.reduce(0,+)
var chunks = selectedSortedTeams().suffix(max).chunked(into: groupStageCount) var chunks = selectedSortedTeams().filter({ $0.wildCardBracket == false }).suffix(max).chunked(into: groupStageCount)
for (index, _) in chunks.enumerated() { for (index, _) in chunks.enumerated() {
if randomize { if randomize {
chunks[index].shuffle() chunks[index].shuffle()

@ -140,12 +140,16 @@ struct GroupStageView: View {
GroupStageTeamView(groupStage: groupStage, team: team) GroupStageTeamView(groupStage: groupStage, team: team)
.environment(self.tournament) .environment(self.tournament)
} label: { } label: {
let players = team.players()
VStack(alignment: .leading, spacing: 4.0) { VStack(alignment: .leading, spacing: 4.0) {
HStack(spacing: 6.0) { HStack(spacing: 6.0) {
Text("#\(groupStagePosition + 1)") Text("#\(groupStagePosition + 1)")
if groupStage.tournamentObject()?.hideWeight() == false { if players.isEmpty == false, groupStage.tournamentObject()?.hideWeight() == false {
Text("Poids \(team.weight)") Text("Poids \(team.weight)")
} }
if players.isEmpty == false, team.isWildCard() {
Text("wildcard").foregroundStyle(.logoRed).font(.caption).italic()
}
Spacer() Spacer()
if team.qualified { if team.qualified {
Text("qualifié") Text("qualifié")
@ -156,8 +160,14 @@ struct GroupStageView: View {
VStack(alignment: .leading) { VStack(alignment: .leading) {
if let teamName = team.name, teamName.isEmpty == false { if let teamName = team.name, teamName.isEmpty == false {
Text(teamName).foregroundStyle(.secondary).font(.footnote) Text(teamName).foregroundStyle(.secondary).font(.footnote)
} else if players.isEmpty {
if team.isWildCard() {
Text("Place réservée wildcard")
} else {
Text("Place réservée")
}
} }
ForEach(team.players()) { player in ForEach(players) { player in
Text(player.playerLabel()).lineLimit(1) Text(player.playerLabel()).lineLimit(1)
.overlay { .overlay {
if player.hasArrived && team.isHere() == false { if player.hasArrived && team.isHere() == false {

@ -74,29 +74,38 @@ struct PlayerBlockView: View {
var body: some View { var body: some View {
HStack { HStack {
VStack(alignment: .leading) { VStack(alignment: .leading) {
if let team { ZStack(alignment: .leading) {
if let teamScore, teamScore.luckyLoser != nil, match.isLoserBracket == false { VStack {
Text("Repêchée").italic().font(.caption) if let teamName = team?.name {
Text(teamName).foregroundStyle(.secondary).font(.footnote)
}
Text("longLabelPlayerOne").lineLimit(1)
Text("longLabelPlayerTwo").lineLimit(1)
} }
.opacity(0)
if let teamName = team.name { if let team {
Text(teamName).foregroundStyle(.secondary).font(.footnote) if let teamScore, teamScore.luckyLoser != nil, match.isLoserBracket == false {
} Text("Repêchée").italic().font(.caption)
ForEach(team.players()) { player in }
Text(player.playerLabel()).lineLimit(1)
.italic(player.isHere() == false) if let teamName = team.name {
.foregroundStyle(player.isHere() == false ? .secondary : .primary) Text(teamName).foregroundStyle(.secondary).font(.footnote)
} } else if team.players().isEmpty {
} else { if team.isWildCard() {
ZStack(alignment: .leading) { Text("Place réservée wildcard")
VStack { } else {
if let teamName = team?.name { Text("Place réservée")
Text(teamName).foregroundStyle(.secondary).font(.footnote) }
}
VStack(alignment: .leading) {
ForEach(team.players()) { player in
Text(player.playerLabel()).lineLimit(1)
.italic(player.isHere() == false)
.foregroundStyle(player.isHere() == false ? .secondary : .primary)
} }
Text("longLabelPlayerOne").lineLimit(1)
Text("longLabelPlayerTwo").lineLimit(1)
} }
.opacity(0) } else {
VStack(alignment: .leading) { VStack(alignment: .leading) {
ForEach(_defaultLabel(), id: \.self) { name in ForEach(_defaultLabel(), id: \.self) { name in
Text(name) Text(name)

@ -11,9 +11,14 @@ struct TeamWeightView: View {
@EnvironmentObject var dataStore: DataStore @EnvironmentObject var dataStore: DataStore
let team: TeamRegistration let team: TeamRegistration
var teamPosition: TeamPosition? = nil var teamPosition: TeamPosition? = nil
var teamIndex: Int? var teamIndex: Int?
init(team: TeamRegistration, teamPosition: TeamPosition? = nil, teamIndex: Int? = nil) {
self.team = team
self.teamPosition = teamPosition
self.teamIndex = teamIndex ?? team.tournamentObject()?.indexOf(team: team)
}
var displayWeight: Bool { var displayWeight: Bool {
team.shouldDisplayRankAndWeight() && team.tournamentObject()?.hideWeight() == false team.shouldDisplayRankAndWeight() && team.tournamentObject()?.hideWeight() == false
} }

@ -121,7 +121,9 @@ struct TeamPickerView: View {
} }
} }
@ViewBuilder
private func _teamListView(_ teams: [TeamRegistration]) -> some View { private func _teamListView(_ teams: [TeamRegistration]) -> some View {
let selectedSortedTeams = tournament.selectedSortedTeams()
ForEach(teams) { team in ForEach(teams) { team in
if searchField.isEmpty || team.contains(searchField) { if searchField.isEmpty || team.contains(searchField) {
Button { Button {
@ -134,15 +136,10 @@ struct TeamPickerView: View {
// presentTeamPickerView = false // presentTeamPickerView = false
// } // }
} label: { } label: {
VStack(alignment: .leading) { let teamIndex = team.index(in: selectedSortedTeams)
if let roundAndMatchTitle = team.bracketMatchTitleAndQualifiedStatus() { TeamRowView(team: team, teamIndex: teamIndex)
Text(roundAndMatchTitle) .environment(\.isEditingTournamentSeed, .constant(false))
.font(.headline) .contentShape(Rectangle())
.frame(maxWidth: .infinity, alignment: .leading)
}
TeamRowView(team: team)
}
.contentShape(Rectangle())
} }
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.buttonStyle(.plain) .buttonStyle(.plain)

@ -9,7 +9,6 @@ import SwiftUI
struct TeamRowView: View { struct TeamRowView: View {
@EnvironmentObject var dataStore: DataStore @EnvironmentObject var dataStore: DataStore
@Environment(\.isEditingTournamentSeed) private var isEditingTournamentSeed
var team: TeamRegistration var team: TeamRegistration
var teamPosition: TeamPosition? = nil var teamPosition: TeamPosition? = nil
@ -22,9 +21,7 @@ struct TeamRowView: View {
TeamWeightView(team: team, teamPosition: teamPosition, teamIndex: teamIndex) TeamWeightView(team: team, teamPosition: teamPosition, teamIndex: teamIndex)
} label: { } label: {
VStack(alignment: .leading) { VStack(alignment: .leading) {
if isEditingTournamentSeed.wrappedValue == false { TeamHeadlineView(team: team)
TeamHeadlineView(team: team)
}
TeamView(team: team) TeamView(team: team)
} }
if displayCallDate { if displayCallDate {
@ -78,23 +75,19 @@ struct TeamRowView: View {
} }
struct TeamHeadlineView: View { struct TeamHeadlineView: View {
@Environment(\.isEditingTournamentSeed) private var isEditingTournamentSeed
let team: TeamRegistration let team: TeamRegistration
var body: some View { var body: some View {
HStack { VStack(alignment: .leading) {
if let groupStage = team.groupStageObject() { if let wildcardLabel = team.wildcardLabel() {
HStack { Text(wildcardLabel).italic().foregroundStyle(.logoRed).font(.caption)
Text(groupStage.groupStageTitle(.title))
if let finalPosition = groupStage.finalPosition(ofTeam: team) {
Text((finalPosition + 1).ordinalFormatted())
}
}
} else if let round = team.initialRound() {
Text(round.roundTitle(.wide))
} }
if let wildcardLabel = team.wildcardLabel() { if let qualifiedStatus = team.qualifiedStatus(hideBracketStatus: isEditingTournamentSeed.wrappedValue == true) {
Text(wildcardLabel).italic().foregroundStyle(.red).font(.caption) Text(qualifiedStatus)
.font(.caption)
.foregroundStyle(.secondary)
} }
} }
} }

@ -216,6 +216,7 @@ struct AddTeamView: View {
let first = strings.first ?? "" let first = strings.first ?? ""
handlePasteString(first) handlePasteString(first)
} }
.disabled(_limitPlayerCount())
.foregroundStyle(.master) .foregroundStyle(.master)
.labelStyle(.titleAndIcon) .labelStyle(.titleAndIcon)
.buttonBorderShape(.capsule) .buttonBorderShape(.capsule)
@ -236,6 +237,7 @@ struct AddTeamView: View {
} label: { } label: {
Label("Coller", systemImage: "doc.on.clipboard").labelStyle(.iconOnly) Label("Coller", systemImage: "doc.on.clipboard").labelStyle(.iconOnly)
} }
.disabled(_limitPlayerCount())
.foregroundStyle(.master) .foregroundStyle(.master)
.labelStyle(.iconOnly) .labelStyle(.iconOnly)
.buttonBorderShape(.capsule) .buttonBorderShape(.capsule)
@ -258,12 +260,20 @@ struct AddTeamView: View {
tournament.unsortedPlayers() tournament.unsortedPlayers()
} }
private func _limitPlayerCount() -> Bool {
if tournament.isAnimation() {
return false
}
return _currentSelection().count >= tournament.significantPlayerCount()
}
@ViewBuilder @ViewBuilder
private func _managementView() -> some View { private func _managementView() -> some View {
Section { Section {
RowButtonView("Ajouter via la base fédérale") { RowButtonView("Ajouter via la base fédérale") {
presentPlayerSearch = true presentPlayerSearch = true
} }
.disabled(_limitPlayerCount())
} footer: { } footer: {
if let rankSourceDate = tournament.rankSourceDate { if let rankSourceDate = tournament.rankSourceDate {
Text("Cherchez dans la base fédérale de \(rankSourceDate.monthYearFormatted), vous y trouverez tous les joueurs ayant participé à au moins un tournoi dans les 12 derniers mois.") Text("Cherchez dans la base fédérale de \(rankSourceDate.monthYearFormatted), vous y trouverez tous les joueurs ayant participé à au moins un tournoi dans les 12 derniers mois.")
@ -288,6 +298,7 @@ struct AddTeamView: View {
presentPlayerCreation = true presentPlayerCreation = true
} }
} }
.disabled(_limitPlayerCount())
} footer: { } footer: {
Text("Si le joueur n'a pas encore de licence ou n'a pas encore participé à une compétition, vous pouvez le créer vous-même.") Text("Si le joueur n'a pas encore de licence ou n'a pas encore participé à une compétition, vous pouvez le créer vous-même.")
} }
@ -541,6 +552,10 @@ struct AddTeamView: View {
// } else { // } else {
// Text("Préparation de l'équipe") // Text("Préparation de l'équipe")
} }
} footer: {
if _limitPlayerCount() {
Text("Taille maximum de l'équipe atteinte, supprimer un joueur pour pouvoir en rajouter un autre").foregroundStyle(.logoRed)
}
} }

@ -49,14 +49,15 @@ struct TournamentStatusView: View {
let event = tournament.eventObject() let event = tournament.eventObject()
let isLastTournament = event?.tournaments.count == 1 let isLastTournament = event?.tournaments.count == 1
tournament.isDeleted = true if tournament.onlineTeams().isEmpty == false {
tournament.isDeleted = true
try dataStore.tournaments.addOrUpdate(instance: tournament) try dataStore.tournaments.addOrUpdate(instance: tournament)
if let event, isLastTournament {
try dataStore.events.delete(instance: event)
} else { } else {
try dataStore.tournaments.delete(instance: tournament) if let event, isLastTournament {
try dataStore.events.delete(instance: event)
} else {
try dataStore.tournaments.delete(instance: tournament)
}
} }
if eventDismiss == false || isLastTournament { if eventDismiss == false || isLastTournament {
navigation.path = NavigationPath() navigation.path = NavigationPath()

@ -81,12 +81,7 @@ struct TableStructureView: View {
Text(structurePreset.localizedDescriptionStructurePresetTitle()) Text(structurePreset.localizedDescriptionStructurePresetTitle())
} }
.onChange(of: structurePreset) { .onChange(of: structurePreset) {
teamCount = structurePreset.tableDimension() + structurePreset.teamsInQualifiers() - structurePreset.qualifiedPerGroupStage() * structurePreset.groupStageCount() _updatePreset()
groupStageCount = structurePreset.groupStageCount()
teamsPerGroupStage = structurePreset.teamsPerGroupStage()
qualifiedPerGroupStage = structurePreset.qualifiedPerGroupStage()
groupStageAdditionalQualified = 0
buildWildcards = tournament.level.wildcardArePossible()
} }
} }
@ -293,8 +288,27 @@ struct TableStructureView: View {
Section { Section {
RowButtonView("Remise-à-zéro", role: .destructive) { RowButtonView("Remise-à-zéro", role: .destructive) {
tournament.removeWildCards()
tournament.deleteGroupStages() tournament.deleteGroupStages()
tournament.deleteStructure() tournament.deleteStructure()
if structurePreset != .manual {
structurePreset = PadelTournamentStructurePreset.manual
} else {
_updatePreset()
}
tournament.teamCount = teamCount
tournament.groupStageCount = groupStageCount
tournament.teamsPerGroupStage = teamsPerGroupStage
tournament.qualifiedPerGroupStage = qualifiedPerGroupStage
tournament.groupStageAdditionalQualified = groupStageAdditionalQualified
do {
try dataStore.tournaments.addOrUpdate(instance: tournament)
} catch {
Logger.error(error)
}
} }
} }
} }
@ -443,11 +457,12 @@ struct TableStructureView: View {
tournament.groupStageAdditionalQualified = groupStageAdditionalQualified tournament.groupStageAdditionalQualified = groupStageAdditionalQualified
if rebuildEverything { if rebuildEverything {
tournament.deleteAndBuildEverything(preset: structurePreset) tournament.removeWildCards()
if structurePreset.hasWildcards(), buildWildcards { if structurePreset.hasWildcards(), buildWildcards {
tournament.addWildCardIfNeeded(structurePreset.wildcardBrackets(), .bracket) tournament.addWildCardIfNeeded(structurePreset.wildcardBrackets(), .bracket)
tournament.addWildCardIfNeeded(structurePreset.wildcardQualifiers(), .groupStage) tournament.addWildCardIfNeeded(structurePreset.wildcardQualifiers(), .groupStage)
} }
tournament.deleteAndBuildEverything(preset: structurePreset)
} else if (rebuildEverything == false && requirements.contains(.groupStage)) { } else if (rebuildEverything == false && requirements.contains(.groupStage)) {
tournament.deleteGroupStages() tournament.deleteGroupStages()
tournament.buildGroupStages() tournament.buildGroupStages()
@ -466,6 +481,15 @@ struct TableStructureView: View {
} }
} }
private func _updatePreset() {
teamCount = structurePreset.tableDimension() + structurePreset.teamsInQualifiers() - structurePreset.qualifiedPerGroupStage() * structurePreset.groupStageCount()
groupStageCount = structurePreset.groupStageCount()
teamsPerGroupStage = structurePreset.teamsPerGroupStage()
qualifiedPerGroupStage = structurePreset.qualifiedPerGroupStage()
groupStageAdditionalQualified = 0
buildWildcards = tournament.level.wildcardArePossible()
}
private func _verifyValueIntegrity() { private func _verifyValueIntegrity() {
if teamCount > 128 { if teamCount > 128 {
teamCount = 128 teamCount = 128

Loading…
Cancel
Save