From 9291d63d054ed8b9fe26ade5a78b4c8cdf272f11 Mon Sep 17 00:00:00 2001 From: Raz Date: Tue, 22 Oct 2024 09:39:26 +0200 Subject: [PATCH] drawlog wip --- PadelClub.xcodeproj/project.pbxproj | 16 ++++ PadelClub/Data/DrawLog.swift | 80 +++++++++++++++++++ PadelClub/Data/Match.swift | 16 +--- PadelClub/Data/TeamRegistration.swift | 28 ++++++- PadelClub/Data/Tournament.swift | 42 +++++++--- PadelClub/Data/TournamentStore.swift | 2 + PadelClub/Utils/PadelRule.swift | 9 +++ PadelClub/Views/Round/DrawLogsView.swift | 51 ++++++++++++ PadelClub/Views/Round/RoundSettingsView.swift | 23 ++++++ 9 files changed, 240 insertions(+), 27 deletions(-) create mode 100644 PadelClub/Data/DrawLog.swift create mode 100644 PadelClub/Views/Round/DrawLogsView.swift diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 0e37f4d..9f57c36 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -424,6 +424,12 @@ FF6087EC2BE26A2F004E1E47 /* BroadcastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6087EB2BE26A2F004E1E47 /* BroadcastView.swift */; }; FF6525C32C8C61B400B9498E /* LoserBracketFromGroupStageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6525C22C8C61B400B9498E /* LoserBracketFromGroupStageView.swift */; }; FF663FBE2BE019EC0031AE83 /* TournamentFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF663FBD2BE019EC0031AE83 /* TournamentFilterView.swift */; }; + FF6761532CC77D2100CC9BF2 /* DrawLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6761522CC77D1900CC9BF2 /* DrawLog.swift */; }; + FF6761542CC77D2100CC9BF2 /* DrawLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6761522CC77D1900CC9BF2 /* DrawLog.swift */; }; + FF6761552CC77D2100CC9BF2 /* DrawLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6761522CC77D1900CC9BF2 /* DrawLog.swift */; }; + FF6761572CC7803600CC9BF2 /* DrawLogsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6761562CC7803600CC9BF2 /* DrawLogsView.swift */; }; + FF6761582CC7803600CC9BF2 /* DrawLogsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6761562CC7803600CC9BF2 /* DrawLogsView.swift */; }; + FF6761592CC7803600CC9BF2 /* DrawLogsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6761562CC7803600CC9BF2 /* DrawLogsView.swift */; }; FF6EC8F72B94773200EA7F5A /* RowButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8F62B94773100EA7F5A /* RowButtonView.swift */; }; FF6EC8FB2B94788600EA7F5A /* TournamentButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8FA2B94788600EA7F5A /* TournamentButtonView.swift */; }; FF6EC8FE2B94792300EA7F5A /* Screen.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8FD2B94792300EA7F5A /* Screen.swift */; }; @@ -1066,6 +1072,8 @@ FF6087EB2BE26A2F004E1E47 /* BroadcastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BroadcastView.swift; sourceTree = ""; }; FF6525C22C8C61B400B9498E /* LoserBracketFromGroupStageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoserBracketFromGroupStageView.swift; sourceTree = ""; }; FF663FBD2BE019EC0031AE83 /* TournamentFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentFilterView.swift; sourceTree = ""; }; + FF6761522CC77D1900CC9BF2 /* DrawLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DrawLog.swift; sourceTree = ""; }; + FF6761562CC7803600CC9BF2 /* DrawLogsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DrawLogsView.swift; sourceTree = ""; }; FF6EC8F62B94773100EA7F5A /* RowButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RowButtonView.swift; sourceTree = ""; }; FF6EC8FA2B94788600EA7F5A /* TournamentButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentButtonView.swift; sourceTree = ""; }; FF6EC8FD2B94792300EA7F5A /* Screen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Screen.swift; sourceTree = ""; }; @@ -1356,6 +1364,7 @@ FFDB1C6C2BB2A02000F1E467 /* AppSettings.swift */, FFC91B002BD85C2F00B29808 /* Court.swift */, FFF116E02BD2A9B600A33B06 /* DateInterval.swift */, + FF6761522CC77D1900CC9BF2 /* DrawLog.swift */, FF6EC9012B94799200EA7F5A /* Coredata */, FF6EC9022B9479B900EA7F5A /* Federal */, ); @@ -1871,6 +1880,7 @@ FFC2DCB12BBE75D40046DB9F /* LoserRoundView.swift */, FFC2DCB32BBE9ECD0046DB9F /* LoserRoundsView.swift */, FF5647122C0B6F380081F995 /* LoserRoundSettingsView.swift */, + FF6761562CC7803600CC9BF2 /* DrawLogsView.swift */, ); path = Round; sourceTree = ""; @@ -2331,6 +2341,7 @@ C44B79112BBDA63A00906534 /* Locale+Extensions.swift in Sources */, FF1F4B742BFA00FC000B4573 /* HtmlService.swift in Sources */, FF967CEA2BAEC70100A9A3BD /* GroupStage.swift in Sources */, + FF6761542CC77D2100CC9BF2 /* DrawLog.swift in Sources */, FF1162812BCF945C000C4809 /* TournamentCashierView.swift in Sources */, C4A47D902B7BBBEC00ADC637 /* StoreManager.swift in Sources */, FF4AB6BB2B9256D50002987F /* SearchViewModel.swift in Sources */, @@ -2391,6 +2402,7 @@ FF2B51552C7A4DAF00FFF126 /* PlanningByCourtView.swift in Sources */, FFA6D7852BB0B795003A31F3 /* FileImportManager.swift in Sources */, FF6EC8FB2B94788600EA7F5A /* TournamentButtonView.swift in Sources */, + FF6761582CC7803600CC9BF2 /* DrawLogsView.swift in Sources */, FFF8ACCD2B92367B008466FA /* FederalPlayer.swift in Sources */, FFBF06602BBD9F6D009D6715 /* NavigationViewModel.swift in Sources */, FF6EC9092B947A5300EA7F5A /* FixedWidthInteger+Extensions.swift in Sources */, @@ -2605,6 +2617,7 @@ FF4CBF952C996C0600151637 /* Locale+Extensions.swift in Sources */, FF4CBF962C996C0600151637 /* HtmlService.swift in Sources */, FF4CBF972C996C0600151637 /* GroupStage.swift in Sources */, + FF6761532CC77D2100CC9BF2 /* DrawLog.swift in Sources */, FF4CBF982C996C0600151637 /* TournamentCashierView.swift in Sources */, FF4CBF992C996C0600151637 /* StoreManager.swift in Sources */, FF4CBF9A2C996C0600151637 /* SearchViewModel.swift in Sources */, @@ -2665,6 +2678,7 @@ FF4CBFD12C996C0600151637 /* PlanningByCourtView.swift in Sources */, FF4CBFD22C996C0600151637 /* FileImportManager.swift in Sources */, FF4CBFD32C996C0600151637 /* TournamentButtonView.swift in Sources */, + FF6761592CC7803600CC9BF2 /* DrawLogsView.swift in Sources */, FF4CBFD42C996C0600151637 /* FederalPlayer.swift in Sources */, FF4CBFD52C996C0600151637 /* NavigationViewModel.swift in Sources */, FF4CBFD62C996C0600151637 /* FixedWidthInteger+Extensions.swift in Sources */, @@ -2858,6 +2872,7 @@ FF70FB142C90584900129CC2 /* Locale+Extensions.swift in Sources */, FF70FB152C90584900129CC2 /* HtmlService.swift in Sources */, FF70FB162C90584900129CC2 /* GroupStage.swift in Sources */, + FF6761552CC77D2100CC9BF2 /* DrawLog.swift in Sources */, FF70FB172C90584900129CC2 /* TournamentCashierView.swift in Sources */, FF70FB182C90584900129CC2 /* StoreManager.swift in Sources */, FF70FB192C90584900129CC2 /* SearchViewModel.swift in Sources */, @@ -2918,6 +2933,7 @@ FF70FB502C90584900129CC2 /* PlanningByCourtView.swift in Sources */, FF70FB512C90584900129CC2 /* FileImportManager.swift in Sources */, FF70FB522C90584900129CC2 /* TournamentButtonView.swift in Sources */, + FF6761572CC7803600CC9BF2 /* DrawLogsView.swift in Sources */, FF70FB532C90584900129CC2 /* FederalPlayer.swift in Sources */, FF70FB542C90584900129CC2 /* NavigationViewModel.swift in Sources */, FF70FB552C90584900129CC2 /* FixedWidthInteger+Extensions.swift in Sources */, diff --git a/PadelClub/Data/DrawLog.swift b/PadelClub/Data/DrawLog.swift new file mode 100644 index 0000000..3d9cb10 --- /dev/null +++ b/PadelClub/Data/DrawLog.swift @@ -0,0 +1,80 @@ +// +// DrawLog.swift +// PadelClub +// +// Created by razmig on 22/10/2024. +// + +import Foundation +import SwiftUI +import LeStorage + +@Observable +final class DrawLog: ModelObject, Storable { + static func resourceName() -> String { return "draw-logs" } + static func tokenExemptedMethods() -> [HTTPMethod] { return [] } + static func filterByStoreIdentifier() -> Bool { return false } + static var relationshipNames: [String] = [] + + var id: String = Store.randomId() + var tournament: String + var drawDate: Date = Date() + var drawSeed: Int? + var drawPosition: Int + var drawTeamPosition: TeamPosition + + internal init(id: String = Store.randomId(), tournament: String, drawDate: Date = Date(), drawSeed: Int?, drawPosition: Int, drawTeamPosition: TeamPosition) { + self.id = id + self.tournament = tournament + self.drawDate = drawDate + self.drawSeed = drawSeed + self.drawPosition = drawPosition + self.drawTeamPosition = drawTeamPosition + } + + func exportedDrawLog() -> String { + [drawDate.localizedDate(), localizedDrawLogLabel(), localizedDrawBranch()].joined(separator: " ") + } + + func localizedDrawSeedLabel() -> String { + if let drawSeed { + return "Tête de série #\(drawSeed + 1)" + } else { + return "Tête de série non trouvé" + } + } + + func localizedDrawLogLabel() -> String { + return [localizedDrawSeedLabel(), positionLabel()].joined(separator: " -> ") + } + + func localizedDrawBranch() -> String { + drawTeamPosition.localizedBranchLabel() + } + + func positionLabel() -> String { + let roundIndex = RoundRule.roundIndex(fromMatchIndex: drawPosition) + return tournamentStore.rounds.first(where: { $0.parent == nil && $0.index == roundIndex })?._matches().first(where: { $0.index == drawPosition })?.roundAndMatchTitle() ?? "" + } + + var tournamentStore: TournamentStore { + return TournamentStore.instance(tournamentId: self.tournament) + } + + override func deleteDependencies() throws { + } + + enum CodingKeys: String, CodingKey { + case _id = "id" + case _tournament = "tournament" + case _drawDate = "drawDate" + case _drawSeed = "drawSeed" + case _drawPosition = "drawPosition" + case _drawTeamPosition = "drawTeamPosition" + } + + func insertOnServer() throws { + self.tournamentStore.drawLogs.writeChangeAndInsertOnServer(instance: self) + } + +} diff --git a/PadelClub/Data/Match.swift b/PadelClub/Data/Match.swift index b7fa039..214665f 100644 --- a/PadelClub/Data/Match.swift +++ b/PadelClub/Data/Match.swift @@ -164,22 +164,8 @@ defer { } @discardableResult - func lockAndGetSeedPosition(atTeamPosition slot: TeamPosition?, opposingSeeding: Bool = false) -> Int { + func lockAndGetSeedPosition(atTeamPosition teamPosition: TeamPosition) -> Int { let matchIndex = index - var teamPosition : TeamPosition { - if let slot { - return slot - } else { - let seedRound = RoundRule.roundIndex(fromMatchIndex: matchIndex) - let numberOfMatches = RoundRule.numberOfMatches(forRoundIndex: seedRound) - let isUpper = RoundRule.matchIndexWithinRound(fromMatchIndex: matchIndex) < (numberOfMatches / 2) - var teamPosition = slot ?? (isUpper ? .one : .two) - if opposingSeeding { - teamPosition = slot ?? (isUpper ? .two : .one) - } - return teamPosition - } - } previousMatch(teamPosition)?.disableMatch() return matchIndex * 2 + teamPosition.rawValue } diff --git a/PadelClub/Data/TeamRegistration.swift b/PadelClub/Data/TeamRegistration.swift index 1b46a68..6993c4b 100644 --- a/PadelClub/Data/TeamRegistration.swift +++ b/PadelClub/Data/TeamRegistration.swift @@ -116,13 +116,37 @@ final class TeamRegistration: ModelObject, Storable { } func setSeedPosition(inSpot match: Match, slot: TeamPosition?, opposingSeeding: Bool) { - let seedPosition: Int = match.lockAndGetSeedPosition(atTeamPosition: slot, opposingSeeding: opposingSeeding) + var teamPosition : TeamPosition { + if let slot { + return slot + } else { + let matchIndex = match.index + let seedRound = RoundRule.roundIndex(fromMatchIndex: matchIndex) + let numberOfMatches = RoundRule.numberOfMatches(forRoundIndex: seedRound) + let isUpper = RoundRule.matchIndexWithinRound(fromMatchIndex: matchIndex) < (numberOfMatches / 2) + var teamPosition = slot ?? (isUpper ? .one : .two) + if opposingSeeding { + teamPosition = slot ?? (isUpper ? .two : .one) + } + return teamPosition + } + } + + let seedPosition: Int = match.lockAndGetSeedPosition(atTeamPosition: teamPosition) tournamentObject()?.resetTeamScores(in: bracketPosition) self.bracketPosition = seedPosition if groupStagePosition != nil && qualified == false { qualified = true } - tournamentObject()?.updateTeamScores(in: bracketPosition) + if let tournament = tournamentObject() { + let drawLog = DrawLog(tournament: tournament.id, drawSeed: index(in: tournament.selectedSortedTeams()), drawPosition: match.index, drawTeamPosition: teamPosition) + do { + try tournamentStore.drawLogs.addOrUpdate(instance: drawLog) + } catch { + Logger.error(error) + } + tournament.updateTeamScores(in: bracketPosition) + } } func expectedSummonDate() -> Date? { diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index a32d6a4..188317a 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -335,6 +335,12 @@ final class Tournament : ModelObject, Storable { override func deleteDependencies() throws { let store = self.tournamentStore + let drawLogs = self.tournamentStore.drawLogs + for drawLog in drawLogs { + try drawLog.deleteDependencies() + } + store.drawLogs.deleteDependencies(drawLogs) + let teams = self.tournamentStore.teamRegistrations for team in teams { try team.deleteDependencies() @@ -727,16 +733,13 @@ defer { let availableSeedOpponentSpot = availableSeedOpponentSpot(inRoundIndex: roundIndex) let availableSeeds = seeds(inSeedGroup: seedGroup) - if seedGroup == SeedInterval(first: 3, last: 4) { - print("availableSeedGroup == SeedInterval(first: 3, last: 4)") - if availableSeedSpot.count == 6 { - var spots = [Match]() - spots.append(availableSeedSpot[1]) - spots.append(availableSeedSpot[4]) - spots = spots.shuffled() - for (index, seed) in availableSeeds.enumerated() { - seed.setSeedPosition(inSpot: spots[index], slot: nil, opposingSeeding: false) - } + if seedGroup == SeedInterval(first: 3, last: 4), availableSeedSpot.count == 6 { + var spots = [Match]() + spots.append(availableSeedSpot[1]) + spots.append(availableSeedSpot[4]) + spots = spots.shuffled() + for (index, seed) in availableSeeds.enumerated() { + seed.setSeedPosition(inSpot: spots[index], slot: nil, opposingSeeding: false) } } else { if availableSeeds.count <= availableSeedSpot.count { @@ -2260,6 +2263,25 @@ defer { rounds().flatMap { $0.loserRoundsAndChildren().flatMap({ $0._matches() }) } } + func seedsCount() -> Int { + teamCount - groupStageSpots() + } + + func lastDrawnDate() -> Date? { + drawLogs().last?.drawDate + } + + func drawLogs() -> [DrawLog] { + self.tournamentStore.drawLogs.sorted(by: \.drawDate) + } + + func exportedDrawLogs() -> String { + var logs : [String] = ["Journal des tirages\n\n"] + logs.append(drawLogs().map { $0.exportedDrawLog() }.joined(separator: "\n\n")) + return logs.joined() + } + + // MARK: - func insertOnServer() throws { diff --git a/PadelClub/Data/TournamentStore.swift b/PadelClub/Data/TournamentStore.swift index a01e6a0..d210849 100644 --- a/PadelClub/Data/TournamentStore.swift +++ b/PadelClub/Data/TournamentStore.swift @@ -26,6 +26,7 @@ class TournamentStore: Store, ObservableObject { fileprivate(set) var teamScores: StoredCollection = StoredCollection.placeholder() fileprivate(set) var matchSchedulers: StoredCollection = StoredCollection.placeholder() + fileprivate(set) var drawLogs: StoredCollection = StoredCollection.placeholder() convenience init(tournament: Tournament) { self.init(identifier: tournament.id, parameter: "tournament") @@ -51,6 +52,7 @@ class TournamentStore: Store, ObservableObject { self.matches = self.registerCollection(synchronized: synchronized, indexed: indexed) self.teamScores = self.registerCollection(synchronized: synchronized, indexed: indexed) self.matchSchedulers = self.registerCollection(synchronized: false, indexed: indexed) + self.drawLogs = self.registerCollection(synchronized: false, indexed: indexed) self.loadCollectionsFromServerIfNoFile() diff --git a/PadelClub/Utils/PadelRule.swift b/PadelClub/Utils/PadelRule.swift index 72abedc..d3b1d45 100644 --- a/PadelClub/Utils/PadelRule.swift +++ b/PadelClub/Utils/PadelRule.swift @@ -996,6 +996,15 @@ enum TeamPosition: Int, Identifiable, Hashable, Codable, CaseIterable { return shortName } } + + func localizedBranchLabel() -> String { + switch self { + case .one: + return "Branche du haut" + case .two: + return "Branche du bas" + } + } } enum SetFormat: Int, Hashable, Codable { diff --git a/PadelClub/Views/Round/DrawLogsView.swift b/PadelClub/Views/Round/DrawLogsView.swift new file mode 100644 index 0000000..83f0358 --- /dev/null +++ b/PadelClub/Views/Round/DrawLogsView.swift @@ -0,0 +1,51 @@ +// +// DrawLogsView.swift +// PadelClub +// +// Created by razmig on 22/10/2024. +// + +import SwiftUI +import LeStorage + +struct DrawLogsView: View { + @Environment(Tournament.self) var tournament + + var drawLogs: [DrawLog] { + tournament.drawLogs().reversed() + } + + var body: some View { + List { + ForEach(drawLogs) { drawLog in + LabeledContent { + Text(drawLog.localizedDrawBranch()) + } label: { + Text(drawLog.localizedDrawSeedLabel()) + Text(drawLog.positionLabel()) + Text(drawLog.drawDate.localizedDate()) + } + } + } + .overlay(content: { + if drawLogs.isEmpty { + ContentUnavailableView("Aucun tirage", systemImage: "dice", description: Text("Aucun tirage au sort n'a été effectué.")) + } + }) + .toolbar(content: { + ToolbarItem(placement: .topBarTrailing) { + Menu { + ShareLink(item: tournament.exportedDrawLogs()) { + Label("Partager les tirages", systemImage: "square.and.arrow.up") + .labelStyle(.titleAndIcon) + } + } label: { + LabelOptions() + } + } + }) + .navigationBarTitleDisplayMode(.inline) + .toolbarBackground(.visible, for: .navigationBar) + .navigationTitle("Journal des tirages") + } +} diff --git a/PadelClub/Views/Round/RoundSettingsView.swift b/PadelClub/Views/Round/RoundSettingsView.swift index 3dcd33e..d58cd1a 100644 --- a/PadelClub/Views/Round/RoundSettingsView.swift +++ b/PadelClub/Views/Round/RoundSettingsView.swift @@ -73,7 +73,30 @@ struct RoundSettingsView: View { Text("Suite à un changement dans votre liste d'inscrits, veuillez vérifier l'intégrité de votre tableau et valider que tout est ok.") } } + + Section { + NavigationLink { + DrawLogsView() + .environment(tournament) + } label: { + Text("Gestionnaire des tirages au sort") + } + + if tournament.rounds().flatMap({ $0.seeds() }).count < tournament.seedsCount(), tournament.lastDrawnDate() != nil { + + NavigationLink { + } label: { + Text("Aperçu du décalage") + } + RowButtonView("Décaler les têtes de série", role: .destructive) { + + } + } + } footer: { + Text("Vous avez une place libre dans votre tableau. Pour respecter le tirage au sort effectué, vous pouvez décaler les têtes de séries.") + } + // Section { // RowButtonView("Enabled", role: .destructive) { // let allMatches = tournament._allMatchesIncludingDisabled()