diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 64a33c4..ee14553 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -253,6 +253,7 @@ FFE103102C366DCD00684FC9 /* EditSharingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE1030F2C366DCD00684FC9 /* EditSharingView.swift */; }; FFE103122C366E5900684FC9 /* ImagePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE103112C366E5900684FC9 /* ImagePickerView.swift */; }; FFE2D2E22C231BEE00D0C7BE /* SupportButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE2D2E12C231BEE00D0C7BE /* SupportButtonView.swift */; }; + FFE8C2C02C7601E80046B243 /* ConfirmButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE8C2BF2C7601E80046B243 /* ConfirmButtonView.swift */; }; FFEF7F4E2BDE69130033D0F0 /* MenuWarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFEF7F4D2BDE69130033D0F0 /* MenuWarningView.swift */; }; FFF0241E2BF48B15001F14B4 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = FFF0241D2BF48B15001F14B4 /* Localizable.strings */; }; FFF03C942BD91D0C00B516FC /* ButtonValidateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF03C932BD91D0C00B516FC /* ButtonValidateView.swift */; }; @@ -596,6 +597,7 @@ FFE1030F2C366DCD00684FC9 /* EditSharingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditSharingView.swift; sourceTree = ""; }; FFE103112C366E5900684FC9 /* ImagePickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePickerView.swift; sourceTree = ""; }; FFE2D2E12C231BEE00D0C7BE /* SupportButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportButtonView.swift; sourceTree = ""; }; + FFE8C2BF2C7601E80046B243 /* ConfirmButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmButtonView.swift; sourceTree = ""; }; FFEF7F4D2BDE69130033D0F0 /* MenuWarningView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuWarningView.swift; sourceTree = ""; }; FFF0241C2BF48B15001F14B4 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; FFF0241F2BF48B1A001F14B4 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; @@ -684,6 +686,7 @@ FFF8ACD72B923F26008466FA /* Extensions */, C425D4042B6D249E002A7B48 /* Assets.xcassets */, FFF024192BF48AEE001F14B4 /* Localization */, + FFA4DA502C75E815002DAA31 /* Files */, FF0EC54D2BB195CA0056B6D1 /* CSV */, FF1F4B802BFA0105000B4573 /* HTML Templates */, C425D4062B6D249E002A7B48 /* Preview Content */, @@ -820,6 +823,7 @@ C4A47D9E2B7D0BCE00ADC637 /* StepperView.swift */, FFCB74162C480411008384D0 /* CopyPasteButtonView.swift */, FF558C622C6CDD020071F9AE /* UnderlineView.swift */, + FFE8C2BF2C7601E80046B243 /* ConfirmButtonView.swift */, ); path = Components; sourceTree = ""; @@ -1228,6 +1232,13 @@ path = Components; sourceTree = ""; }; + FFA4DA502C75E815002DAA31 /* Files */ = { + isa = PBXGroup; + children = ( + ); + path = Files; + sourceTree = ""; + }; FFBF41802BF73EA2001B24CB /* Event */ = { isa = PBXGroup; children = ( @@ -1601,6 +1612,7 @@ FF967CF62BAED51600A9A3BD /* TournamentRunningView.swift in Sources */, FF8F264D2BAE0B4100650388 /* TournamentDatePickerView.swift in Sources */, FFF116E32BD2AF4800A33B06 /* CourtAvailabilitySettingsView.swift in Sources */, + FFE8C2C02C7601E80046B243 /* ConfirmButtonView.swift in Sources */, C4FC2E272C2AABC90021F3BF /* PasswordField.swift in Sources */, FF967D042BAEF1C300A9A3BD /* MatchRowView.swift in Sources */, C44B79112BBDA63A00906534 /* Locale+Extensions.swift in Sources */, @@ -1939,7 +1951,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 7; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; @@ -1964,7 +1976,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.1; + MARKETING_VERSION = 1.0.2; MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; OTHER_SWIFT_FLAGS = "-Onone"; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; @@ -1989,7 +2001,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 7; + CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; @@ -2012,7 +2024,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.1; + MARKETING_VERSION = 1.0.2; MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; OTHER_SWIFT_FLAGS = "-Xfrontend -warn-long-function-bodies=5 -Xfrontend -warn-long-expression-type-checking=20 -Xfrontend -warn-long-function-bodies=50"; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; diff --git a/PadelClub/Data/Match.swift b/PadelClub/Data/Match.swift index 26af8ec..148abb1 100644 --- a/PadelClub/Data/Match.swift +++ b/PadelClub/Data/Match.swift @@ -163,6 +163,7 @@ defer { } func isSeededBy(team: TeamRegistration, inTeamPosition teamPosition: TeamPosition) -> Bool { + guard let roundObject, roundObject.isUpperBracket() else { return false } guard let bracketPosition = team.bracketPosition else { return false } return index * 2 + teamPosition.rawValue == bracketPosition } @@ -344,10 +345,38 @@ defer { // } // forwardMatch._toggleByeState(state) } - + + func isSeededBy(team: TeamRegistration) -> Bool { + isSeededBy(team: team, inTeamPosition: .one) || isSeededBy(team: team, inTeamPosition: .two) + } + + func isSeeded() -> Bool { + return isSeededAt(.one) || isSeededAt(.two) + } + + func isSeededAt(_ teamPosition: TeamPosition) -> Bool { + if let team = team(teamPosition) { + return isSeededBy(team: team) + } + return false + } + func _toggleMatchDisableState(_ state: Bool, forward: Bool = false) { //if disabled == state { return } disabled = state + if state == true { + let teams = teams() + for team in teams { + if isSeededBy(team: team) { + team.bracketPosition = nil + do { + try self.tournamentStore.teamRegistrations.addOrUpdate(instance: team) + } catch { + Logger.error(error) + } + } + } + } //byeState = false do { try self.tournamentStore.matches.addOrUpdate(instance: self) diff --git a/PadelClub/Data/PlayerRegistration.swift b/PadelClub/Data/PlayerRegistration.swift index 91dec26..aa70f03 100644 --- a/PadelClub/Data/PlayerRegistration.swift +++ b/PadelClub/Data/PlayerRegistration.swift @@ -304,6 +304,21 @@ final class PlayerRegistration: ModelObject, Storable { } } + func hasHomonym() -> Bool { + let federalContext = PersistenceController.shared.localContainer.viewContext + let fetchRequest = ImportedPlayer.fetchRequest() + let predicate = NSPredicate(format: "firstName == %@ && lastName == %@", firstName, lastName) + fetchRequest.predicate = predicate + + do { + let count = try federalContext.count(for: fetchRequest) + return count > 1 + } catch { + + } + return false + } + enum CodingKeys: String, CodingKey { case _id = "id" case _teamRegistration = "teamRegistration" diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index cb9fe18..5ab4fd8 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -952,6 +952,10 @@ defer { return duplicates } + func homonyms(in players: [PlayerRegistration]) -> [PlayerRegistration] { + players.filter({ $0.hasHomonym() }) + } + func unsortedPlayers() -> [PlayerRegistration] { return Array(self.tournamentStore.playerRegistrations) } diff --git a/PadelClub/Utils/URLs.swift b/PadelClub/Utils/URLs.swift index dd3f7d2..b53cc97 100644 --- a/PadelClub/Utils/URLs.swift +++ b/PadelClub/Utils/URLs.swift @@ -24,7 +24,7 @@ enum URLs: String, Identifiable { case tenup = "https://tenup.fft.fr" case padelCompetitionGeneralGuide = "https://fft-site.cdn.prismic.io/fft-site/Zqi2PB5LeNNTxlrS_1-REGLESGENERALESDELACOMPETITION-ANNEESPORTIVE2025.pdf" case padelCompetitionSpecificGuide = "https://fft-site.cdn.prismic.io/fft-site/Zqi4ax5LeNNTxlsu_3-CAHIERDESCHARGESDESTOURNOIS-ANNEESPORTIVE2025.pdf" - case padelRules = "https://fft-site.cdn.prismic.io/fft-site/ZgLnkMcYqOFdyF7i_L%27arbitragedupadel-édition2023_0.pdf" + case padelRules = "https://xlr.alwaysdata.net/static/rules/padel-rules-2024.pdf" case restingDischarge = "https://club.fft.fr/tennisfirmidecazeville/60120370_d/data_1/pdf/fo/formlairededechargederesponsabilitetournoidepadel.pdf" case appReview = "https://apps.apple.com/app/padel-club/id6484163558?mt=8&action=write-review" case appDescription = "https://padelclub.app/download/" diff --git a/PadelClub/Views/Components/ConfirmButtonView.swift b/PadelClub/Views/Components/ConfirmButtonView.swift new file mode 100644 index 0000000..61e94b1 --- /dev/null +++ b/PadelClub/Views/Components/ConfirmButtonView.swift @@ -0,0 +1,40 @@ +// +// ConfirmButtonView.swift +// PadelClub +// +// Created by razmig on 21/08/2024. +// + +import SwiftUI + +struct ConfirmButtonView: View { + @State private var showingConfirmDialog = false + @State private var actionConfirmed = false + + var shouldConfirm: Bool + var message: String + var confirmButtonText: String = "OK" + var cancelButtonText: String = "Annuler" + var onConfirm: () -> Void + var label: () -> T + + var body: some View { + Button { + if shouldConfirm { + showingConfirmDialog = true + } else { + onConfirm() + } + } label: { + label() + } + .confirmationDialog("Attention", isPresented: $showingConfirmDialog) { + Button(confirmButtonText, role: .destructive) { + onConfirm() + } + Button(cancelButtonText, role: .cancel) { } + } message: { + Text(message) + } + } +} diff --git a/PadelClub/Views/Match/MatchDetailView.swift b/PadelClub/Views/Match/MatchDetailView.swift index cd96649..0944147 100644 --- a/PadelClub/Views/Match/MatchDetailView.swift +++ b/PadelClub/Views/Match/MatchDetailView.swift @@ -249,6 +249,17 @@ struct MatchDetailView: View { // Logger.error(error) // } // } + + Toggle(isOn: .init(get: { + return match.confirmed + }, set: { value in + match.confirmed = value + save() + })) { + Text(match.confirmed ? "Confirmé" : "Non confirmé") + } + + Divider() if match.courtIndex != nil { Button(role: .destructive) { @@ -275,7 +286,16 @@ struct MatchDetailView: View { } label: { Text("Supprimer les scores") } - + Divider() + Button(role: .destructive) { + match.resetTeamScores(outsideOf: []) + match.resetMatch() + match.confirmed = false + save() + } label: { + Text("Remise-à-zéro") + } + } label: { LabelOptions() } diff --git a/PadelClub/Views/Match/MatchSetupView.swift b/PadelClub/Views/Match/MatchSetupView.swift index 62cd255..296c4b4 100644 --- a/PadelClub/Views/Match/MatchSetupView.swift +++ b/PadelClub/Views/Match/MatchSetupView.swift @@ -9,7 +9,8 @@ import SwiftUI import LeStorage struct MatchSetupView: View { - + static let confirmationMessage = "Au moins une tête de série a été placée dans la branche de ce match dans les tours précédents. En plaçant une équipe sur ici, les équipes déjà placées dans la même branche seront retirées du tableau et devront être replacées." + @EnvironmentObject var dataStore: DataStore var match: Match @@ -32,6 +33,8 @@ struct MatchSetupView: View { let team = scores.isEmpty ? nil : match.team(teamPosition) let teamScore = (team != nil) ? scores.first(where: { $0.teamRegistration == team!.id }) : nil let walkOutSpot = teamScore?.walkOut == 1 + + let shouldConfirm = match.previousMatch(teamPosition)?.isSeeded() == true if let team, teamScore?.walkOut == nil { VStack(alignment: .leading, spacing: 0) { @@ -42,11 +45,11 @@ struct MatchSetupView: View { _removeTeam(team: team, teamPosition: teamPosition) } label: { TeamRowView(team: team, teamPosition: teamPosition) - .swipeActions(edge: .trailing, allowsFullSwipe: false) { - _removeTeam(team: team, teamPosition: teamPosition) - } } .buttonStyle(.plain) + .swipeActions(edge: .trailing, allowsFullSwipe: false) { + _removeTeam(team: team, teamPosition: teamPosition) + } } } else { VStack(alignment: .leading) { @@ -56,7 +59,7 @@ struct MatchSetupView: View { } HStack { let luckyLosers = walkOutSpot ? match.luckyLosers() : [] - TeamPickerView(groupStagePosition: nil, luckyLosers: luckyLosers, teamPicked: { team in + TeamPickerView(shouldConfirm: shouldConfirm, groupStagePosition: nil, luckyLosers: luckyLosers, teamPicked: { team in print(team.pasteData()) if walkOutSpot || team.bracketPosition != nil { match.setLuckyLoser(team: team, teamPosition: teamPosition) @@ -100,7 +103,7 @@ struct MatchSetupView: View { } if availableQualifiedTeams.isEmpty == false { - Button { + ConfirmButtonView(shouldConfirm: shouldConfirm, message: MatchSetupView.confirmationMessage) { if let randomTeam = availableQualifiedTeams.randomElement() { randomTeam.setSeedPosition(inSpot: match, slot: teamPosition, opposingSeeding: false) do { @@ -120,7 +123,7 @@ struct MatchSetupView: View { } ForEach(availableSeedGroups, id: \.self) { seedGroup in - Button { + ConfirmButtonView(shouldConfirm: shouldConfirm, message: MatchSetupView.confirmationMessage) { if let randomTeam = tournament.randomSeed(fromSeedGroup: seedGroup) { randomTeam.setSeedPosition(inSpot: match, slot: teamPosition, opposingSeeding: false) do { @@ -157,7 +160,7 @@ struct MatchSetupView: View { .underline() } } else { - Button { + ConfirmButtonView(shouldConfirm: shouldConfirm, message: MatchSetupView.confirmationMessage) { _ = match.lockAndGetSeedPosition(atTeamPosition: teamPosition) do { try tournamentStore.matches.addOrUpdate(instance: match) @@ -187,7 +190,7 @@ struct MatchSetupView: View { } catch { Logger.error(error) } - match.updateTeamScores() + //match.updateTeamScores() match.previousMatches().forEach { previousMatch in if previousMatch.disabled { previousMatch.enableMatch() diff --git a/PadelClub/Views/Team/TeamPickerView.swift b/PadelClub/Views/Team/TeamPickerView.swift index 445a1b6..4eb1980 100644 --- a/PadelClub/Views/Team/TeamPickerView.swift +++ b/PadelClub/Views/Team/TeamPickerView.swift @@ -14,6 +14,7 @@ struct TeamPickerView: View { @State private var confirmTeam: TeamRegistration? @State private var presentTeamPickerView: Bool = false @State private var searchField: String = "" + var shouldConfirm: Bool = false var groupStagePosition: Int? = nil var luckyLosers: [TeamRegistration] = [] let teamPicked: ((TeamRegistration) -> (Void)) @@ -26,7 +27,7 @@ struct TeamPickerView: View { } var body: some View { - Button { + ConfirmButtonView(shouldConfirm: shouldConfirm, message: MatchSetupView.confirmationMessage) { presentTeamPickerView = true } label: { Text("Choisir") diff --git a/PadelClub/Views/Tournament/Screen/AddTeamView.swift b/PadelClub/Views/Tournament/Screen/AddTeamView.swift index 75246aa..56569a4 100644 --- a/PadelClub/Views/Tournament/Screen/AddTeamView.swift +++ b/PadelClub/Views/Tournament/Screen/AddTeamView.swift @@ -34,6 +34,8 @@ struct AddTeamView: View { @State private var autoSelect: Bool = false @State private var presentationCount: Int = 0 @State private var confirmDuplicate: Bool = false + @State private var homonyms: [PlayerRegistration] = [] + @State private var confirmHomonym: Bool = false var tournamentStore: TournamentStore { return self.tournament.tournamentStore @@ -66,9 +68,22 @@ struct AddTeamView: View { var body: some View { _buildingTeamView() .navigationBarBackButtonHidden(true) + .alert("Présence d'homonyme", isPresented: $confirmHomonym) { + Button("Créer l'équipe quand même") { + _createTeam(checkDuplicates: false, checkHomonym: false) + } + + Button("Annuler", role: .cancel) { + confirmHomonym = false + } + + } message: { + let plural : String = homonyms.count > 1 ? "nt chacun" : "" + Text(homonyms.map({ $0.playerLabel() }).joined(separator: ", ") + " possède" + plural + " au moins un homonyme dans la base fédérale, vérifiez bien leur licence.") + } .alert("Cette équipe existe déjà", isPresented: $confirmDuplicate) { Button("Créer l'équipe quand même") { - _createTeam(checkDuplicates: false) + _createTeam(checkDuplicates: false, checkHomonym: true) } Button("Annuler", role: .cancel) { @@ -275,13 +290,22 @@ struct AddTeamView: View { return false } - private func _createTeam(checkDuplicates: Bool) { + private func _createTeam(checkDuplicates: Bool, checkHomonym: Bool) { if checkDuplicates && _isDuplicate() { confirmDuplicate = true return } let players = _currentSelection() + + if checkHomonym { + homonyms = players.filter({ $0.hasHomonym() }) + if homonyms.isEmpty == false { + confirmHomonym = true + return + } + } + let team = tournament.addTeam(players) do { try self.tournamentStore.teamRegistrations.addOrUpdate(instance: team) @@ -368,11 +392,11 @@ struct AddTeamView: View { if editedTeam == nil { if createdPlayerIds.isEmpty { RowButtonView("Bloquer une place") { - _createTeam(checkDuplicates: false) + _createTeam(checkDuplicates: false, checkHomonym: false) } } else { RowButtonView("Ajouter l'équipe") { - _createTeam(checkDuplicates: true) + _createTeam(checkDuplicates: true, checkHomonym: true) } } } else { diff --git a/PadelClub/Views/Tournament/Screen/Components/InscriptionInfoView.swift b/PadelClub/Views/Tournament/Screen/Components/InscriptionInfoView.swift index fb2c518..2114d81 100644 --- a/PadelClub/Views/Tournament/Screen/Components/InscriptionInfoView.swift +++ b/PadelClub/Views/Tournament/Screen/Components/InscriptionInfoView.swift @@ -21,6 +21,7 @@ struct InscriptionInfoView: View { @State private var playersWithoutValidLicense : [PlayerRegistration] = [] @State private var entriesFromBeachPadel : [TeamRegistration] = [] @State private var playersMissing : [TeamRegistration] = [] + @State private var homonyms : [PlayerRegistration] = [] var body: some View { List { @@ -126,6 +127,22 @@ struct InscriptionInfoView: View { .listRowView(color: .logoRed) } + Section { + DisclosureGroup { + ForEach(homonyms) { player in + ImportedPlayerView(player: player) + } + } label: { + LabeledContent { + Text(homonyms.count.formatted()) + } label: { + Text("Homonymes") + } + } + .listRowView(color: .logoRed) + } + + Section { DisclosureGroup { ForEach(problematicPlayers) { player in @@ -208,6 +225,7 @@ struct InscriptionInfoView: View { callDateIssue = selectedTeams.filter { $0.callDate != nil && tournament.isStartDateIsDifferentThanCallDate($0) } waitingList = tournament.waitingListTeams(in: selectedTeams, includingWalkOuts: true) duplicates = tournament.duplicates(in: players) + homonyms = tournament.homonyms(in: players) problematicPlayers = players.filter({ $0.sex == nil }) inadequatePlayers = tournament.inadequatePlayers(in: players) playersWithoutValidLicense = tournament.playersWithoutValidLicense(in: players)