Merge branch 'main' into sync3

sync3
Laurent 5 months ago
commit d35e312c3f
  1. 12
      PadelClub.xcodeproj/project.pbxproj
  2. 14
      PadelClub/Utils/FileImportManager.swift
  3. 10
      PadelClub/Utils/HtmlGenerator.swift
  4. 14
      PadelClub/Utils/Tips.swift
  5. 8
      PadelClub/Views/Calling/GroupStageCallingView.swift
  6. 120
      PadelClub/Views/Cashier/Event/EventStatusView.swift
  7. 2
      PadelClub/Views/Components/StepperView.swift
  8. 8
      PadelClub/Views/Navigation/Umpire/PadelClubView.swift
  9. 19
      PadelClub/Views/Planning/PlanningView.swift
  10. 12
      PadelClub/Views/Player/PlayerDetailView.swift
  11. 7
      PadelClub/Views/Team/EditingTeamView.swift
  12. 2
      PadelClub/Views/Tournament/ConsolationTournamentImportView.swift
  13. 33
      PadelClub/Views/Tournament/FileImportView.swift

@ -3128,7 +3128,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.2.35;
MARKETING_VERSION = 1.2.37;
PRODUCT_BUNDLE_IDENTIFIER = app.padelclub;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -3174,7 +3174,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.2.35;
MARKETING_VERSION = 1.2.37;
PRODUCT_BUNDLE_IDENTIFIER = app.padelclub;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -3293,7 +3293,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.2.34;
MARKETING_VERSION = 1.2.37;
PRODUCT_BUNDLE_IDENTIFIER = app.padelclub;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -3338,7 +3338,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.2.34;
MARKETING_VERSION = 1.2.37;
PRODUCT_BUNDLE_IDENTIFIER = app.padelclub;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -3382,7 +3382,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.2.34;
MARKETING_VERSION = 1.2.37;
PRODUCT_BUNDLE_IDENTIFIER = app.padelclub.beta;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -3425,7 +3425,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.2.34;
MARKETING_VERSION = 1.2.37;
PRODUCT_BUNDLE_IDENTIFIER = app.padelclub.beta;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";

@ -132,14 +132,16 @@ class FileImportManager {
let weight: Int
let tournamentCategory: TournamentCategory
let tournamentAgeCategory: FederalTournamentAge
let tournamentLevel: TournamentLevel
let previousTeam: TeamRegistration?
var registrationDate: Date? = nil
var name: String? = nil
init(players: [PlayerRegistration], tournamentCategory: TournamentCategory, tournamentAgeCategory: FederalTournamentAge, previousTeam: TeamRegistration?, registrationDate: Date? = nil, name: String? = nil, tournament: Tournament) {
init(players: [PlayerRegistration], tournamentCategory: TournamentCategory, tournamentAgeCategory: FederalTournamentAge, tournamentLevel: TournamentLevel, previousTeam: TeamRegistration?, registrationDate: Date? = nil, name: String? = nil, tournament: Tournament) {
self.players = Set(players)
self.tournamentCategory = tournamentCategory
self.tournamentAgeCategory = tournamentAgeCategory
self.tournamentLevel = tournamentLevel
self.name = name
self.previousTeam = previousTeam
if players.count < 2 {
@ -152,7 +154,7 @@ class FileImportManager {
}
let significantPlayerCount = 2
let pl = players.prefix(significantPlayerCount).map { $0.computedRank }
let missingPl = (missing.map { tournament.unrankValue(for: $0 == 1 ? true : false ) ?? ($0 == 1 ? 90_000 : 10_000) }).prefix(significantPlayerCount)
let missingPl = (missing.map { tournament.unrankValue(for: $0 == 1 ? true : false ) ?? ($0 == 1 ? 90_415 : 10_000) }).prefix(significantPlayerCount)
self.weight = pl.reduce(0,+) + missingPl.reduce(0,+)
} else {
self.weight = players.map { $0.computedRank }.reduce(0,+)
@ -314,7 +316,7 @@ class FileImportManager {
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)
let team = TeamHolder(players: players, tournamentCategory: tournamentCategory, tournamentAgeCategory: tournamentAgeCategory, tournamentLevel: tournament.tournamentLevel, previousTeam: tournament.findTeam(players), tournament: tournament)
results.append(team)
}
}
@ -377,7 +379,7 @@ class FileImportManager {
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)
let team = TeamHolder(players: players, tournamentCategory: tournamentCategory, tournamentAgeCategory: tournamentAgeCategory, tournamentLevel: tournament.tournamentLevel, previousTeam: tournament.findTeam(players), tournament: tournament)
results.append(team)
}
}
@ -426,7 +428,7 @@ class FileImportManager {
return nil
}
let team = TeamHolder(players: registeredPlayers, tournamentCategory: tournament.tournamentCategory, tournamentAgeCategory: tournament.federalTournamentAge, previousTeam: tournament.findTeam(registeredPlayers), registrationDate: registrationDate, tournament: tournament)
let team = TeamHolder(players: registeredPlayers, tournamentCategory: tournament.tournamentCategory, tournamentAgeCategory: tournament.federalTournamentAge, tournamentLevel: tournament.tournamentLevel, previousTeam: tournament.findTeam(registeredPlayers), registrationDate: registrationDate, tournament: tournament)
results.append(team)
}
}
@ -486,7 +488,7 @@ class FileImportManager {
}
}
return TeamHolder(players: players, tournamentCategory: tournament.tournamentCategory, tournamentAgeCategory: tournament.federalTournamentAge, previousTeam: nil, name: teamName, tournament: tournament)
return TeamHolder(players: players, tournamentCategory: tournament.tournamentCategory, tournamentAgeCategory: tournament.federalTournamentAge, tournamentLevel: tournament.tournamentLevel, previousTeam: nil, name: teamName, tournament: tournament)
}
return results
}

@ -62,6 +62,8 @@ class HtmlGenerator: ObservableObject {
func generateWebView(webView: WKWebView) {
self.webView = webView
#if targetEnvironment(simulator)
#else
self.webView.evaluateJavaScript("document.readyState", completionHandler: { (complete, error) in
print("evaluateJavaScript", "readystage", complete, error)
if complete != nil {
@ -78,9 +80,12 @@ class HtmlGenerator: ObservableObject {
})
}
})
#endif
}
func generateGroupStage(webView: WKWebView) {
#if targetEnvironment(simulator)
#else
webView.evaluateJavaScript("document.readyState", completionHandler: { (complete, error) in
if complete != nil {
webView.evaluateJavaScript("document.documentElement.scrollHeight", completionHandler: { (height, error) in
@ -115,7 +120,7 @@ class HtmlGenerator: ObservableObject {
})
}
})
#endif
}
func buildPDF() {
@ -149,6 +154,8 @@ class HtmlGenerator: ObservableObject {
}
func createPage() {
#if targetEnvironment(simulator)
#else
let config = WKPDFConfiguration()
config.rect = rects[pdfDocument.pageCount]
webView.createPDF(configuration: config){ result in
@ -167,6 +174,7 @@ class HtmlGenerator: ObservableObject {
self.completionHandler?(.failure(error))
}
}
#endif
}
func generateHtml() -> String {

@ -646,6 +646,20 @@ struct ShouldTournamentBeOverTip: Tip {
}
}
struct UpdatePlannedDatesTip: Tip {
var title: Text {
Text("Mettre à jour la programmation des matchs")
}
var message: Text? {
Text("Tous les matchs dans le futur verront leur dates plannifiées mis à jour dans la section Programmation du site Padel Club.")
}
var image: Image? {
Image(systemName: "arrow.trianglehead.2.clockwise.rotate.90.circle")
}
}
struct TipStyleModifier: ViewModifier {
@Environment(\.colorScheme) var colorScheme
var tint: Color?

@ -45,6 +45,14 @@ struct GroupStageCallingView: View {
PlayersWithoutContactView(players: groupStages.flatMap({ $0.unsortedTeams() }).flatMap({ $0.unsortedPlayers() }).sorted(by: \.computedRank))
if let startDate = tournament.groupStageStartDate() {
Section {
CallView(teams: tournament.groupStageTeams(), callDate: startDate, matchFormat: tournament.groupStageMatchFormat, roundLabel: "poule")
} header: {
Text("Convoquer toutes les équipes de poules à la même heure")
}
}
_sameTimeGroupStageView(groupStages: groupStages)
ForEach(groupStages) { groupStage in

@ -7,17 +7,28 @@
import SwiftUI
import PadelClubData
import LeStorage
struct EventStatusView: View {
@State private var teamsCount: Int?
@State private var includeWaitingList: Bool = false
@EnvironmentObject var networkMonitor: NetworkMonitor
@State private var contactType: ContactType? = nil
@State private var contactMethod: Int = 1
@State private var contactRecipients: Set<String> = Set()
@State private var sentError: ContactManagerError? = nil
let tournaments: [Tournament]
var event: Event?
init(tournament: Tournament) {
self.tournaments = [tournament]
}
init(event: Event) {
self.event = event
self.tournaments = event.confirmedTournaments()
}
@ -97,11 +108,118 @@ struct EventStatusView: View {
}
}
}
if event != nil {
_contactAllButtonView()
}
}
.task {
await self._calculateTeamsCount()
}
.toolbarBackground(.visible, for: .navigationBar)
.navigationBarTitleDisplayMode(.inline)
.alert("Un problème est survenu", isPresented: messageSentFailed) {
Button("OK") {
}
} message: {
Text(_networkErrorMessage)
}
.sheet(item: $contactType) { contactType in
Group {
switch contactType {
case .message(_, let recipients, let body, _):
MessageComposeView(recipients: recipients, body: body) { result in
switch result {
case .cancelled:
break
case .failed:
self.sentError = .messageFailed
case .sent:
if networkMonitor.connected == false {
self.contactType = nil
self.sentError = .messageNotSent
}
@unknown default:
break
}
}
case .mail(_, let recipients, let bccRecipients, let body, let subject, _):
MailComposeView(recipients: recipients, bccRecipients: bccRecipients, body: body, subject: subject) { result in
switch result {
case .cancelled, .saved:
self.contactType = nil
case .failed:
self.contactType = nil
self.sentError = .mailFailed
case .sent:
if networkMonitor.connected == false {
self.contactType = nil
self.sentError = .mailNotSent
}
@unknown default:
break
}
}
}
}
.tint(.master)
}
}
private func _contactAllButtonView() -> some View {
Section {
Toggle("Inclure les équipes en liste d'attente", isOn: $includeWaitingList)
RowButtonView("Contacter toutes les équipes", systemImage: "paperplane") {
var teams = [TeamRegistration]()
if includeWaitingList {
teams = tournaments.flatMap { $0.allTeamsWithoutWalkOut() }
} else {
teams = tournaments.flatMap { $0.selectedSortedTeams() }
}
_contact(teams: teams)
}
} footer: {
Text("Permet de rédiger un mail à tous les équipes de tous les tournois.")
}
.disabled(StoreCenter.main.isAuthenticated == false)
}
func finalMessage() -> String? {
event?.eventLinksPasteData()
}
func subjectMessage() -> String? {
event?.eventTitle()
}
var messageSentFailed: Binding<Bool> {
Binding {
sentError != nil
} set: { newValue in
if newValue == false {
sentError = nil
}
}
}
func umpiresEmail() -> [String] {
let mails = tournaments.compactMap({
$0.umpireMail()
})
return mails.flatMap({ $0 })
}
fileprivate func _contact(teams: [TeamRegistration]) {
contactType = .mail(date: nil, recipients: umpiresEmail(), bccRecipients: teams.flatMap { $0.unsortedPlayers() }.compactMap { $0.email }, body: finalMessage(), subject: subjectMessage(), tournamentBuild: nil)
}
private var _networkErrorMessage: String {
ContactManagerError.getNetworkErrorMessage(sentError: sentError, networkMonitorConnected: networkMonitor.connected)
}
}

@ -95,7 +95,7 @@ struct StepperView: View {
}
fileprivate func _plusIsDisabled() -> Bool {
count >= (maximum ?? 90_000)
count >= (maximum ?? 90_415)
}
fileprivate func _add() {

@ -160,7 +160,7 @@ struct PadelClubView: View {
if let maleUnrankedValue = monthData.maleUnrankedValue {
Text(maleUnrankedValue.formatted())
} else {
Text(90_000.formatted())
Text(90_415.formatted())
}
} label: {
Text("Rang d'un non classé")
@ -179,9 +179,9 @@ struct PadelClubView: View {
Text("Dames")
}
#if DEBUG
RowButtonView("recalc") {
await _calculateLastRank(dataSource: monthData.monthKey)
}
RowButtonView("Recalculer les non classés") {
await _calculateLastRank(dataSource: monthData.monthKey)
}
#endif
} header: {
HStack {

@ -19,7 +19,7 @@ struct PlanningView: View {
@State private var showFinishedMatches: Bool = false
@State private var enableMove: Bool = false
@Environment(\.editMode) private var editMode
let updatePlannedDatesTip = UpdatePlannedDatesTip()
let allMatches: [Match]
let timeSlotMoveOptionTip = TimeSlotMoveOptionTip()
@ -189,6 +189,23 @@ struct PlanningView: View {
filterOption == .byCourt || showFinishedMatches ? .fill : .none)
}
Button("Mettre à jour", systemImage: "arrow.trianglehead.2.clockwise.rotate.90.circle") {
let now = Date()
matches.forEach {
if let startDate = $0.startDate, startDate > now {
$0.plannedStartDate = $0.startDate
}
}
let groupByTournaments = matches.grouped { match in
match.currentTournament()
}
groupByTournaments.forEach { tournament, matches in
tournament?.tournamentStore?.matches.addOrUpdate(contentOfs: matches)
}
}
.popoverTip(updatePlannedDatesTip)
}
}
})

@ -138,9 +138,17 @@ struct PlayerDetailView: View {
}
}
if player.isMalePlayer() == false && tournament.tournamentCategory == .men, let rank = player.rank {
let maxMaleUnrankedValue: Int = tournament.maleUnrankedValue ?? 90_415
if player.isMalePlayer() == false && tournament.tournamentCategory == .men && (player.rank == maxMaleUnrankedValue || player.rank == nil) {
Section {
Text("Une joueuse non classée dans un tournoi messieurs aura le rang d'un joueur non classé.")
} header: {
Text("Ré-assimilation")
}
} else if player.isMalePlayer() == false && tournament.tournamentCategory == .men, let rank = player.rank {
Section {
let value = PlayerRegistration.addon(for: rank, manMax: tournament.maleUnrankedValue ?? 0, womanMax: tournament.femaleUnrankedValue ?? 0)
let value = PlayerRegistration.addon(for: rank, manMax: maxMaleUnrankedValue, womanMax: tournament.femaleUnrankedValue ?? 0)
LabeledContent {
Text(value.formatted())
} label: {

@ -30,7 +30,7 @@ struct EditingTeamView: View {
@State private var refundMessage: String?
@State private var registrationDateModified: Date
@State private var uniqueRandomIndex: Int
@State private var isDeleting: Bool = false
var messageSentFailed: Binding<Bool> {
Binding {
@ -44,6 +44,10 @@ struct EditingTeamView: View {
var hasChanged: Binding<Bool> {
Binding {
if isDeleting {
return false
}
if canSaveWithoutWarning() {
return false
}
@ -286,6 +290,7 @@ struct EditingTeamView: View {
Section {
RowButtonView("Effacer l'équipe", role: .destructive, systemImage: "trash") {
isDeleting = true
_resetTeam()
team.deleteTeamScores()
do {

@ -259,7 +259,7 @@ struct ConsolationTournamentImportView: View {
return playerCopy
}
let teamHolder = FileImportManager.TeamHolder.init(players: players, tournamentCategory: tournament.category, tournamentAgeCategory: tournament.federalAgeCategory, previousTeam: tournament.findTeam(players), registrationDate: Date(), name: team.name, tournament: tournament)
let teamHolder = FileImportManager.TeamHolder.init(players: players, tournamentCategory: tournament.category, tournamentAgeCategory: tournament.federalAgeCategory, tournamentLevel: tournament.tournamentLevel, previousTeam: tournament.findTeam(players), registrationDate: Date(), name: team.name, tournament: tournament)
return teamHolder
}

@ -116,7 +116,7 @@ struct FileImportView: View {
if tournament.isAnimation() {
return teams.sorted(by: \.weight)
}
return teams.filter { $0.tournamentCategory == tournament.tournamentCategory && $0.tournamentAgeCategory == tournament.federalTournamentAge }.sorted(by: \.weight)
return teams.filter { $0.tournamentLevel == tournament.tournamentLevel && $0.tournamentCategory == tournament.tournamentCategory && $0.tournamentAgeCategory == tournament.federalTournamentAge }.sorted(by: \.weight)
}
private func _deleteTeams(teams: [TeamRegistration]) async {
@ -174,7 +174,7 @@ struct FileImportView: View {
RowButtonView("Démarrer l'importation") {
if let fileContent {
do {
try await _startImport(fileContent: fileContent)
try await _startImport(fileContent: fileContent, allTournaments: false)
} catch {
errorMessage = error.localizedDescription
}
@ -198,7 +198,7 @@ struct FileImportView: View {
multiImport = true
if let fileContent {
do {
try await _startImport(fileContent: fileContent)
try await _startImport(fileContent: fileContent, allTournaments: true)
} catch {
errorMessage = error.localizedDescription
}
@ -286,7 +286,7 @@ struct FileImportView: View {
Task {
if let fileContent {
do {
try await _startImport(fileContent: fileContent)
try await _startImport(fileContent: fileContent, allTournaments: false)
} catch {
errorMessage = error.localizedDescription
}
@ -305,7 +305,7 @@ struct FileImportView: View {
Task {
if let fileContent {
do {
try await _startImport(fileContent: fileContent)
try await _startImport(fileContent: fileContent, allTournaments: false)
} catch {
errorMessage = error.localizedDescription
}
@ -353,14 +353,20 @@ struct FileImportView: View {
}
}
let unfound = _getUnfound(tournament: tournament, fromTeams: _filteredTeams)
let unfound = _getUnfound(tournament: tournament, fromTeams: _filteredTeams).sorted(by: \.weight)
if unfound.isEmpty == false {
Section {
LabeledContent {
Text(unfound.count.formatted())
DisclosureGroup {
ForEach(unfound) {
TeamRowView(team: $0)
}
} label: {
Text("Équipe\(unfound.count.pluralSuffix) précédente\(unfound.count.pluralSuffix) introuvable\(unfound.count.pluralSuffix)")
LabeledContent {
Text(unfound.count.formatted())
} label: {
Text("Équipe\(unfound.count.pluralSuffix) précédente\(unfound.count.pluralSuffix) introuvable\(unfound.count.pluralSuffix)")
}
}
} footer: {
Text("Équipes de votre liste précédente non détectées dans ce nouveau fichier")
@ -429,6 +435,7 @@ struct FileImportView: View {
}
selectedFile.stopAccessingSecurityScopedResource()
} catch {
Logger.error(error)
errorMessage = error.localizedDescription
}
}
@ -437,6 +444,7 @@ struct FileImportView: View {
}
}
case .failure(let error):
Logger.error(error)
errorMessage = error.localizedDescription
}
})
@ -520,6 +528,7 @@ struct FileImportView: View {
struct CombinedCategory: Identifiable, Hashable {
let tournamentLevel: TournamentLevel
let tournamentCategory: TournamentCategory
let federalTournamentAge: FederalTournamentAge
@ -528,7 +537,7 @@ struct FileImportView: View {
}
}
private func _startImport(fileContent: String) async throws {
private func _startImport(fileContent: String, allTournaments: Bool) async throws {
await MainActor.run {
errorMessage = nil
teams.removeAll()
@ -538,10 +547,10 @@ struct FileImportView: View {
}
let event: Event? = tournament.eventObject()
if let event, event.federalTournaments().count > 1 {
if let event, event.federalTournaments().count > 1, allTournaments == true {
var categoriesDone: [CombinedCategory] = []
for someTournament in event.federalTournaments() {
let combinedCategory = CombinedCategory(tournamentCategory: someTournament.tournamentCategory, federalTournamentAge: someTournament.federalTournamentAge)
let combinedCategory = CombinedCategory(tournamentLevel: someTournament.tournamentLevel, tournamentCategory: someTournament.tournamentCategory, federalTournamentAge: someTournament.federalTournamentAge)
if categoriesDone.contains(combinedCategory) == false {
let _teams = try await FileImportManager.shared.createTeams(from: fileContent, tournament: someTournament, fileProvider: fileProvider, checkingCategoryDisabled: false, chunkByParameter: chunkByParameter)
self.teams += _teams

Loading…
Cancel
Save