Merge branch 'main'

clubs
Raz 1 year ago
commit c2fd9f1c8d
  1. 20
      PadelClub.xcodeproj/project.pbxproj
  2. 31
      PadelClub/Data/Match.swift
  3. 15
      PadelClub/Data/PlayerRegistration.swift
  4. 4
      PadelClub/Data/Tournament.swift
  5. 2
      PadelClub/Utils/URLs.swift
  6. 40
      PadelClub/Views/Components/ConfirmButtonView.swift
  7. 22
      PadelClub/Views/Match/MatchDetailView.swift
  8. 21
      PadelClub/Views/Match/MatchSetupView.swift
  9. 3
      PadelClub/Views/Team/TeamPickerView.swift
  10. 32
      PadelClub/Views/Tournament/Screen/AddTeamView.swift
  11. 18
      PadelClub/Views/Tournament/Screen/Components/InscriptionInfoView.swift

@ -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 = "<group>"; };
FFE103112C366E5900684FC9 /* ImagePickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePickerView.swift; sourceTree = "<group>"; };
FFE2D2E12C231BEE00D0C7BE /* SupportButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportButtonView.swift; sourceTree = "<group>"; };
FFE8C2BF2C7601E80046B243 /* ConfirmButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmButtonView.swift; sourceTree = "<group>"; };
FFEF7F4D2BDE69130033D0F0 /* MenuWarningView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuWarningView.swift; sourceTree = "<group>"; };
FFF0241C2BF48B15001F14B4 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = "<group>"; };
FFF0241F2BF48B1A001F14B4 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
@ -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 = "<group>";
@ -1228,6 +1232,13 @@
path = Components;
sourceTree = "<group>";
};
FFA4DA502C75E815002DAA31 /* Files */ = {
isa = PBXGroup;
children = (
);
path = Files;
sourceTree = "<group>";
};
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;

@ -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)

@ -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"

@ -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)
}

@ -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/"

@ -0,0 +1,40 @@
//
// ConfirmButtonView.swift
// PadelClub
//
// Created by razmig on 21/08/2024.
//
import SwiftUI
struct ConfirmButtonView<T:View>: 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)
}
}
}

@ -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()
}

@ -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()

@ -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")

@ -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 {

@ -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)

Loading…
Cancel
Save