add final ranking

fix stuff on teams update
multistore
Razmig Sarkissian 1 year ago
parent e7df5cef10
commit b4e8a09603
  1. 16
      PadelClub/Data/TeamRegistration.swift
  2. 24
      PadelClub/Data/Tournament.swift
  3. 10
      PadelClub/Views/GroupStage/GroupStageSettingsView.swift
  4. 4
      PadelClub/Views/GroupStage/GroupStagesView.swift
  5. 15
      PadelClub/Views/Navigation/Ongoing/OngoingView.swift
  6. 10
      PadelClub/Views/Round/RoundSettingsView.swift
  7. 4
      PadelClub/Views/Round/RoundsView.swift
  8. 27
      PadelClub/Views/Shared/SelectablePlayerListView.swift
  9. 31
      PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift
  10. 1
      PadelClub/Views/Tournament/Screen/TableStructureView.swift
  11. 50
      PadelClub/Views/Tournament/Screen/TournamentRankView.swift
  12. 66
      PadelClub/Views/Tournament/TournamentBuildView.swift
  13. 2
      PadelClub/Views/Tournament/TournamentInitView.swift
  14. 2
      PadelClub/Views/Tournament/TournamentView.swift

@ -33,6 +33,8 @@ class TeamRegistration: ModelObject, Storable {
var lockedWeight: Int?
var confirmationDate: Date?
var qualified: Bool = false
var finalRanking: Int?
var pointsEarned: Int?
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
@ -353,6 +355,8 @@ class TeamRegistration: ModelObject, Storable {
case _lockedWeight = "lockedWeight"
case _confirmationDate = "confirmationDate"
case _qualified = "qualified"
case _finalRanking = "finalRanking"
case _pointsEarned = "pointsEarned"
}
func encode(to encoder: Encoder) throws {
@ -439,6 +443,18 @@ class TeamRegistration: ModelObject, Storable {
}
try container.encode(qualified, forKey: ._qualified)
if let finalRanking {
try container.encode(finalRanking, forKey: ._finalRanking)
} else {
try container.encodeNil(forKey: ._finalRanking)
}
if let pointsEarned {
try container.encode(pointsEarned, forKey: ._pointsEarned)
} else {
try container.encodeNil(forKey: ._pointsEarned)
}
}
}

@ -49,6 +49,10 @@ class Tournament : ModelObject, Storable {
var publishGroupStages: Bool = false
var publishBrackets: Bool = false
//local
var shouldVerifyGroupStage: Bool = false
var shouldVerifyBracket: Bool = false
@ObservationIgnored
var navigationPath: [Screen] = []
@ -460,7 +464,7 @@ class Tournament : ModelObject, Storable {
}
func groupStageTeams() -> [TeamRegistration] {
selectedSortedTeams().filter({ $0.bracketPosition == nil && $0.groupStagePosition != nil })
selectedSortedTeams().filter({ $0.groupStagePosition != nil })
}
func seeds() -> [TeamRegistration] {
@ -939,7 +943,7 @@ class Tournament : ModelObject, Storable {
let groupStageTeams = groupStage.teams(true)
for (index, team) in groupStageTeams.enumerated() {
if team.qualified == false {
let groupStageWidth = max(((index == qualifiedPerGroupStage) ? teamsPerGroupStage - groupStageAdditionalQualified : teamsPerGroupStage) * (index - qualifiedPerGroupStage), 0)
let groupStageWidth = max(((index == qualifiedPerGroupStage) ? groupStageCount - groupStageAdditionalQualified : groupStageCount) * (index - qualifiedPerGroupStage), 0)
let _index = baseRank + groupStageWidth + 1
if let existingTeams = teams[_index] {
@ -1125,6 +1129,14 @@ class Tournament : ModelObject, Storable {
}
func bracketStatus() -> String {
let availableSeeds = availableSeeds()
if availableSeeds.isEmpty == false {
return "placer \(availableSeeds.count) tête\(availableSeeds.count.pluralSuffix) de série"
}
let availableQualifiedTeams = availableQualifiedTeams()
if availableQualifiedTeams.isEmpty == false {
return "placer \(availableQualifiedTeams.count) qualifié" + availableQualifiedTeams.count.pluralSuffix
}
if let round = getActiveRound() {
return [round.roundTitle(), round.roundStatus()].joined(separator: " ")
} else {
@ -1133,6 +1145,10 @@ class Tournament : ModelObject, Storable {
}
func groupStageStatus() -> String {
let groupStageTeamsCount = groupStageTeams().count
if groupStageTeamsCount == 0 || groupStageTeamsCount != teamsPerGroupStage * groupStageCount {
return "à faire"
}
let runningGroupStages = groupStages().filter({ $0.isRunning() })
if groupStagesAreOver() { return "terminées" }
if runningGroupStages.isEmpty {
@ -1152,9 +1168,13 @@ class Tournament : ModelObject, Storable {
}
func structureDescriptionLocalizedLabel() -> String {
if state() == .initial {
return "à valider"
} else {
let groupStageLabel: String? = groupStageCount > 0 ? groupStageCount.formatted() + " poule\(groupStageCount.pluralSuffix)" : nil
return [teamCount.formatted() + " équipes", groupStageLabel].compactMap({ $0 }).joined(separator: ", ")
}
}
func deleteAndBuildEverything() {
deleteStructure()

@ -28,6 +28,16 @@ struct GroupStageSettingsView: View {
var body: some View {
List {
if tournament.shouldVerifyGroupStage {
Section {
RowButtonView("Valider les poules", role: .destructive) {
tournament.shouldVerifyGroupStage = false
}
} footer: {
Text("Suite à changement dans votre liste d'inscrits, veuillez vérifier l'intégrité de vos poules et valider que tout est ok.")
}
}
#if DEBUG
Section {
RowButtonView("delete all group stages") {

@ -53,7 +53,9 @@ struct GroupStagesView: View {
init(tournament: Tournament) {
self.tournament = tournament
if tournament.unsortedTeams().filter({ $0.groupStagePosition != nil }).isEmpty {
if tournament.shouldVerifyGroupStage {
_selectedDestination = State(wrappedValue: nil)
} else if tournament.unsortedTeams().filter({ $0.groupStagePosition != nil }).isEmpty {
_selectedDestination = State(wrappedValue: nil)
} else {
let gs = tournament.getActiveGroupStage()

@ -26,21 +26,8 @@ struct OngoingView: View {
List {
ForEach(matches) { match in
Section {
MatchRowView(match: match, matchViewStyle: .sectionedStandardStyle)
MatchRowView(match: match, matchViewStyle: .standardStyle)
} header: {
HStack {
if let roundTitle = match.roundTitle() {
Text(roundTitle)
}
Text(match.matchTitle(.short))
Spacer()
if let courtName = match.courtName() {
Spacer()
Text(courtName)
}
}
} footer: {
if let tournament = match.currentTournament() {
Text(tournament.tournamentTitle())
}

@ -14,6 +14,16 @@ struct RoundSettingsView: View {
var body: some View {
List {
if tournament.shouldVerifyBracket {
Section {
RowButtonView("Valider le tableau", role: .destructive) {
tournament.shouldVerifyBracket = false
}
} footer: {
Text("Suite à changement dans votre liste d'inscrits, veuillez vérifier l'intégrité de votre tableau et valider que tout est ok.")
}
}
// Section {
// RowButtonView("Enabled", role: .destructive) {
// let allMatches = tournament._allMatchesIncludingDisabled()

@ -14,7 +14,11 @@ struct RoundsView: View {
init(tournament: Tournament) {
self.tournament = tournament
if tournament.shouldVerifyBracket {
_selectedRound = State(wrappedValue: nil)
} else {
_selectedRound = State(wrappedValue: tournament.getActiveRound())
}
if tournament.availableSeeds().isEmpty == false || tournament.availableQualifiedTeams().isEmpty == false {
_isEditingTournamentSeed = State(wrappedValue: true)
}

@ -109,6 +109,7 @@ struct SelectablePlayerListView: View {
}
.scrollDismissesKeyboard(.immediately)
.navigationBarBackButtonHidden(searchViewModel.allowMultipleSelection)
.toolbarBackground(.visible, for: .bottomBar)
// .toolbarRole(searchViewModel.allowMultipleSelection ? .navigationStack : .editor)
.interactiveDismissDisabled(searchViewModel.selectedPlayers.isEmpty == false)
.navigationTitle(searchViewModel.label(forDataSet: searchViewModel.dataSet))
@ -158,6 +159,8 @@ struct SelectablePlayerListView: View {
if searchViewModel.selectedPlayers.isEmpty && searchViewModel.filterSelectionEnabled {
searchViewModel.filterSelectionEnabled = false
} else {
searchViewModel.filterSelectionEnabled = true
}
}
.onChange(of: searchViewModel.dataSet) {
@ -178,25 +181,21 @@ struct SelectablePlayerListView: View {
}
if searchViewModel.selectedPlayers.isEmpty == false {
ToolbarItem(placement: .bottomBar) {
Button {
searchViewModel.filterSelectionEnabled.toggle()
} label: {
if searchViewModel.filterSelectionEnabled {
Image(systemName: "line.3.horizontal.decrease.circle.fill")
} else {
Image(systemName: "line.3.horizontal.decrease.circle")
ToolbarItem(placement: .topBarTrailing) {
ButtonValidateView {
if let playerSelectionAction {
playerSelectionAction(searchViewModel.selectedPlayers)
}
dismiss()
}
}
ToolbarItem(placement: .status) {
Button {
if let playerSelectionAction {
playerSelectionAction(searchViewModel.selectedPlayers)
let count = searchViewModel.selectedPlayers.count
VStack(spacing: 0) {
Text(count.formatted() + " joueur" + count.pluralSuffix + " séléctionné" + count.pluralSuffix).font(.footnote).foregroundStyle(.secondary)
FooterButtonView("\(searchViewModel.filterSelectionEnabled ? "masquer" : "voir") la liste") {
searchViewModel.filterSelectionEnabled.toggle()
}
dismiss()
} label: {
Text("Ajouter le" + searchViewModel.selectedPlayers.count.pluralSuffix + " \(searchViewModel.selectedPlayers.count) joueur" + searchViewModel.selectedPlayers.count.pluralSuffix)
}
}
}

@ -32,6 +32,8 @@ struct InscriptionManagerView: View {
@State private var confirmUpdateRank = false
@State private var selectionSearchField: String?
@State private var autoSelect: Bool = false
@State private var teamsHash: Int?
@State private var presentationCount: Int = 0
let slideToDeleteTip = SlideToDeleteTip()
let inscriptionManagerWomanRankTip = InscriptionManagerWomanRankTip()
@ -60,6 +62,19 @@ struct InscriptionManagerView: 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)
}
var body: some View {
VStack(spacing: 0) {
_managementView()
@ -71,6 +86,22 @@ struct InscriptionManagerView: View {
_teamRegisteredView()
}
}
.onAppear {
self.presentationCount += 1
if self.teamsHash == nil {
self.teamsHash = _simpleHash(ids: tournament.selectedSortedTeams().map { $0.id })
}
}
.onDisappear {
self.presentationCount -= 1
if self.presentationCount == 0 {
let newHash = _simpleHash(ids: tournament.selectedSortedTeams().map { $0.id })
if let teamsHash {
self.tournament.shouldVerifyBracket = newHash != teamsHash
self.tournament.shouldVerifyGroupStage = newHash != teamsHash
}
}
}
.sheet(isPresented: $isLearningMore) {
LearnMoreSheetView(tournament: tournament)
.tint(.master)

@ -203,7 +203,6 @@ struct TableStructureView: View {
presentRefreshStructureWarning = true
}
}
.disabled(updatedElements.isEmpty)
.confirmationDialog("Refaire la structure", isPresented: $presentRefreshStructureWarning, actions: {
if requirements.allSatisfy({ $0 == .groupStage }) {

@ -16,6 +16,47 @@ struct TournamentRankView: View {
var body: some View {
List {
Section {
let matchs = tournament.runningMatches(tournament.allMatches())
let rankingPublished = tournament.selectedSortedTeams().allSatisfy({ $0.finalRanking != nil })
LabeledContent {
Text(matchs.count.formatted())
} label: {
Text("Matchs en cours")
}
LabeledContent {
if rankingPublished {
Image(systemName: "checkmark")
} else {
Image(systemName: "xmark")
}
} label: {
Text("Classement publié")
}
RowButtonView("Publier le classement", role: .destructive) {
rankings.keys.sorted().forEach { rank in
if let rankedTeams = rankings[rank] {
rankedTeams.forEach { team in
team.finalRanking = rank
team.pointsEarned = tournament.tournamentLevel.points(for: rank - 1, count: tournament.teamCount)
}
}
}
_save()
}
} footer: {
FooterButtonView("masquer le classement") {
tournament.unsortedTeams().forEach { team in
team.finalRanking = nil
team.pointsEarned = nil
}
_save()
}
}
let keys = rankings.keys.sorted()
ForEach(keys, id: \.self) { key in
if let rankedTeams = rankings[key] {
@ -86,7 +127,6 @@ struct TournamentRankView: View {
}
}
}
.listStyle(.grouped)
.onAppear {
let finalRanks = tournament.finalRanking()
finalRanks.keys.sorted().forEach { rank in
@ -99,6 +139,14 @@ struct TournamentRankView: View {
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.visible, for: .navigationBar)
}
private func _save() {
do {
try dataStore.teamRegistrations.addOrUpdate(contentOfs: tournament.unsortedTeams())
} catch {
Logger.error(error)
}
}
}
#Preview {

@ -12,6 +12,42 @@ struct TournamentBuildView: View {
@ViewBuilder
var body: some View {
if tournament.hasEnded() {
Section {
NavigationLink(value: Screen.rankings) {
Text("Classement")
}
}
}
Section {
if tournament.groupStageCount > 0 {
NavigationLink(value: Screen.groupStage) {
LabeledContent {
Text(tournament.groupStageStatus())
} label: {
Text("Poules")
if tournament.shouldVerifyGroupStage {
Text("Veuillez vérifier les poules").foregroundStyle(.logoRed)
}
}
}
}
if tournament.rounds().isEmpty == false {
NavigationLink(value: Screen.round) {
LabeledContent {
Text(tournament.bracketStatus())
} label: {
Text("Tableau")
if tournament.shouldVerifyBracket {
Text("Veuillez vérifier la tableau").foregroundStyle(.logoRed)
}
}
}
}
}
Section {
if tournament.state() != .finished {
NavigationLink(value: Screen.schedule) {
@ -47,36 +83,6 @@ struct TournamentBuildView: View {
}
}
}
if tournament.hasEnded() {
Section {
NavigationLink(value: Screen.rankings) {
Text("Classement")
}
}
}
Section {
if tournament.groupStageCount > 0 {
NavigationLink(value: Screen.groupStage) {
LabeledContent {
Text(tournament.groupStageStatus())
} label: {
Text("Poules")
}
}
}
if tournament.rounds().isEmpty == false {
NavigationLink(value: Screen.round) {
LabeledContent {
Text(tournament.bracketStatus())
} label: {
Text("Tableau")
}
}
}
}
}
}

@ -28,7 +28,6 @@ struct TournamentInitView: View {
NavigationLink(value: Screen.structure) {
LabeledContent {
Text(tournament.structureDescriptionLocalizedLabel())
.tint(.master)
} label: {
LabelStructure()
}
@ -37,7 +36,6 @@ struct TournamentInitView: View {
NavigationLink(value: Screen.settings) {
LabeledContent {
Text(tournament.settingsDescriptionLocalizedLabel())
.tint(.master)
} label: {
LabelSettings()
}

@ -143,7 +143,7 @@ struct TournamentView: View {
}
Divider()
if tournament.state() == .running {
if tournament.state() == .running || tournament.state() == .finished {
NavigationLink(value: Screen.event) {
Text("Gestion de l'événement")
}

Loading…
Cancel
Save