fix crash when when replacing heads

fix undetected tournament
fix re-ranking efficiency
add option to handle re-ranks for all tournaments in a month
add option to handle refresh online reg list in a month
add automatic refresh in inscription manager and tournamentcell
display online reg count in inscription manager
reshow button to delete all teams and let the jap know it's locked due to online reg
sync2
Raz 9 months ago
parent 018e77fda7
commit e029295b6a
  1. 4
      PadelClub.xcodeproj/project.pbxproj
  2. 12
      PadelClub/Data/Federal/FederalTournament.swift
  3. 15
      PadelClub/Data/Match.swift
  4. 20
      PadelClub/Data/PlayerRegistration.swift
  5. 91
      PadelClub/Data/Tournament.swift
  6. 71
      PadelClub/Utils/SwiftParser.swift
  7. 67
      PadelClub/Views/Navigation/Agenda/EventListView.swift
  8. 2
      PadelClub/Views/Round/RoundSettingsView.swift
  9. 19
      PadelClub/Views/Tournament/FileImportView.swift
  10. 19
      PadelClub/Views/Tournament/Screen/Components/UpdateSourceRankDateView.swift
  11. 44
      PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift
  12. 9
      PadelClub/Views/Tournament/Screen/RegistrationSetupView.swift
  13. 11
      PadelClub/Views/Tournament/Screen/TableStructureView.swift
  14. 22
      PadelClub/Views/Tournament/Shared/TournamentCellView.swift

@ -3344,7 +3344,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.1.14; MARKETING_VERSION = 1.1.15;
PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@ -3389,7 +3389,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.1.14; MARKETING_VERSION = 1.1.15;
PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";

@ -236,12 +236,12 @@ struct CategorieAge: Codable {
var tournamentAge: FederalTournamentAge? { var tournamentAge: FederalTournamentAge? {
if let id { if let id {
return FederalTournamentAge(rawValue: id) return FederalTournamentAge(rawValue: id) ?? .senior
} }
if let libelle { if let libelle {
return FederalTournamentAge.allCases.first(where: { $0.localizedFederalAgeLabel().localizedCaseInsensitiveContains(libelle) }) return FederalTournamentAge.allCases.first(where: { $0.localizedFederalAgeLabel().localizedCaseInsensitiveContains(libelle) }) ?? .senior
} }
return nil return .senior
} }
} }
@ -295,7 +295,7 @@ struct Serie: Codable {
var sexe: String? var sexe: String?
var tournamentCategory: TournamentCategory? { var tournamentCategory: TournamentCategory? {
TournamentCategory.allCases.first(where: { $0.requestLabel == code }) TournamentCategory.allCases.first(where: { $0.requestLabel == code }) ?? .men
} }
} }
@ -348,9 +348,9 @@ struct TypeEpreuve: Codable {
var tournamentLevel: TournamentLevel? { var tournamentLevel: TournamentLevel? {
if let code, let value = Int(code.removingFirstCharacter) { if let code, let value = Int(code.removingFirstCharacter) {
return TournamentLevel(rawValue: value) return TournamentLevel(rawValue: value) ?? .p100
} }
return nil return .p100
} }
} }

@ -410,12 +410,15 @@ defer {
} }
} }
//byeState = false //byeState = false
roundObject?._cachedSeedInterval = nil
name = nil if state != currentState {
do { roundObject?._cachedSeedInterval = nil
try self.tournamentStore.matches.addOrUpdate(instance: self) name = nil
} catch { do {
Logger.error(error) try self.tournamentStore.matches.addOrUpdate(instance: self)
} catch {
Logger.error(error)
}
} }
if single == false { if single == false {

@ -301,21 +301,23 @@ final class PlayerRegistration: ModelObject, Storable {
return await withTaskGroup(of: Line?.self) { group in return await withTaskGroup(of: Line?.self) { group in
for source in filteredSources { for source in filteredSources {
group.addTask { group.addTask {
guard !Task.isCancelled else { print("Cancelled"); return nil } guard !Task.isCancelled else { return nil }
return try? await source.first { $0.rawValue.contains(";\(license);") } return try? await source.first { $0.rawValue.contains(";\(license);") }
} }
} }
if let first = await group.first(where: { $0 != nil }) { for await result in group {
group.cancelAll() if let result {
return first group.cancelAll() // Stop other tasks as soon as we find a match
return result
}
} }
return nil return nil
} }
} }
func historyFromName(from sources: [CSVParser]) async throws -> Line? { func historyFromName(from sources: [CSVParser]) async throws -> Line? {
#if DEBUG_TIME #if DEBUG
let start = Date() let start = Date()
defer { defer {
let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000) let duration = Duration.milliseconds(Date().timeIntervalSince(start) * 1_000)
@ -338,9 +340,11 @@ final class PlayerRegistration: ModelObject, Storable {
} }
} }
if let first = await group.first(where: { $0 != nil }) { for await result in group {
group.cancelAll() if let result {
return first group.cancelAll() // Stop other tasks as soon as we find a match
return result
}
} }
return nil return nil
} }

@ -70,6 +70,10 @@ final class Tournament : ModelObject, Storable {
var maximumPlayerPerTeam: Int = 2 var maximumPlayerPerTeam: Int = 2
var information: String? = nil var information: String? = nil
//local variable
var refreshInProgress: Bool = false
var refreshRanking: Bool = false
@ObservationIgnored @ObservationIgnored
var navigationPath: [Screen] = [] var navigationPath: [Screen] = []
@ -1513,8 +1517,8 @@ defer {
} }
} }
func updateRank(to newDate: Date?) async throws { func updateRank(to newDate: Date?, forceRefreshLockWeight: Bool, providedSources: [CSVParser]?) async throws {
refreshRanking = true
#if DEBUG_TIME #if DEBUG_TIME
let start = Date() let start = Date()
defer { defer {
@ -1549,16 +1553,42 @@ defer {
let lastRankMan = monthData?.maleUnrankedValue ?? 0 let lastRankMan = monthData?.maleUnrankedValue ?? 0
let lastRankWoman = monthData?.femaleUnrankedValue ?? 0 let lastRankWoman = monthData?.femaleUnrankedValue ?? 0
// Fetch only the required files var chunkedParsers: [CSVParser] = []
let dataURLs = SourceFileManager.shared.allFiles.filter { $0.dateFromPath == newDate } if let providedSources {
guard !dataURLs.isEmpty else { return } // Early return if no files found chunkedParsers = providedSources
} else {
// Fetch only the required files
let dataURLs = SourceFileManager.shared.allFiles.filter { $0.dateFromPath == newDate }
guard !dataURLs.isEmpty else { return } // Early return if no files found
let sources = dataURLs.map { CSVParser(url: $0) }
chunkedParsers = try await chunkAllSources(sources: sources, size: 10000)
}
let sources = dataURLs.map { CSVParser(url: $0) }
let players = unsortedPlayers() let players = unsortedPlayers()
try await players.concurrentForEach { player in try await players.concurrentForEach { player in
let lastRank = (player.sex == .female) ? lastRankWoman : lastRankMan let lastRank = (player.sex == .female) ? lastRankWoman : lastRankMan
try await player.updateRank(from: sources, lastRank: lastRank) try await player.updateRank(from: chunkedParsers, lastRank: lastRank)
player.setComputedRank(in: self)
}
if providedSources == nil {
try chunkedParsers.forEach { chunk in
try FileManager.default.removeItem(at: chunk.url)
}
} }
try tournamentStore.playerRegistrations.addOrUpdate(contentOfs: players)
let unsortedTeams = unsortedTeams()
unsortedTeams.forEach { team in
team.setWeight(from: team.players(), inTournamentCategory: tournamentCategory)
if forceRefreshLockWeight {
team.lockedWeight = team.weight
}
}
try tournamentStore.teamRegistrations.addOrUpdate(contentOfs: unsortedTeams)
refreshRanking = false
} }
@ -2424,12 +2454,15 @@ defer {
func updateSeedsBracketPosition() async { func updateSeedsBracketPosition() async {
await removeAllSeeds() await removeAllSeeds(saveTeamsAtTheEnd: false)
let drawLogs = drawLogs().reversed() let drawLogs = drawLogs().reversed()
let seeds = seeds() let seeds = seeds()
for (index, seed) in seeds.enumerated() {
if let drawLog = drawLogs.first(where: { $0.drawSeed == index }) { await MainActor.run {
drawLog.updateTeamBracketPosition(seed) for (index, seed) in seeds.enumerated() {
if let drawLog = drawLogs.first(where: { $0.drawSeed == index }) {
drawLog.updateTeamBracketPosition(seed)
}
} }
} }
@ -2440,11 +2473,13 @@ defer {
} }
} }
func removeAllSeeds() async { func removeAllSeeds(saveTeamsAtTheEnd: Bool) async {
let teams = unsortedTeams() let teams = unsortedTeams()
teams.forEach({ team in teams.forEach({ team in
team.bracketPosition = nil team.bracketPosition = nil
team._cachedRestingTime = nil team._cachedRestingTime = nil
team.finalRanking = nil
team.pointsEarned = nil
}) })
let allMatches = allRoundMatches() let allMatches = allRoundMatches()
let ts = allMatches.flatMap { match in let ts = allMatches.flatMap { match in
@ -2471,12 +2506,13 @@ defer {
Logger.error(error) Logger.error(error)
} }
do { if saveTeamsAtTheEnd {
try tournamentStore.teamRegistrations.addOrUpdate(contentOfs: teams) do {
} catch { try tournamentStore.teamRegistrations.addOrUpdate(contentOfs: teams)
Logger.error(error) } catch {
Logger.error(error)
}
} }
updateTournamentState()
} }
func addNewRound(_ roundIndex: Int) async { func addNewRound(_ roundIndex: Int) async {
@ -2608,6 +2644,27 @@ defer {
return false return false
} }
func rankSourceShouldBeRefreshed() -> Date? {
if let mostRecentDate = SourceFileManager.shared.lastDataSourceDate(), let currentRankSourceDate = rankSourceDate, currentRankSourceDate < mostRecentDate, hasEnded() == false {
return mostRecentDate
} else {
return nil
}
}
func onlineTeams() -> [TeamRegistration] {
unsortedTeams().filter({ $0.hasRegisteredOnline() })
}
func refreshTeamList() async throws {
guard enableOnlineRegistration, refreshInProgress == false, hasEnded() == false else { return }
refreshInProgress = true
try await self.tournamentStore.playerRegistrations.loadDataFromServerIfAllowed(clear: true)
try await self.tournamentStore.teamScores.loadDataFromServerIfAllowed(clear: true)
try await self.tournamentStore.teamRegistrations.loadDataFromServerIfAllowed(clear: true)
refreshInProgress = false
}
// MARK: - // MARK: -
func insertOnServer() throws { func insertOnServer() throws {

@ -70,18 +70,18 @@ struct Line: Identifiable {
struct CSVParser: AsyncSequence, AsyncIteratorProtocol { struct CSVParser: AsyncSequence, AsyncIteratorProtocol {
typealias Element = Line typealias Element = Line
private let url: URL let url: URL
private var lineIterator: LineIterator private var lineIterator: LineIterator
private let seperator: Character private let separator: Character
private let quoteCharacter: Character = "\"" private let quoteCharacter: Character = "\""
private var lineNumber = 0 private var lineNumber = 0
private let date: Date private let date: Date
let maleData: Bool let maleData: Bool
init(url: URL, seperator: Character = ";") { init(url: URL, separator: Character = ";") {
self.date = url.dateFromPath self.date = url.dateFromPath
self.url = url self.url = url
self.seperator = seperator self.separator = separator
self.lineIterator = url.lines.makeAsyncIterator() self.lineIterator = url.lines.makeAsyncIterator()
self.maleData = url.path().contains(SourceFile.messieurs.rawValue) self.maleData = url.path().contains(SourceFile.messieurs.rawValue)
} }
@ -127,7 +127,7 @@ struct CSVParser: AsyncSequence, AsyncIteratorProtocol {
func makeAsyncIterator() -> CSVParser { func makeAsyncIterator() -> CSVParser {
return self return self
} }
private func split(line: String) -> [String?] { private func split(line: String) -> [String?] {
var data = [String?]() var data = [String?]()
var inQuote = false var inQuote = false
@ -139,7 +139,7 @@ struct CSVParser: AsyncSequence, AsyncIteratorProtocol {
inQuote = !inQuote inQuote = !inQuote
continue continue
case seperator: case separator:
if !inQuote { if !inQuote {
data.append(currentString.isEmpty ? nil : currentString) data.append(currentString.isEmpty ? nil : currentString)
currentString = "" currentString = ""
@ -157,4 +157,63 @@ struct CSVParser: AsyncSequence, AsyncIteratorProtocol {
return data return data
} }
/// Splits the CSV file into multiple temporary CSV files, each containing `size` lines.
/// Returns an array of new `CSVParser` instances pointing to these chunked files.
func getChunkedParser(size: Int) async throws -> [CSVParser] {
var chunkedParsers: [CSVParser] = []
var currentChunk: [String] = []
var iterator = self.makeAsyncIterator()
var chunkIndex = 0
while let line = try await iterator.next()?.rawValue {
currentChunk.append(line)
// When the chunk reaches the desired size, write it to a file
if currentChunk.count == size {
let chunkURL = try writeChunkToFile(chunk: currentChunk, index: chunkIndex)
chunkedParsers.append(CSVParser(url: chunkURL, separator: self.separator))
chunkIndex += 1
currentChunk.removeAll()
}
}
// Handle remaining lines (if any)
if !currentChunk.isEmpty {
let chunkURL = try writeChunkToFile(chunk: currentChunk, index: chunkIndex)
chunkedParsers.append(CSVParser(url: chunkURL, separator: self.separator))
}
return chunkedParsers
}
/// Writes a chunk of CSV lines to a temporary file and returns its URL.
private func writeChunkToFile(chunk: [String], index: Int) throws -> URL {
let tempDirectory = FileManager.default.temporaryDirectory
let chunkURL = tempDirectory.appendingPathComponent("\(url.lastPathComponent)-\(index).csv")
let chunkData = chunk.joined(separator: "\n")
try chunkData.write(to: chunkURL, atomically: true, encoding: .utf8)
return chunkURL
}
}
/// Process all large CSV files concurrently and gather all mini CSVs.
func chunkAllSources(sources: [CSVParser], size: Int) async throws -> [CSVParser] {
var allChunks: [CSVParser] = []
await withTaskGroup(of: [CSVParser].self) { group in
for source in sources {
group.addTask {
return (try? await source.getChunkedParser(size: size)) ?? []
}
}
for await miniCSVs in group {
allChunks.append(contentsOf: miniCSVs)
}
}
return allChunks
} }

@ -17,6 +17,11 @@ struct EventListView: View {
let tournaments: [FederalTournamentHolder] let tournaments: [FederalTournamentHolder]
let sortAscending: Bool let sortAscending: Bool
var lastDataSource: Date? {
guard let _lastDataSource = dataStore.appSettings.lastDataSource else { return nil }
return URL.importDateFormatter.date(from: _lastDataSource)
}
var body: some View { var body: some View {
let groupedTournamentsByDate = Dictionary(grouping: federalDataViewModel.filteredFederalTournaments(from: tournaments)) { $0.startDate.startOfMonth } let groupedTournamentsByDate = Dictionary(grouping: federalDataViewModel.filteredFederalTournaments(from: tournaments)) { $0.startDate.startOfMonth }
switch viewStyle { switch viewStyle {
@ -101,6 +106,41 @@ struct EventListView: View {
@ViewBuilder @ViewBuilder
private func _options(_ pcTournaments: [Tournament]) -> some View { private func _options(_ pcTournaments: [Tournament]) -> some View {
if let lastDataSource, pcTournaments.anySatisfy({ $0.rankSourceShouldBeRefreshed() != nil && $0.hasEnded() == false }) {
Section {
Button {
Task {
do {
let dataURLs = SourceFileManager.shared.allFiles.filter { $0.dateFromPath == lastDataSource }
guard !dataURLs.isEmpty else { return } // Early return if no files found
let sources = dataURLs.map { CSVParser(url: $0) }
let chunkedParsers = try await chunkAllSources(sources: sources, size: 10000)
try await pcTournaments.concurrentForEach { tournament in
if let mostRecentDate = tournament.rankSourceShouldBeRefreshed() {
try await tournament.updateRank(to: mostRecentDate, forceRefreshLockWeight: false, providedSources: chunkedParsers)
}
}
try chunkedParsers.forEach { chunk in
try FileManager.default.removeItem(at: chunk.url)
}
try dataStore.tournaments.addOrUpdate(contentOfs: pcTournaments)
} catch {
Logger.error(error)
}
}
} label: {
Text("Rafraîchir les classements")
}
} header: {
Text("Source disponible : \(lastDataSource.monthYearFormatted)")
}
Divider()
}
Section { Section {
if pcTournaments.anySatisfy({ $0.isPrivate == true }) { if pcTournaments.anySatisfy({ $0.isPrivate == true }) {
Button { Button {
@ -135,7 +175,7 @@ struct EventListView: View {
Text("Visibilité sur Padel Club") Text("Visibilité sur Padel Club")
} }
Divider() Divider()
if pcTournaments.anySatisfy({ $0.hasEnded() == false && $0.enableOnlineRegistration == false && $0.onlineRegistrationCanBeEnabled() }) || pcTournaments.anySatisfy({ $0.enableOnlineRegistration == true }) { if pcTournaments.anySatisfy({ $0.hasEnded() == false && $0.enableOnlineRegistration == false && $0.onlineRegistrationCanBeEnabled() }) || pcTournaments.anySatisfy({ $0.enableOnlineRegistration == true && $0.hasEnded() == false }) {
Section { Section {
if pcTournaments.anySatisfy({ $0.hasEnded() == false && $0.enableOnlineRegistration == false && $0.onlineRegistrationCanBeEnabled() }) { if pcTournaments.anySatisfy({ $0.hasEnded() == false && $0.enableOnlineRegistration == false && $0.onlineRegistrationCanBeEnabled() }) {
Button { Button {
@ -152,7 +192,23 @@ struct EventListView: View {
} }
} }
if pcTournaments.anySatisfy({ $0.enableOnlineRegistration == true }) { if pcTournaments.anySatisfy({ $0.enableOnlineRegistration == true && $0.hasEnded() == false }) {
Button {
Task {
do {
try await pcTournaments.concurrentForEach { tournament in
try await tournament.refreshTeamList()
}
} catch {
Logger.error(error)
}
}
} label: {
Text("Rafraîchir la liste des équipes inscrites en ligne")
}
Button { Button {
pcTournaments.forEach { tournament in pcTournaments.forEach { tournament in
tournament.enableOnlineRegistration = false tournament.enableOnlineRegistration = false
@ -207,6 +263,13 @@ struct EventListView: View {
private func _tournamentView(_ tournament: Tournament) -> some View { private func _tournamentView(_ tournament: Tournament) -> some View {
NavigationLink(value: tournament) { NavigationLink(value: tournament) {
TournamentCellView(tournament: tournament, shouldTournamentBeOver: tournament.shouldTournamentBeOver()) TournamentCellView(tournament: tournament, shouldTournamentBeOver: tournament.shouldTournamentBeOver())
.task {
do {
try await tournament.refreshTeamList()
} catch {
Logger.error(error)
}
}
} }
.listRowView(isActive: tournament.enableOnlineRegistration, color: .green, hideColorVariation: true) .listRowView(isActive: tournament.enableOnlineRegistration, color: .green, hideColorVariation: true)
.contextMenu { .contextMenu {

@ -158,7 +158,7 @@ struct RoundSettingsView: View {
private func _removeAllSeeds() async { private func _removeAllSeeds() async {
await tournament.removeAllSeeds() await tournament.removeAllSeeds(saveTeamsAtTheEnd: true)
self.isEditingTournamentSeed.wrappedValue = true self.isEditingTournamentSeed.wrappedValue = true
} }

@ -120,10 +120,10 @@ struct FileImportView: View {
return teams.filter { $0.tournamentCategory == tournament.tournamentCategory && $0.tournamentAgeCategory == tournament.federalTournamentAge }.sorted(by: \.weight) return teams.filter { $0.tournamentCategory == tournament.tournamentCategory && $0.tournamentAgeCategory == tournament.federalTournamentAge }.sorted(by: \.weight)
} }
private func _deleteTeams() async { private func _deleteTeams(teams: [TeamRegistration]) async {
await MainActor.run { await MainActor.run {
do { do {
try tournamentStore.teamRegistrations.delete(contentOfs: tournament.unsortedTeams()) try tournamentStore.teamRegistrations.delete(contentOfs: teams)
} catch { } catch {
Logger.error(error) Logger.error(error)
} }
@ -140,9 +140,18 @@ struct FileImportView: View {
} }
} }
if tournament.unsortedTeams().count > 0, tournament.enableOnlineRegistration == false { let unsortedTeams = tournament.unsortedTeams()
RowButtonView("Effacer les équipes déjà inscrites", role: .destructive) { let onlineTeams = unsortedTeams.filter({ $0.hasRegisteredOnline() })
await _deleteTeams() if unsortedTeams.count > 0 {
Section {
RowButtonView("Effacer les équipes déjà inscrites", role: .destructive) {
await _deleteTeams(teams: unsortedTeams)
}
.disabled(onlineTeams.isEmpty == false)
} footer: {
if onlineTeams.isEmpty == false {
Text("Ce tournoi contient des inscriptions en ligne, vous ne pouvez pas effacer toute votre liste d'inscription d'un coup.")
}
} }
} }

@ -42,24 +42,7 @@ struct UpdateSourceRankDateView: View {
updatingRank = true updatingRank = true
Task { Task {
do { do {
try await tournament.updateRank(to: currentRankSourceDate) try await tournament.updateRank(to: currentRankSourceDate, forceRefreshLockWeight: forceRefreshLockWeight, providedSources: nil)
let unsortedPlayers = tournament.unsortedPlayers()
tournament.unsortedPlayers().forEach { player in
player.setComputedRank(in: tournament)
}
try tournamentStore.playerRegistrations.addOrUpdate(contentOfs: unsortedPlayers)
let unsortedTeams = tournament.unsortedTeams()
unsortedTeams.forEach { team in
team.setWeight(from: team.players(), inTournamentCategory: tournament.tournamentCategory)
if forceRefreshLockWeight {
team.lockedWeight = team.weight
}
}
try tournamentStore.teamRegistrations.addOrUpdate(contentOfs: unsortedTeams)
try dataStore.tournaments.addOrUpdate(instance: tournament) try dataStore.tournaments.addOrUpdate(instance: tournament)
} catch { } catch {
Logger.error(error) Logger.error(error)

@ -51,7 +51,6 @@ struct InscriptionManagerView: View {
@State private var pasteString: String? @State private var pasteString: String?
@State private var registrationIssues: Int? = nil @State private var registrationIssues: Int? = nil
@State private var refreshResult: String? = nil @State private var refreshResult: String? = nil
@State private var refreshInProgress: Bool = false
@State private var refreshStatus: Bool? @State private var refreshStatus: Bool?
@State private var showLegendView: Bool = false @State private var showLegendView: Bool = false
@ -259,6 +258,9 @@ struct InscriptionManagerView: View {
} }
} }
} }
.task {
await _refreshList()
}
.refreshable { .refreshable {
await _refreshList() await _refreshList()
} }
@ -543,28 +545,27 @@ struct InscriptionManagerView: View {
// } // }
// //
private func _refreshList() async { private func _refreshList() async {
if refreshInProgress { return } if tournament.enableOnlineRegistration == false { return }
if tournament.hasEnded() { return }
if tournament.refreshInProgress { return }
refreshResult = nil refreshResult = nil
refreshStatus = nil refreshStatus = nil
refreshInProgress = true
do { do {
try await self.tournamentStore.playerRegistrations.loadDataFromServerIfAllowed(clear: true)
try await self.tournamentStore.teamScores.loadDataFromServerIfAllowed(clear: true) try await self.tournament.refreshTeamList()
try await self.tournamentStore.teamRegistrations.loadDataFromServerIfAllowed(clear: true)
_setHash() _setHash()
self.refreshResult = "la synchronization a réussi" self.refreshResult = "La synchronization a réussi"
self.refreshStatus = true self.refreshStatus = true
refreshInProgress = false
} catch { } catch {
Logger.error(error) Logger.error(error)
self.refreshResult = "la synchronization a échoué" self.refreshResult = "La synchronization a échoué"
self.refreshStatus = false self.refreshStatus = false
refreshInProgress = false tournament.refreshInProgress = false
} }
} }
@ -717,7 +718,7 @@ struct InscriptionManagerView: View {
@ViewBuilder @ViewBuilder
private func _rankHandlerView() -> some View { private func _rankHandlerView() -> some View {
if let mostRecentDate = SourceFileManager.shared.lastDataSourceDate(), let currentRankSourceDate, currentRankSourceDate < mostRecentDate, tournament.hasEnded() == false { if let mostRecentDate = tournament.rankSourceShouldBeRefreshed() {
Section { Section {
TipView(rankUpdateTip) { action in TipView(rankUpdateTip) { action in
self.currentRankSourceDate = mostRecentDate self.currentRankSourceDate = mostRecentDate
@ -845,19 +846,22 @@ struct InscriptionManagerView: View {
} }
} label: { } label: {
LabeledContent { LabeledContent {
if refreshInProgress { if tournament.refreshInProgress {
ProgressView() ProgressView()
} else if let refreshStatus { } else {
if refreshStatus { Text(tournament.unsortedTeams().filter({ $0.hasRegisteredOnline() }).count.formatted())
Image(systemName: "checkmark").foregroundStyle(.green).font(.headline) .fontWeight(.bold)
} else {
Image(systemName: "xmark").foregroundStyle(.logoRed).font(.headline)
}
} }
} label: { } label: {
Text("Récupérer les inscriptions en ligne") if let refreshStatus {
Text("Inscriptions en ligne")
} else if tournament.refreshInProgress {
Text("Récupération des inscrits en ligne")
} else {
Text("Récupérer des inscrits en ligne")
}
if let refreshResult { if let refreshResult {
Text(refreshResult) Text(refreshResult).foregroundStyle(.secondary)
} }
} }
} }

@ -27,7 +27,8 @@ struct RegistrationSetupView: View {
@State private var showMoreInfos: Bool = false @State private var showMoreInfos: Bool = false
@State private var hasChanges: Bool = false @State private var hasChanges: Bool = false
@State private var displayWarning: Bool = false
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
init(tournament: Tournament) { init(tournament: Tournament) {
@ -74,6 +75,11 @@ struct RegistrationSetupView: View {
var body: some View { var body: some View {
List { List {
if displayWarning, tournament.enableOnlineRegistration, tournament.onlineTeams().isEmpty == false {
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. Pour l'instant Padel Club ne saura pas les prévenir automatiquement, vous devrez les contacter via l'écran de gestion des inscriptions.")
.foregroundStyle(.logoRed)
}
Section { Section {
Toggle(isOn: $enableOnlineRegistration) { Toggle(isOn: $enableOnlineRegistration) {
Text("Activer") Text("Activer")
@ -249,6 +255,7 @@ struct RegistrationSetupView: View {
} }
.onChange(of: targetTeamCount) { .onChange(of: targetTeamCount) {
displayWarning = true
_hasChanged() _hasChanged()
} }

@ -21,6 +21,8 @@ struct TableStructureView: View {
@State private var updatedElements: Set<StructureElement> = Set() @State private var updatedElements: Set<StructureElement> = Set()
@State private var structurePreset: PadelTournamentStructurePreset = .manual @State private var structurePreset: PadelTournamentStructurePreset = .manual
@State private var buildWildcards: Bool = true @State private var buildWildcards: Bool = true
@State private var displayWarning: Bool = false
@FocusState private var stepperFieldIsFocused: Bool @FocusState private var stepperFieldIsFocused: Bool
var qualifiedFromGroupStage: Int { var qualifiedFromGroupStage: Int {
@ -58,7 +60,11 @@ struct TableStructureView: View {
@ViewBuilder @ViewBuilder
var body: some View { var body: some View {
List { List {
if displayWarning, tournament.enableOnlineRegistration, tournament.onlineTeams().isEmpty == false {
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. Pour l'instant Padel Club ne saura pas les prévenir automatiquement, vous devrez les contacter via l'écran de gestion des inscriptions.")
.foregroundStyle(.logoRed)
}
if tournament.state() != .build { if tournament.state() != .build {
Section { Section {
Picker(selection: $structurePreset) { Picker(selection: $structurePreset) {
@ -91,6 +97,9 @@ struct TableStructureView: View {
} label: { } label: {
Text("Nombre d'équipes") Text("Nombre d'équipes")
} }
.onChange(of: teamCount) {
displayWarning = true
}
LabeledContent { LabeledContent {
StepperView(count: $groupStageCount, minimum: 0, maximum: maxGroupStages) { StepperView(count: $groupStageCount, minimum: 0, maximum: maxGroupStages) {

@ -114,7 +114,9 @@ struct TournamentCellView: View {
} }
Spacer() Spacer()
if let tournament = tournament as? Tournament, displayStyle == .wide { if let tournament = tournament as? Tournament, displayStyle == .wide {
if tournament.isCanceled { if tournament.refreshInProgress || tournament.refreshRanking {
ProgressView()
} else if tournament.isCanceled {
Text("Annulé".uppercased()) Text("Annulé".uppercased())
.capsule(foreground: .white, background: .logoRed) .capsule(foreground: .white, background: .logoRed)
} else if shouldTournamentBeOver { } else if shouldTournamentBeOver {
@ -164,6 +166,24 @@ struct TournamentCellView: View {
Text(build.category.localizedLabel()) Text(build.category.localizedLabel())
Text(build.age.localizedFederalAgeLabel()) Text(build.age.localizedFederalAgeLabel())
} }
if displayStyle == .wide, let tournament = tournament as? Tournament {
if tournament.enableOnlineRegistration {
let value: Int = tournament.onlineTeams().count
HStack {
Spacer()
if value == 0 {
Text("(dont aucune équipe inscrite en ligne)").foregroundStyle(.secondary).font(.footnote)
} else {
Text("(dont " + value.formatted() + " équipe\(value.pluralSuffix) inscrite\(value.pluralSuffix) en ligne)").foregroundStyle(.secondary).font(.footnote)
}
}
}
if tournament.refreshRanking {
Text("mise à jour des classements des joueurs").foregroundStyle(.secondary).font(.footnote)
} else if tournament.refreshInProgress {
Text("synchronisation des inscriptions en ligne").foregroundStyle(.secondary).font(.footnote)
}
}
} }
} }
.font(.caption) .font(.caption)

Loading…
Cancel
Save