diff --git a/PadelClub/Views/Tournament/Screen/AddTeamView.swift b/PadelClub/Views/Tournament/Screen/AddTeamView.swift index 8c71827..5ebb60e 100644 --- a/PadelClub/Views/Tournament/Screen/AddTeamView.swift +++ b/PadelClub/Views/Tournament/Screen/AddTeamView.swift @@ -10,7 +10,7 @@ import LeStorage import CoreData struct AddTeamView: View { - + @Environment(\.dismiss) var dismiss private var fetchRequest: FetchRequest @@ -45,7 +45,7 @@ struct AddTeamView: View { @State private var displayWarningNotEnoughCharacter: Bool = false @State private var testMessageIndex: Int = 0 @State private var presentLocalMultiplayerSearch: Bool = false - + var tournamentStore: TournamentStore? { return self.tournament.tournamentStore } @@ -61,11 +61,11 @@ struct AddTeamView: View { createdPlayers.insert(player) createdPlayerIds.insert(player.id) } - + _createdPlayers = .init(wrappedValue: createdPlayers) _createdPlayerIds = .init(wrappedValue: createdPlayerIds) } - + let request: NSFetchRequest = ImportedPlayer.fetchRequest() request.sortDescriptors = [NSSortDescriptor(keyPath: \ImportedPlayer.rank, ascending: true)] request.fetchLimit = 1000 @@ -77,7 +77,7 @@ struct AddTeamView: View { _textHeight = .init(wrappedValue: Self._calculateHeight(text: pasteString)) cancelShouldDismiss = true } - + fetchRequest = FetchRequest(fetchRequest: request, animation: .default) } @@ -97,7 +97,7 @@ struct AddTeamView: View { computedBody } } - + var computedBody: some View { List(selection: $createdPlayerIds) { _buildingTeamView() @@ -127,7 +127,7 @@ struct AddTeamView: View { Button("Créer l'équipe quand même") { _createTeam(checkDuplicates: false, checkHomonym: false) } - + Button("Annuler", role: .cancel) { confirmHomonym = false } @@ -140,7 +140,7 @@ struct AddTeamView: View { Button("Créer l'équipe quand même") { _createTeam(checkDuplicates: false, checkHomonym: true) } - + Button("Annuler", role: .cancel) { confirmDuplicate = false } @@ -221,11 +221,11 @@ struct AddTeamView: View { .buttonBorderShape(.capsule) } } - + ToolbarItem(placement: .topBarTrailing) { Button { let generalString = UIPasteboard.general.string ?? "" - + #if targetEnvironment(simulator) let s = testMessages[testMessageIndex % testMessages.count] handlePasteString(s) @@ -249,15 +249,15 @@ struct AddTeamView: View { .navigationTitle(editedTeam == nil ? "Ajouter une équipe" : "Modifier l'équipe") .environment(\.editMode, Binding.constant(EditMode.active)) } - + private func _isEditingTeam() -> Bool { createdPlayerIds.isEmpty == false || editedTeam != nil || pasteString != nil } - + var unsortedPlayers: [PlayerRegistration] { tournament.unsortedPlayers() } - + @ViewBuilder private func _managementView() -> some View { Section { @@ -269,7 +269,7 @@ struct AddTeamView: View { Text("Cherchez dans la base fédérale de \(rankSourceDate.monthYearFormatted), vous y trouverez tous les joueurs ayant participé à au moins un tournoi dans les 12 derniers mois.") } } - + if tournament.isAnimation(), createdPlayers.isEmpty == true { Section { RowButtonView("Ajouter plusieurs joueurs du club") { @@ -279,7 +279,7 @@ struct AddTeamView: View { Text("Crée une équipe par joueur sélectionné") } } - + Section { RowButtonView("Créer un non classé / non licencié") { if let pasteString, pasteString.isEmpty == false { @@ -292,7 +292,7 @@ struct AddTeamView: View { Text("Si le joueur n'a pas encore de licence ou n'a pas encore participé à une compétition, vous pouvez le créer vous-même.") } } - + private func _addPlayerSex() -> Int { switch tournament.tournamentCategory { case .men, .unlisted: @@ -304,11 +304,11 @@ struct AddTeamView: View { } } - + private func _filterOption() -> PlayerFilterOption { return tournament.tournamentCategory.playerFilterOption } - + private func _currentSelection() -> Set { var currentSelection = Set() createdPlayerIds.compactMap { id in @@ -318,7 +318,7 @@ struct AddTeamView: View { player.setComputedRank(in: tournament) currentSelection.insert(player) } - + createdPlayerIds.compactMap { id in createdPlayers.first(where: { id == $0.id }) }.forEach { @@ -334,7 +334,7 @@ struct AddTeamView: View { }.forEach { player in currentSelection.append(player.license) } - + createdPlayerIds.compactMap { id in createdPlayers.first(where: { id == $0.id }) }.forEach { @@ -342,7 +342,7 @@ struct AddTeamView: View { } return currentSelection } - + private func _isDuplicate() -> Bool { if tournament.isAnimation() { return false } let ids : [String?] = _currentSelectionIds() @@ -351,15 +351,15 @@ struct AddTeamView: View { } return false } - + private func _createTeam(checkDuplicates: Bool, checkHomonym: Bool) { if checkDuplicates && _isDuplicate() { confirmDuplicate = true return } - + let players = _currentSelection() - + if checkHomonym { homonyms = players.filter({ $0.hasHomonym() }) if homonyms.isEmpty == false { @@ -367,27 +367,27 @@ struct AddTeamView: View { return } } - + let team = tournament.addTeam(players) self.tournamentStore?.teamRegistrations.addOrUpdate(instance: team) self.tournamentStore?.playerRegistrations.addOrUpdate(contentOfs: players) - - pasteString = nil - editableTextField = "" - createdPlayers.removeAll() - createdPlayerIds.removeAll() - - if team.players().count > 1 { - dismiss() - } else { - editedTeam = team - team.unsortedPlayers().forEach { player in - createdPlayers.insert(player) - createdPlayerIds.insert(player.id) - } - } + + pasteString = nil + editableTextField = "" + createdPlayers.removeAll() + createdPlayerIds.removeAll() + + if team.players().count > 1 { + dismiss() + } else { + editedTeam = team + team.unsortedPlayers().forEach { player in + createdPlayers.insert(player) + createdPlayerIds.insert(player.id) + } + } } - + private func _updateTeam(checkDuplicates: Bool) { guard let editedTeam else { return } if checkDuplicates && _isDuplicate() { @@ -399,7 +399,7 @@ struct AddTeamView: View { editedTeam.updatePlayers(players, inTournamentCategory: tournament.tournamentCategory) self.tournamentStore?.teamRegistrations.addOrUpdate(instance: editedTeam) self.tournamentStore?.playerRegistrations.addOrUpdate(contentOfs: players) - + pasteString = nil editableTextField = "" @@ -407,7 +407,7 @@ struct AddTeamView: View { dismiss() } } - + // Calculating the height based on the content of the TextEditor static private func _calculateHeight(text: String) -> CGFloat { let size = CGSize(width: UIScreen.main.bounds.width - 32, height: .infinity) @@ -420,23 +420,15 @@ struct AddTeamView: View { ) return max(boundingRect.height + 20, 40) // Add some padding and set a minimum height } - - struct PasteStringSection: View { - - let pasteString: String? - @Binding var editableTextField: String - @Binding var textHeight: CGFloat - @FocusState var focusedField: AddTeamView.FocusField? - var handlePasteString: (String) -> Void - @Binding var displayWarningNotEnoughCharacter: Bool - - var body: some View { + + @ViewBuilder + private func _buildingTeamView() -> some View { if let pasteString { Section { TextEditor(text: $editableTextField) .frame(height: textHeight) .onChange(of: editableTextField) { - textHeight = AddTeamView._calculateHeight(text: pasteString) + textHeight = Self._calculateHeight(text: pasteString) } .focused($focusedField, equals: .pasteField) .toolbar { @@ -468,104 +460,121 @@ struct AddTeamView: View { FooterButtonView("effacer le texte") { self.focusedField = nil self.editableTextField = "" - self.handlePasteString("") + self.pasteString = nil } } } } - } - } - - - @ViewBuilder - private func _buildingTeamView() -> some View { - - PasteStringSection( - pasteString: pasteString, - editableTextField: $editableTextField, - textHeight: $textHeight, - focusedField: _focusedField, - handlePasteString: handlePasteString, - displayWarningNotEnoughCharacter: $displayWarningNotEnoughCharacter - ) + + Section { + ForEach(createdPlayerIds.sorted(), id: \.self) { id in + if let p = createdPlayers.first(where: { $0.id == id }) { + VStack(alignment: .leading, spacing: 0) { + if let player = unsortedPlayers.first(where: { ($0.licenceId == p.licenceId && $0.licenceId != nil) }), editedTeam?.includes(player: player) == false { + Text("Déjà inscrit !").foregroundStyle(.logoRed).bold() + } + if tournament.isPlayerAgeInadequate(player: p) { + Text("Âge invalide !").foregroundStyle(.logoRed).bold() + } + if tournament.isPlayerRankInadequate(player: p) { + Text("Trop bien classé !").foregroundStyle(.logoRed).bold() + } + PlayerView(player: p).tag(p.id) + .environment(tournament) + } + } + if let p = fetchPlayers.first(where: { $0.license == id }) { + VStack(alignment: .leading, spacing: 0) { + if let pasteString, pasteString.isEmpty == false, unsortedPlayers.first(where: { $0.licenceId == p.license }) != nil { + Text("Déjà inscrit !").foregroundStyle(.logoRed).bold() + } + if tournament.isPlayerAgeInadequate(player: p) { + Text("Âge invalide !").foregroundStyle(.logoRed).bold() + } + if tournament.isPlayerRankInadequate(player: p) { + Text("Trop bien classé !").foregroundStyle(.logoRed).bold() + } + ImportedPlayerView(player: p).tag(p.license!) + } + } + } + if editedTeam == nil { + if createdPlayerIds.isEmpty { + RowButtonView("Bloquer une place") { + _createTeam(checkDuplicates: false, checkHomonym: false) + } + } else { + RowButtonView("Ajouter l'équipe") { + _createTeam(checkDuplicates: true, checkHomonym: true) + } + } + } else { + RowButtonView("Confirmer") { + _updateTeam(checkDuplicates: false) + dismiss() + } + } + } header: { + let _currentSelection = _currentSelection() + let selectedSortedTeams = tournament.selectedSortedTeams() + let rank = _currentSelection.map { + $0.computedRank + }.reduce(0, +) + let teamIndex = selectedSortedTeams.firstIndex(where: { $0.weight >= rank }) ?? selectedSortedTeams.count + if _currentSelection.isEmpty == false, tournament.hideWeight() == false, rank > 0 { + HStack(spacing: 16.0) { + VStack(alignment: .leading, spacing: 0) { + Text("Rang").font(.caption) + Text("#" + (teamIndex + 1).formatted()) + } - TeamSelectionSection( - createdPlayerIds: createdPlayerIds, - createdPlayers: createdPlayers, - unsortedPlayers: unsortedPlayers, - fetchPlayers: fetchPlayers, - editedTeam: editedTeam, - pasteString: pasteString, - tournament: tournament, - _createTeam: _createTeam, - _updateTeam: _updateTeam, - dismiss: dismiss, - _currentSelection: _currentSelection - ) + VStack(alignment: .leading, spacing: 0) { + Text("Poids").font(.caption) + Text(rank.formatted()) + } + Spacer() + VStack(alignment: .trailing, spacing: 0) { + Text("").font(.caption) + Text(tournament.cutLabel(index: teamIndex, teamCount: selectedSortedTeams.count)) + } + } +// } else { +// Text("Préparation de l'équipe") + } + } + if let pasteString, pasteString.isEmpty == false { - let sortedPlayers = _searchFilteredPlayers() - - if sortedPlayers.isEmpty { - ContentUnavailableView { - Label("Aucun résultat", systemImage: "person.2.slash") - } description: { - Text("Aucun joueur classé n'a été trouvé dans ce message. Attention, si un joueur n'a pas joué de tournoi dans les 12 derniers, Padel Club ne pourra pas le trouver.") - } actions: { - RowButtonView("Créer un joueur non classé") { - selectionSearchField = pasteString - } + let sortedPlayers = _searchFilteredPlayers() + + if sortedPlayers.isEmpty { + ContentUnavailableView { + Label("Aucun résultat", systemImage: "person.2.slash") + } description: { + Text("Aucun joueur classé n'a été trouvé dans ce message. Attention, si un joueur n'a pas joué de tournoi dans les 12 derniers, Padel Club ne pourra pas le trouver.") + } actions: { + RowButtonView("Créer un joueur non classé") { + selectionSearchField = pasteString + } + + RowButtonView("Chercher dans la base") { + presentPlayerSearch = true + } - RowButtonView("Chercher dans la base") { - presentPlayerSearch = true + RowButtonView("Effacer cette recherche") { + self.pasteString = nil + self.editableTextField = "" + } } - RowButtonView("Effacer cette recherche") { - self.pasteString = nil - self.editableTextField = "" - } + } else { + _listOfPlayers(searchFilteredPlayers: sortedPlayers, pasteString: pasteString) } - } else { - _listOfPlayers(searchFilteredPlayers: sortedPlayers, pasteString: pasteString) + _managementView() } - } else { - _managementView() - } } - -// -// if let pasteString, pasteString.isEmpty == false { -// let sortedPlayers = _searchFilteredPlayers() -// -// if sortedPlayers.isEmpty { -// ContentUnavailableView { -// Label("Aucun résultat", systemImage: "person.2.slash") -// } description: { -// Text("Aucun joueur classé n'a été trouvé dans ce message. Attention, si un joueur n'a pas joué de tournoi dans les 12 derniers, Padel Club ne pourra pas le trouver.") -// } actions: { -// RowButtonView("Créer un joueur non classé") { -// selectionSearchField = pasteString -// } -// -// RowButtonView("Chercher dans la base") { -// presentPlayerSearch = true -// } -// -// RowButtonView("Effacer cette recherche") { -// self.pasteString = nil -// self.editableTextField = "" -// } -// } -// -// } else { -// _listOfPlayers(searchFilteredPlayers: sortedPlayers, pasteString: pasteString) -// } -// } else { -// _managementView() -// } -// } - + @MainActor func hitForSearch(_ ip: ImportedPlayer, _ pasteString: String?) -> Int { guard let pasteString else { return 0 } @@ -602,7 +611,7 @@ struct AddTeamView: View { } return 1 } - + private func handlePasteString(_ first: String) { if first.isEmpty == false { fetchPlayers.nsPredicate = SearchViewModel.pastePredicate(pasteField: first, mostRecentDate: SourceFileManager.shared.mostRecentDateAvailable, filterOption: _filterOption()) @@ -617,7 +626,7 @@ struct AddTeamView: View { @ViewBuilder private func _listOfPlayers(searchFilteredPlayers: [ImportedPlayer], pasteString: String) -> some View { let sortedPlayers = _sortedPlayers(searchFilteredPlayers: searchFilteredPlayers, pasteString: pasteString) - + Section { ForEach(sortedPlayers) { player in ImportedPlayerView(player: player).tag(player.license!) @@ -628,7 +637,7 @@ struct AddTeamView: View { } } - + private func _searchFilteredPlayers() -> [ImportedPlayer] { if searchField.isEmpty { return Array(fetchPlayers) @@ -636,171 +645,12 @@ struct AddTeamView: View { return fetchPlayers.filter({ $0.contains(searchField) }) } } - + private func _sortedPlayers(searchFilteredPlayers: [ImportedPlayer], pasteString: String) -> [ImportedPlayer] { return searchFilteredPlayers.sorted(by: { hitForSearch($0, pasteString) > hitForSearch($1, pasteString) }) } } -struct TeamSelectionSection: View { - let createdPlayerIds: Set - let createdPlayers: Set - let unsortedPlayers: [PlayerRegistration] - let fetchPlayers: FetchedResults - let editedTeam: TeamRegistration? - let pasteString: String? - let tournament: Tournament - let _createTeam: (Bool, Bool) -> Void - let _updateTeam: (Bool) -> Void - let dismiss: DismissAction - let _currentSelection: () -> Set - - var body: some View { - Section { - PlayerListView(createdPlayerIds: createdPlayerIds, - createdPlayers: createdPlayers, - unsortedPlayers: unsortedPlayers, - fetchPlayers: fetchPlayers, - editedTeam: editedTeam, - pasteString: pasteString, - tournament: tournament) - - ActionButton(editedTeam: editedTeam, - createdPlayerIds: createdPlayerIds, - _createTeam: _createTeam, - _updateTeam: _updateTeam, - dismiss: dismiss) - } header: { - TeamHeader(tournament: tournament, - _currentSelection: _currentSelection) - } - } -} - -struct PlayerListView: View { - let createdPlayerIds: Set - let createdPlayers: Set - let unsortedPlayers: [PlayerRegistration] - let fetchPlayers: FetchedResults - let editedTeam: TeamRegistration? - let pasteString: String? - let tournament: Tournament - - var body: some View { - ForEach(createdPlayerIds.sorted(), id: \.self) { id in - if let p = createdPlayers.first(where: { $0.id == id }) { - CreatedPlayerView(player: p, unsortedPlayers: unsortedPlayers, editedTeam: editedTeam, tournament: tournament) - } - if let p = fetchPlayers.first(where: { $0.license == id }) { - FetchedPlayerView(player: p, unsortedPlayers: unsortedPlayers, pasteString: pasteString, tournament: tournament) - } - } - } -} - -struct CreatedPlayerView: View { - let player: PlayerRegistration - let unsortedPlayers: [PlayerRegistration] - let editedTeam: TeamRegistration? - let tournament: Tournament - - var body: some View { - VStack(alignment: .leading, spacing: 0) { - if let existingPlayer = unsortedPlayers.first(where: { ($0.licenceId == player.licenceId && $0.licenceId != nil) }), editedTeam?.includes(player: existingPlayer) == false { - Text("Déjà inscrit !").foregroundStyle(.logoRed).bold() - } - if tournament.isPlayerAgeInadequate(player: player) { - Text("Âge invalide !").foregroundStyle(.logoRed).bold() - } - if tournament.isPlayerRankInadequate(player: player) { - Text("Trop bien classé !").foregroundStyle(.logoRed).bold() - } - PlayerView(player: player).tag(player.id) - .environment(tournament) - } - } -} - -struct FetchedPlayerView: View { - let player: ImportedPlayer - let unsortedPlayers: [PlayerRegistration] - let pasteString: String? - let tournament: Tournament - - var body: some View { - VStack(alignment: .leading, spacing: 0) { - if let pasteString, pasteString.isEmpty == false, unsortedPlayers.first(where: { $0.licenceId == player.license }) != nil { - Text("Déjà inscrit !").foregroundStyle(.logoRed).bold() - } - if tournament.isPlayerAgeInadequate(player: player) { - Text("Âge invalide !").foregroundStyle(.logoRed).bold() - } - if tournament.isPlayerRankInadequate(player: player) { - Text("Trop bien classé !").foregroundStyle(.logoRed).bold() - } - ImportedPlayerView(player: player).tag(player.license!) - } - } -} - -struct ActionButton: View { - let editedTeam: TeamRegistration? - let createdPlayerIds: Set - let _createTeam: (Bool, Bool) -> Void - let _updateTeam: (Bool) -> Void - let dismiss: DismissAction - - var body: some View { - if editedTeam == nil { - if createdPlayerIds.isEmpty { - RowButtonView("Bloquer une place") { - _createTeam(false, false) - } - } else { - RowButtonView("Ajouter l'équipe") { - _createTeam(true, true) - } - } - } else { - RowButtonView("Confirmer") { - _updateTeam(false) - dismiss() - } - } - } -} - -struct TeamHeader: View { - let tournament: Tournament - let _currentSelection: () -> Set - - var body: some View { - let currentSelection = _currentSelection() - let selectedSortedTeams = tournament.selectedSortedTeams() - let rank = currentSelection.map { $0.computedRank }.reduce(0, +) - let teamIndex = selectedSortedTeams.firstIndex(where: { $0.weight >= rank }) ?? selectedSortedTeams.count - - if !currentSelection.isEmpty, !tournament.hideWeight(), rank > 0 { - HStack(spacing: 16.0) { - VStack(alignment: .leading, spacing: 0) { - Text("Rang").font(.caption) - Text("#" + (teamIndex + 1).formatted()) - } - - VStack(alignment: .leading, spacing: 0) { - Text("Poids").font(.caption) - Text(rank.formatted()) - } - Spacer() - VStack(alignment: .trailing, spacing: 0) { - Text("").font(.caption) - Text(tournament.cutLabel(index: teamIndex, teamCount: selectedSortedTeams.count)) - } - } - } - } -} - let testMessages = [ "Anthony dovetta ( 3620578 K )et christophe capeau ( 4666443v)", """ @@ -827,6 +677,6 @@ Tullou Benjamin 8990867f """, """ Sms Julien La Croix +33622886688 -Salut Raz, c'est ! Ju Lacroix J'espère que tu vas bien depuis le temps! Est-ce que tu peux nous inscrire au 1000 de Bandol avec Derek Gerson stp? +Salut Raz, c'est ! Ju Lacroix J'espère que tu vas bien depuis le temps! Est-ce que tu peux nous inscrire au 1000 de Bandol avec Derek Gerson stp? """ ]