diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index dabca92..51c8962 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -893,6 +893,9 @@ FF967D0D2BAF3EB300A9A3BD /* MatchDateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D0C2BAF3EB200A9A3BD /* MatchDateView.swift */; }; FF967D0F2BAF63B000A9A3BD /* PlayerBlockView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D0E2BAF63B000A9A3BD /* PlayerBlockView.swift */; }; FF9AC3952BE3627B00C2E883 /* GroupStageTeamReplacementView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9AC3942BE3627B00C2E883 /* GroupStageTeamReplacementView.swift */; }; + FF9B442B2D950E48002448C8 /* ConsolationTournamentImportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9B442A2D950E48002448C8 /* ConsolationTournamentImportView.swift */; }; + FF9B442C2D950E48002448C8 /* ConsolationTournamentImportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9B442A2D950E48002448C8 /* ConsolationTournamentImportView.swift */; }; + FF9B442D2D950E48002448C8 /* ConsolationTournamentImportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9B442A2D950E48002448C8 /* ConsolationTournamentImportView.swift */; }; FFA1B1292BB71773006CE248 /* PadelClubButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA1B1282BB71773006CE248 /* PadelClubButtonView.swift */; }; FFA252A92CDB70520074E63F /* PlayerStatisticView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA252A82CDB70520074E63F /* PlayerStatisticView.swift */; }; FFA252AA2CDB70520074E63F /* PlayerStatisticView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA252A82CDB70520074E63F /* PlayerStatisticView.swift */; }; @@ -1347,6 +1350,7 @@ FF967D0C2BAF3EB200A9A3BD /* MatchDateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchDateView.swift; sourceTree = ""; }; FF967D0E2BAF63B000A9A3BD /* PlayerBlockView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerBlockView.swift; sourceTree = ""; }; FF9AC3942BE3627B00C2E883 /* GroupStageTeamReplacementView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupStageTeamReplacementView.swift; sourceTree = ""; }; + FF9B442A2D950E48002448C8 /* ConsolationTournamentImportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsolationTournamentImportView.swift; sourceTree = ""; }; FFA1B1282BB71773006CE248 /* PadelClubButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PadelClubButtonView.swift; sourceTree = ""; }; FFA252A82CDB70520074E63F /* PlayerStatisticView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerStatisticView.swift; sourceTree = ""; }; FFA252AC2CDB734A0074E63F /* UmpireStatisticView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UmpireStatisticView.swift; sourceTree = ""; }; @@ -1879,6 +1883,7 @@ FF967CF52BAED51600A9A3BD /* TournamentRunningView.swift */, C4A47D882B7BBB5000ADC637 /* Subscription */, FF089EBE2BB0B14600F0AEC7 /* FileImportView.swift */, + FF9B442A2D950E48002448C8 /* ConsolationTournamentImportView.swift */, FF3F74F92B91A018004CFE0E /* Screen */, FF3F74F82B919FB2004CFE0E /* Shared */, ); @@ -2715,6 +2720,7 @@ FF82CFC52B911F5B00B0CAF2 /* OrganizedTournamentView.swift in Sources */, FFF964572BC26B3400EEF017 /* RoundScheduleEditorView.swift in Sources */, FF59FFB32B90EFAC0061EFF9 /* EventListView.swift in Sources */, + FF9B442D2D950E48002448C8 /* ConsolationTournamentImportView.swift in Sources */, FF8F263D2BAD627A00650388 /* TournamentConfiguratorView.swift in Sources */, FFC1E10C2BAC7FB0008D6F59 /* ClubImportView.swift in Sources */, FF558C632C6CDD020071F9AE /* UnderlineView.swift in Sources */, @@ -2908,6 +2914,7 @@ FF4CBF4B2C996C0600151637 /* Patcher.swift in Sources */, FF4CBF4C2C996C0600151637 /* FileImportView.swift in Sources */, FF4CBF4D2C996C0600151637 /* StepperView.swift in Sources */, + FF9B442C2D950E48002448C8 /* ConsolationTournamentImportView.swift in Sources */, FF4CBF4E2C996C0600151637 /* RoundsView.swift in Sources */, FF4CBF4F2C996C0600151637 /* FederalTournament.swift in Sources */, FF4CBF502C996C0600151637 /* TournamentInitView.swift in Sources */, @@ -3201,6 +3208,7 @@ FF70FACA2C90584900129CC2 /* Patcher.swift in Sources */, FF70FACB2C90584900129CC2 /* FileImportView.swift in Sources */, FF70FACC2C90584900129CC2 /* StepperView.swift in Sources */, + FF9B442B2D950E48002448C8 /* ConsolationTournamentImportView.swift in Sources */, FF70FACD2C90584900129CC2 /* RoundsView.swift in Sources */, FF70FACE2C90584900129CC2 /* FederalTournament.swift in Sources */, FF70FACF2C90584900129CC2 /* TournamentInitView.swift in Sources */, diff --git a/PadelClub/Views/Tournament/ConsolationTournamentImportView.swift b/PadelClub/Views/Tournament/ConsolationTournamentImportView.swift new file mode 100644 index 0000000..835e416 --- /dev/null +++ b/PadelClub/Views/Tournament/ConsolationTournamentImportView.swift @@ -0,0 +1,241 @@ +// +// ConsolationTournamentImportView.swift +// PadelClub +// +// Created by razmig on 27/03/2025. +// + +import SwiftUI +import LeStorage + +struct ConsolationTournamentImportView: View { + @EnvironmentObject var dataStore: DataStore + @Environment(Tournament.self) var tournament: Tournament + @Environment(\.dismiss) private var dismiss + + @State private var selectedTournament: Tournament? + @State private var selectedImportSelector: ImportSelector = .groupStage + @State private var selectedImportType: ImportType = .losers + + enum ImportType: Int, Identifiable, CaseIterable { + case losers + case winners + case all + + func importTypeLocalizedLabel() -> String { + switch self { + case .losers: + return "Perdants" + case .winners: + return "Gagnants" + case .all: + return "Tous" + } + } + + var id: Int { + self.rawValue + } + } + + + enum ImportSelector: Identifiable, Hashable { + case groupStage + case round(index: Int) + case all + + func importSelectorLocalizedLabel() -> String { + switch self { + case .groupStage: + return "Poule" + case .round(let index): + return RoundRule.roundName(fromRoundIndex: index) + case .all: + return "Tous" + } + } + + var id: String { + switch self { + case .groupStage, .all: + return String(describing: self) + case .round(let index): + return String(describing: index) + } + } + } + + var importSelectors: [ImportSelector] { + guard let selectedTournament, selectedTournament.unsortedTeams().isEmpty == false else { + return [] + } + + var _imports = [ImportSelector]() + + + if selectedTournament.groupStages().isEmpty == false { + if selectedTournament.groupStageTeams().isEmpty == false { + _imports.append(.groupStage) + } + } + + selectedTournament.rounds().forEach { round in + if round.teams().isEmpty == false { + _imports.append(.round(index: round.index)) + } + } + + _imports.append(.all) + + return _imports + } + + var tournaments: [Tournament] { + dataStore.tournaments.filter({ $0.isDeleted == false && $0.id != tournament.id }).sorted(by: \.startDate, order: .descending) + } + + var body: some View { + List { + Picker(selection: $selectedTournament) { + Text("Aucun tournoi").tag(nil as Tournament?) + ForEach(tournaments) { tournament in + TournamentCellView(tournament: tournament).tag(tournament) + } + } label: { + if selectedTournament == nil { + Text("Choisir la source") + } else { + EmptyView() + } + } + .pickerStyle(.navigationLink) + + let importSelectors = importSelectors + + if let selectedTournament { + if importSelectors.isEmpty == false { + Section { + Picker(selection: $selectedImportSelector) { + ForEach(importSelectors) { importSelector in + Text(importSelector.importSelectorLocalizedLabel()) + .tag(importSelector) + } + } label: { + Text("Équipes") + } + + if selectedImportSelector != .all { + Picker(selection: $selectedImportType) { + ForEach(ImportType.allCases) { importType in + Text(importType.importTypeLocalizedLabel()) + .tag(importType) + } + } label: { + Text("Type") + } + } + } + } + + let unsortedTeams = tournament.unsortedTeams() + let onlineTeams = unsortedTeams.filter({ $0.hasRegisteredOnline() }) + if unsortedTeams.count > 0 { + Section { + RowButtonView("Effacer les équipes actuelles", 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.") + } + } + } + + let teams = _teamsToImport(selectedTournament: selectedTournament) + + Section { + RowButtonView("Importer les équipes") { + await _importTeams(selectedTournament: selectedTournament, teams: teams) + dismiss() + } + .disabled(teams.isEmpty) + } footer: { + if teams.isEmpty { + Text("Aucune équipe détectée").foregroundStyle(.logoRed) + } + } + } + } + .onChange(of: selectedTournament) { + selectedImportSelector = importSelectors.first ?? .groupStage + selectedImportType = .losers + } + .navigationTitle("Importation") + .navigationBarTitleDisplayMode(.inline) + .toolbarBackground(.visible, for: .navigationBar) + } + + private func _teamsToImport(selectedTournament: Tournament) -> [TeamRegistration] { + var teams = [TeamRegistration]() + switch selectedImportSelector { + case .all: + teams = selectedTournament.unsortedTeams() + case .groupStage: + teams = selectedTournament.groupStageTeams().filter({ + switch selectedImportType { + case .losers: + return $0.qualified == false + case .winners: + return $0.qualified + case .all: + return true + } + }) + + + case .round(let index): + if let round = selectedTournament.rounds().filter({ $0.index == index }).first { + if selectedImportType == .all { + teams = round.teams() + } else { + teams = round.playedMatches().compactMap({ match in + if selectedImportType == .losers { + return match.loser() + } else if selectedImportType == .winners { + return match.winner() + } else { + return nil + } + }) + } + } + } + + return teams + } + + private func _importTeams(selectedTournament: Tournament, teams: [TeamRegistration]) async { + let teamHolders = teams.map { team in + let players = team.players().map { player in + let playerCopy = PlayerRegistration(firstName: player.firstName, lastName: player.lastName, licenceId: player.licenceId, rank: player.rank, sex: player.sex, tournamentPlayed: player.tournamentPlayed, points: player.points, clubName: player.clubName, ligueName: player.ligueName, assimilation: player.assimilation, phoneNumber: player.phoneNumber, email: player.email, birthdate: player.birthdate, computedRank: player.computedRank, source: player.source, hasArrived: false) + 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) + + return teamHolder + } + + tournament.rankSourceDate = selectedTournament.rankSourceDate + dataStore.tournaments.addOrUpdate(instance: tournament) + tournament.importTeams(teamHolders) + } + + private func _deleteTeams(teams: [TeamRegistration]) async { + await MainActor.run { + tournament.tournamentStore?.teamRegistrations.delete(contentOfs: teams) + } + } +} + diff --git a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift index 74f7667..536d27e 100644 --- a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift +++ b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift @@ -407,7 +407,7 @@ struct InscriptionManagerView: View { } Divider() - _sharingTeamsMenuView() + Button { presentImportView = true } label: { @@ -416,6 +416,10 @@ struct InscriptionManagerView: View { Link(destination: URLs.beachPadel.url) { Label("beach-padel.app.fft.fr", systemImage: "safari") } + + Divider() + _importTeamsMenuView() + _sharingTeamsMenuView() } else { _sharingTeamsMenuView() @@ -446,6 +450,8 @@ struct InscriptionManagerView: View { Divider() + _importTeamsMenuView() + Button { presentImportView = true } label: { @@ -482,6 +488,15 @@ struct InscriptionManagerView: View { } } + private func _importTeamsMenuView() -> some View { + NavigationLink { + ConsolationTournamentImportView() + .environment(tournament) + } label: { + Label("Importer des paires", systemImage: "square.and.arrow.down") + } + } + var walkoutTeams: [TeamRegistration] { tournament.walkoutTeams() }