diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 74d334d..10b7a3f 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -90,6 +90,9 @@ FF17CA532CBE4788003C7323 /* BracketCallingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF17CA522CBE4788003C7323 /* BracketCallingView.swift */; }; FF17CA542CBE4788003C7323 /* BracketCallingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF17CA522CBE4788003C7323 /* BracketCallingView.swift */; }; FF17CA552CBE4788003C7323 /* BracketCallingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF17CA522CBE4788003C7323 /* BracketCallingView.swift */; }; + FF17CA572CC02FEA003C7323 /* CoachListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF17CA562CC02FEA003C7323 /* CoachListView.swift */; }; + FF17CA582CC02FEB003C7323 /* CoachListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF17CA562CC02FEA003C7323 /* CoachListView.swift */; }; + FF17CA592CC02FEB003C7323 /* CoachListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF17CA562CC02FEA003C7323 /* CoachListView.swift */; }; FF1CBC1B2BB53D1F0036DAAB /* FederalTournament.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1CBC182BB53D1F0036DAAB /* FederalTournament.swift */; }; FF1CBC1D2BB53DC10036DAAB /* Calendar+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1CBC1C2BB53DC10036DAAB /* Calendar+Extensions.swift */; }; FF1CBC1F2BB53E0C0036DAAB /* FederalTournamentSearchScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1CBC1E2BB53E0C0036DAAB /* FederalTournamentSearchScope.swift */; }; @@ -989,6 +992,7 @@ FF17CA482CB915A1003C7323 /* MultiCourtPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiCourtPickerView.swift; sourceTree = ""; }; FF17CA4C2CB9243E003C7323 /* FollowUpMatchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowUpMatchView.swift; sourceTree = ""; }; FF17CA522CBE4788003C7323 /* BracketCallingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BracketCallingView.swift; sourceTree = ""; }; + FF17CA562CC02FEA003C7323 /* CoachListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoachListView.swift; sourceTree = ""; }; FF1CBC182BB53D1F0036DAAB /* FederalTournament.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FederalTournament.swift; sourceTree = ""; }; FF1CBC1C2BB53DC10036DAAB /* Calendar+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Calendar+Extensions.swift"; sourceTree = ""; }; FF1CBC1E2BB53E0C0036DAAB /* FederalTournamentSearchScope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FederalTournamentSearchScope.swift; sourceTree = ""; }; @@ -1815,6 +1819,7 @@ FF967D0A2BAF3D4C00A9A3BD /* TeamPickerView.swift */, FF089EB52BB00A3800F0AEC7 /* TeamRowView.swift */, FF1162862BD004AD000C4809 /* EditingTeamView.swift */, + FF17CA562CC02FEA003C7323 /* CoachListView.swift */, FF025AD62BD0C0FB00A86CF8 /* Components */, ); path = Team; @@ -2412,6 +2417,7 @@ C49EF01B2BD6A1E80077B5AA /* URLs.swift in Sources */, FFCFC0142BBC59FC00B82851 /* MatchDescriptor.swift in Sources */, FF8F264C2BAE0B4100650388 /* TournamentFormatSelectionView.swift in Sources */, + FF17CA572CC02FEA003C7323 /* CoachListView.swift in Sources */, FFBF065E2BBD8040009D6715 /* MatchListView.swift in Sources */, C425D4012B6D249D002A7B48 /* PadelClubApp.swift in Sources */, FF8F26432BADFE5B00650388 /* TournamentSettingsView.swift in Sources */, @@ -2684,6 +2690,7 @@ FF4CBFF02C996C0600151637 /* URLs.swift in Sources */, FF4CBFF12C996C0600151637 /* MatchDescriptor.swift in Sources */, FF4CBFF22C996C0600151637 /* TournamentFormatSelectionView.swift in Sources */, + FF17CA592CC02FEB003C7323 /* CoachListView.swift in Sources */, FF4CBFF32C996C0600151637 /* MatchListView.swift in Sources */, FF4CBFF42C996C0600151637 /* PadelClubApp.swift in Sources */, FF4CBFF52C996C0600151637 /* TournamentSettingsView.swift in Sources */, @@ -2935,6 +2942,7 @@ FF70FB6F2C90584900129CC2 /* URLs.swift in Sources */, FF70FB702C90584900129CC2 /* MatchDescriptor.swift in Sources */, FF70FB712C90584900129CC2 /* TournamentFormatSelectionView.swift in Sources */, + FF17CA582CC02FEB003C7323 /* CoachListView.swift in Sources */, FF70FB722C90584900129CC2 /* MatchListView.swift in Sources */, FF70FB732C90584900129CC2 /* PadelClubApp.swift in Sources */, FF70FB742C90584900129CC2 /* TournamentSettingsView.swift in Sources */, diff --git a/PadelClub/Views/Calling/BracketCallingView.swift b/PadelClub/Views/Calling/BracketCallingView.swift index 0ba3512..9aa7ec9 100644 --- a/PadelClub/Views/Calling/BracketCallingView.swift +++ b/PadelClub/Views/Calling/BracketCallingView.swift @@ -60,11 +60,14 @@ struct BracketCallingView: View { var body: some View { List { - NavigationLink { - TeamsCallingView(teams: teams.filter({ $0.callDate == nil })) - .environment(tournament) - } label: { - LabeledContent("Équipes non contactées", value: teams.filter({ $0.callDate == nil }).count.formatted()) + let uncalledTeams = teams.filter({ $0.callDate == nil }) + if uncalledTeams.isEmpty == false { + NavigationLink { + TeamsCallingView(teams: uncalledTeams) + .environment(tournament) + } label: { + LabeledContent("Équipe\(uncalledTeams.count.pluralSuffix) non contactée\(uncalledTeams.count.pluralSuffix)", value: uncalledTeams.count.formatted()) + } } PlayersWithoutContactView(players: teams.flatMap({ $0.unsortedPlayers() }).sorted(by: \.computedRank)) @@ -119,8 +122,15 @@ struct BracketCallingView: View { @ViewBuilder private func _roundView(round: Round, seeds: [TeamRegistration]) -> some View { List { - NavigationLink("Équipes non contactées") { - TeamsCallingView(teams: seeds.filter({ $0.callDate == nil })) + + let uncalledTeams = seeds.filter({ $0.callDate == nil }) + if uncalledTeams.isEmpty == false { + NavigationLink { + TeamsCallingView(teams: uncalledTeams) + .environment(tournament) + } label: { + LabeledContent("Équipe\(uncalledTeams.count.pluralSuffix) non contactée\(uncalledTeams.count.pluralSuffix)", value: uncalledTeams.count.formatted()) + } } let startDate = round.startDate ?? round.playedMatches().first?.startDate diff --git a/PadelClub/Views/Calling/GroupStageCallingView.swift b/PadelClub/Views/Calling/GroupStageCallingView.swift index 357df0f..6a419e1 100644 --- a/PadelClub/Views/Calling/GroupStageCallingView.swift +++ b/PadelClub/Views/Calling/GroupStageCallingView.swift @@ -15,11 +15,14 @@ struct GroupStageCallingView: View { let groupStages = tournament.groupStages() List { - NavigationLink { - TeamsCallingView(teams: groupStages.flatMap({ $0.unsortedTeams() }).filter({ $0.callDate == nil })) - .environment(tournament) - } label: { - LabeledContent("Équipes non contactées", value: groupStages.flatMap({ $0.unsortedTeams() }).filter({ $0.callDate == nil }).count.formatted()) + let uncalled = groupStages.flatMap({ $0.unsortedTeams() }).filter({ $0.callDate == nil }) + if uncalled.isEmpty == false { + NavigationLink { + TeamsCallingView(teams: uncalled) + .environment(tournament) + } label: { + LabeledContent("Équipe\(uncalled.count.pluralSuffix) non contactée\(uncalled.count.pluralSuffix)", value: uncalled.count.formatted()) + } } PlayersWithoutContactView(players: groupStages.flatMap({ $0.unsortedTeams() }).flatMap({ $0.unsortedPlayers() }).sorted(by: \.computedRank)) diff --git a/PadelClub/Views/Calling/SeedsCallingView.swift b/PadelClub/Views/Calling/SeedsCallingView.swift index abfcec7..452bd9d 100644 --- a/PadelClub/Views/Calling/SeedsCallingView.swift +++ b/PadelClub/Views/Calling/SeedsCallingView.swift @@ -14,12 +14,15 @@ struct SeedsCallingView: View { var body: some View { List { let tournamentRounds = tournament.rounds() + let uncalledSeeds = tournament.seededTeams().filter({ $0.callDate == nil }) - NavigationLink { - TeamsCallingView(teams: tournament.seededTeams().filter({ $0.callDate == nil })) - .environment(tournament) - } label: { - LabeledContent("Équipes non contactées", value: tournament.seededTeams().filter({ $0.callDate == nil }).count.formatted()) + if uncalledSeeds.isEmpty == false { + NavigationLink { + TeamsCallingView(teams: uncalledSeeds) + .environment(tournament) + } label: { + LabeledContent("Équipe\(uncalledSeeds.count.pluralSuffix) non contactée\(uncalledSeeds.count.pluralSuffix)", value: uncalledSeeds.count.formatted()) + } } PlayersWithoutContactView(players: tournament.seededTeams().flatMap({ $0.unsortedPlayers() }).sorted(by: \.computedRank)) @@ -63,8 +66,14 @@ struct SeedsCallingView: View { } } - NavigationLink("Équipes non contactées") { - TeamsCallingView(teams: round.teams().filter({ $0.callDate == nil })) + let uncalledTeams = round.teams().filter({ $0.callDate == nil }) + if uncalledTeams.isEmpty == false { + NavigationLink { + TeamsCallingView(teams: uncalledTeams) + .environment(tournament) + } label: { + LabeledContent("Équipe\(uncalledTeams.count.pluralSuffix) non contactée\(uncalledTeams.count.pluralSuffix)", value: uncalledTeams.count.formatted()) + } } diff --git a/PadelClub/Views/Calling/TeamsCallingView.swift b/PadelClub/Views/Calling/TeamsCallingView.swift index 9b7b06a..2c9117a 100644 --- a/PadelClub/Views/Calling/TeamsCallingView.swift +++ b/PadelClub/Views/Calling/TeamsCallingView.swift @@ -21,31 +21,34 @@ struct TeamsCallingView: View { let label = "\(justCalled.count.formatted()) / \(teams.count.formatted()) convoquées (dont \(called.count.formatted()) au bon horaire)" - Text(label) - - Section { - ForEach(teams) { team in - Menu { - _menuOptions(team: team) - } label: { - HStack { - TeamRowView(team: team, displayCallDate: true) - Spacer() - Menu { - _menuOptions(team: team) - } label: { - LabelOptions().labelStyle(.iconOnly) + if teams.isEmpty == false { + Section { + Text(label) + } footer: { + Text("Vous pouvez indiquer si une équipe vous a confirmé sa convocation en appuyant sur 􀍡") + } + + Section { + ForEach(teams) { team in + Menu { + _menuOptions(team: team) + } label: { + HStack { + TeamRowView(team: team, displayCallDate: true) + Spacer() + Menu { + _menuOptions(team: team) + } label: { + LabelOptions().labelStyle(.iconOnly) + } } } + .buttonStyle(.plain) + .listRowView(isActive: team.confirmed(), color: .green, hideColorVariation: true) } - .buttonStyle(.plain) - .listRowView(isActive: team.confirmed(), color: .green, hideColorVariation: true) - } - } footer: { - HStack { - Text("Vous pouvez indiquer si une équipe vous a confirmé sa convocation en appuyant sur ") - Image(systemName: "ellipsis.circle").font(.footnote) } + } else { + ContentUnavailableView("Aucune équipe", systemImage: "person.2.slash") } } .headerProminence(.increased) diff --git a/PadelClub/Views/Match/Components/MatchTeamDetailView.swift b/PadelClub/Views/Match/Components/MatchTeamDetailView.swift index cf952d0..f12eb91 100644 --- a/PadelClub/Views/Match/Components/MatchTeamDetailView.swift +++ b/PadelClub/Views/Match/Components/MatchTeamDetailView.swift @@ -31,9 +31,15 @@ struct MatchTeamDetailView: View { @ViewBuilder private func _teamDetailView(_ team: TeamRegistration, inTournament tournament: Tournament?) -> some View { Section { + if let teamName = team.name, teamName.isEmpty == false { + Text(teamName).foregroundStyle(.secondary).font(.footnote) + } ForEach(team.players()) { player in EditablePlayerView(player: player, editingOptions: _editingOptions()) } + if let coachList = team.comment, coachList.isEmpty == false { + Text("Coachs : " + coachList).foregroundStyle(.secondary).font(.footnote) + } } header: { TeamHeaderView(team: team, teamIndex: tournament?.indexOf(team: team)) } diff --git a/PadelClub/Views/Team/CoachListView.swift b/PadelClub/Views/Team/CoachListView.swift new file mode 100644 index 0000000..0a4ca87 --- /dev/null +++ b/PadelClub/Views/Team/CoachListView.swift @@ -0,0 +1,56 @@ +// +// CoachListView.swift +// PadelClub +// +// Created by razmig on 16/10/2024. +// + +import SwiftUI +import LeStorage + +struct CoachListView: View { + @Environment(Tournament.self) var tournament + @State private var coachNames: String + var team: TeamRegistration + + init(team: TeamRegistration) { + self.team = team + _coachNames = .init(wrappedValue: team.comment ?? "") + } + + var body: some View { + Section { + TextField("Coach", text: $coachNames) + .autocorrectionDisabled() + .keyboardType(.alphabet) + .frame(maxWidth: .infinity) + .submitLabel(.done) + .onSubmit(of: .text) { + let trimmed = coachNames.trimmed + if trimmed.isEmpty { + team.comment = nil + } else { + team.comment = trimmed + } + _save() + } + } header: { + Text("Coachs") + } footer: { + FooterButtonView("effacer") { + coachNames = "" + team.comment = nil + _save() + } + } + } + + private func _save() { + do { + try self.tournament.tournamentStore.teamRegistrations.addOrUpdate(instance: team) + } catch { + Logger.error(error) + } + } + +} diff --git a/PadelClub/Views/Team/EditingTeamView.swift b/PadelClub/Views/Team/EditingTeamView.swift index 173d693..9949e67 100644 --- a/PadelClub/Views/Team/EditingTeamView.swift +++ b/PadelClub/Views/Team/EditingTeamView.swift @@ -164,6 +164,10 @@ struct EditingTeamView: View { Text("Nom de l'équipe") } + if tournament.tournamentLevel.coachingIsAuthorized { + CoachListView(team: team) + } + Section { RowButtonView("Retirer des poules", role: .destructive) { team.resetGroupeStagePosition() diff --git a/PadelClub/Views/Tournament/FileImportView.swift b/PadelClub/Views/Tournament/FileImportView.swift index 7e236d0..a25219b 100644 --- a/PadelClub/Views/Tournament/FileImportView.swift +++ b/PadelClub/Views/Tournament/FileImportView.swift @@ -84,11 +84,21 @@ struct FileImportView: View { @State private var multiImport: Bool = false @State private var presentFormatHelperView: Bool = false @State private var validatedTournamentIds: Set = Set() - @State private var chunkByParameter: Bool = true + @State private var chunkMode: ChunkMode = .byParameter - enum ChunkMode { + enum ChunkMode: Int, Identifiable, CaseIterable { + var id: Int { self.rawValue } case byParameter case byCoupleOfLines + + func localizedChunkModeLabel() -> String { + switch self { + case .byParameter: + return "Nom d'équipe" + case .byCoupleOfLines: + return "Groupe de 2 lignes" + } + } } init(defaultFileProvider: FileImportManager.FileProvider = .frenchFederation) { @@ -99,6 +109,10 @@ struct FileImportView: View { return self.tournament.tournamentStore } + var chunkByParameter: Bool { + return chunkMode == .byParameter + } + private func filteredTeams(tournament: Tournament) -> [FileImportManager.TeamHolder] { if tournament.isAnimation() { return teams.sorted(by: \.weight) @@ -146,14 +160,7 @@ struct FileImportView: View { } if fileProvider == .custom || fileProvider == .customAutoSearch { - Toggle(isOn: $chunkByParameter) { - Text("Détection des équipes") - if chunkByParameter { - Text("via le nom de l'équipe") - } else { - Text("couple de deux lignes") - } - } + _chunkModePickerView() } RowButtonView("Démarrer l'importation") { @@ -595,6 +602,17 @@ struct FileImportView: View { Logger.error(error) } } + + private func _chunkModePickerView() -> some View { + Picker(selection: $chunkMode) { + ForEach(ChunkMode.allCases) { mode in + Text(mode.localizedChunkModeLabel()).tag(mode) + } + } label: { + Text("Détection des équipes") + } + } + } //#Preview {