From 0fe8728a8abbdecd492506c223378e73da62e041 Mon Sep 17 00:00:00 2001 From: Raz Date: Tue, 13 May 2025 15:05:52 +0200 Subject: [PATCH 01/27] add accountGroupStageBreakTime groupStageRotationDifference in matchscheduler and uniqueRandomIndex in team reg --- .../Views/Planning/PlanningSettingsView.swift | 98 +++++-------------- PadelClub/Views/Team/EditingTeamView.swift | 22 +++++ PadelClubTests/ServerDataTests.swift | 3 +- 3 files changed, 46 insertions(+), 77 deletions(-) diff --git a/PadelClub/Views/Planning/PlanningSettingsView.swift b/PadelClub/Views/Planning/PlanningSettingsView.swift index 2d56d71..2850ba4 100644 --- a/PadelClub/Views/Planning/PlanningSettingsView.swift +++ b/PadelClub/Views/Planning/PlanningSettingsView.swift @@ -462,6 +462,15 @@ struct PlanningSettingsView: View { } header: { Text("Classement") } + + Section { + Toggle(isOn: $matchScheduler.accountGroupStageBreakTime) { + Text("Tenir compte des temps de pause réglementaires") + } + } header: { + Text("Poule") + } + Section { Toggle(isOn: $matchScheduler.rotationDifferenceIsImportant) { @@ -469,18 +478,27 @@ struct PlanningSettingsView: View { } LabeledContent { - StepperView(count: $matchScheduler.upperBracketRotationDifference, minimum: 0, maximum: 2) + StepperView(count: $matchScheduler.upperBracketRotationDifference, minimum: 0) } label: { Text("Tableau") } .disabled(matchScheduler.rotationDifferenceIsImportant == false) LabeledContent { - StepperView(count: $matchScheduler.loserBracketRotationDifference, minimum: 0, maximum: 2) + StepperView(count: $matchScheduler.loserBracketRotationDifference, minimum: 0) } label: { Text("Classement") } .disabled(matchScheduler.rotationDifferenceIsImportant == false) + + LabeledContent { + StepperView(count: $matchScheduler.groupStageRotationDifference, minimum: 0) + } label: { + Text("Poule") + } + .disabled(matchScheduler.rotationDifferenceIsImportant == false) + + } footer: { Text("Cette option ajoute du temps entre 2 rotations, permettant ainsi de mieux configurer plusieurs tournois se déroulant en même temps.") } @@ -517,83 +535,11 @@ struct PlanningSettingsView: View { } private func _groupMatchesByDay(matches: [Match]) -> [Date: [Match]] { - var matchesByDay = [Date: [Match]]() - let calendar = Calendar.current - - for match in matches { - // Extract day/month/year and create a date with only these components - let components = calendar.dateComponents([.year, .month, .day], from: match.computedStartDateForSorting) - let strippedDate = calendar.date(from: components)! - - // Group matches by the strippedDate (only day/month/year) - if matchesByDay[strippedDate] == nil { - matchesByDay[strippedDate] = [] - } - - let shouldIncludeMatch: Bool - switch match.matchType { - case .groupStage: - shouldIncludeMatch = !matchesByDay[strippedDate]!.filter { $0.groupStage != nil }.compactMap { $0.groupStage }.contains(match.groupStage!) - case .bracket: - shouldIncludeMatch = !matchesByDay[strippedDate]!.filter { $0.round != nil }.compactMap { $0.round }.contains(match.round!) - case .loserBracket: - shouldIncludeMatch = true - } - - if shouldIncludeMatch { - matchesByDay[strippedDate]!.append(match) - } - } - - return matchesByDay + tournament.groupMatchesByDay(matches: matches) } private func _matchCountPerDay(matchesByDay: [Date: [Match]], tournament: Tournament) -> [Date: NSCountedSet] { - let days = matchesByDay.keys - var matchCountPerDay = [Date: NSCountedSet]() - - for day in days { - if let matches = matchesByDay[day] { - var groupStageCount = 0 - let countedSet = NSCountedSet() - - for match in matches { - switch match.matchType { - case .groupStage: - if let groupStage = match.groupStageObject { - if groupStageCount < groupStage.size - 1 { - groupStageCount = groupStage.size - 1 - } - } - case .bracket: - countedSet.add(match.matchFormat) - case .loserBracket: - break - } - } - - if groupStageCount > 0 { - for _ in 0.. some View { diff --git a/PadelClub/Views/Team/EditingTeamView.swift b/PadelClub/Views/Team/EditingTeamView.swift index 236ecfe..70199fb 100644 --- a/PadelClub/Views/Team/EditingTeamView.swift +++ b/PadelClub/Views/Team/EditingTeamView.swift @@ -29,6 +29,8 @@ struct EditingTeamView: View { @State private var isProcessingRefund = false @State private var refundMessage: String? @State private var registrationDateModified: Date + @State private var uniqueRandomIndex: Int + var messageSentFailed: Binding { Binding { @@ -48,6 +50,7 @@ struct EditingTeamView: View { return registrationDate != team.registrationDate + || uniqueRandomIndex != team.uniqueRandomIndex || walkOut != team.walkOut || wildCardBracket != team.wildCardBracket || wildCardGroupStage != team.wildCardGroupStage @@ -69,6 +72,7 @@ struct EditingTeamView: View { _walkOut = State(wrappedValue: team.walkOut) _wildCardBracket = State(wrappedValue: team.wildCardBracket) _wildCardGroupStage = State(wrappedValue: team.wildCardGroupStage) + _uniqueRandomIndex = .init(wrappedValue: team.uniqueRandomIndex) } private func _resetTeam() { @@ -77,6 +81,7 @@ struct EditingTeamView: View { team.wildCardGroupStage = false team.walkOut = false team.wildCardBracket = false + team.uniqueRandomIndex = 0 } var body: some View { @@ -206,6 +211,16 @@ struct EditingTeamView: View { } } + Section { + LabeledContent { + StepperView(count: $uniqueRandomIndex, minimum: 0) + } label: { + Text("Ordre à poids de paire égal") + } + } footer: { + Text("Si plusieurs équipes ont le même poids et que leur position est tiré au sort, ce champ permet de les positionner correctement dans l'ordre croissant.") + } + Section { HStack { TextField("Nom de l'équipe", text: $name) @@ -287,6 +302,7 @@ struct EditingTeamView: View { team.wildCardBracket = wildCardBracket team.wildCardGroupStage = wildCardGroupStage team.walkOut = walkOut + team.uniqueRandomIndex = uniqueRandomIndex _save() } @@ -295,6 +311,7 @@ struct EditingTeamView: View { walkOut = team.walkOut wildCardBracket = team.wildCardBracket wildCardGroupStage = team.wildCardGroupStage + uniqueRandomIndex = team.uniqueRandomIndex } }, message: { Text("Ce changement peut entraîner l'entrée ou la sortie d'une équipe de votre sélection. Padel Club préviendra automatiquement une équipe inscrite en ligne de son nouveau statut.") @@ -394,6 +411,11 @@ struct EditingTeamView: View { } } } + .onChange(of: uniqueRandomIndex) { + if canSaveWithoutWarning() { + _save() + } + } .onChange(of: [walkOut, wildCardBracket, wildCardGroupStage]) { if canSaveWithoutWarning() { if walkOut == false && team.walkOut == true { diff --git a/PadelClubTests/ServerDataTests.swift b/PadelClubTests/ServerDataTests.swift index 9a18b02..bf013f2 100644 --- a/PadelClubTests/ServerDataTests.swift +++ b/PadelClubTests/ServerDataTests.swift @@ -270,7 +270,7 @@ final class ServerDataTests: XCTestCase { return } - let teamRegistration = TeamRegistration(tournament: tournamentId, groupStage: groupStageId, registrationDate: Date(), callDate: Date(), bracketPosition: 1, groupStagePosition: 2, comment: "comment", source: "source", sourceValue: "source V", logo: "logo", name: "Stax", walkOut: true, wildCardBracket: true, wildCardGroupStage: true, weight: 1, lockedWeight: 11, confirmationDate: Date(), qualified: true) + let teamRegistration = TeamRegistration(tournament: tournamentId, groupStage: groupStageId, registrationDate: Date(), callDate: Date(), bracketPosition: 1, groupStagePosition: 2, comment: "comment", source: "source", sourceValue: "source V", logo: "logo", name: "Stax", walkOut: true, wildCardBracket: true, wildCardGroupStage: true, weight: 1, lockedWeight: 11, confirmationDate: Date(), qualified: true, finalRanking: 100, pointsEarned: 10, uniqueRandomIndex: 1) teamRegistration.storeId = "123" if let tr: TeamRegistration = try await StoreCenter.main.service().post(teamRegistration) { @@ -297,6 +297,7 @@ final class ServerDataTests: XCTestCase { assert(tr.qualified == teamRegistration.qualified) assert(tr.finalRanking == teamRegistration.finalRanking) assert(tr.pointsEarned == teamRegistration.pointsEarned) + assert(tr.uniqueRandomIndex == teamRegistration.uniqueRandomIndex) } else { XCTFail("missing data") } From ecdc46a9689fd6eb496673b8ed534e5704baaa44 Mon Sep 17 00:00:00 2001 From: Raz Date: Wed, 14 May 2025 07:39:10 +0200 Subject: [PATCH 02/27] fix randomu unique index not saving --- PadelClub/Views/Team/EditingTeamView.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/PadelClub/Views/Team/EditingTeamView.swift b/PadelClub/Views/Team/EditingTeamView.swift index 70199fb..5d915bc 100644 --- a/PadelClub/Views/Team/EditingTeamView.swift +++ b/PadelClub/Views/Team/EditingTeamView.swift @@ -413,6 +413,7 @@ struct EditingTeamView: View { } .onChange(of: uniqueRandomIndex) { if canSaveWithoutWarning() { + team.uniqueRandomIndex = uniqueRandomIndex _save() } } From 12142cde372aab3ab3040cb4ff52681f44a32d35 Mon Sep 17 00:00:00 2001 From: Raz Date: Wed, 14 May 2025 20:22:49 +0200 Subject: [PATCH 03/27] add planning feature --- PadelClub/Views/Cashier/Event/EventView.swift | 11 +- .../CourtAvailabilitySettingsView.swift | 27 +- .../Views/Planning/PlanningByCourtView.swift | 1 - PadelClub/Views/Planning/PlanningView.swift | 800 ++++++++++++------ 4 files changed, 555 insertions(+), 284 deletions(-) diff --git a/PadelClub/Views/Cashier/Event/EventView.swift b/PadelClub/Views/Cashier/Event/EventView.swift index 99ae840..cb0214e 100644 --- a/PadelClub/Views/Cashier/Event/EventView.swift +++ b/PadelClub/Views/Cashier/Event/EventView.swift @@ -18,6 +18,7 @@ enum EventDestination: Identifiable, Selectable, Equatable { case links case tournaments(Event) case cashier + case eventPlanning var id: String { return String(describing: self) @@ -33,6 +34,8 @@ enum EventDestination: Identifiable, Selectable, Equatable { return "Tournois" case .cashier: return "Finance" + case .eventPlanning: + return "Planning" } } @@ -42,7 +45,7 @@ enum EventDestination: Identifiable, Selectable, Equatable { return nil case .tournaments(let event): return event.tournaments.count - case .cashier: + case .cashier, .eventPlanning: return nil } } @@ -77,7 +80,7 @@ struct EventView: View { } func allDestinations() -> [EventDestination] { - [.club(event), .tournaments(event), .cashier] + [.club(event), .eventPlanning, .tournaments(event), .cashier] } var body: some View { @@ -90,6 +93,10 @@ struct EventView: View { switch selectedEventDestination { case .club(let event): EventClubSettingsView(event: event) + case .eventPlanning: + let allMatches = event.tournaments.flatMap { $0.allMatches() } + PlanningView(matches: allMatches, selectedScheduleDestination: .constant(.planning)) + .environment(\.matchViewStyle, .feedStyle) case .links: EventLinksView(event: event) case .tournaments(let event): diff --git a/PadelClub/Views/Planning/CourtAvailabilitySettingsView.swift b/PadelClub/Views/Planning/CourtAvailabilitySettingsView.swift index 4223373..e34c906 100644 --- a/PadelClub/Views/Planning/CourtAvailabilitySettingsView.swift +++ b/PadelClub/Views/Planning/CourtAvailabilitySettingsView.swift @@ -256,13 +256,27 @@ struct CourtAvailabilityEditorView: View { struct DateAdjusterView: View { @Binding var date: Date + var time: Int? + var matchFormat: MatchFormat? var body: some View { - HStack { - _createButton(label: "-1h", timeOffset: -1, component: .hour) - _createButton(label: "-30m", timeOffset: -30, component: .minute) - _createButton(label: "+30m", timeOffset: 30, component: .minute) - _createButton(label: "+1h", timeOffset: 1, component: .hour) + HStack(spacing: 4) { + if let matchFormat { + _createButton(label: "-\(matchFormat.defaultEstimatedDuration)m", timeOffset: -matchFormat.defaultEstimatedDuration, component: .minute) + _createButton(label: "+\(matchFormat.defaultEstimatedDuration)m", timeOffset: +matchFormat.defaultEstimatedDuration, component: .minute) + _createButton(label: "-\(matchFormat.estimatedTimeWithBreak)m", timeOffset: -matchFormat.estimatedTimeWithBreak, component: .minute) + _createButton(label: "+\(matchFormat.estimatedTimeWithBreak)m", timeOffset: +matchFormat.estimatedTimeWithBreak, component: .minute) + } else if let time { + _createButton(label: "-\(time)m", timeOffset: -time, component: .minute) + _createButton(label: "-\(time/2)m", timeOffset: -time/2, component: .minute) + _createButton(label: "+\(time/2)m", timeOffset: time/2, component: .minute) + _createButton(label: "+\(time)m", timeOffset: time, component: .minute) + } else { + _createButton(label: "-1h", timeOffset: -1, component: .hour) + _createButton(label: "-30m", timeOffset: -30, component: .minute) + _createButton(label: "+30m", timeOffset: 30, component: .minute) + _createButton(label: "+1h", timeOffset: 1, component: .hour) + } } .font(.headline) } @@ -272,6 +286,9 @@ struct DateAdjusterView: View { date = Calendar.current.date(byAdding: component, value: timeOffset, to: date) ?? date }) { Text(label) + .lineLimit(1) + .font(.footnote) + .underline() .frame(maxWidth: .infinity) // Make buttons take equal space } .buttonStyle(.borderedProminent) diff --git a/PadelClub/Views/Planning/PlanningByCourtView.swift b/PadelClub/Views/Planning/PlanningByCourtView.swift index f9ac7b8..d3447f9 100644 --- a/PadelClub/Views/Planning/PlanningByCourtView.swift +++ b/PadelClub/Views/Planning/PlanningByCourtView.swift @@ -114,7 +114,6 @@ struct PlanningByCourtView: View { let match = _sortedMatches[index] Section { MatchRowView(match: match) - .matchViewStyle(.feedStyle) } header: { if let startDate = match.startDate { if index > 0 { diff --git a/PadelClub/Views/Planning/PlanningView.swift b/PadelClub/Views/Planning/PlanningView.swift index b72441d..c964187 100644 --- a/PadelClub/Views/Planning/PlanningView.swift +++ b/PadelClub/Views/Planning/PlanningView.swift @@ -5,45 +5,45 @@ // Created by Razmig Sarkissian on 07/04/2024. // -import SwiftUI import LeStorage -import TipKit import PadelClubData +import SwiftUI +import TipKit struct PlanningView: View { - + @EnvironmentObject var dataStore: DataStore - @Environment(Tournament.self) var tournament: Tournament @State private var selectedDay: Date? @Binding var selectedScheduleDestination: ScheduleDestination? @State private var filterOption: PlanningFilterOption = .byDefault @State private var showFinishedMatches: Bool = false @State private var enableMove: Bool = false - + @Environment(\.editMode) private var editMode + let allMatches: [Match] let timeSlotMoveOptionTip = TimeSlotMoveOptionTip() - + init(matches: [Match], selectedScheduleDestination: Binding) { self.allMatches = matches _selectedScheduleDestination = selectedScheduleDestination } - + var matches: [Match] { allMatches.filter({ showFinishedMatches || $0.endDate == nil }) } - - var timeSlots: [Date:[Match]] { + + var timeSlots: [Date: [Match]] { Dictionary(grouping: matches) { $0.startDate ?? .distantFuture } } - - func days(timeSlots: [Date:[Match]]) -> [Date] { + + func days(timeSlots: [Date: [Match]]) -> [Date] { Set(timeSlots.keys.map { $0.startOfDay }).sorted() } - - func keys(timeSlots: [Date:[Match]]) -> [Date] { + + func keys(timeSlots: [Date: [Match]]) -> [Date] { timeSlots.keys.sorted() } - + private func _computedTitle(days: [Date]) -> String { if let selectedDay { return selectedDay.formatted(.dateTime.day().weekday().month()) @@ -55,6 +55,23 @@ struct PlanningView: View { } } } + + private func _confirmationMode() -> Bool { + enableMove || editMode?.wrappedValue == .active + } + + private var enableEditionBinding: Binding { + Binding { + editMode?.wrappedValue == .active + } set: { value in + if value { + editMode?.wrappedValue = .active + } else { + editMode?.wrappedValue = .inactive + } + } + + } var body: some View { let timeSlots = self.timeSlots @@ -62,119 +79,144 @@ struct PlanningView: View { let days = self.days(timeSlots: timeSlots) let matches = matches let notSlots = matches.allSatisfy({ $0.startDate == nil }) - BySlotView(days: days, keys: keys, timeSlots: timeSlots, matches: matches, selectedDay: selectedDay) - .environment(\.filterOption, filterOption) - .environment(\.showFinishedMatches, showFinishedMatches) - .environment(\.enableMove, enableMove) - .navigationTitle(Text(_computedTitle(days: days))) - .navigationBarBackButtonHidden(enableMove) - .toolbar(content: { - if days.count > 1 { - ToolbarTitleMenu { - Picker(selection: $selectedDay) { - Text("Tous les jours").tag(nil as Date?) - ForEach(days, id: \.self) { day in - if day.monthYearFormatted == Date.distantFuture.monthYearFormatted { - Text("Sans horaire").tag(day as Date?) - } else { - Text(day.formatted(.dateTime.day().weekday().month())).tag(day as Date?) - } + BySlotView( + days: days, keys: keys, timeSlots: timeSlots, matches: matches, selectedDay: selectedDay + ) + .environment(\.filterOption, filterOption) + .environment(\.showFinishedMatches, showFinishedMatches) + .environment(\.enableMove, enableMove) + .navigationTitle(Text(_computedTitle(days: days))) + .navigationBarBackButtonHidden(_confirmationMode()) + .toolbar(content: { + if days.count > 1 { + ToolbarTitleMenu { + Picker(selection: $selectedDay) { + Text("Tous les jours").tag(nil as Date?) + ForEach(days, id: \.self) { day in + if day.monthYearFormatted == Date.distantFuture.monthYearFormatted { + Text("Sans horaire").tag(day as Date?) + } else { + Text(day.formatted(.dateTime.day().weekday().month())).tag( + day as Date?) } - } label: { - Text("Jour") } - .pickerStyle(.automatic) - .disabled(enableMove) + } label: { + Text("Jour") } + .pickerStyle(.automatic) + .disabled(_confirmationMode()) } - - if enableMove { - ToolbarItem(placement: .topBarLeading) { - Button("Annuler") { - enableMove = false - } + } + + if _confirmationMode() { + ToolbarItem(placement: .topBarLeading) { + Button("Annuler") { + enableMove = false + enableEditionBinding.wrappedValue = false } - - ToolbarItem(placement: .topBarTrailing) { + } + if enableMove { + ToolbarItemGroup(placement: .topBarTrailing) { Button("Sauver") { - do { - try self.tournament.tournamentStore?.matches.addOrUpdate(contentOfs: allMatches) - } catch { - Logger.error(error) + let groupByTournaments = allMatches.grouped { match in + match.currentTournament() } - + groupByTournaments.forEach { tournament, matches in + tournament?.tournamentStore?.matches.addOrUpdate(contentOfs: matches) + } + enableMove = false } } - - } else { - - ToolbarItemGroup(placement: .topBarTrailing) { - if notSlots == false { + } + } else { + if notSlots == false { + ToolbarItemGroup(placement: .bottomBar) { + HStack { + CourtOptionsView(timeSlots: timeSlots, underlined: false) + Spacer() Toggle(isOn: $enableMove) { - Label("Déplacer", systemImage: "rectangle.2.swap") + Label { + Text("Déplacer") + } icon: { + Image(systemName: "rectangle.2.swap") + } } .popoverTip(timeSlotMoveOptionTip) + .disabled(_confirmationMode()) + Spacer() + Toggle(isOn: enableEditionBinding) { + Text("Modifier") + } + .disabled(_confirmationMode()) } - - Menu { - Section { - Picker(selection: $showFinishedMatches) { - Text("Afficher tous les matchs").tag(true) - Text("Masquer les matchs terminés").tag(false) - } label: { - Text("Option de filtrage") - } - .labelsHidden() - .pickerStyle(.inline) - } header: { + } + } + ToolbarItemGroup(placement: .topBarTrailing) { + Menu { + Section { + Picker(selection: $showFinishedMatches) { + Text("Afficher tous les matchs").tag(true) + Text("Masquer les matchs terminés").tag(false) + } label: { Text("Option de filtrage") } - - Divider() - - Section { - Picker(selection: $filterOption) { - ForEach(PlanningFilterOption.allCases) { - Text($0.localizedPlanningLabel()).tag($0) - } - } label: { - Text("Option de triage") + .labelsHidden() + .pickerStyle(.inline) + } header: { + Text("Option de filtrage") + } + + Divider() + + Section { + Picker(selection: $filterOption) { + ForEach(PlanningFilterOption.allCases) { + Text($0.localizedPlanningLabel()).tag($0) } - .labelsHidden() - .pickerStyle(.inline) - } header: { + } label: { Text("Option de triage") - } - } label: { - Label("Trier", systemImage: "line.3.horizontal.decrease.circle") - .symbolVariant(filterOption == .byCourt || showFinishedMatches ? .fill : .none) + .labelsHidden() + .pickerStyle(.inline) + } header: { + Text("Option de triage") + } - + } label: { + Label("Trier", systemImage: "line.3.horizontal.decrease.circle") + .symbolVariant( + filterOption == .byCourt || showFinishedMatches ? .fill : .none) } + } - }) - .overlay { - if notSlots { - ContentUnavailableView { - Label("Aucun horaire défini", systemImage: "clock.badge.questionmark") - } description: { - Text("Vous n'avez pas encore défini d'horaire pour les différentes phases du tournoi") - } actions: { - RowButtonView("Horaire intelligent") { - selectedScheduleDestination = nil - } + } + }) + .overlay { + if notSlots { + ContentUnavailableView { + Label("Aucun horaire défini", systemImage: "clock.badge.questionmark") + } description: { + Text( + "Vous n'avez pas encore défini d'horaire pour les différentes phases du tournoi" + ) + } actions: { + RowButtonView("Horaire intelligent") { + selectedScheduleDestination = nil } } } + } } - + struct BySlotView: View { - @Environment(Tournament.self) var tournament: Tournament @Environment(\.filterOption) private var filterOption @Environment(\.showFinishedMatches) private var showFinishedMatches @Environment(\.enableMove) private var enableMove + @Environment(\.editMode) private var editMode + @State private var selectedIds = Set() + @State private var showDateUpdateView: Bool = false + @State private var dateToUpdate: Date = Date() let days: [Date] let keys: [Date] @@ -184,15 +226,15 @@ struct PlanningView: View { let timeSlotMoveTip = TimeSlotMoveTip() var body: some View { - List { - + List(selection: $selectedIds) { if enableMove { TipView(timeSlotMoveTip) .tipStyle(tint: .logoYellow, asSection: true) } - + if !matches.allSatisfy({ $0.startDate == nil }) { - ForEach(days.filter({ selectedDay == nil || selectedDay == $0 }), id: \.self) { day in + ForEach(days.filter({ selectedDay == nil || selectedDay == $0 }), id: \.self) { + day in DaySectionView( day: day, keys: keys.filter({ $0.dayInt == day.dayInt }), @@ -202,15 +244,108 @@ struct PlanningView: View { } } } + .toolbar(content: { + if editMode?.wrappedValue == .active { + ToolbarItem(placement: .bottomBar) { + Button { + showDateUpdateView = true + } label: { + Text("Modifier la date des matchs sélectionnés") + } + .disabled(selectedIds.isEmpty) + } + } + }) + .sheet(isPresented: $showDateUpdateView, onDismiss: { + selectedIds.removeAll() + }) { + let selectedMatches = matches.filter({ selectedIds.contains($0.stringId) }) + DateUpdateView(selectedMatches: selectedMatches) + } } } + + struct DateUpdateView: View { + @Environment(\.dismiss) var dismiss + + let selectedMatches: [Match] + let selectedFormats: [MatchFormat] + @State private var dateToUpdate: Date + + init(selectedMatches: [Match]) { + self.selectedMatches = selectedMatches + self.selectedFormats = Array(Set(selectedMatches.map({ match in + match.matchFormat + }))) + _dateToUpdate = .init(wrappedValue: selectedMatches.first?.startDate ?? Date()) + } + + var body: some View { + NavigationStack { + List { + Section { + DatePicker(selection: $dateToUpdate) { + Text(dateToUpdate.formatted(.dateTime.weekday(.wide))).font(.headline) + } + } + + Section { + DateAdjusterView(date: $dateToUpdate) + DateAdjusterView(date: $dateToUpdate, time: 10) + ForEach(selectedFormats, id: \.self) { matchFormat in + DateAdjusterView(date: $dateToUpdate, matchFormat: matchFormat) + } + } + + Section { + ForEach(selectedMatches) { match in + MatchRowView(match: match) + } + } header: { + Text("Matchs à modifier") + } + + } + .navigationTitle("Modification de la date") + .navigationBarTitleDisplayMode(.inline) + .toolbarBackground(.visible, for: .navigationBar) + .toolbar(content: { + ToolbarItem(placement: .topBarLeading) { + Button("Annuler", role: .cancel) { + dismiss() + } + } + ToolbarItem(placement: .topBarTrailing) { + Button("Valider") { + _updateDate() + } + } + }) + } + } + + private func _updateDate() { + selectedMatches.forEach { match in + match.startDate = dateToUpdate + } + + let groupByTournaments = selectedMatches.grouped { match in + match.currentTournament() + } + groupByTournaments.forEach { tournament, matches in + tournament?.tournamentStore?.matches.addOrUpdate(contentOfs: matches) + } + dismiss() + } + + } struct DaySectionView: View { - @Environment(Tournament.self) var tournament: Tournament @Environment(\.filterOption) private var filterOption @Environment(\.showFinishedMatches) private var showFinishedMatches @Environment(\.enableMove) private var enableMove + @Environment(\.editMode) private var editMode let day: Date let keys: [Date] @@ -222,31 +357,40 @@ struct PlanningView: View { ForEach(keys, id: \.self) { key in TimeSlotSectionView( key: key, - matches: timeSlots[key]?.sorted(by: filterOption == .byDefault ? \.computedOrder : \.courtIndexForSorting) ?? [] + matches: timeSlots[key]?.sorted( + by: filterOption == .byDefault + ? \.computedOrder : \.courtIndexForSorting) ?? [] ) } .onMove(perform: enableMove ? moveSection : nil) } header: { HeaderView(day: day, timeSlots: timeSlots) } footer: { - if day.monthYearFormatted == Date.distantFuture.monthYearFormatted { - Text("Il s'agit des matchs qui n'ont pas réussi à être placé par Padel Club. Peut-être à cause de créneaux indisponibles, d'autres tournois ou des réglages.") + VStack(alignment: .leading) { + if day.monthYearFormatted == Date.distantFuture.monthYearFormatted { + Text( + "Il s'agit des matchs qui n'ont pas réussi à être placé par Padel Club. Peut-être à cause de créneaux indisponibles, d'autres tournois ou des réglages." + ) + } + + CourtOptionsView(timeSlots: timeSlots, underlined: true) } } } - + func moveSection(from source: IndexSet, to destination: Int) { let daySlots = keys.filter { $0.dayInt == day.dayInt }.sorted() - + guard let sourceIdx = source.first, sourceIdx < daySlots.count, - destination <= daySlots.count else { + destination <= daySlots.count + else { return } - - // Create a mutable copy of the time slots for this day + + // Create a mutable copy of the time slots for this day var slotsToUpdate = daySlots - + let updateRange = min(sourceIdx, destination)...max(sourceIdx, destination) // Perform the move in the array @@ -256,7 +400,7 @@ struct PlanningView: View { } else { slotsToUpdate.insert(sourceTime, at: destination) } - + // Update matches by swapping their startDates for index in updateRange { // Find the new time slot for these matches @@ -264,7 +408,7 @@ struct PlanningView: View { guard let newStartTime = daySlots[safe: index] else { continue } guard let matchesToUpdate = timeSlots[oldStartTime] else { continue } - // Update each match with the new start time + // Update each match with the new start time for match in matchesToUpdate { match.startDate = newStartTime } @@ -272,22 +416,47 @@ struct PlanningView: View { } } - struct TimeSlotSectionView: View { @Environment(\.enableMove) private var enableMove + @Environment(\.editMode) private var editMode let key: Date let matches: [Match] - + + @State private var isExpanded: Bool = false + @State private var showDateUpdateView: Bool = false + var body: some View { if !matches.isEmpty { if enableMove { TimeSlotHeaderView(key: key, matches: matches) } else { - DisclosureGroup { + DisclosureGroup(isExpanded: $isExpanded) { MatchListView(matches: matches) } label: { TimeSlotHeaderView(key: key, matches: matches) } + .contextMenu { + PlanningView.CourtOptionsView(timeSlots: [key: matches], underlined: false) + + Button { + showDateUpdateView = true + } label: { + Text("Modifier la date") + } + + } + .sheet(isPresented: $showDateUpdateView, onDismiss: { + }) { + PlanningView.DateUpdateView(selectedMatches: matches) + } + +// .onChange(of: editMode?.wrappedValue) { +// if editMode?.wrappedValue == .active, isExpanded == false { +// isExpanded = true +// } else if editMode?.wrappedValue == .inactive, isExpanded == true { +// isExpanded = false +// } +// } } } } @@ -297,7 +466,7 @@ struct PlanningView: View { let matches: [Match] var body: some View { - ForEach(matches) { match in + ForEach(matches, id: \.stringId) { match in NavigationLink { MatchDetailView(match: match) .matchViewStyle(.sectionedStandardStyle) @@ -309,6 +478,7 @@ struct PlanningView: View { } struct MatchRowView: View { + @Environment(\.matchViewStyle) private var matchViewStyle let match: Match var body: some View { @@ -319,15 +489,20 @@ struct PlanningView: View { } label: { if let groupStage = match.groupStageObject { Text(groupStage.groupStageTitle(.title)) + Text(match.matchTitle()) } else if let round = match.roundObject { Text(round.roundTitle()) + if round.index > 0 { + Text(match.matchTitle()) + } + } + if matchViewStyle == .feedStyle, let tournament = match.currentTournament() { + Text(tournament.tournamentTitle()) } - Text(match.matchTitle()) } } } - struct HeaderView: View { @Environment(\.filterOption) private var filterOption @Environment(\.showFinishedMatches) private var showFinishedMatches @@ -365,7 +540,6 @@ struct PlanningView: View { struct TimeSlotHeaderView: View { let key: Date let matches: [Match] - @Environment(Tournament.self) var tournament: Tournament var body: some View { LabeledContent { @@ -378,176 +552,251 @@ struct PlanningView: View { .font(.title) .fontWeight(.semibold) } + + let names = matches.sorted(by: \.computedOrder) + .compactMap({ $0.roundTitle() }) + .reduce(into: [String]()) { uniqueNames, name in + if !uniqueNames.contains(name) { + uniqueNames.append(name) + } + } + Text(names.joined(separator: ", ")).lineLimit(1).truncationMode(.tail) + // if matches.count <= matches.first?.courtCount() ?? { + // } else { + // Text(matches.count.formatted().appending(" matchs")) + // } + + } + } + } + + struct CourtOptionsView: View { + let timeSlots: [Date: [Match]] + let underlined: Bool + var allMatches: [Match] { + timeSlots.flatMap { $0.value } + } + + private func _removeCourts() { + allMatches.forEach { match in + match.courtIndex = nil + } + } + + private func _eventCourtCount() -> Int { timeSlots.first?.value.first?.currentTournament()?.eventObject()?.eventCourtCount() ?? 2 + } + + private func _save() { + let groupByTournaments = allMatches.grouped { match in + match.currentTournament() + } + groupByTournaments.forEach { tournament, matches in + tournament?.tournamentStore?.matches.addOrUpdate(contentOfs: matches) + } + } + + var body: some View { + Menu { + Button("Supprimer") { + _removeCourts() + _save() + } - if matches.count <= tournament.courtCount { - let names = matches.sorted(by: \.computedOrder) - .compactMap({ $0.roundTitle() }) - .reduce(into: [String]()) { uniqueNames, name in - if !uniqueNames.contains(name) { - uniqueNames.append(name) + Button("Tirer au sort") { + _removeCourts() + + let eventCourtCount = _eventCourtCount() + + for slot in timeSlots { + var courtsAvailable = Array(0...eventCourtCount) + let matches = slot.value + matches.forEach { match in + if let rand = courtsAvailable.randomElement() { + match.courtIndex = rand + courtsAvailable.remove(elements: [rand]) } } - Text(names.joined(separator: ", ")).lineLimit(1).truncationMode(.tail) - } else { - Text(matches.count.formatted().appending(" matchs")) + } + _save() + } + Button("Fixer par ordre croissant") { + _removeCourts() + + let eventCourtCount = _eventCourtCount() + + for slot in timeSlots { + var courtsAvailable = Array(0.. Int { -// timeSlots.filter { $0.key.dayInt == dayInt }.flatMap({ $0.value }).count -// } -// -// private func _timeSlotView(key: Date, matches: [Match]) -> some View { -// LabeledContent { -// Text(self._formattedMatchCount(matches.count)) -// } label: { -// if key.monthYearFormatted == Date.distantFuture.monthYearFormatted { -// Text("Aucun horaire") -// } else { -// Text(key.formatted(date: .omitted, time: .shortened)).font(.title).fontWeight(.semibold) -// } -// if matches.count <= tournament.courtCount { -// let names = matches.sorted(by: \.computedOrder) -// .compactMap({ $0.roundTitle() }) -// .reduce(into: [String]()) { uniqueNames, name in -// if !uniqueNames.contains(name) { -// uniqueNames.append(name) -// } -// } -// Text(names.joined(separator: ", ")).lineLimit(1).truncationMode(.tail) -// } else { -// Text(matches.count.formatted().appending(" matchs")) -// } -// } -// } -// -// fileprivate func _formattedMatchCount(_ count: Int) -> String { -// return "\(count.formatted()) match\(count.pluralSuffix)" -// } -// } + // struct BySlotView: View { + // @Environment(Tournament.self) var tournament: Tournament + // let days: [Date] + // let keys: [Date] + // let timeSlots: [Date:[Match]] + // let matches: [Match] + // let selectedDay: Date? + // let filterOption: PlanningFilterOption + // let showFinishedMatches: Bool + // + // var body: some View { + // List { + // if matches.allSatisfy({ $0.startDate == nil }) == false { + // ForEach(days.filter({ selectedDay == nil || selectedDay == $0 }), id: \.self) { day in + // Section { + // ForEach(keys.filter({ $0.dayInt == day.dayInt }), id: \.self) { key in + // if let _matches = timeSlots[key]?.sorted(by: filterOption == .byDefault ? \.computedOrder : \.courtIndexForSorting) { + // DisclosureGroup { + // ForEach(_matches) { match in + // NavigationLink { + // MatchDetailView(match: match) + // .matchViewStyle(.sectionedStandardStyle) + // + // } label: { + // LabeledContent { + // if let courtName = match.courtName() { + // Text(courtName) + // } + // } label: { + // if let groupStage = match.groupStageObject { + // Text(groupStage.groupStageTitle(.title)) + // } else if let round = match.roundObject { + // Text(round.roundTitle()) + // } + // Text(match.matchTitle()) + // } + // } + // } + // } label: { + // _timeSlotView(key: key, matches: _matches) + // } + // } + // } + // .onMove(perform: moveSection) + // } header: { + // HStack { + // if day.monthYearFormatted == Date.distantFuture.monthYearFormatted { + // Text("Sans horaire") + // } else { + // Text(day.formatted(.dateTime.day().weekday().month())) + // } + // Spacer() + // let count = _matchesCount(inDayInt: day.dayInt, timeSlots: timeSlots) + // if showFinishedMatches { + // Text(self._formattedMatchCount(count)) + // } else { + // Text(self._formattedMatchCount(count) + " restant\(count.pluralSuffix)") + // } + // } + // } footer: { + // if day.monthYearFormatted == Date.distantFuture.monthYearFormatted { + // Text("Il s'agit des matchs qui n'ont pas réussi à être placé par Padel Club. Peut-être à cause de créneaux indisponibles, d'autres tournois ou des réglages.") + // } + // } + // .headerProminence(.increased) + // } + // } + // } + // } + // + // func moveSection(from source: IndexSet, to destination: Int) { + // let daySlots = keys.filter { selectedDay == nil || $0.dayInt == selectedDay?.dayInt }.sorted() + // + // guard let sourceIdx = source.first, + // sourceIdx < daySlots.count, + // destination <= daySlots.count else { + // return + // } + // + // // Create a mutable copy of the time slots for this day + // var slotsToUpdate = daySlots + // + // let updateRange = min(sourceIdx, destination)...max(sourceIdx, destination) - 1 + // print(updateRange) + // + // // Perform the move in the array + // let sourceTime = slotsToUpdate.remove(at: sourceIdx) + // if sourceIdx < destination { + // slotsToUpdate.insert(sourceTime, at: destination - 1) + // } else { + // slotsToUpdate.insert(sourceTime, at: destination) + // } + // + // // Update matches by swapping their startDates + // for index in updateRange { + // // Find the new time slot for these matches + // let oldStartTime = slotsToUpdate[index] + // let newStartTime = daySlots[index] + // guard let matchesToUpdate = timeSlots[oldStartTime] else { continue } + // print("moving", oldStartTime, "to", newStartTime) + // + // // Update each match with the new start time + // for match in matchesToUpdate { + // match.startDate = newStartTime + // } + // } + // + // try? self.tournament.tournamentStore?.matches.addOrUpdate(contentOfs: matches) + // } + // + // + // private func _matchesCount(inDayInt dayInt: Int, timeSlots: [Date:[Match]]) -> Int { + // timeSlots.filter { $0.key.dayInt == dayInt }.flatMap({ $0.value }).count + // } + // + // private func _timeSlotView(key: Date, matches: [Match]) -> some View { + // LabeledContent { + // Text(self._formattedMatchCount(matches.count)) + // } label: { + // if key.monthYearFormatted == Date.distantFuture.monthYearFormatted { + // Text("Aucun horaire") + // } else { + // Text(key.formatted(date: .omitted, time: .shortened)).font(.title).fontWeight(.semibold) + // } + // if matches.count <= tournament.courtCount { + // let names = matches.sorted(by: \.computedOrder) + // .compactMap({ $0.roundTitle() }) + // .reduce(into: [String]()) { uniqueNames, name in + // if !uniqueNames.contains(name) { + // uniqueNames.append(name) + // } + // } + // Text(names.joined(separator: ", ")).lineLimit(1).truncationMode(.tail) + // } else { + // Text(matches.count.formatted().appending(" matchs")) + // } + // } + // } + // + // fileprivate func _formattedMatchCount(_ count: Int) -> String { + // return "\(count.formatted()) match\(count.pluralSuffix)" + // } + // } } enum PlanningFilterOption: Int, CaseIterable, Identifiable { var id: Int { self.rawValue } - + case byDefault case byCourt - + func localizedPlanningLabel() -> String { switch self { case .byCourt: @@ -558,7 +807,6 @@ enum PlanningFilterOption: Int, CaseIterable, Identifiable { } } - struct FilterOptionKey: EnvironmentKey { static let defaultValue: PlanningFilterOption = .byDefault } From f814bc84c556100b0b22afca33833daf258a9028 Mon Sep 17 00:00:00 2001 From: Raz Date: Wed, 14 May 2025 20:23:09 +0200 Subject: [PATCH 04/27] update jap list export update for debug --- PadelClub/Data/Federal/FederalTournament.swift | 7 ++++++- .../Agenda/TournamentLookUpView.swift | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/PadelClub/Data/Federal/FederalTournament.swift b/PadelClub/Data/Federal/FederalTournament.swift index 5feb4e1..0021712 100644 --- a/PadelClub/Data/Federal/FederalTournament.swift +++ b/PadelClub/Data/Federal/FederalTournament.swift @@ -81,6 +81,11 @@ struct FederalTournament: Identifiable, Codable { var dateFin, dateValidation: Date? var codePostalEngagement, codeClub: String? var prixEspece: Int? + var japPhoneNumber: String? + + mutating func updateJapPhoneNumber(phone: String?) { + self.japPhoneNumber = phone + } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) @@ -249,7 +254,7 @@ struct FederalTournament: Identifiable, Codable { } var japMessage: String { - [nomClub, jugeArbitre?.nom, jugeArbitre?.prenom, courrielEngagement, installation?.telephone].compactMap({$0}).joined(separator: ";") + [nomClub, jugeArbitre?.nom, jugeArbitre?.prenom, courrielEngagement, japPhoneNumber].compactMap({$0}).joined(separator: ";") } func umpireLabel() -> String { diff --git a/PadelClub/Views/Navigation/Agenda/TournamentLookUpView.swift b/PadelClub/Views/Navigation/Agenda/TournamentLookUpView.swift index 7cecadc..5f9410f 100644 --- a/PadelClub/Views/Navigation/Agenda/TournamentLookUpView.swift +++ b/PadelClub/Views/Navigation/Agenda/TournamentLookUpView.swift @@ -133,6 +133,9 @@ struct TournamentLookUpView: View { Menu { #if DEBUG if tournaments.isEmpty == false { + Button("Gather Mobile Phone") { + _gatherNumbers() + } Section { ShareLink(item: pastedTournaments) { Label("Par texte", systemImage: "square.and.arrow.up") @@ -180,6 +183,20 @@ struct TournamentLookUpView: View { private var liguesFound: [String] { Set(tournaments.compactMap { $0.nomLigue }).sorted() } + + private func _gatherNumbers() { + Task { + print("Doing.....") + for i in 0.. Date: Wed, 14 May 2025 20:27:47 +0200 Subject: [PATCH 05/27] v1.2.25 --- PadelClub.xcodeproj/project.pbxproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 3b64fd9..8a51347 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -3120,7 +3120,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.24; + MARKETING_VERSION = 1.2.25; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3166,7 +3166,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.24; + MARKETING_VERSION = 1.2.25; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3285,7 +3285,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.24; + MARKETING_VERSION = 1.2.25; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3330,7 +3330,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.24; + MARKETING_VERSION = 1.2.25; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3374,7 +3374,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.24; + MARKETING_VERSION = 1.2.25; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub.beta; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3416,7 +3416,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.24; + MARKETING_VERSION = 1.2.25; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub.beta; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; From 1b4a0204c140df4a7e9176160f40c0c98442330e Mon Sep 17 00:00:00 2001 From: Raz Date: Thu, 15 May 2025 15:18:04 +0200 Subject: [PATCH 06/27] fix event settings stuff --- .../Cashier/Event/EventCreationView.swift | 25 ++++--- .../Cashier/Event/EventSettingsView.swift | 15 ++++ .../Cashier/Event/EventTournamentsView.swift | 73 ++++++++++++++++--- PadelClub/Views/Cashier/Event/EventView.swift | 2 +- PadelClub/Views/Planning/PlanningView.swift | 6 +- 5 files changed, 94 insertions(+), 27 deletions(-) diff --git a/PadelClub/Views/Cashier/Event/EventCreationView.swift b/PadelClub/Views/Cashier/Event/EventCreationView.swift index fcef799..b63e77b 100644 --- a/PadelClub/Views/Cashier/Event/EventCreationView.swift +++ b/PadelClub/Views/Cashier/Event/EventCreationView.swift @@ -71,6 +71,19 @@ struct EventCreationView: View { .multilineTextAlignment(.leading) .frame(maxWidth: .infinity) .focused($textFieldIsFocus) + .toolbar { + if textFieldIsFocus { + ToolbarItem(placement: .keyboard) { + HStack { + Spacer() + Button("Valider") { + textFieldIsFocus = false + } + .buttonStyle(.bordered) + } + } + } + } LabeledContent { Text(tournaments.count.formatted()) } label: { @@ -93,18 +106,6 @@ struct EventCreationView: View { } } .toolbar { - if textFieldIsFocus { - ToolbarItem(placement: .keyboard) { - HStack { - Spacer() - Button("Valider") { - textFieldIsFocus = false - } - .buttonStyle(.bordered) - } - } - } - ToolbarItem(placement: .cancellationAction) { Button("Annuler", role: .cancel) { dismiss() diff --git a/PadelClub/Views/Cashier/Event/EventSettingsView.swift b/PadelClub/Views/Cashier/Event/EventSettingsView.swift index 49dd216..9d24df8 100644 --- a/PadelClub/Views/Cashier/Event/EventSettingsView.swift +++ b/PadelClub/Views/Cashier/Event/EventSettingsView.swift @@ -15,6 +15,7 @@ struct EventSettingsView: View { @State private var eventName: String = "" @State private var pageLink: PageLink = .teams @State private var tournamentInformation: String = "" + @State private var eventStartDate: Date @FocusState private var focusedField: Tournament.CodingKeys? func eventLinksPasteData() -> String { @@ -43,6 +44,7 @@ struct EventSettingsView: View { init(event: Event) { self.event = event _eventName = State(wrappedValue: event.name ?? "") + _eventStartDate = .init(wrappedValue: event.eventStartDate()) _tournamentInformation = State(wrappedValue: event.tournaments.first?.information ?? "") } @@ -67,6 +69,19 @@ struct EventSettingsView: View { } } + Section { + DatePicker(selection: $eventStartDate) { + Text(eventStartDate.formatted(.dateTime.weekday(.wide)).capitalized).lineLimit(1) + } + .onChange(of: eventStartDate) { + event.tournaments.forEach { tournament in + tournament.startDate = eventStartDate + } + + dataStore.tournaments.addOrUpdate(contentOfs: event.tournaments) + } + } + if event.tournaments.first?.dayDuration == 3, event.tournaments.count == 3 { Section { RowButtonView("Répartir les tournois") { diff --git a/PadelClub/Views/Cashier/Event/EventTournamentsView.swift b/PadelClub/Views/Cashier/Event/EventTournamentsView.swift index 3901c77..3924aab 100644 --- a/PadelClub/Views/Cashier/Event/EventTournamentsView.swift +++ b/PadelClub/Views/Cashier/Event/EventTournamentsView.swift @@ -44,19 +44,68 @@ struct EventTournamentsView: View { } } footer: { if event.tournaments.count > 1 { - if mainTournament == nil { - FooterButtonView("c'est le tournoi principal") { - self.mainTournament = tournament - } - } else if mainTournament == tournament { - FooterButtonView("ce n'est pas le tournoi principal") { - self.mainTournament = tournament + if let mainTournament, mainTournament == tournament { + Menu { + Button("Formats") { + tournaments.forEach { tournament in + tournament.groupStageMatchFormat = mainTournament.groupStageMatchFormat + tournament.loserBracketMatchFormat = mainTournament.loserBracketMatchFormat + tournament.matchFormat = mainTournament.matchFormat + } + dataStore.tournaments.addOrUpdate(contentOfs: tournaments) + } + + Button("Infos JAP") { + tournaments.forEach { tournament in + tournament.setupUmpireSettings(defaultTournament: mainTournament) + } + dataStore.tournaments.addOrUpdate(contentOfs: tournaments) + } + + Button("Réglages Inscriptions") { + tournaments.forEach { tournament in + tournament.setupRegistrationSettings(templateTournament: mainTournament) + } + dataStore.tournaments.addOrUpdate(contentOfs: tournaments) + } + } label: { + Text("Copier des réglages sur les autres tournois") + .underline() + .multilineTextAlignment(.leading) } - } else if let mainTournament { - FooterButtonView("coller les réglages du tournoi principal") { - tournament.setupUmpireSettings(defaultTournament: mainTournament) - tournament.setupRegistrationSettings(templateTournament: mainTournament) - dataStore.tournaments.addOrUpdate(instance: tournament) + + } else { + Menu { + if tournament != self.mainTournament { + Button("Définir comme tournoi principal") { + self.mainTournament = tournament + } + } + + if let mainTournament { + + Divider() + + Button("Copier les formats du tournoi principal") { + tournament.groupStageMatchFormat = mainTournament.groupStageMatchFormat + tournament.loserBracketMatchFormat = mainTournament.loserBracketMatchFormat + tournament.matchFormat = mainTournament.matchFormat + dataStore.tournaments.addOrUpdate(instance: tournament) + } + + Button("Copier les infos JAP du tournoi principal") { + tournament.setupUmpireSettings(defaultTournament: mainTournament) + dataStore.tournaments.addOrUpdate(instance: tournament) + } + + Button("Copier les réglages des inscriptions du tournoi principal") { + tournament.setupRegistrationSettings(templateTournament: mainTournament) + dataStore.tournaments.addOrUpdate(instance: tournament) + } + } + } label: { + Text("Options rapides pour certains réglages") + .underline() } } } diff --git a/PadelClub/Views/Cashier/Event/EventView.swift b/PadelClub/Views/Cashier/Event/EventView.swift index cb0214e..365442d 100644 --- a/PadelClub/Views/Cashier/Event/EventView.swift +++ b/PadelClub/Views/Cashier/Event/EventView.swift @@ -95,7 +95,7 @@ struct EventView: View { EventClubSettingsView(event: event) case .eventPlanning: let allMatches = event.tournaments.flatMap { $0.allMatches() } - PlanningView(matches: allMatches, selectedScheduleDestination: .constant(.planning)) + PlanningView(matches: allMatches, selectedScheduleDestination: .constant(nil)) .environment(\.matchViewStyle, .feedStyle) case .links: EventLinksView(event: event) diff --git a/PadelClub/Views/Planning/PlanningView.swift b/PadelClub/Views/Planning/PlanningView.swift index c964187..937bf9c 100644 --- a/PadelClub/Views/Planning/PlanningView.swift +++ b/PadelClub/Views/Planning/PlanningView.swift @@ -201,8 +201,10 @@ struct PlanningView: View { "Vous n'avez pas encore défini d'horaire pour les différentes phases du tournoi" ) } actions: { - RowButtonView("Horaire intelligent") { - selectedScheduleDestination = nil + if selectedScheduleDestination != nil { + RowButtonView("Horaire intelligent") { + selectedScheduleDestination = nil + } } } } From 4f98a956b10f733b6b7a43bcb563f2f3d22c4cab Mon Sep 17 00:00:00 2001 From: Raz Date: Fri, 16 May 2025 00:04:44 +0200 Subject: [PATCH 07/27] add csv helper fix refresh table round view --- PadelClub.xcodeproj/project.pbxproj | 12 ++++++------ PadelClub/Views/Round/LoserRoundsView.swift | 4 ++-- PadelClub/Views/Round/RoundView.swift | 7 +++++++ 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 8a51347..927766e 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -3120,7 +3120,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.25; + MARKETING_VERSION = 1.2.26; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3166,7 +3166,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.25; + MARKETING_VERSION = 1.2.26; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3285,7 +3285,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.25; + MARKETING_VERSION = 1.2.26; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3330,7 +3330,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.25; + MARKETING_VERSION = 1.2.26; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3374,7 +3374,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.25; + MARKETING_VERSION = 1.2.26; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub.beta; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3416,7 +3416,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.25; + MARKETING_VERSION = 1.2.26; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub.beta; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/PadelClub/Views/Round/LoserRoundsView.swift b/PadelClub/Views/Round/LoserRoundsView.swift index fd2e837..1b5046d 100644 --- a/PadelClub/Views/Round/LoserRoundsView.swift +++ b/PadelClub/Views/Round/LoserRoundsView.swift @@ -13,14 +13,14 @@ class UpperRound: Identifiable, Selectable { let round: Round var loserRounds: [LoserRound] = [] let title: String - let playedMatches: [Match] + var playedMatches: [Match] var correspondingLoserRoundTitle: String init(round: Round) { self.round = round let title = round.roundTitle(.short) self.title = title - self.playedMatches = round.playedMatches() + self.playedMatches = [] self.correspondingLoserRoundTitle = "Match de classement \(title)" } diff --git a/PadelClub/Views/Round/RoundView.swift b/PadelClub/Views/Round/RoundView.swift index 1f8892d..bb0cb10 100644 --- a/PadelClub/Views/Round/RoundView.swift +++ b/PadelClub/Views/Round/RoundView.swift @@ -27,6 +27,10 @@ struct RoundView: View { var upperRound: UpperRound + func _refreshRound() { + self.upperRound.playedMatches = self.upperRound.round.playedMatches() + } + init(upperRound: UpperRound) { self.upperRound = upperRound // let seeds = upperRound.round.seeds() @@ -252,6 +256,9 @@ struct RoundView: View { } } } + .onAppear(perform: { + self._refreshRound() + }) .task { await MainActor.run { let seeds = self.upperRound.round.seeds() From 266caec83b038cc18a99153a01c20c38c4676b4a Mon Sep 17 00:00:00 2001 From: Raz Date: Fri, 16 May 2025 00:25:47 +0200 Subject: [PATCH 08/27] fix issue with court setup ordering in planning --- PadelClub.xcodeproj/project.pbxproj | 12 ++++++------ PadelClub/Views/Planning/PlanningView.swift | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 927766e..3efa087 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -3094,7 +3094,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; @@ -3141,7 +3141,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; @@ -3259,7 +3259,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; @@ -3305,7 +3305,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; @@ -3351,7 +3351,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; @@ -3394,7 +3394,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; diff --git a/PadelClub/Views/Planning/PlanningView.swift b/PadelClub/Views/Planning/PlanningView.swift index 937bf9c..62babea 100644 --- a/PadelClub/Views/Planning/PlanningView.swift +++ b/PadelClub/Views/Planning/PlanningView.swift @@ -630,7 +630,7 @@ struct PlanningView: View { for slot in timeSlots { var courtsAvailable = Array(0.. Date: Fri, 16 May 2025 08:09:07 +0200 Subject: [PATCH 09/27] fix issue with loser round view --- PadelClub.xcodeproj/project.pbxproj | 12 ++++++------ PadelClub/Views/Round/LoserRoundView.swift | 1 + PadelClub/Views/Round/LoserRoundsView.swift | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 3efa087..395f58e 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -3094,7 +3094,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; @@ -3141,7 +3141,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; @@ -3259,7 +3259,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; @@ -3305,7 +3305,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; @@ -3351,7 +3351,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; @@ -3394,7 +3394,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; diff --git a/PadelClub/Views/Round/LoserRoundView.swift b/PadelClub/Views/Round/LoserRoundView.swift index dce0ef7..235c7c1 100644 --- a/PadelClub/Views/Round/LoserRoundView.swift +++ b/PadelClub/Views/Round/LoserRoundView.swift @@ -48,6 +48,7 @@ struct LoserRoundView: View { if isEditingTournamentSeed.wrappedValue == true { RowButtonView(match.disabled ? "Jouer ce match" : "Ne pas jouer ce match", role: .destructive) { match._toggleMatchDisableState(!match.disabled, single: true) + loserBracket.updateEnabledMatches() } } } diff --git a/PadelClub/Views/Round/LoserRoundsView.swift b/PadelClub/Views/Round/LoserRoundsView.swift index 1b5046d..53179cd 100644 --- a/PadelClub/Views/Round/LoserRoundsView.swift +++ b/PadelClub/Views/Round/LoserRoundsView.swift @@ -20,7 +20,7 @@ class UpperRound: Identifiable, Selectable { self.round = round let title = round.roundTitle(.short) self.title = title - self.playedMatches = [] + self.playedMatches = round.playedMatches() self.correspondingLoserRoundTitle = "Match de classement \(title)" } From 9cb968c441146184197a1e356e70599776ead19a Mon Sep 17 00:00:00 2001 From: Raz Date: Sun, 18 May 2025 19:55:00 +0200 Subject: [PATCH 10/27] fix live scoring fix p1500 points repartition fix planning view stuff --- PadelClub/Views/Planning/PlanningView.swift | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/PadelClub/Views/Planning/PlanningView.swift b/PadelClub/Views/Planning/PlanningView.swift index 62babea..66138e8 100644 --- a/PadelClub/Views/Planning/PlanningView.swift +++ b/PadelClub/Views/Planning/PlanningView.swift @@ -33,7 +33,7 @@ struct PlanningView: View { } var timeSlots: [Date: [Match]] { - Dictionary(grouping: matches) { $0.startDate ?? .distantFuture } + Dictionary(grouping: matches) { $0.plannedStartDate ?? $0.startDate ?? .distantFuture } } func days(timeSlots: [Date: [Match]]) -> [Date] { @@ -279,7 +279,7 @@ struct PlanningView: View { self.selectedFormats = Array(Set(selectedMatches.map({ match in match.matchFormat }))) - _dateToUpdate = .init(wrappedValue: selectedMatches.first?.startDate ?? Date()) + _dateToUpdate = .init(wrappedValue: selectedMatches.first?.plannedStartDate ?? selectedMatches.first?.startDate ?? Date()) } var body: some View { @@ -328,7 +328,15 @@ struct PlanningView: View { private func _updateDate() { selectedMatches.forEach { match in - match.startDate = dateToUpdate + if match.hasStarted() || match.hasEnded() { + match.plannedStartDate = dateToUpdate + } else { + let hasStarted = match.currentTournament()?.hasStarted() == true + match.startDate = dateToUpdate + if hasStarted { + match.plannedStartDate = dateToUpdate + } + } } let groupByTournaments = selectedMatches.grouped { match in @@ -501,6 +509,8 @@ struct PlanningView: View { if matchViewStyle == .feedStyle, let tournament = match.currentTournament() { Text(tournament.tournamentTitle()) } + + Text(match.startDate?.formattedAsHourMinute() ?? "--") } } } From 7cd866185eb25972fc7703c7ead41ca7143812a8 Mon Sep 17 00:00:00 2001 From: Raz Date: Mon, 19 May 2025 18:13:45 +0200 Subject: [PATCH 11/27] fix registration import issue fix p2000 stuff --- PadelClub.xcodeproj/project.pbxproj | 24 +++--- .../PlayerRegistration+Extensions.swift | 1 + .../TeamRegistration+Extensions.swift | 4 + PadelClub/Utils/FileImportManager.swift | 10 ++- .../Views/Cashier/CashierDetailView.swift | 12 ++- .../Views/Cashier/CashierSettingsView.swift | 84 +++++++++++++++---- PadelClub/Views/Club/ClubDetailView.swift | 4 +- PadelClub/Views/Player/PlayerDetailView.swift | 6 +- .../Views/Tournament/Screen/AddTeamView.swift | 4 + .../TournamentGeneralSettingsView.swift | 46 ++++++++-- .../Screen/RegistrationSetupView.swift | 16 +++- .../Views/Tournament/TournamentView.swift | 4 + 12 files changed, 166 insertions(+), 49 deletions(-) diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 395f58e..e11f252 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -3094,7 +3094,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; @@ -3120,7 +3120,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.26; + MARKETING_VERSION = 1.2.27; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3141,7 +3141,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; @@ -3166,7 +3166,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.26; + MARKETING_VERSION = 1.2.27; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3259,7 +3259,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; @@ -3285,7 +3285,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.26; + MARKETING_VERSION = 1.2.27; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3305,7 +3305,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; @@ -3330,7 +3330,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.26; + MARKETING_VERSION = 1.2.27; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3351,7 +3351,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; @@ -3374,7 +3374,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.26; + MARKETING_VERSION = 1.2.27; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub.beta; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3394,7 +3394,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; @@ -3416,7 +3416,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.26; + MARKETING_VERSION = 1.2.27; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub.beta; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/PadelClub/Extensions/PlayerRegistration+Extensions.swift b/PadelClub/Extensions/PlayerRegistration+Extensions.swift index e702d05..abf75df 100644 --- a/PadelClub/Extensions/PlayerRegistration+Extensions.swift +++ b/PadelClub/Extensions/PlayerRegistration+Extensions.swift @@ -21,6 +21,7 @@ extension PlayerRegistration { self.tournamentPlayed = importedPlayer.tournamentPlayed self.points = importedPlayer.getPoints() self.clubName = importedPlayer.clubName?.prefixTrimmed(200) + self.clubCode = importedPlayer.clubCode?.replaceCharactersFromSet(characterSet: .whitespaces).prefixTrimmed(20) self.ligueName = importedPlayer.ligueName?.prefixTrimmed(200) self.assimilation = importedPlayer.assimilation?.prefixTrimmed(50) self.source = .frenchFederation diff --git a/PadelClub/Extensions/TeamRegistration+Extensions.swift b/PadelClub/Extensions/TeamRegistration+Extensions.swift index dd1e8a1..2a80c2a 100644 --- a/PadelClub/Extensions/TeamRegistration+Extensions.swift +++ b/PadelClub/Extensions/TeamRegistration+Extensions.swift @@ -45,6 +45,10 @@ extension TeamRegistration { player.captain = oldPlayer.captain player.assimilation = oldPlayer.assimilation player.ligueName = oldPlayer.ligueName + player.registrationStatus = oldPlayer.registrationStatus + player.timeToConfirm = oldPlayer.timeToConfirm + player.paymentId = oldPlayer.paymentId + player.clubMember = oldPlayer.clubMember } } } diff --git a/PadelClub/Utils/FileImportManager.swift b/PadelClub/Utils/FileImportManager.swift index 01d2286..3e9c1d4 100644 --- a/PadelClub/Utils/FileImportManager.swift +++ b/PadelClub/Utils/FileImportManager.swift @@ -307,9 +307,11 @@ class FileImportManager { if (tournamentCategory == tournament.tournamentCategory && tournamentAgeCategory == tournament.federalTournamentAge) || checkingCategoryDisabled { let playerOne = PlayerRegistration(federalData: Array(resultOne[0...7]), sex: sexPlayerOne, sexUnknown: sexUnknown) playerOne?.setComputedRank(in: tournament) + playerOne?.setClubMember(for: tournament) let playerTwo = PlayerRegistration(federalData: Array(resultTwo[0...7]), sex: sexPlayerTwo, sexUnknown: sexUnknown) playerTwo?.setComputedRank(in: tournament) - + playerTwo?.setClubMember(for: tournament) + let players = [playerOne, playerTwo].compactMap({ $0 }) if players.isEmpty == false { let team = TeamHolder(players: players, tournamentCategory: tournamentCategory, tournamentAgeCategory: tournamentAgeCategory, previousTeam: tournament.findTeam(players), tournament: tournament) @@ -368,9 +370,11 @@ class FileImportManager { let playerOne = PlayerRegistration(federalData: Array(result[0...7]), sex: sexPlayerOne, sexUnknown: sexUnknown) playerOne?.setComputedRank(in: tournament) + playerOne?.setClubMember(for: tournament) let playerTwo = PlayerRegistration(federalData: Array(result[8...]), sex: sexPlayerTwo, sexUnknown: sexUnknown) playerTwo?.setComputedRank(in: tournament) - + playerTwo?.setClubMember(for: tournament) + let players = [playerOne, playerTwo].compactMap({ $0 }) if players.isEmpty == false { let team = TeamHolder(players: players, tournamentCategory: tournamentCategory, tournamentAgeCategory: tournamentAgeCategory, previousTeam: tournament.findTeam(players), tournament: tournament) @@ -404,6 +408,7 @@ class FileImportManager { let registeredPlayers = found?.map({ importedPlayer in let player = PlayerRegistration(importedPlayer: importedPlayer) player.setComputedRank(in: tournament) + player.setClubMember(for: tournament) return player }) if let registeredPlayers, registeredPlayers.isEmpty == false { @@ -466,6 +471,7 @@ class FileImportManager { if let found, autoSearch { let player = PlayerRegistration(importedPlayer: found) player.setComputedRank(in: tournament) + player.setClubMember(for: tournament) player.email = email player.phoneNumber = phoneNumber return player diff --git a/PadelClub/Views/Cashier/CashierDetailView.swift b/PadelClub/Views/Cashier/CashierDetailView.swift index cb7ccdd..22bba64 100644 --- a/PadelClub/Views/Cashier/CashierDetailView.swift +++ b/PadelClub/Views/Cashier/CashierDetailView.swift @@ -203,16 +203,14 @@ struct CashierDetailView: View { DisclosureGroup { let selectedPlayers = tournament.selectedPlayers() ForEach(PlayerPaymentType.allCases) { type in - let count = selectedPlayers.filter({ $0.paymentType == type }).count - if count > 0 { + let players = selectedPlayers.filter({ $0.paymentType == type }) + if players.count > 0 { LabeledContent { - if let entryFee = tournament.entryFee { - let sum = Double(count) * entryFee - Text(sum.formatted(.currency(code: Locale.defaultCurrency()))) - } + let sum = players.compactMap({ $0.paidAmount(tournament) }).reduce(0.0, +) + Text(sum.formatted(.currency(code: Locale.defaultCurrency()))) } label: { Text(type.localizedLabel()) - Text(count.formatted()) + Text(players.count.formatted()) } } } diff --git a/PadelClub/Views/Cashier/CashierSettingsView.swift b/PadelClub/Views/Cashier/CashierSettingsView.swift index f97e7ac..01110d4 100644 --- a/PadelClub/Views/Cashier/CashierSettingsView.swift +++ b/PadelClub/Views/Cashier/CashierSettingsView.swift @@ -13,25 +13,50 @@ struct CashierSettingsView: View { @EnvironmentObject var dataStore: DataStore @State private var entryFee: Double? = nil + @State private var clubMemberFeeDeduction: Double? = nil @Bindable var tournament: Tournament @FocusState private var focusedField: Tournament.CodingKeys? let priceTags: [Double] = [15.0, 20.0, 25.0] - + let deductionTags: [Double] = [5.0, 10.0] + init(tournament: Tournament) { self.tournament = tournament _entryFee = State(wrappedValue: tournament.entryFee) + _clubMemberFeeDeduction = State(wrappedValue: tournament.clubMemberFeeDeduction) } var body: some View { List { Section { - TextField(tournament.isFree() ? "Gratuite" : "Inscription", value: $entryFee, format: .currency(code: Locale.defaultCurrency())) - .keyboardType(.decimalPad) - .multilineTextAlignment(.trailing) - .frame(maxWidth: .infinity) - .focused($focusedField, equals: ._entryFee) + LabeledContent { + TextField(tournament.isFree() ? "Gratuite" : "Inscription", value: $entryFee, format: .currency(code: Locale.defaultCurrency())) + .keyboardType(.decimalPad) + .multilineTextAlignment(.trailing) + .frame(maxWidth: .infinity) + .focused($focusedField, equals: ._entryFee) + } label: { + Text("Frais d'inscription") + } + LabeledContent { + TextField("Réduction", value: $clubMemberFeeDeduction, format: .currency(code: Locale.defaultCurrency())) + .keyboardType(.decimalPad) + .multilineTextAlignment(.trailing) + .frame(maxWidth: .infinity) + .focused($focusedField, equals: ._clubMemberFeeDeduction) + .onChange(of: focusedField) { + if focusedField == ._clubMemberFeeDeduction { + DispatchQueue.main.async { + UIApplication.shared.sendAction(#selector(UIResponder.selectAll(_:)), to: nil, from: nil, for: nil) + } + } + } + } label: { + Text("Réduction membre") + } + .disabled(tournament.isFree()) + } header: { - Text("Prix de l'inscription") + Text("Frais d'inscription") } footer: { Text("Si vous souhaitez que Padel Club vous aide à suivre les encaissements, indiquer un prix d'inscription. Sinon Padel Club vous aidera à suivre simplement l'arrivée et la présence des joueurs.") } @@ -104,27 +129,43 @@ struct CashierSettingsView: View { ToolbarItem(placement: .keyboard) { HStack { - if tournament.isFree() { - ForEach(priceTags, id: \.self) { priceTag in - Button(priceTag.formatted(.currency(code: Locale.defaultCurrency()))) { - entryFee = priceTag - tournament.entryFee = priceTag + if focusedField == ._entryFee { + if tournament.isFree() { + ForEach(priceTags, id: \.self) { priceTag in + Button(priceTag.formatted(.currency(code: Locale.defaultCurrency()))) { + entryFee = priceTag + tournament.entryFee = priceTag + focusedField = nil + } + .buttonStyle(.bordered) + } + } else { + Button("Gratuit") { + entryFee = nil + tournament.entryFee = nil + focusedField = nil + } + .buttonStyle(.bordered) + + } + } else if focusedField == ._clubMemberFeeDeduction { + ForEach(deductionTags, id: \.self) { deductionTag in + Button(deductionTag.formatted(.currency(code: Locale.defaultCurrency()).precision(.fractionLength(0)))) { + clubMemberFeeDeduction = deductionTag + tournament.clubMemberFeeDeduction = deductionTag focusedField = nil } .buttonStyle(.bordered) } - } else { Button("Gratuit") { - entryFee = nil - tournament.entryFee = nil + clubMemberFeeDeduction = entryFee + tournament.clubMemberFeeDeduction = clubMemberFeeDeduction focusedField = nil } .buttonStyle(.bordered) - } Spacer() Button("Valider") { - tournament.entryFee = entryFee focusedField = nil } .buttonStyle(.bordered) @@ -132,7 +173,14 @@ struct CashierSettingsView: View { } } } - .onChange(of: tournament.entryFee) { + .onChange(of: focusedField) { old, new in + if old == ._entryFee { + tournament.entryFee = entryFee + } else if old == ._clubMemberFeeDeduction { + tournament.clubMemberFeeDeduction = clubMemberFeeDeduction + } + } + .onChange(of: [tournament.entryFee, tournament.clubMemberFeeDeduction]) { _save() } } diff --git a/PadelClub/Views/Club/ClubDetailView.swift b/PadelClub/Views/Club/ClubDetailView.swift index 8fa26c2..1c0f9eb 100644 --- a/PadelClub/Views/Club/ClubDetailView.swift +++ b/PadelClub/Views/Club/ClubDetailView.swift @@ -245,10 +245,10 @@ struct ClubDetailView: View { CourtView(court: court) } .onChange(of: zipCode) { - club.zipCode = zipCode + club.zipCode = zipCode.prefixTrimmed(10) } .onChange(of: city) { - club.city = city + club.city = city.prefixTrimmed(100) } .onDisappear { if displayContext == .edition && clubDeleted == false { diff --git a/PadelClub/Views/Player/PlayerDetailView.swift b/PadelClub/Views/Player/PlayerDetailView.swift index 9a07ef1..7bc6597 100644 --- a/PadelClub/Views/Player/PlayerDetailView.swift +++ b/PadelClub/Views/Player/PlayerDetailView.swift @@ -80,6 +80,10 @@ struct PlayerDetailView: View { Toggle("Joueur sur place", isOn: $player.hasArrived) Toggle("Capitaine", isOn: $player.captain).disabled(player.hasPaidOnline()) //Toggle("Coach", isOn: $player.coach) + + Toggle(isOn: $player.clubMember) { + Text("Membre du club") + } } Section { @@ -256,7 +260,7 @@ struct PlayerDetailView: View { // } // } } - .onChange(of: [player.hasArrived, player.captain, player.coach]) { + .onChange(of: [player.hasArrived, player.captain, player.coach, player.clubMember]) { _save() } .onChange(of: player.sex) { diff --git a/PadelClub/Views/Tournament/Screen/AddTeamView.swift b/PadelClub/Views/Tournament/Screen/AddTeamView.swift index 382fbed..66f6a1a 100644 --- a/PadelClub/Views/Tournament/Screen/AddTeamView.swift +++ b/PadelClub/Views/Tournament/Screen/AddTeamView.swift @@ -155,6 +155,7 @@ struct AddTeamView: View { players.forEach { player in let newPlayer = PlayerRegistration(importedPlayer: player) newPlayer.setComputedRank(in: tournament) + newPlayer.setClubMember(for: tournament) createdPlayers = Set() createdPlayerIds = Set() createdPlayers.insert(newPlayer) @@ -176,6 +177,7 @@ struct AddTeamView: View { players.forEach { player in let newPlayer = PlayerRegistration(importedPlayer: player) newPlayer.setComputedRank(in: tournament) + newPlayer.setClubMember(for: tournament) createdPlayers.insert(newPlayer) createdPlayerIds.insert(newPlayer.id) } @@ -183,6 +185,7 @@ struct AddTeamView: View { searchViewModel.selectedPlayers.forEach { player in let newPlayer = PlayerRegistration(importedPlayer: player) newPlayer.setComputedRank(in: tournament) + newPlayer.setClubMember(for: tournament) createdPlayers.insert(newPlayer) createdPlayerIds.insert(newPlayer.id) } @@ -336,6 +339,7 @@ struct AddTeamView: View { }.forEach { player in let player = PlayerRegistration(importedPlayer: player) player.setComputedRank(in: tournament) + player.setClubMember(for: tournament) currentSelection.insert(player) } diff --git a/PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift b/PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift index 0c40db0..66d7de0 100644 --- a/PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift +++ b/PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift @@ -16,6 +16,7 @@ struct TournamentGeneralSettingsView: View { @State private var tournamentName: String = "" @State private var tournamentInformation: String = "" @State private var entryFee: Double? = nil + @State private var clubMemberFeeDeduction: Double? = nil @State private var umpireCustomMail: String @State private var umpireCustomPhone: String @State private var umpireCustomContact: String @@ -24,12 +25,14 @@ struct TournamentGeneralSettingsView: View { @FocusState private var focusedField: Tournament.CodingKeys? let priceTags: [Double] = [15.0, 20.0, 25.0] + let deductionTags: [Double] = [5.0, 10.0] init(tournament: Tournament) { self.tournament = tournament _tournamentName = State(wrappedValue: tournament.name ?? "") _tournamentInformation = State(wrappedValue: tournament.information ?? "") _entryFee = State(wrappedValue: tournament.entryFee) + _clubMemberFeeDeduction = State(wrappedValue: tournament.clubMemberFeeDeduction) _umpireCustomMail = State(wrappedValue: tournament.umpireCustomMail ?? "") _umpireCustomPhone = State(wrappedValue: tournament.umpireCustomPhone ?? "") _umpireCustomContact = State(wrappedValue: tournament.umpireCustomContact ?? "") @@ -58,6 +61,24 @@ struct TournamentGeneralSettingsView: View { } label: { Text("Inscription") } + + LabeledContent { + TextField("Réduction", value: $clubMemberFeeDeduction, format: .currency(code: Locale.defaultCurrency())) + .keyboardType(.decimalPad) + .multilineTextAlignment(.trailing) + .frame(maxWidth: .infinity) + .focused($focusedField, equals: ._clubMemberFeeDeduction) + .onChange(of: focusedField) { + if focusedField == ._clubMemberFeeDeduction { + DispatchQueue.main.async { + UIApplication.shared.sendAction(#selector(UIResponder.selectAll(_:)), to: nil, from: nil, for: nil) + } + } + } + } label: { + Text("Réduction membre") + } + .disabled(tournament.isFree()) } footer: { Text("Si vous souhaitez que Padel Club vous aide à suivre les encaissements, indiquer un prix d'inscription. Sinon Padel Club vous aidera à suivre simplement l'arrivée et la présence des joueurs.") } @@ -172,6 +193,21 @@ struct TournamentGeneralSettingsView: View { .buttonStyle(.bordered) } + } else if focusedField == ._clubMemberFeeDeduction { + ForEach(deductionTags, id: \.self) { deductionTag in + Button(deductionTag.formatted(.currency(code: Locale.defaultCurrency()).precision(.fractionLength(0)))) { + clubMemberFeeDeduction = deductionTag + tournament.clubMemberFeeDeduction = deductionTag + focusedField = nil + } + .buttonStyle(.bordered) + } + Button("Gratuit") { + clubMemberFeeDeduction = entryFee + tournament.clubMemberFeeDeduction = clubMemberFeeDeduction + focusedField = nil + } + .buttonStyle(.bordered) } else { if focusedField == ._name, tournamentName.isEmpty == false { Button("Effacer") { @@ -214,7 +250,7 @@ struct TournamentGeneralSettingsView: View { .onChange(of: tournament.startDate) { _save() } - .onChange(of: tournament.entryFee) { + .onChange(of: [tournament.entryFee, tournament.clubMemberFeeDeduction]) { _save() } .onChange(of: [tournament.name, tournament.information, tournament.umpireCustomMail, tournament.umpireCustomPhone, tournament.umpireCustomContact]) { @@ -243,6 +279,8 @@ struct TournamentGeneralSettingsView: View { } } else if old == ._entryFee { tournament.entryFee = entryFee + } else if old == ._clubMemberFeeDeduction { + tournament.clubMemberFeeDeduction = clubMemberFeeDeduction } else if old == ._umpireCustomMail { _confirmUmpireMail() } else if old == ._umpireCustomPhone { @@ -301,11 +339,7 @@ struct TournamentGeneralSettingsView: View { } private func _save() { - do { - try dataStore.tournaments.addOrUpdate(instance: tournament) - } catch { - Logger.error(error) - } + dataStore.tournaments.addOrUpdate(instance: tournament) } private func _customUmpireView() -> some View { diff --git a/PadelClub/Views/Tournament/Screen/RegistrationSetupView.swift b/PadelClub/Views/Tournament/Screen/RegistrationSetupView.swift index 355b6b2..3378fec 100644 --- a/PadelClub/Views/Tournament/Screen/RegistrationSetupView.swift +++ b/PadelClub/Views/Tournament/Screen/RegistrationSetupView.swift @@ -32,6 +32,7 @@ struct RegistrationSetupView: View { @State private var isTemplate: Bool @State private var isCorporateTournament: Bool @State private var isValidating = false + @State private var unregisterDeltaInHours: Int // Online Payment @State private var enableOnlinePayment: Bool @@ -56,6 +57,7 @@ struct RegistrationSetupView: View { _enableOnlineRegistration = .init(wrappedValue: tournament.enableOnlineRegistration) _isTemplate = .init(wrappedValue: tournament.isTemplate) _isCorporateTournament = .init(wrappedValue: tournament.isCorporateTournament) + _unregisterDeltaInHours = .init(wrappedValue: tournament.unregisterDeltaInHours) // Registration Date Limit if let registrationDateLimit = tournament.registrationDateLimit { _registrationDateLimit = .init(wrappedValue: registrationDateLimit) @@ -215,6 +217,18 @@ struct RegistrationSetupView: View { Text("Si une date de fermeture des inscriptions en ligne est définie, alors plus aucune inscription ne sera possible après cette date. Sinon, la date du début du tournoi ou la date de clôture des inscriptions seront utilisées.") } + Section { + LabeledContent { + StepperView(count: $unregisterDeltaInHours) + } label: { + Text("\(unregisterDeltaInHours)h avant") + } + } header: { + Text("Limite de désinscription") + } footer: { + Text("Empêche la désinscription plusieurs heures avant le début du tournoi") + } + Section { if displayWarning() { Text("Attention, l'inscription en ligne est activée et vous avez des équipes inscrites en ligne, en modifiant la structure ces équipes seront intégrées ou retirées de votre sélection d'équipes. Padel Club saura prévenir les équipes inscrites en ligne automatiquement.") @@ -550,7 +564,7 @@ struct RegistrationSetupView: View { tournament.enableOnlineRegistration = enableOnlineRegistration tournament.isTemplate = isTemplate tournament.isCorporateTournament = isCorporateTournament - + tournament.unregisterDeltaInHours = unregisterDeltaInHours if enableOnlineRegistration { tournament.accountIsRequired = userAccountIsRequired tournament.licenseIsRequired = licenseIsRequired diff --git a/PadelClub/Views/Tournament/TournamentView.swift b/PadelClub/Views/Tournament/TournamentView.swift index 81a00f9..0c84848 100644 --- a/PadelClub/Views/Tournament/TournamentView.swift +++ b/PadelClub/Views/Tournament/TournamentView.swift @@ -261,6 +261,10 @@ struct TournamentView: View { LabelStructure() } + NavigationLink(value: Screen.cashier) { + Text(tournament.isFree() ? "Présence" : "Encaissement") + } + NavigationLink(value: Screen.rankings) { LabeledContent { if tournament.publishRankings == false { From afa8b4bdf7f70bdc8a098f81ee50bdc37b3d3bac Mon Sep 17 00:00:00 2001 From: Raz Date: Mon, 19 May 2025 18:19:01 +0200 Subject: [PATCH 12/27] import more forgotten variables fron previous playerreg into new playerreg from beach padel --- PadelClub/Extensions/TeamRegistration+Extensions.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/PadelClub/Extensions/TeamRegistration+Extensions.swift b/PadelClub/Extensions/TeamRegistration+Extensions.swift index 2a80c2a..c3c2140 100644 --- a/PadelClub/Extensions/TeamRegistration+Extensions.swift +++ b/PadelClub/Extensions/TeamRegistration+Extensions.swift @@ -47,6 +47,8 @@ extension TeamRegistration { player.ligueName = oldPlayer.ligueName player.registrationStatus = oldPlayer.registrationStatus player.timeToConfirm = oldPlayer.timeToConfirm + player.sex = oldPlayer.sex + player.paymentType = oldPlayer.paymentType player.paymentId = oldPlayer.paymentId player.clubMember = oldPlayer.clubMember } From 2b3f102ac3f73e00e8d4ab76f7037dcd1adba547 Mon Sep 17 00:00:00 2001 From: Raz Date: Mon, 19 May 2025 19:18:31 +0200 Subject: [PATCH 13/27] piste au lieu de terrains --- PadelClub/Views/Club/CourtView.swift | 4 ++-- .../Club/Shared/ClubCourtSetupView.swift | 4 ++-- .../Components/GroupStageSettingsView.swift | 2 +- PadelClub/Views/Match/MatchDetailView.swift | 8 ++++---- .../Ongoing/OngoingDestination.swift | 6 +++--- .../Navigation/Ongoing/OngoingView.swift | 2 +- .../Components/MultiCourtPickerView.swift | 2 +- .../CourtAvailabilitySettingsView.swift | 6 +++--- .../Views/Planning/PlanningByCourtView.swift | 8 ++++---- .../Views/Planning/PlanningSettingsView.swift | 20 +++++++++---------- PadelClub/Views/Planning/PlanningView.swift | 4 ++-- PadelClub/Views/Score/FollowUpMatchView.swift | 4 ++-- .../TournamentClubSettingsView.swift | 10 +++++----- .../Screen/TournamentSettingsView.swift | 2 +- .../Views/Tournament/TournamentInitView.swift | 2 +- 15 files changed, 42 insertions(+), 42 deletions(-) diff --git a/PadelClub/Views/Club/CourtView.swift b/PadelClub/Views/Club/CourtView.swift index bb1b3dd..6de7b16 100644 --- a/PadelClub/Views/Club/CourtView.swift +++ b/PadelClub/Views/Club/CourtView.swift @@ -44,7 +44,7 @@ struct CourtView: View { } } } label: { - Text("Nom du terrain") + Text("Nom de la piste") } } footer: { if court.name?.isEmpty == false { @@ -65,7 +65,7 @@ struct CourtView: View { Text("Sortie autorisée") } Toggle(isOn: $court.indoor) { - Text("Terrain intérieur") + Text("Piste intérieur") } } } diff --git a/PadelClub/Views/Club/Shared/ClubCourtSetupView.swift b/PadelClub/Views/Club/Shared/ClubCourtSetupView.swift index 540f829..bbb6ef9 100644 --- a/PadelClub/Views/Club/Shared/ClubCourtSetupView.swift +++ b/PadelClub/Views/Club/Shared/ClubCourtSetupView.swift @@ -19,7 +19,7 @@ struct ClubCourtSetupView: View { @ViewBuilder var body: some View { Section { - TournamentFieldsManagerView(localizedStringKey: "Terrains du club", count: $club.courtCount) + TournamentFieldsManagerView(localizedStringKey: "Pistes du club", count: $club.courtCount) .disabled(displayContext == .lockedForEditing) .onChange(of: club.courtCount) { if displayContext != .addition { @@ -53,7 +53,7 @@ struct ClubCourtSetupView: View { _courtView(atIndex: courtIndex, tournamentClub: club) } } header: { - Text("Nom des terrains") + Text("Nom des pistes") } footer: { if displayContext == .lockedForEditing && hideLockForEditingMessage == false { Text("Édition impossible, vous n'êtes pas le créateur de ce club.").foregroundStyle(.logoRed) diff --git a/PadelClub/Views/GroupStage/Components/GroupStageSettingsView.swift b/PadelClub/Views/GroupStage/Components/GroupStageSettingsView.swift index a7968eb..d823228 100644 --- a/PadelClub/Views/GroupStage/Components/GroupStageSettingsView.swift +++ b/PadelClub/Views/GroupStage/Components/GroupStageSettingsView.swift @@ -64,7 +64,7 @@ struct GroupStageSettingsView: View { } Section { - CourtPicker(title: "Terrain dédié", selection: $courtIndex, maxCourt: tournament.courtCount) + CourtPicker(title: "Piste dédié", selection: $courtIndex, maxCourt: tournament.courtCount) RowButtonView("Confirmer", role: .destructive) { groupStage._matches().forEach { match in match.setCourt(courtIndex) diff --git a/PadelClub/Views/Match/MatchDetailView.swift b/PadelClub/Views/Match/MatchDetailView.swift index 7f0fa2a..6b0e3c8 100644 --- a/PadelClub/Views/Match/MatchDetailView.swift +++ b/PadelClub/Views/Match/MatchDetailView.swift @@ -325,7 +325,7 @@ struct MatchDetailView: View { match.removeCourt() save() } label: { - Text("Supprimer le terrain") + Text("Supprimer la piste") } } Button(role: .destructive) { @@ -405,7 +405,7 @@ struct MatchDetailView: View { } } label: { VStack(alignment: .leading) { - Text("terrain").font(.footnote).foregroundStyle(.secondary) + Text("piste").font(.footnote).foregroundStyle(.secondary) if let courtName = match.courtName() { Text(courtName) .foregroundStyle(Color.master) @@ -472,7 +472,7 @@ struct MatchDetailView: View { DisclosureGroup(isExpanded: $isEditing) { startingOptionView } label: { - Text("Modifier l'horaire et le terrain") + Text("Modifier l'horaire et la piste") } } @@ -536,7 +536,7 @@ struct MatchDetailView: View { } } } label: { - Text("Terrain") + Text("Piste") } .onChange(of: fieldSetup) { if let courtIndex = fieldSetup.courtIndex { diff --git a/PadelClub/Views/Navigation/Ongoing/OngoingDestination.swift b/PadelClub/Views/Navigation/Ongoing/OngoingDestination.swift index 3018ec0..a416e09 100644 --- a/PadelClub/Views/Navigation/Ongoing/OngoingDestination.swift +++ b/PadelClub/Views/Navigation/Ongoing/OngoingDestination.swift @@ -58,9 +58,9 @@ enum OngoingDestination: Int, CaseIterable, Identifiable, Selectable, Equatable case .followUp: ContentUnavailableView("Aucun match à suivre", systemImage: "figure.tennis", description: Text("Tous vos matchs planifiés et confirmés, seront visibles ici, quelque soit le tournoi.")) case .court: - ContentUnavailableView("Aucun match en cours", systemImage: "sportscourt", description: Text("Tous vos terrains correspondant aux matchs en cours seront visibles ici, quelque soit le tournoi.")) + ContentUnavailableView("Aucun match en cours", systemImage: "sportscourt", description: Text("Toutes vos pistes correspondant aux matchs en cours seront visibles ici, quelque soit le tournoi.")) case .free: - ContentUnavailableView("Aucun terrain libre", systemImage: "sportscourt", description: Text("Les terrains libres seront visibles ici, quelque soit le tournoi.")) + ContentUnavailableView("Aucune piste libre", systemImage: "sportscourt", description: Text("Les pistes libres seront visibles ici, quelque soit le tournoi.")) case .over: ContentUnavailableView("Aucun match terminé", systemImage: "clock.badge.xmark", description: Text("Les matchs terminés seront visibles ici, quelque soit le tournoi.")) } @@ -73,7 +73,7 @@ enum OngoingDestination: Int, CaseIterable, Identifiable, Selectable, Equatable case .followUp: return "À suivre" case .court: - return "Terrains" + return "Pistes" case .free: return "Libres" case .over: diff --git a/PadelClub/Views/Navigation/Ongoing/OngoingView.swift b/PadelClub/Views/Navigation/Ongoing/OngoingView.swift index b6d1f8f..b0ab784 100644 --- a/PadelClub/Views/Navigation/Ongoing/OngoingView.swift +++ b/PadelClub/Views/Navigation/Ongoing/OngoingView.swift @@ -89,7 +89,7 @@ struct OngoingCourtView: View { List { ForEach(filterMode.sortedCourtIndex, id: \.self) { index in let courtFilteredMatches = filteredMatches.filter({ $0.courtIndex == index }) - let title : String = (index == nil ? "Aucun terrain défini" : "Terrain #\(index! + 1)") + let title : String = (index == nil ? "Aucune piste définie" : "Piste #\(index! + 1)") if (filterMode == .free && courtFilteredMatches.isEmpty) || (filterMode == .court && courtFilteredMatches.isEmpty == false) { Section { MatchListView(section: "En cours", matches: courtFilteredMatches, hideWhenEmpty: true, isExpanded: false) diff --git a/PadelClub/Views/Planning/Components/MultiCourtPickerView.swift b/PadelClub/Views/Planning/Components/MultiCourtPickerView.swift index 8cbe429..448c15d 100644 --- a/PadelClub/Views/Planning/Components/MultiCourtPickerView.swift +++ b/PadelClub/Views/Planning/Components/MultiCourtPickerView.swift @@ -32,7 +32,7 @@ struct MultiCourtPickerView: View { } } } - .navigationTitle("Terrains disponibles") + .navigationTitle("Pistes disponibles") .toolbarBackground(.visible, for: .navigationBar) .environment(\.editMode, Binding.constant(EditMode.active)) } diff --git a/PadelClub/Views/Planning/CourtAvailabilitySettingsView.swift b/PadelClub/Views/Planning/CourtAvailabilitySettingsView.swift index e34c906..5ef5247 100644 --- a/PadelClub/Views/Planning/CourtAvailabilitySettingsView.swift +++ b/PadelClub/Views/Planning/CourtAvailabilitySettingsView.swift @@ -99,9 +99,9 @@ struct CourtAvailabilitySettingsView: View { .overlay { if courtsUnavailability.isEmpty { ContentUnavailableView { - Label("Tous les terrains sont disponibles", systemImage: "checkmark.circle.fill").tint(.green) + Label("Tous les pistes sont disponibles", systemImage: "checkmark.circle.fill").tint(.green) } description: { - Text("Vous pouvez précisez l'indisponibilité d'une ou plusieurs terrains, que ce soit pour une journée entière ou un créneau précis.") + Text("Vous pouvez précisez l'indisponibilité d'une ou plusieurs pistes, que ce soit pour une journée entière ou un créneau précis.") } actions: { RowButtonView("Ajouter une indisponibilité", systemImage: "plus.circle.fill") { showingPopover = true @@ -175,7 +175,7 @@ struct CourtAvailabilityEditorView: View { NavigationStack { Form { Section { - CourtPicker(title: "Terrain", selection: $courtIndex, maxCourt: tournament.courtCount) + CourtPicker(title: "Piste", selection: $courtIndex, maxCourt: tournament.courtCount) } Section { diff --git a/PadelClub/Views/Planning/PlanningByCourtView.swift b/PadelClub/Views/Planning/PlanningByCourtView.swift index d3447f9..2a6a12a 100644 --- a/PadelClub/Views/Planning/PlanningByCourtView.swift +++ b/PadelClub/Views/Planning/PlanningByCourtView.swift @@ -91,13 +91,13 @@ struct PlanningByCourtView: View { Picker(selection: $selectedCourt) { ForEach(courts, id: \.self) { courtIndex in if courtIndex == Int.max { - Label("Sans terrain", systemImage: "rectangle.slash").tag(Int.max) + Label("Sans piste", systemImage: "rectangle.slash").tag(Int.max) } else { Text(tournament.courtName(atIndex: courtIndex)).tag(courtIndex) } } } label: { - Text("Terrain") + Text("Piste") } .pickerStyle(.automatic) } @@ -135,7 +135,7 @@ struct PlanningByCourtView: View { ContentUnavailableView { Label("Aucun match planifié", systemImage: "clock.badge.questionmark") } description: { - Text("Aucun match n'a été planifié sur ce terrain et au jour sélectionné") + Text("Aucun match n'a été planifié sur cette piste et au jour sélectionné") } actions: { } } @@ -143,7 +143,7 @@ struct PlanningByCourtView: View { ContentUnavailableView { Label("Aucun match planifié", systemImage: "clock.badge.questionmark") } description: { - Text("Aucun match n'a été planifié sur ce terrain et au jour sélectionné") + Text("Aucun match n'a été planifié sur cette piste et au jour sélectionné") } actions: { } } diff --git a/PadelClub/Views/Planning/PlanningSettingsView.swift b/PadelClub/Views/Planning/PlanningSettingsView.swift index 2850ba4..db24db4 100644 --- a/PadelClub/Views/Planning/PlanningSettingsView.swift +++ b/PadelClub/Views/Planning/PlanningSettingsView.swift @@ -63,7 +63,7 @@ struct PlanningSettingsView: View { Text("\(tournament.dayDuration) jour" + tournament.dayDuration.pluralSuffix) } - TournamentFieldsManagerView(localizedStringKey: "Terrains maximum", count: $tournament.courtCount) + TournamentFieldsManagerView(localizedStringKey: "Pistes maximum", count: $tournament.courtCount) if let event = tournament.eventObject() { NavigationLink { @@ -85,7 +85,7 @@ struct PlanningSettingsView: View { LabeledContent { Text(matchScheduler.courtsAvailable.count.formatted() + "/" + tournament.courtCount.formatted()) } label: { - Text("Sélection des terrains") + Text("Sélection des pistes") if matchScheduler.courtsAvailable.count > tournament.courtCount { Text("Attention !") .tint(.red) @@ -97,7 +97,7 @@ struct PlanningSettingsView: View { if tournament.courtCount < club.courtCount { let plural = tournament.courtCount.pluralSuffix let verb = tournament.courtCount > 1 ? "seront" : "sera" - Text("En réduisant les terrains maximum, seul\(plural) le\(plural) \(tournament.courtCount) premier\(plural) terrain\(plural) \(verb) utilisé\(plural)") + Text(", par contre, si vous gardez le nombre de terrains du club, vous pourrez plutôt préciser quel terrain n'est pas disponible.") + Text("En réduisant les pistes maximum, seule\(plural) le\(plural) \(tournament.courtCount) première\(plural) piste\(plural) \(verb) utilisée\(plural)") + Text(", par contre, si vous gardez le nombre de pistes du club, vous pourrez plutôt préciser quelle piste n'est pas disponible.") } else if tournament.courtCount > club.courtCount { let isCreatedByUser = club.hasBeenCreated(by: StoreCenter.main.userId) Button { @@ -109,10 +109,10 @@ struct PlanningSettingsView: View { } } label: { if isCreatedByUser { - Text("Vous avez indiqué plus de terrains dans ce tournoi que dans le club. ") + Text("Vous avez indiqué plus de pistes dans ce tournoi que dans le club. ") + Text("Mettre à jour le club ?").underline().foregroundStyle(.master) } else { - Label("Vous avez indiqué plus de terrains dans ce tournoi que dans le club.", systemImage: "exclamationmark.triangle.fill").foregroundStyle(.logoRed) + Label("Vous avez indiqué plus de pistes dans ce tournoi que dans le club.", systemImage: "exclamationmark.triangle.fill").foregroundStyle(.logoRed) } } .buttonStyle(.plain) @@ -122,7 +122,7 @@ struct PlanningSettingsView: View { } if issueFound { - Text("Padel Club n'a pas réussi à définir un horaire pour tous les matchs de ce tournoi, à cause de la programmation d'autres tournois ou de l'indisponibilité des terrains.") + Text("Padel Club n'a pas réussi à définir un horaire pour tous les matchs de ce tournoi, à cause de la programmation d'autres tournois ou de l'indisponibilité des pistes.") .foregroundStyle(.logoRed) } @@ -141,7 +141,7 @@ struct PlanningSettingsView: View { } } footer: { if let event, event.tournaments.count > 1 { - Text("Cette option fait en sorte qu'un terrain pris par un match d'un autre tournoi de cet événement soit toujours considéré comme libre.") + Text("Cette option fait en sorte qu'une piste prise par un match d'un autre tournoi de cet événement soit toujours considéré comme libre.") } } @@ -421,13 +421,13 @@ struct PlanningSettingsView: View { Section { Toggle(isOn: $matchScheduler.randomizeCourts) { - Text("Distribuer les terrains au hasard") + Text("Distribuer les pistes au hasard") } } Section { Toggle(isOn: $matchScheduler.shouldTryToFillUpCourtsAvailable) { - Text("Remplir au maximum les terrains d'une rotation") + Text("Remplir au maximum les pistes d'une rotation") } } footer: { Text("Tout en tenant compte de l'option ci-dessous, Padel Club essaiera de remplir les créneaux à chaque rotation.") @@ -438,7 +438,7 @@ struct PlanningSettingsView: View { Text("Équilibrer les matchs d'une manche") } } footer: { - Text("Cette option permet de programmer une manche sur plusieurs rotation de manière équilibrée dans le cas où il y a plus de matchs à jouer dans cette manche que de terrains.") + Text("Cette option permet de programmer une manche sur plusieurs rotation de manière équilibrée dans le cas où il y a plus de matchs à jouer dans cette manche que de pistes.") } Section { diff --git a/PadelClub/Views/Planning/PlanningView.swift b/PadelClub/Views/Planning/PlanningView.swift index 66138e8..a72e874 100644 --- a/PadelClub/Views/Planning/PlanningView.swift +++ b/PadelClub/Views/Planning/PlanningView.swift @@ -651,7 +651,7 @@ struct PlanningView: View { _save() } } label: { - Text("Terrains") + Text("Pistes") .underline(underlined) } @@ -812,7 +812,7 @@ enum PlanningFilterOption: Int, CaseIterable, Identifiable { func localizedPlanningLabel() -> String { switch self { case .byCourt: - return "Par terrain" + return "Par piste" case .byDefault: return "Par ordre des matchs" } diff --git a/PadelClub/Views/Score/FollowUpMatchView.swift b/PadelClub/Views/Score/FollowUpMatchView.swift index 9e0fdab..2b65381 100644 --- a/PadelClub/Views/Score/FollowUpMatchView.swift +++ b/PadelClub/Views/Score/FollowUpMatchView.swift @@ -69,7 +69,7 @@ struct FollowUpMatchView: View { case .index: return "Ordre prévu" case .court: - return "Terrain" + return "Piste" case .restingTime: return "Temps de repos" case .winner: @@ -285,7 +285,7 @@ struct FollowUpMatchView: View { } } } label: { - Text("Sur le terrain") + Text("Sur la piste") } .labelsHidden() .underline() diff --git a/PadelClub/Views/Tournament/Screen/Components/TournamentClubSettingsView.swift b/PadelClub/Views/Tournament/Screen/Components/TournamentClubSettingsView.swift index 6d63c78..ce67e4b 100644 --- a/PadelClub/Views/Tournament/Screen/Components/TournamentClubSettingsView.swift +++ b/PadelClub/Views/Tournament/Screen/Components/TournamentClubSettingsView.swift @@ -22,7 +22,7 @@ struct TournamentClubSettingsView: View { let selectedClub = event?.clubObject() Section { - TournamentFieldsManagerView(localizedStringKey: "Terrains pour le tournoi", count: $tournament.courtCount) + TournamentFieldsManagerView(localizedStringKey: "Pistes pour le tournoi", count: $tournament.courtCount) .onChange(of: tournament.courtCount) { do { try dataStore.tournaments.addOrUpdate(instance: tournament) @@ -36,7 +36,7 @@ struct TournamentClubSettingsView: View { CourtAvailabilitySettingsView(event: event) .environment(tournament) } label: { - Text("Indisponibilités des terrains") + Text("Indisponibilités des pistes") } } } footer: { @@ -44,7 +44,7 @@ struct TournamentClubSettingsView: View { if tournament.courtCount < club.courtCount { let plural = tournament.courtCount.pluralSuffix let verb = tournament.courtCount > 1 ? "seront" : "sera" - Text("En réduisant les terrains maximum, seul\(plural) le\(plural) \(tournament.courtCount) premier\(plural) terrain\(plural) \(verb) utilisé\(plural)") + Text(", par contre, si vous gardez le nombre de terrains du club, vous pourrez plutôt préciser quel terrain n'est pas disponible.") + Text("En réduisant les pistes maximum, seule\(plural) le\(plural) \(tournament.courtCount) première\(plural) piste\(plural) \(verb) utilisée\(plural)") + Text(", par contre, si vous gardez le nombre de pistes du club, vous pourrez plutôt préciser quelle piste n'est pas disponible.") } else if tournament.courtCount > club.courtCount { let isCreatedByUser = club.hasBeenCreated(by: StoreCenter.main.userId) Button { @@ -56,10 +56,10 @@ struct TournamentClubSettingsView: View { } } label: { if isCreatedByUser { - Text("Vous avez indiqué plus de terrains dans ce tournoi que dans le club.") + Text("Vous avez indiqué plus de pistes dans ce tournoi que dans le club.") + Text("Mettre à jour le club ?").underline().foregroundStyle(.master) } else { - Label("Vous avez indiqué plus de terrains dans ce tournoi que dans le club.", systemImage: "exclamationmark.triangle.fill").foregroundStyle(.logoRed) + Label("Vous avez indiqué plus de pistes dans ce tournoi que dans le club.", systemImage: "exclamationmark.triangle.fill").foregroundStyle(.logoRed) } } .buttonStyle(.plain) diff --git a/PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift b/PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift index b11cc37..a9b29aa 100644 --- a/PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift +++ b/PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift @@ -30,7 +30,7 @@ enum TournamentSettings: Identifiable, Selectable, Equatable { case .general: return "Général" case .club: - return "Terrains" + return "Pistes" case .tournamentType: return "Type" } diff --git a/PadelClub/Views/Tournament/TournamentInitView.swift b/PadelClub/Views/Tournament/TournamentInitView.swift index bb13869..b54e5c7 100644 --- a/PadelClub/Views/Tournament/TournamentInitView.swift +++ b/PadelClub/Views/Tournament/TournamentInitView.swift @@ -78,7 +78,7 @@ struct TournamentInitView: View { Text(tournament.localizedTournamentType()) } label: { Text("Réglages du tournoi") - Text("Formats, terrains, prix et plus") + Text("Formats, pistes, prix et plus") } } From faaff120d793ee922cb6d6d55c4ad6765e7cdc7f Mon Sep 17 00:00:00 2001 From: Raz Date: Tue, 20 May 2025 11:36:21 +0200 Subject: [PATCH 14/27] add loser bracket in pdf --- PadelClub.xcodeproj/project.pbxproj | 12 +++--- .../HTML Templates/bracket-template.html | 2 +- PadelClub/HTML Templates/match-template.html | 13 +++++-- .../HTML Templates/tournament-template.html | 23 ++++++++++- PadelClub/Utils/HtmlGenerator.swift | 2 +- PadelClub/Utils/HtmlService.swift | 38 +++++++++++++++++-- .../Tournament/Screen/PrintSettingsView.swift | 37 ++++++++++-------- 7 files changed, 95 insertions(+), 32 deletions(-) diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index e11f252..5f4f4ae 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -3120,7 +3120,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.27; + MARKETING_VERSION = 1.2.28; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3166,7 +3166,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.27; + MARKETING_VERSION = 1.2.28; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3285,7 +3285,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.27; + MARKETING_VERSION = 1.2.28; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3330,7 +3330,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.27; + MARKETING_VERSION = 1.2.28; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3374,7 +3374,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.27; + MARKETING_VERSION = 1.2.28; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub.beta; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3416,7 +3416,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.27; + MARKETING_VERSION = 1.2.28; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub.beta; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/PadelClub/HTML Templates/bracket-template.html b/PadelClub/HTML Templates/bracket-template.html index 8c3ec91..b23a2a3 100644 --- a/PadelClub/HTML Templates/bracket-template.html +++ b/PadelClub/HTML Templates/bracket-template.html @@ -1,5 +1,5 @@
    -
  • +
  •  {{roundLabel}}
    {{formatLabel}}
  • diff --git a/PadelClub/HTML Templates/match-template.html b/PadelClub/HTML Templates/match-template.html index aba166a..bf0365d 100644 --- a/PadelClub/HTML Templates/match-template.html +++ b/PadelClub/HTML Templates/match-template.html @@ -1,8 +1,13 @@ -
  • +
  • {{entrantOne}} +
    {{matchDescriptionTop}}
  • -
  • {{matchDescription}}
  • -
  • - {{entrantTwo}} +
  • +
  • +
  • +
    + {{entrantTwo}} +
    +
    {{matchDescriptionBottom}}
  •  
  • diff --git a/PadelClub/HTML Templates/tournament-template.html b/PadelClub/HTML Templates/tournament-template.html index e98ebf0..03c77d2 100644 --- a/PadelClub/HTML Templates/tournament-template.html +++ b/PadelClub/HTML Templates/tournament-template.html @@ -92,11 +92,32 @@ overflow: hidden; text-overflow: ellipsis; } + + .game { + /* Ensure the game container is a positioning context for the overlay */ + position: relative; + /* Add any other existing styles for your game list items */ + } + + .match-description-overlay { + /* Position the overlay directly on top of the game item */ + position: absolute; + top: 0; + left: 0; + transform: translateY(100%); + width: 100%; + height: 100%; + display: flex; /* Enable flexbox for centering */ + justify-content: center; /* Center horizontally */ + align-items: center; /* Center vertically (if needed) */ + font-size: 1em; /* Optional: Adjust font size */ + /* Add any other desired styling for the overlay */ + } -

    {{tournamentTitle}} - {{tournamentStartDate}}

    +

    {{tournamentTitle}} - {{tournamentStartDate}}

    {{brackets}}
    diff --git a/PadelClub/Utils/HtmlGenerator.swift b/PadelClub/Utils/HtmlGenerator.swift index a790cb4..656c8aa 100644 --- a/PadelClub/Utils/HtmlGenerator.swift +++ b/PadelClub/Utils/HtmlGenerator.swift @@ -176,7 +176,7 @@ class HtmlGenerator: ObservableObject { func generateLoserBracketHtml(upperRound: Round) -> String { //HtmlService.groupstage(bracket: tournament.orderedBrackets.first!).html() - HtmlService.loserBracket(upperRound: upperRound).html(headName: displayHeads, withRank: displayRank, withTeamIndex: displayTeamIndex, withScore: displayScore) + HtmlService.loserBracket(upperRound: upperRound, hideTitle: false).html(headName: displayHeads, withRank: displayRank, withTeamIndex: displayTeamIndex, withScore: displayScore) } var pdfURL: URL? { diff --git a/PadelClub/Utils/HtmlService.swift b/PadelClub/Utils/HtmlService.swift index dbe7808..da7e2f0 100644 --- a/PadelClub/Utils/HtmlService.swift +++ b/PadelClub/Utils/HtmlService.swift @@ -12,7 +12,7 @@ enum HtmlService { case template(tournament: Tournament) case bracket(round: Round) - case loserBracket(upperRound: Round) + case loserBracket(upperRound: Round, hideTitle: Bool) case match(match: Match) case player(entrant: TeamRegistration) case hiddenPlayer @@ -187,11 +187,17 @@ enum HtmlService { var template = html if let entrantOne = match.team(.one) { template = template.replacingOccurrences(of: "{{entrantOne}}", with: HtmlService.player(entrant: entrantOne).html(headName: headName, withRank: withRank, withTeamIndex: withTeamIndex, withScore: withScore)) + if withScore, let top = match.topPreviousRoundMatch(), top.hasEnded() { + template = template.replacingOccurrences(of: "{{matchDescriptionTop}}", with: [top.scoreLabel(winnerFirst:true)].compactMap({ $0 }).joined(separator: "\n")) + } } else { template = template.replacingOccurrences(of: "{{entrantOne}}", with: HtmlService.hiddenPlayer.html(headName: headName, withRank: withRank, withTeamIndex: withTeamIndex, withScore: withScore)) } if let entrantTwo = match.team(.two) { template = template.replacingOccurrences(of: "{{entrantTwo}}", with: HtmlService.player(entrant: entrantTwo).html(headName: headName, withRank: withRank, withTeamIndex: withTeamIndex, withScore: withScore)) + if withScore, let bottom = match.bottomPreviousRoundMatch(), bottom.hasEnded() { + template = template.replacingOccurrences(of: "{{matchDescriptionBottom}}", with: [bottom.scoreLabel(winnerFirst:true)].compactMap({ $0 }).joined(separator: "\n")) + } } else { template = template.replacingOccurrences(of: "{{entrantTwo}}", with: HtmlService.hiddenPlayer.html(headName: headName, withRank: withRank, withTeamIndex: withTeamIndex, withScore: withScore)) } @@ -206,9 +212,10 @@ enum HtmlService { } else if match.teamWon(atPosition: .two) == true { template = template.replacingOccurrences(of: "{{entrantTwoWon}}", with: "winner") } - template = template.replacingOccurrences(of: "{{matchDescription}}", with: [match.localizedStartDate(), match.scoreLabel()].joined(separator: "\n")) +// template = template.replacingOccurrences(of: "{{matchDescription}}", with: [match.localizedStartDate(), match.scoreLabel()].joined(separator: "\n")) } - template = template.replacingOccurrences(of: "{{matchDescription}}", with: "") + template = template.replacingOccurrences(of: "{{matchDescriptionTop}}", with: "") + template = template.replacingOccurrences(of: "{{matchDescriptionBottom}}", with: "") return template case .bracket(let round): var template = "" @@ -216,16 +223,31 @@ enum HtmlService { for (_, match) in round._matches().enumerated() { template = template.appending(HtmlService.match(match: match).html(headName: headName, withRank: withRank, withTeamIndex: withTeamIndex, withScore: withScore)) } + bracket = html.replacingOccurrences(of: "{{match-template}}", with: template) bracket = bracket.replacingOccurrences(of: "{{roundLabel}}", with: round.roundTitle()) bracket = bracket.replacingOccurrences(of: "{{formatLabel}}", with: round.matchFormat.formatTitle()) return bracket - case .loserBracket(let upperRound): + case .loserBracket(let upperRound, let hideTitle): var template = html + template = template.replacingOccurrences(of: "{{minHeight}}", with: withTeamIndex ? "226" : "156") template = template.replacingOccurrences(of: "{{tournamentTitle}}", with: upperRound.correspondingLoserRoundTitle()) + if let tournamentStartDate = upperRound.initialStartDate()?.localizedDate() { + template = template.replacingOccurrences(of: "{{tournamentStartDate}}", with: tournamentStartDate) + } else { + template = template.replacingOccurrences(of: "{{tournamentStartDate}}", with: "") + } + + template = template.replacingOccurrences(of: "{{titleHidden}}", with: hideTitle ? "hidden" : "") + var brackets = "" for round in upperRound.loserRounds() { brackets = brackets.appending(HtmlService.bracket(round: round).html(headName: headName, withRank: withRank, withTeamIndex: withTeamIndex, withScore: withScore)) + + if round.index == 1 { + let sub = HtmlService.loserBracket(upperRound: round, hideTitle: true).html(headName: headName, withRank: withRank, withTeamIndex: withTeamIndex, withScore: withScore) + template = template.appending(sub) + } } let winnerName = "" let winner = """ @@ -240,6 +262,14 @@ enum HtmlService { brackets = brackets.appending(winner) template = template.replacingOccurrences(of: "{{brackets}}", with: brackets) + + for round in upperRound.loserRounds() { + if round.index > 1 { + let sub = HtmlService.loserBracket(upperRound: round, hideTitle: true).html(headName: headName, withRank: withRank, withTeamIndex: withTeamIndex, withScore: withScore) + template = template.appending(sub) + } + } + return template case .template(let tournament): var template = html diff --git a/PadelClub/Views/Tournament/Screen/PrintSettingsView.swift b/PadelClub/Views/Tournament/Screen/PrintSettingsView.swift index f5b351b..0693086 100644 --- a/PadelClub/Views/Tournament/Screen/PrintSettingsView.swift +++ b/PadelClub/Views/Tournament/Screen/PrintSettingsView.swift @@ -47,9 +47,9 @@ struct PrintSettingsView: View { Text("Tableau") }) -// Toggle(isOn: $generator.includeLoserBracket, label: { -// Text("Tableau des matchs de classements") -// }) + Toggle(isOn: $generator.includeLoserBracket, label: { + Text("Tableau des matchs de classements") + }) if tournament.groupStages().isEmpty == false { Toggle(isOn: $generator.includeGroupStage, label: { @@ -132,18 +132,18 @@ struct PrintSettingsView: View { } label: { Text("Aperçu du tableau") } -// -// ForEach(tournament.rounds()) { round in -// if round.index > 0 { -// NavigationLink { -// WebViewPreview(round: round) -// .environmentObject(generator) -// } label: { -// Text("Aperçu \(round.correspondingLoserRoundTitle())") -// } -// } -// } -// + + ForEach(tournament.rounds()) { round in + if round.index > 0 { + NavigationLink { + WebViewPreview(round: round) + .environmentObject(generator) + } label: { + Text("Aperçu \(round.correspondingLoserRoundTitle())") + } + } + } + ForEach(tournament.groupStages()) { groupStage in NavigationLink { WebViewPreview(groupStage: groupStage) @@ -182,6 +182,13 @@ struct PrintSettingsView: View { Text("Poule") } } + + if let round = tournament.rounds().first { + ShareLink(item: generator.generateLoserBracketHtml(upperRound: round)) { + Text("Classement") + } + } + } header: { Text("Partager le code source HTML") } From 579b4fdce22d89cd54a0ad870f6b5855b04e273d Mon Sep 17 00:00:00 2001 From: Raz Date: Tue, 20 May 2025 19:22:03 +0200 Subject: [PATCH 15/27] fix wildcard pick issue --- .../Views/GroupStage/GroupStageView.swift | 2 +- PadelClub/Views/Match/MatchSetupView.swift | 2 +- PadelClub/Views/Team/EditingTeamView.swift | 22 ++++++++---- PadelClub/Views/Team/TeamPickerView.swift | 36 +++++++++++++++---- 4 files changed, 48 insertions(+), 14 deletions(-) diff --git a/PadelClub/Views/GroupStage/GroupStageView.swift b/PadelClub/Views/GroupStage/GroupStageView.swift index f0bff80..bc8acb9 100644 --- a/PadelClub/Views/GroupStage/GroupStageView.swift +++ b/PadelClub/Views/GroupStage/GroupStageView.swift @@ -241,7 +241,7 @@ struct GroupStageView: View { VStack(alignment: .leading, spacing: 0) { Text("#\(index + 1)") .font(.caption) - TeamPickerView(groupStagePosition: index, matchTypeContext: .groupStage, teamPicked: { team in + TeamPickerView(groupStagePosition: index, pickTypeContext: .groupStage, teamPicked: { team in print(team.pasteData()) team.groupStage = groupStage.id team.groupStagePosition = index diff --git a/PadelClub/Views/Match/MatchSetupView.swift b/PadelClub/Views/Match/MatchSetupView.swift index 97094b7..67cd78c 100644 --- a/PadelClub/Views/Match/MatchSetupView.swift +++ b/PadelClub/Views/Match/MatchSetupView.swift @@ -64,7 +64,7 @@ struct MatchSetupView: View { } HStack { let luckyLosers = walkOutSpot ? match.luckyLosers() : [] - TeamPickerView(shouldConfirm: shouldConfirm, round: match.roundObject, matchTypeContext: matchTypeContext, luckyLosers: luckyLosers, teamPicked: { team in + TeamPickerView(shouldConfirm: shouldConfirm, round: match.roundObject, pickTypeContext: matchTypeContext == .bracket ? .bracket : .loserBracket, luckyLosers: luckyLosers, teamPicked: { team in print(team.pasteData()) if walkOutSpot || team.bracketPosition != nil || matchTypeContext == .loserBracket { match.setLuckyLoser(team: team, teamPosition: teamPosition) diff --git a/PadelClub/Views/Team/EditingTeamView.swift b/PadelClub/Views/Team/EditingTeamView.swift index 5d915bc..55b433a 100644 --- a/PadelClub/Views/Team/EditingTeamView.swift +++ b/PadelClub/Views/Team/EditingTeamView.swift @@ -101,12 +101,22 @@ struct EditingTeamView: View { HStack { CopyPasteButtonView(pasteValue: team.playersPasteData()) Spacer() - NavigationLink { - GroupStageTeamReplacementView(team: team) - .environment(tournament) - } label: { - Text("Chercher à remplacer") - .underline() + if team.isWildCard(), team.unsortedPlayers().isEmpty { + TeamPickerView(pickTypeContext: .wildcard) { teamregistration in + teamregistration.wildCardBracket = team.wildCardBracket + teamregistration.wildCardGroupStage = team.wildCardGroupStage + tournament.tournamentStore?.teamRegistrations.addOrUpdate(instance: teamregistration) + tournament.tournamentStore?.teamRegistrations.delete(instance: team) + dismiss() + } + } else { + NavigationLink { + GroupStageTeamReplacementView(team: team) + .environment(tournament) + } label: { + Text("Chercher à remplacer") + .underline() + } } } } diff --git a/PadelClub/Views/Team/TeamPickerView.swift b/PadelClub/Views/Team/TeamPickerView.swift index 15282da..f8003a6 100644 --- a/PadelClub/Views/Team/TeamPickerView.swift +++ b/PadelClub/Views/Team/TeamPickerView.swift @@ -8,6 +8,13 @@ import SwiftUI import PadelClubData +public enum TeamPickType: String { + case bracket = "bracket" + case groupStage = "groupStage" + case loserBracket = "loserBracket" + case wildcard = "wildcard" +} + struct TeamPickerView: View { @EnvironmentObject var dataStore: DataStore @Environment(Tournament.self) var tournament: Tournament @@ -20,7 +27,7 @@ struct TeamPickerView: View { var shouldConfirm: Bool = false var groupStagePosition: Int? = nil var round: Round? = nil - var matchTypeContext: MatchType = .bracket + var pickTypeContext: TeamPickType = .bracket var luckyLosers: [TeamRegistration] = [] let teamPicked: ((TeamRegistration) -> (Void)) @@ -31,21 +38,34 @@ struct TeamPickerView: View { } } + var wording: String { + switch pickTypeContext { + case .bracket: + return "Choisir" + case .groupStage: + return "Choisir" + case .loserBracket: + return "Choisir" + case .wildcard: + return "Choisir la wildcard" + } + } + var body: some View { ConfirmButtonView(shouldConfirm: shouldConfirm, message: MatchSetupView.confirmationMessage) { presentTeamPickerView = true } label: { - Text("Choisir") + Text(wording) .underline() } .sheet(isPresented: $presentTeamPickerView) { NavigationStack { List { - if matchTypeContext == .loserBracket, let losers = round?.parentRound?.losers() { + if pickTypeContext == .loserBracket, let losers = round?.parentRound?.losers() { _sectionView(losers.sorted(by: \.weight, order: sortOrder), title: "Perdant du tour précédent") } - if matchTypeContext == .loserBracket, let losers = round?.previousRound()?.winners() { + if pickTypeContext == .loserBracket, let losers = round?.previousRound()?.winners() { _sectionView(losers.sorted(by: \.weight, order: sortOrder), title: "Gagnant du tour précédent") } @@ -63,7 +83,11 @@ struct TeamPickerView: View { let teams = tournament.selectedSortedTeams() - if matchTypeContext == .loserBracket { + if pickTypeContext == .wildcard { + _sectionView(tournament.waitingListSortedTeams(selectedSortedTeams: teams).sorted(by: \.weight, order: sortOrder), title: "Liste d'attente") + } + + if pickTypeContext == .loserBracket { _sectionView(teams.filter({ $0.inGroupStage() && $0.qualified == false }).sorted(by: \.weight, order: sortOrder), title: "Non qualifié de poules") } @@ -145,7 +169,7 @@ struct TeamPickerView: View { .frame(maxWidth: .infinity) .buttonStyle(.plain) .id(team.id) - .listRowView(isActive: matchTypeContext == .loserBracket && round?.teams().map({ $0.id }).contains(team.id) == true, color: .green, hideColorVariation: true) + .listRowView(isActive: pickTypeContext == .loserBracket && round?.teams().map({ $0.id }).contains(team.id) == true, color: .green, hideColorVariation: true) // .confirmationDialog("Attention", isPresented: confirmationRequest, titleVisibility: .visible) { // Button("Retirer du tableau", role: .destructive) { // teamPicked(confirmTeam!) From 03782af8c29a87e1e444204f93eb7647879a45b0 Mon Sep 17 00:00:00 2001 From: Raz Date: Tue, 20 May 2025 19:27:53 +0200 Subject: [PATCH 16/27] fix ui for validating datepicker --- PadelClub/Views/Team/EditingTeamView.swift | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/PadelClub/Views/Team/EditingTeamView.swift b/PadelClub/Views/Team/EditingTeamView.swift index 55b433a..4efa7eb 100644 --- a/PadelClub/Views/Team/EditingTeamView.swift +++ b/PadelClub/Views/Team/EditingTeamView.swift @@ -188,15 +188,18 @@ struct EditingTeamView: View { DatePicker(selection: $registrationDateModified) { if registrationDate != registrationDateModified { HStack { - FooterButtonView("Valider") { + Button("Valider", systemImage: "checkmark.circle") { registrationDate = registrationDateModified } + .tint(.green) Divider() - FooterButtonView("Annuler", role: .cancel) { + Button("Annuler", systemImage: "xmark.circle", role: .cancel) { registrationDateModified = registrationDate } - .foregroundStyle(.blue) + .tint(.logoRed) } + .labelStyle(.iconOnly) + .buttonStyle(.borderedProminent) } else { Text("Inscription") Text(registrationDateModified.localizedWeekDay().capitalized) From ce3c3650cec216ee03cf82c604f0c3c0ba50b9c8 Mon Sep 17 00:00:00 2001 From: Raz Date: Tue, 20 May 2025 19:33:23 +0200 Subject: [PATCH 17/27] fix payment faq wording --- PadelClub/Views/Shared/PaymentInfoSheetView.swift | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/PadelClub/Views/Shared/PaymentInfoSheetView.swift b/PadelClub/Views/Shared/PaymentInfoSheetView.swift index 4f4a440..7c0f297 100644 --- a/PadelClub/Views/Shared/PaymentInfoSheetView.swift +++ b/PadelClub/Views/Shared/PaymentInfoSheetView.swift @@ -29,29 +29,23 @@ struct PaymentInfoSheetView: View { - Tous les paiements sont traités via Stripe, une plateforme sécurisée de paiement en ligne Remboursements : - - Les remboursements peuvent être activés ou désactivés par l'organisateur + - Les remboursements en ligne peuvent être activés ou désactivés par l'organisateur - Si activés, une date limite de remboursement peut être définie - - Aucun remboursement n'est possible après cette date limite - - Les remboursements sont automatiquement traités via la même méthode de paiement utilisée + - Aucun remboursement en ligne n'est possible après cette date limite + - Les remboursements en ligne sont automatiquement traités via la même méthode de paiement utilisée Commissions et frais : - Padel Club prélève une commission de \(stripePlatformFee)% sur chaque transaction - - Cette commission couvre les frais de service et de maintenance de la plateforme - Des frais supplémentaires de Stripe s'appliquent (\(stripePercentageFee)% + \(stripeFixedFee)€ par transaction) - - Le montant total des frais est indiqué clairement avant validation du paiement Exigences pour les organisateurs : - L'organisateur doit avoir un compte Stripe valide pour recevoir les paiements - Le compte Stripe doit être vérifié et connecté à Padel Club - Sans compte Stripe connecté, l'option de paiement en ligne ne peut pas être activée - - Les fonds sont directement versés sur le compte bancaire associé au compte Stripe de l'organisateur Sécurité : - Toutes les transactions sont sécurisées et chiffrées - Padel Club ne stocke pas les informations de carte bancaire - - La conformité RGPD et PCI-DSS est assurée par Stripe - - En cas de problème avec un paiement, veuillez contacter l'organisateur du tournoi ou le support Padel Club. """ } From b84f5199290fe2c328330e50aad96dd59815da37 Mon Sep 17 00:00:00 2001 From: Raz Date: Tue, 20 May 2025 19:33:46 +0200 Subject: [PATCH 18/27] v1.2.29 --- PadelClub.xcodeproj/project.pbxproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 5f4f4ae..029492d 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -3120,7 +3120,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.28; + MARKETING_VERSION = 1.2.29; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3166,7 +3166,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.28; + MARKETING_VERSION = 1.2.29; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3285,7 +3285,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.28; + MARKETING_VERSION = 1.2.29; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3330,7 +3330,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.28; + MARKETING_VERSION = 1.2.29; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3374,7 +3374,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.28; + MARKETING_VERSION = 1.2.29; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub.beta; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3416,7 +3416,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.28; + MARKETING_VERSION = 1.2.29; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub.beta; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; From 0dea3ae550546b23b9c1e542dfb079a166cbb16a Mon Sep 17 00:00:00 2001 From: Raz Date: Thu, 22 May 2025 09:08:09 +0200 Subject: [PATCH 19/27] add event link sharing --- .../Cashier/Event/EventSettingsView.swift | 39 ++++++++++++++++--- .../Tournament/Screen/BroadcastView.swift | 3 ++ 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/PadelClub/Views/Cashier/Event/EventSettingsView.swift b/PadelClub/Views/Cashier/Event/EventSettingsView.swift index 9d24df8..d0e8024 100644 --- a/PadelClub/Views/Cashier/Event/EventSettingsView.swift +++ b/PadelClub/Views/Cashier/Event/EventSettingsView.swift @@ -23,6 +23,20 @@ struct EventSettingsView: View { var link = [String]() link.append(event.eventTitle()) + link.append("\n\n") + + link.append(tournamentInformation) + + link.append("\n\n") + + if let url = event.shareURL() { + var tournamentLink = [String]() + tournamentLink.append("Lien de l'événement") + tournamentLink.append(url.absoluteString) + let eventLink = tournamentLink.joined(separator: "\n") + link.append(eventLink) + } + link.append("\n\n") link.append("Retrouvez toutes les infos en suivant le\(tournaments.count.pluralSuffix) lien\(tournaments.count.pluralSuffix) ci-dessous :") link.append("\n\n") @@ -150,12 +164,8 @@ struct EventSettingsView: View { }) .toolbarBackground(.visible, for: .navigationBar) .toolbar { - if let tenupId = event.tenupId { - ToolbarItem(placement: .topBarTrailing) { - Link(destination: URL(string:"https://tenup.fft.fr/tournoi/\(tenupId)")!) { - Text("Tenup") - } - } + ToolbarItem(placement: .topBarTrailing) { + _linkLabel() } if focusedField != nil { @@ -200,6 +210,23 @@ struct EventSettingsView: View { } } + private func _linkLabel() -> some View { + Menu { + if let url = event.shareURL() { + ShareLink(item: url) { + Text("Lien de l'événement sur Padel Club") + } + } + if let tenupId = event.tenupId { + ShareLink(item: URL(string:"https://tenup.fft.fr/tournoi/\(tenupId)")!) { + Text("Tenup") + } + } + } label: { + Text("Liens") + } + } + private func _save() { dataStore.events.addOrUpdate(instance: event) } diff --git a/PadelClub/Views/Tournament/Screen/BroadcastView.swift b/PadelClub/Views/Tournament/Screen/BroadcastView.swift index 54eb097..5382d73 100644 --- a/PadelClub/Views/Tournament/Screen/BroadcastView.swift +++ b/PadelClub/Views/Tournament/Screen/BroadcastView.swift @@ -321,6 +321,9 @@ struct BroadcastView: View { Section { let club = tournament.club() actionForURL(title: (club == nil) ? "Aucun club indiqué pour ce tournoi" : club!.clubTitle(), description: "Page du club", url: club?.shareURL()) + if let event = tournament.eventObject() { + actionForURL(title: event.eventTitle(), description: "Page de l'évémement", url: event.shareURL()) + } actionForURL(title: "Padel Club", url: URLs.main.url) } header: { Text("Autres liens") From 5d9c1b9cea92b63eb013a1ee9a700340d0963b7e Mon Sep 17 00:00:00 2001 From: Raz Date: Thu, 22 May 2025 12:24:27 +0200 Subject: [PATCH 20/27] clean up groupstage loser bracket and init --- PadelClub/Views/GroupStage/GroupStagesSettingsView.swift | 2 +- PadelClubTests/ServerDataTests.swift | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/PadelClub/Views/GroupStage/GroupStagesSettingsView.swift b/PadelClub/Views/GroupStage/GroupStagesSettingsView.swift index 67dff1c..f87f6d3 100644 --- a/PadelClub/Views/GroupStage/GroupStagesSettingsView.swift +++ b/PadelClub/Views/GroupStage/GroupStagesSettingsView.swift @@ -78,7 +78,7 @@ struct GroupStagesSettingsView: View { Section { if tournament.groupStageLoserBracket() == nil { RowButtonView("Ajouter des matchs de classements", role: .destructive) { - let round = Round(tournament: tournament.id, index: 0, matchFormat: tournament.loserRoundFormat, groupStageLoserBracket: true) + let round = Round(tournament: tournament.id, index: 0, format: tournament.loserRoundFormat, groupStageLoserBracket: true) do { try tournamentStore?.rounds.addOrUpdate(instance: round) diff --git a/PadelClubTests/ServerDataTests.swift b/PadelClubTests/ServerDataTests.swift index bf013f2..9e06d95 100644 --- a/PadelClubTests/ServerDataTests.swift +++ b/PadelClubTests/ServerDataTests.swift @@ -236,9 +236,7 @@ final class ServerDataTests: XCTestCase { let rounds: [Round] = try await StoreCenter.main.service().get() let parentRoundId = rounds.first?.id - let round = Round(tournament: tournamentId, index: 1, parent: parentRoundId, matchFormat: MatchFormat.nineGames, startDate: Date(), groupStageLoserBracket: false, loserBracketMode: .manual, - plannedStartDate: Date() - ) + let round = Round(tournament: tournamentId, index: 1, parent: parentRoundId, format: MatchFormat.nineGames, startDate: Date(), groupStageLoserBracket: false, loserBracketMode: .manual, plannedStartDate: Date()) round.storeId = "abc" if let r: Round = try await StoreCenter.main.service().post(round) { From 432e78f7270496da9d5657d41447a155cc9ab985 Mon Sep 17 00:00:00 2001 From: Laurent Date: Thu, 22 May 2025 14:49:15 +0200 Subject: [PATCH 21/27] adds TTC for price --- .../Views/Tournament/Subscription/SubscriptionView.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/PadelClub/Views/Tournament/Subscription/SubscriptionView.swift b/PadelClub/Views/Tournament/Subscription/SubscriptionView.swift index e51a67c..8673763 100644 --- a/PadelClub/Views/Tournament/Subscription/SubscriptionView.swift +++ b/PadelClub/Views/Tournament/Subscription/SubscriptionView.swift @@ -26,10 +26,11 @@ extension Product { return StoreItem(rawValue: self.id)! } var formattedPrice: String { + let ttcPrice = "\(self.displayPrice) TTC" if let period = self.subscription?.subscriptionPeriod { - return self.displayPrice + " / " + period.unit.label + return "\(ttcPrice) / \(period.unit.label)" } - return self.displayPrice + return ttcPrice } } @@ -101,6 +102,7 @@ class SubscriptionModel: ObservableObject, StoreDelegate { } else { self.totalPrice = product.displayPrice } + self.totalPrice += " TTC" } else { self.totalPrice = "" } From c59e5ecf9f7d38eb37f88d2df1c621f76e48b56f Mon Sep 17 00:00:00 2001 From: Raz Date: Thu, 22 May 2025 15:36:47 +0200 Subject: [PATCH 22/27] v1.2.30 --- PadelClub.xcodeproj/project.pbxproj | 12 ++++++------ PadelClub/Views/Planning/PlanningView.swift | 7 +++++-- .../Tournament/Screen/RegistrationSetupView.swift | 6 ++++-- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 029492d..66d8d28 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -3120,7 +3120,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.29; + MARKETING_VERSION = 1.2.30; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3166,7 +3166,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.29; + MARKETING_VERSION = 1.2.30; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3285,7 +3285,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.29; + MARKETING_VERSION = 1.2.30; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3330,7 +3330,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.29; + MARKETING_VERSION = 1.2.30; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3374,7 +3374,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.29; + MARKETING_VERSION = 1.2.30; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub.beta; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3416,7 +3416,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.29; + MARKETING_VERSION = 1.2.30; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub.beta; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/PadelClub/Views/Planning/PlanningView.swift b/PadelClub/Views/Planning/PlanningView.swift index a72e874..0aa4a35 100644 --- a/PadelClub/Views/Planning/PlanningView.swift +++ b/PadelClub/Views/Planning/PlanningView.swift @@ -493,8 +493,11 @@ struct PlanningView: View { var body: some View { LabeledContent { - if let courtName = match.courtName() { - Text(courtName) + VStack(alignment: .center) { + if let courtName = match.courtName() { + Text(courtName).foregroundStyle(.primary) + } + Text(match.matchFormat.shortFormat + " (~" + match.getDuration().formatted() + "m)").font(.footnote).foregroundStyle(.tertiary) } } label: { if let groupStage = match.groupStageObject { diff --git a/PadelClub/Views/Tournament/Screen/RegistrationSetupView.swift b/PadelClub/Views/Tournament/Screen/RegistrationSetupView.swift index 3378fec..07a713c 100644 --- a/PadelClub/Views/Tournament/Screen/RegistrationSetupView.swift +++ b/PadelClub/Views/Tournament/Screen/RegistrationSetupView.swift @@ -186,8 +186,10 @@ struct RegistrationSetupView: View { } Section { + Text("Par défaut, sans date définie, les inscriptions en ligne sont possible dès son activation.") + Toggle(isOn: $openingRegistrationDateEnabled) { - Text("Définir une date") + Text("Définir une date ultérieur") } if openingRegistrationDateEnabled { @@ -413,7 +415,7 @@ struct RegistrationSetupView: View { } Toggle(isOn: $enableOnlinePaymentRefund) { - Text("Autoriser les remboursements") + Text("Autoriser les remboursements en ligne") } if enableOnlinePaymentRefund { From ee35304bcdf1c95bf08ba0a0049751205cffc734 Mon Sep 17 00:00:00 2001 From: Raz Date: Thu, 22 May 2025 18:36:49 +0200 Subject: [PATCH 23/27] fix event sharing stuff --- PadelClub.xcodeproj/project.pbxproj | 12 +++--- .../Cashier/Event/EventSettingsView.swift | 40 ++++++++++++++++++- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 66d8d28..afc39f2 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -3094,7 +3094,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; @@ -3141,7 +3141,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; @@ -3259,7 +3259,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; @@ -3305,7 +3305,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; @@ -3351,7 +3351,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; @@ -3394,7 +3394,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; diff --git a/PadelClub/Views/Cashier/Event/EventSettingsView.swift b/PadelClub/Views/Cashier/Event/EventSettingsView.swift index d0e8024..d75603a 100644 --- a/PadelClub/Views/Cashier/Event/EventSettingsView.swift +++ b/PadelClub/Views/Cashier/Event/EventSettingsView.swift @@ -128,7 +128,9 @@ struct EventSettingsView: View { } footer: { Text("Ce texte sera indiqué dans le champ information de tous les tournois de l'événement") } - + + _message(eventPasteMessage: _eventPasteMessage()) + if event.club != nil { let eventLinksPasteData = eventLinksPasteData() Section { @@ -210,6 +212,42 @@ struct EventSettingsView: View { } } + private func _eventPasteMessage() -> String { + var paste = [String]() + if let name = event.name { + paste.append(name) + paste.append("\n") + } + + paste.append(event.formattedDateInterval()) + paste.append("\n") + + paste.append(tournamentInformation) + paste.append("\n") + + if let url = event.shareURL() { + paste.append(url.absoluteString) + paste.append("\n") + } + + return paste.joined(separator: "\n") + } + + + private func _message(eventPasteMessage: String) -> some View { + Section { + Text(eventPasteMessage).foregroundStyle(.secondary) + } header: { + Text("Message à partager") + } footer: { + HStack { + CopyPasteButtonView(pasteValue: eventPasteMessage) + Spacer() + ShareLink(item: eventPasteMessage) + } + } + } + private func _linkLabel() -> some View { Menu { if let url = event.shareURL() { From 235edb08f29c5432454e915bfe661569fd832986 Mon Sep 17 00:00:00 2001 From: Raz Date: Thu, 22 May 2025 19:09:59 +0200 Subject: [PATCH 24/27] fix crash --- PadelClub/Extensions/Tournament+Extensions.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/PadelClub/Extensions/Tournament+Extensions.swift b/PadelClub/Extensions/Tournament+Extensions.swift index 1e7c1cd..e3eab9f 100644 --- a/PadelClub/Extensions/Tournament+Extensions.swift +++ b/PadelClub/Extensions/Tournament+Extensions.swift @@ -43,7 +43,7 @@ extension Tournament { } func addTeam(_ players: Set, registrationDate: Date? = nil, name: String? = nil) -> TeamRegistration { - let team = TeamRegistration(tournament: id, registrationDate: registrationDate, name: name) + let team = TeamRegistration(tournament: id, registrationDate: registrationDate ?? Date(), name: name) team.setWeight(from: Array(players), inTournamentCategory: tournamentCategory) players.forEach { player in player.teamRegistration = team.id @@ -76,7 +76,7 @@ extension Tournament { guard let tournamentStore = self.tournamentStore else { return } let teams = (0.. Date: Mon, 26 May 2025 17:02:51 +0200 Subject: [PATCH 25/27] fix seed random pick missing for more than 32 players fix missing parameters taken from template --- PadelClub.xcodeproj/project.pbxproj | 24 ++++++++++++------------ PadelClub/Views/Round/RoundView.swift | 16 ++++++++++++++++ 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index afc39f2..68556a1 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -3094,7 +3094,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; @@ -3120,7 +3120,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.30; + MARKETING_VERSION = 1.2.31; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3141,7 +3141,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; @@ -3166,7 +3166,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.30; + MARKETING_VERSION = 1.2.31; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3259,7 +3259,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; @@ -3285,7 +3285,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.30; + MARKETING_VERSION = 1.2.31; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3305,7 +3305,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; @@ -3330,7 +3330,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.30; + MARKETING_VERSION = 1.2.31; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3351,7 +3351,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; @@ -3374,7 +3374,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.30; + MARKETING_VERSION = 1.2.31; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub.beta; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3394,7 +3394,7 @@ CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; @@ -3416,7 +3416,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.30; + MARKETING_VERSION = 1.2.31; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub.beta; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/PadelClub/Views/Round/RoundView.swift b/PadelClub/Views/Round/RoundView.swift index bb0cb10..b97768b 100644 --- a/PadelClub/Views/Round/RoundView.swift +++ b/PadelClub/Views/Round/RoundView.swift @@ -139,6 +139,22 @@ struct RoundView: View { } } } else { + + let seeds = upperRound.round.seeds() + if upperRound.round.seeds().isEmpty == false { + RowButtonView("Retirer les têtes de séries", role: .destructive) { + seeds.forEach { tr in + tr.bracketPosition = nil + } + + let teamScores = upperRound.playedMatches.flatMap { match in + match.teamScores + } + tournamentStore?.teamScores.delete(contentOfs: teamScores) + tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: seeds) + } + } + let isRoundValidForSeeding = tournament.isRoundValidForSeeding(roundIndex: upperRound.round.index) let availableSeeds = tournament.availableSeeds() let availableQualifiedTeams = tournament.availableQualifiedTeams() From 4560ebb30a5e7805c7b1cafbbdb9e6bc99999c87 Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Thu, 29 May 2025 07:13:10 +0200 Subject: [PATCH 26/27] fix issue with stripe account onboarding --- PadelClub.xcodeproj/project.pbxproj | 12 +- .../Network/StripeValidationService.swift | 157 +++++++++++++++++- .../Screen/RegistrationSetupView.swift | 149 ++++++++++++----- 3 files changed, 259 insertions(+), 59 deletions(-) diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 68556a1..8bcced7 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -3120,7 +3120,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.31; + MARKETING_VERSION = 1.2.32; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3166,7 +3166,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.31; + MARKETING_VERSION = 1.2.32; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3285,7 +3285,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.31; + MARKETING_VERSION = 1.2.32; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3330,7 +3330,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.31; + MARKETING_VERSION = 1.2.32; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3374,7 +3374,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.31; + MARKETING_VERSION = 1.2.32; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub.beta; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3416,7 +3416,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.31; + MARKETING_VERSION = 1.2.32; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub.beta; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/PadelClub/Utils/Network/StripeValidationService.swift b/PadelClub/Utils/Network/StripeValidationService.swift index dce0266..e0548da 100644 --- a/PadelClub/Utils/Network/StripeValidationService.swift +++ b/PadelClub/Utils/Network/StripeValidationService.swift @@ -10,12 +10,16 @@ import LeStorage class StripeValidationService { - static func validateStripeAccountID(_ accountID: String) async throws -> ValidationResponse { - let service = try StoreCenter.main.service() + // MARK: - Validate Stripe Account + static func validateStripeAccount(accountId: String) async throws -> ValidationResponse { + let service = try StoreCenter.main.service() var urlRequest = try service._baseRequest(servicePath: "validate-stripe-account/", method: .post, requiresToken: true) - let body = ["account_id": accountID] - urlRequest.httpBody = try JSONEncoder().encode(body) + var body: [String: Any] = [:] + + body["account_id"] = accountId + + urlRequest.httpBody = try JSONSerialization.data(withJSONObject: body) do { let (data, response) = try await URLSession.shared.data(for: urlRequest) @@ -23,17 +27,79 @@ class StripeValidationService { guard let httpResponse = response as? HTTPURLResponse else { throw ValidationError.invalidResponse } + switch httpResponse.statusCode { case 200...299: let decodedResponse = try JSONDecoder().decode(ValidationResponse.self, from: data) return decodedResponse - case 400: - // Handle bad request + case 400, 403, 404: + // Handle client errors - still decode as ValidationResponse let errorResponse = try JSONDecoder().decode(ValidationResponse.self, from: data) return errorResponse - case 403: - // Handle permission error - let errorResponse = try JSONDecoder().decode(ValidationResponse.self, from: data) + default: + throw ValidationError.invalidResponse + } + } catch let error as ValidationError { + throw error + } catch { + throw ValidationError.networkError(error) + } + } + + // MARK: - Create Stripe Connect Account + static func createStripeConnectAccount() async throws -> CreateAccountResponse { + let service = try StoreCenter.main.service() + let urlRequest = try service._baseRequest(servicePath: "stripe/create-account/", method: .post, requiresToken: true) + do { + let (data, response) = try await URLSession.shared.data(for: urlRequest) + + guard let httpResponse = response as? HTTPURLResponse else { + throw ValidationError.invalidResponse + } + + switch httpResponse.statusCode { + case 200...299: + let decodedResponse = try JSONDecoder().decode(CreateAccountResponse.self, from: data) + return decodedResponse + case 400, 403, 404: + let errorResponse = try JSONDecoder().decode(CreateAccountResponse.self, from: data) + return errorResponse + default: + throw ValidationError.invalidResponse + } + } catch let error as ValidationError { + throw error + } catch { + throw ValidationError.networkError(error) + } + } + + // MARK: - Create Stripe Account Link + static func createStripeAccountLink(_ accountId: String? = nil) async throws -> CreateLinkResponse { + let service = try StoreCenter.main.service() + var urlRequest = try service._baseRequest(servicePath: "stripe/create-account-link/", method: .post, requiresToken: true) + + var body: [String: Any] = [:] + + if let accountId = accountId { + body["account_id"] = accountId + } + + urlRequest.httpBody = try JSONSerialization.data(withJSONObject: body) + + do { + let (data, response) = try await URLSession.shared.data(for: urlRequest) + + guard let httpResponse = response as? HTTPURLResponse else { + throw ValidationError.invalidResponse + } + + switch httpResponse.statusCode { + case 200...299: + let decodedResponse = try JSONDecoder().decode(CreateLinkResponse.self, from: data) + return decodedResponse + case 400, 403, 404: + let errorResponse = try JSONDecoder().decode(CreateLinkResponse.self, from: data) return errorResponse default: throw ValidationError.invalidResponse @@ -46,17 +112,67 @@ class StripeValidationService { } } +// MARK: - Response Models + struct ValidationResponse: Codable { let valid: Bool + let canProcessPayments: Bool? + let onboardingComplete: Bool? + let needsOnboarding: Bool? let account: AccountDetails? let error: String? + + enum CodingKeys: String, CodingKey { + case valid + case canProcessPayments = "can_process_payments" + case onboardingComplete = "onboarding_complete" + case needsOnboarding = "needs_onboarding" + case account + case error + } } struct AccountDetails: Codable { let id: String + let chargesEnabled: Bool? + let payoutsEnabled: Bool? + let detailsSubmitted: Bool? enum CodingKeys: String, CodingKey { case id + case chargesEnabled = "charges_enabled" + case payoutsEnabled = "payouts_enabled" + case detailsSubmitted = "details_submitted" + } +} + +struct CreateAccountResponse: Codable { + let success: Bool + let accountId: String? + let message: String? + let existing: Bool? + let error: String? + + enum CodingKeys: String, CodingKey { + case success + case accountId = "account_id" + case message + case existing + case error + } +} + +struct CreateLinkResponse: Codable { + let success: Bool + let url: URL? + let accountId: String? + let error: String? + + enum CodingKeys: String, CodingKey { + case success + case url + case accountId = "account_id" + case error } } @@ -64,5 +180,28 @@ enum ValidationError: Error { case invalidResponse case networkError(Error) case invalidData + case encodingError + case urlNotFound + case accountNotFound + case onlinePaymentNotEnabled + + var localizedDescription: String { + switch self { + case .invalidResponse: + return "Réponse du serveur invalide" + case .networkError(let error): + return "Erreur réseau : \(error.localizedDescription)" + case .invalidData: + return "Données reçues invalides" + case .encodingError: + return "Échec de l'encodage des données de la requête" + case .accountNotFound: + return "Le compte n'a pas pu être généré" + case .urlNotFound: + return "Le lien pour utiliser un compte stripe n'a pas pu être généré" + case .onlinePaymentNotEnabled: + return "Le paiement en ligne n'a pas pu être activé pour ce tournoi" + } + } } diff --git a/PadelClub/Views/Tournament/Screen/RegistrationSetupView.swift b/PadelClub/Views/Tournament/Screen/RegistrationSetupView.swift index 07a713c..4b51a39 100644 --- a/PadelClub/Views/Tournament/Screen/RegistrationSetupView.swift +++ b/PadelClub/Views/Tournament/Screen/RegistrationSetupView.swift @@ -10,6 +10,7 @@ import SwiftUI import PadelClubData struct RegistrationSetupView: View { + @Environment(\.openURL) private var openURL @EnvironmentObject var dataStore: DataStore @Bindable var tournament: Tournament @State private var enableOnlineRegistration: Bool @@ -41,7 +42,7 @@ struct RegistrationSetupView: View { @State private var refundDateLimit: Date @State private var refundDateLimitEnabled: Bool @State private var stripeAccountId: String - @State private var stripeAccountIdIsInvalid: Bool = false + @State private var stripeAccountIdIsInvalid: Bool? @State private var paymentConfig: PaymentConfig? @State private var timeToConfirmConfig: TimeToConfirmConfig? @@ -50,6 +51,10 @@ struct RegistrationSetupView: View { @State private var hasChanges: Bool = false + @State private var stripeOnBoardingURL: URL? = nil + @State private var errorMessage: String? = nil + @State private var presentErrorAlert: Bool = false + @Environment(\.dismiss) private var dismiss init(tournament: Tournament) { @@ -320,7 +325,6 @@ struct RegistrationSetupView: View { ToolbarItem(placement: .topBarTrailing) { ButtonValidateView(role: .destructive) { _save() - dismiss() } } } @@ -331,7 +335,7 @@ struct RegistrationSetupView: View { HStack { Button("Effacer") { stripeAccountId = "" - stripeAccountIdIsInvalid = false + stripeAccountIdIsInvalid = nil tournament.stripeAccountId = nil } .buttonStyle(.borderless) @@ -344,7 +348,13 @@ struct RegistrationSetupView: View { } } } - + .alert("Paiement en ligne", isPresented: $presentErrorAlert, actions: { + Button("Fermer") { + self.presentErrorAlert = false + } + }, message: { + Text(ValidationError.onlinePaymentNotEnabled.localizedDescription) + }) .toolbarRole(.editor) .headerProminence(.increased) .navigationTitle("Inscription en ligne") @@ -435,32 +445,6 @@ struct RegistrationSetupView: View { Text("Revenu Padel Club") } } - - if isCorporateTournament == false, dataStore.user.registrationPaymentMode.requiresStripe() { - VStack(alignment: .leading) { - LabeledContent { - if isValidating { - ProgressView() - } else if focusedField == nil, stripeAccountIdIsInvalid == false, stripeAccountId.isEmpty == false, isValidating == false { - Image(systemName: "checkmark.circle.fill").foregroundStyle(.green) - } - } label: { - TextField("Identifiant du compte Stripe", text: $stripeAccountId) - .frame(maxWidth: .infinity) - .focused($focusedField, equals: ._stripeAccountId) - .disabled(isValidating) - .keyboardType(.alphabet) - .textContentType(nil) - .autocorrectionDisabled() - } - if stripeAccountIdIsInvalid { - Text("Identifiant Stripe invalide. Vous ne pouvez pas activer le paiement en ligne.").foregroundStyle(.logoRed) - Button("Ré-essayer") { - _confirmStripeAccountId() - } - } - } - } } header: { Text("Paiement en ligne") } footer: { @@ -488,13 +472,81 @@ struct RegistrationSetupView: View { .onChange(of: refundDateLimit) { _hasChanged() } - .onChange(of: focusedField) { old, new in - if old == ._stripeAccountId { - _confirmStripeAccountId() - } - } - if dataStore.user.registrationPaymentMode.requiresStripe() { + if isCorporateTournament == false, dataStore.user.registrationPaymentMode.requiresStripe() { + Section { + LabeledContent { + if isValidating { + ProgressView() + } else if focusedField == nil, stripeAccountIdIsInvalid == false, stripeAccountId.isEmpty == false, isValidating == false { + Image(systemName: "checkmark.circle.fill").foregroundStyle(.green) + } + } label: { + TextField("Identifiant du compte Stripe", text: $stripeAccountId) + .frame(maxWidth: .infinity) + .focused($focusedField, equals: ._stripeAccountId) + .disabled(isValidating) + .keyboardType(.alphabet) + .textContentType(nil) + .autocorrectionDisabled() + } + .onChange(of: focusedField) { old, new in + if old == ._stripeAccountId { + _confirmStripeAccountId() + } + } + + if stripeAccountIdIsInvalid == true { + Text("Identifiant Stripe invalide. Vous ne pouvez pas activer le paiement en ligne.").foregroundStyle(.logoRed) + } + + if stripeAccountId.isEmpty == false { + Button("Vérifier le compte Stripe") { + _confirmStripeAccountId() + } + .disabled(isValidating) + } + + if let errorMessage { + Text(errorMessage).foregroundStyle(.logoRed) + } + + RowButtonView("Connecter ou créer un compte Stripe", role: .destructive) { + errorMessage = nil + stripeAccountIdIsInvalid = nil + stripeAccountId = "" + stripeOnBoardingURL = nil + do { + let createStripeAccountResponse = try await StripeValidationService.createStripeConnectAccount() + print("createStripeAccountResponse", createStripeAccountResponse) + + guard let accounId = createStripeAccountResponse.accountId else { + throw ValidationError.accountNotFound + } + + let createStripeAccountLinkResponse = try await StripeValidationService.createStripeAccountLink(accounId) + print("createStripeAccountLinkResponse", createStripeAccountLinkResponse) + stripeOnBoardingURL = createStripeAccountLinkResponse.url + stripeAccountIdIsInvalid = nil + stripeAccountId = accounId + + if let stripeOnBoardingURL { + openURL(stripeOnBoardingURL) + } else { + throw ValidationError.urlNotFound + } + + } catch { + self.errorMessage = error.localizedDescription + Logger.error(error) + } + } + } header: { + Text("Compte Stripe") + } footer: { + Text("Vous devez connecter un compte Stripe à Padel Club. En cliquant sur le bouton ci-dessus, vous serez dirigé vers Stripe pour choisir votre compte Stripe à connecter ou pour en créer un.") + } + Section { let fixedFee = RegistrationPaymentMode.stripeFixedFee // Fixed fee in euros let percentageFee = RegistrationPaymentMode.stripePercentageFee @@ -526,11 +578,10 @@ struct RegistrationSetupView: View { // Text("Aucune commission Padel Club ne sera prélevée.").foregroundStyle(.logoRed).bold() } } - } private func _confirmStripeAccountId() { - stripeAccountIdIsInvalid = false + stripeAccountIdIsInvalid = nil if stripeAccountId.isEmpty { tournament.stripeAccountId = nil } else if stripeAccountId.count >= 5, stripeAccountId.starts(with: "acct_") { @@ -544,13 +595,12 @@ struct RegistrationSetupView: View { Task { isValidating = true do { - let response = try await StripeValidationService.validateStripeAccountID(accId) + let response = try await StripeValidationService.validateStripeAccount(accountId: accId) + print("validateStripeAccount", response) stripeAccountId = accId - stripeAccountIdIsInvalid = response.valid == false - enableOnlinePayment = response.valid + stripeAccountIdIsInvalid = response.canProcessPayments == false } catch { stripeAccountIdIsInvalid = true - enableOnlinePayment = false } isValidating = false } @@ -567,6 +617,8 @@ struct RegistrationSetupView: View { tournament.isTemplate = isTemplate tournament.isCorporateTournament = isCorporateTournament tournament.unregisterDeltaInHours = unregisterDeltaInHours + + var shouldDismiss = true if enableOnlineRegistration { tournament.accountIsRequired = userAccountIsRequired tournament.licenseIsRequired = licenseIsRequired @@ -589,6 +641,12 @@ struct RegistrationSetupView: View { tournament.stripeAccountId = stripeAccountId } else { tournament.stripeAccountId = nil + + if enableOnlinePayment, isCorporateTournament == false, dataStore.user.registrationPaymentMode.requiresStripe() { + enableOnlinePayment = false + tournament.enableOnlinePayment = false + shouldDismiss = false + } } } else { tournament.accountIsRequired = true @@ -626,8 +684,11 @@ struct RegistrationSetupView: View { } self.dataStore.tournaments.addOrUpdate(instance: tournament) - - dismiss() + if shouldDismiss { + dismiss() + } else { + presentErrorAlert = true + } } } From 6d7c6d35b21fd1a9d53388a4f39d2c515e0176f7 Mon Sep 17 00:00:00 2001 From: Razmig Sarkissian Date: Sun, 1 Jun 2025 09:25:57 +0200 Subject: [PATCH 27/27] fix update month data rank issue --- PadelClub.xcodeproj/project.pbxproj | 13 +++++++------ PadelClub/Extensions/Tournament+Extensions.swift | 14 ++++---------- .../Components/GroupStageSettingsView.swift | 2 +- .../Components/UpdateSourceRankDateView.swift | 2 +- 4 files changed, 13 insertions(+), 18 deletions(-) diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 8bcced7..2745959 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -3099,6 +3099,7 @@ DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; + ENABLE_DEBUG_DYLIB = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = PadelClub/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "Padel Club"; @@ -3120,7 +3121,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.32; + MARKETING_VERSION = 1.2.33; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3166,7 +3167,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.32; + MARKETING_VERSION = 1.2.33; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3285,7 +3286,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.32; + MARKETING_VERSION = 1.2.33; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3330,7 +3331,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.32; + MARKETING_VERSION = 1.2.33; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3374,7 +3375,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.32; + MARKETING_VERSION = 1.2.33; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub.beta; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3416,7 +3417,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.32; + MARKETING_VERSION = 1.2.33; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub.beta; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/PadelClub/Extensions/Tournament+Extensions.swift b/PadelClub/Extensions/Tournament+Extensions.swift index e3eab9f..c637a6f 100644 --- a/PadelClub/Extensions/Tournament+Extensions.swift +++ b/PadelClub/Extensions/Tournament+Extensions.swift @@ -277,13 +277,7 @@ extension Tournament { newMonthData.maleUnrankedValue = await lastRankMan newMonthData.femaleUnrankedValue = await lastRankWoman - - do { - try DataStore.shared.monthData.addOrUpdate(instance: newMonthData) - } catch { - Logger.error(error) - } - + DataStore.shared.monthData.addOrUpdate(instance: newMonthData) monthData = newMonthData } @@ -303,7 +297,7 @@ extension Tournament { } let players = unsortedPlayers() - try await players.concurrentForEach { player in + for player in players { let lastRank = (player.sex == .female) ? lastRankWoman : lastRankMan try await player.updateRank(from: chunkedParsers, lastRank: lastRank) player.setComputedRank(in: self) @@ -315,7 +309,7 @@ extension Tournament { } } - try tournamentStore?.playerRegistrations.addOrUpdate(contentOfs: players) + tournamentStore?.playerRegistrations.addOrUpdate(contentOfs: players) let unsortedTeams = unsortedTeams() unsortedTeams.forEach { team in @@ -324,7 +318,7 @@ extension Tournament { team.lockedWeight = team.weight } } - try tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: unsortedTeams) + tournamentStore?.teamRegistrations.addOrUpdate(contentOfs: unsortedTeams) refreshRanking = false } diff --git a/PadelClub/Views/GroupStage/Components/GroupStageSettingsView.swift b/PadelClub/Views/GroupStage/Components/GroupStageSettingsView.swift index d823228..865e42c 100644 --- a/PadelClub/Views/GroupStage/Components/GroupStageSettingsView.swift +++ b/PadelClub/Views/GroupStage/Components/GroupStageSettingsView.swift @@ -179,7 +179,7 @@ struct GroupStageSettingsView: View { } Section { - RowButtonView("Rafraichir", role: .destructive) { + RowButtonView("Rafraîchir", role: .destructive) { let playedMatches = groupStage.playedMatches() playedMatches.forEach { match in match.updateTeamScores() diff --git a/PadelClub/Views/Tournament/Screen/Components/UpdateSourceRankDateView.swift b/PadelClub/Views/Tournament/Screen/Components/UpdateSourceRankDateView.swift index 35186b1..7095821 100644 --- a/PadelClub/Views/Tournament/Screen/Components/UpdateSourceRankDateView.swift +++ b/PadelClub/Views/Tournament/Screen/Components/UpdateSourceRankDateView.swift @@ -45,7 +45,7 @@ struct UpdateSourceRankDateView: View { do { try await tournament.updateRank(to: currentRankSourceDate, forceRefreshLockWeight: forceRefreshLockWeight, providedSources: nil) - try dataStore.tournaments.addOrUpdate(instance: tournament) + dataStore.tournaments.addOrUpdate(instance: tournament) } catch { Logger.error(error) }