@ -22,13 +22,13 @@ struct InscriptionManagerView: View {
@ State private var presentPlayerSearch : Bool = false
@ State private var presentPlayerCreation : Bool = false
@ State private var presentImportView : Bool = false
@ State private var isLearningMore : Bool = false
@ State private var createdPlayers : Set < PlayerRegistration > = Set ( )
@ State private var createdPlayerIds : Set < String > = Set ( )
@ State private var editedTeam : TeamRegistration ?
@ State private var pasteString : String ?
@ State private var currentRankSourceDate : Date ?
@ State private var confirmUpdateRank = false
@ State private var updatingRank = false
@ State private var selectionSearchField : String ?
let slideToDeleteTip = SlideToDeleteTip ( )
@ -38,9 +38,12 @@ struct InscriptionManagerView: View {
let searchTip = InscriptionManagerSearchInputTip ( )
let createTip = InscriptionManagerCreateInputTip ( )
let rankUpdateTip = InscriptionManagerRankUpdateTip ( )
let padelBeachExportTip = PadelBeachExportTip ( )
let padelBeachImportTip = PadelBeachImportTip ( )
let categoryOption : PlayerFilterOption
let filterable : Bool
let dates = Set ( SourceFileManager . shared . allFilesSortedByDate ( true ) . map ( { $0 . dateFromPath } ) ) . sorted ( ) . reversed ( )
init ( tournament : Tournament ) {
self . tournament = tournament
@ -55,289 +58,10 @@ struct InscriptionManagerView: View {
}
}
private func _searchSource ( ) -> String ? {
selectionSearchField ? ? pasteString
}
private func _pastePredicate ( pasteField : String , mostRecentDate : Date ? ) -> NSPredicate ? {
let text = pasteField . canonicalVersion
let nameComponents = text . components ( separatedBy : . whitespacesAndNewlines ) . compactMap { $0 . isEmpty ? nil : $0 } . filter ( { $0 != " de " && $0 != " la " && $0 != " le " && $0 . count > 1 } )
var andPredicates = [ NSPredicate ] ( )
var orPredicates = [ NSPredicate ] ( )
// s e l f . w o r d s C o u n t = n a m e C o m p o n e n t s . c o u n t
if _filterOption ( ) = = . male {
andPredicates . append ( NSPredicate ( format : " male == YES " ) )
} else if _filterOption ( ) = = . female {
andPredicates . append ( NSPredicate ( format : " male == NO " ) )
}
if let mostRecentDate {
andPredicates . append ( NSPredicate ( format : " importDate == %@ " , mostRecentDate as CVarArg ) )
}
if nameComponents . count > 1 {
orPredicates = nameComponents . pairs ( ) . map {
return NSPredicate ( format : " (firstName contains[cd] %@ AND lastName contains[cd] %@) OR (firstName contains[cd] %@ AND lastName contains[cd] %@) " , $0 , $1 , $1 , $0 ) }
} else {
orPredicates = nameComponents . map { NSPredicate ( format : " firstName contains[cd] %@ OR lastName contains[cd] %@ " , $0 , $0 ) }
}
let matches = text . licencesFound ( )
let licensesPredicates = matches . map { NSPredicate ( format : " license contains[cd] %@ " , $0 ) }
orPredicates = orPredicates + licensesPredicates
var predicate = NSCompoundPredicate ( andPredicateWithSubpredicates : andPredicates )
if orPredicates . isEmpty = = false {
predicate = NSCompoundPredicate ( andPredicateWithSubpredicates : [ predicate , NSCompoundPredicate ( orPredicateWithSubpredicates : orPredicates ) ] )
}
return predicate
}
private func _currentSelection ( ) -> Set < PlayerRegistration > {
var currentSelection = Set < PlayerRegistration > ( )
createdPlayerIds . compactMap { id in
fetchPlayers . first ( where : { id = = $0 . license } )
} . forEach { player in
let player = PlayerRegistration ( importedPlayer : player )
player . setWeight ( in : tournament )
currentSelection . insert ( player )
}
createdPlayerIds . compactMap { id in
createdPlayers . first ( where : { id = = $0 . id } )
} . forEach {
currentSelection . insert ( $0 )
}
return currentSelection
}
private func _createTeam ( ) {
tournament . addTeam ( _currentSelection ( ) )
createdPlayers . removeAll ( )
createdPlayerIds . removeAll ( )
pasteString = nil
}
private func _updateTeam ( ) {
editedTeam ? . updatePlayers ( _currentSelection ( ) )
createdPlayers . removeAll ( )
createdPlayerIds . removeAll ( )
pasteString = nil
editedTeam = nil
}
private func _buildingTeamView ( ) -> some View {
List ( selection : $ createdPlayerIds ) {
Section {
ForEach ( createdPlayerIds . sorted ( ) , id : \ . self ) { id in
if let p = createdPlayers . first ( where : { $0 . id = = id } ) {
PlayerView ( player : p ) . tag ( p . id )
}
if let p = fetchPlayers . first ( where : { $0 . license = = id } ) {
ImportedPlayerView ( player : p ) . tag ( p . license ! )
}
}
// F o r E a c h ( c r e a t e d P l a y e r s . s o r t e d ( b y : \ . c o m p u t e d R a n k ) ) { p l a y e r i n
// P l a y e r V i e w ( p l a y e r : p l a y e r ) . t a g ( p l a y e r . i d )
// }
}
if editedTeam = = nil {
if createdPlayerIds . isEmpty {
RowButtonView ( title : " Bloquer une place " ) {
_createTeam ( )
}
} else {
RowButtonView ( title : " Ajouter l'équipe " ) {
_createTeam ( )
}
}
} else {
RowButtonView ( title : " Modifier l'équipe " ) {
_updateTeam ( )
}
}
if let pasteString {
Section {
Text ( pasteString )
} footer : {
HStack {
Text ( " contenu du presse-papier " )
Spacer ( )
Button ( " effacer " , role : . destructive ) {
self . pasteString = nil
self . createdPlayers . removeAll ( )
self . createdPlayerIds . removeAll ( )
}
. buttonStyle ( . borderless )
}
}
if fetchPlayers . isEmpty {
ContentUnavailableView {
Label ( " Aucun résultat " , systemImage : " person.2.slash " )
} description : {
Text ( " Aucun joueur classé n'a été trouvé dans ce message. " )
} actions : {
RowButtonView ( title : " Créer un joueur non classé " ) {
presentPlayerCreation = true
}
RowButtonView ( title : " Effacer cette recherche " ) {
self . pasteString = nil
}
}
} else {
Section {
ForEach ( fetchPlayers . sorted ( by : { $0 . hitForSearch ( pasteString ) > $1 . hitForSearch ( pasteString ) } ) ) { player in
ImportedPlayerView ( player : player ) . tag ( player . license ! )
}
} header : {
Text ( fetchPlayers . count . formatted ( ) + " résultat " + fetchPlayers . count . pluralSuffix )
}
}
}
}
. onReceive ( fetchPlayers . publisher . count ( ) ) { _ in // < - - h e r e
if let pasteString , count = = 2 {
fetchPlayers . filter { $0 . hitForSearch ( pasteString ) >= hitTarget } . sorted ( by : { $0 . hitForSearch ( pasteString ) > $1 . hitForSearch ( pasteString ) } ) . forEach { player in
createdPlayerIds . insert ( player . license ! )
}
}
}
. environment ( \ . editMode , Binding . constant ( EditMode . active ) )
}
var count : Int {
return fetchPlayers . filter { $0 . hitForSearch ( pasteString ? ? " " ) >= hitTarget } . count
}
var hitTarget : Int {
if ( pasteString ? . matches ( of : / [ 1 - 9 ] [ 0 - 9 ] { 5 , 7 } / ) . count ? ? 0 ) > 1 {
if fetchPlayers . filter ( { $0 . hitForSearch ( pasteString ? ? " " ) = = 100 } ) . count = = 2 { return 100 }
} else {
return 2
}
return 1
}
private func _teamRegisteredView ( ) -> some View {
List {
Section {
_rankHandlerView ( )
let duplicates = tournament . duplicates ( )
DisclosureGroup {
if duplicates . isEmpty = = false {
ForEach ( duplicates ) { player in
PlayerView ( player : player )
}
}
} label : {
LabeledContent {
Text ( duplicates . count . formatted ( ) )
} label : {
Text ( " Doublons " )
}
}
} header : {
Text ( " Informations " )
}
if tournament . tournamentCategory = = . men && tournament . femalePlayers ( ) . isEmpty = = false {
Section {
TipView ( inscriptionManagerWomanRankTip )
. tipStyle ( tint : nil )
}
}
Section {
TipView ( slideToDeleteTip )
. tipStyle ( tint : nil )
}
let unfilteredTeams = tournament . teams ( )
let teams = searchField . isEmpty ? unfilteredTeams : unfilteredTeams . filter ( { $0 . contains ( searchField . canonicalVersion ) } )
if teams . isEmpty && searchField . isEmpty = = false {
ContentUnavailableView {
Label ( " Aucun résultat " , systemImage : " person.2.slash " )
} description : {
Text ( " \( searchField ) est introuvable dans les équipes inscrites. " )
} actions : {
RowButtonView ( title : " Modifier la recherche " ) {
searchField = " "
presentSearch = true
}
RowButtonView ( title : " Créer une équipe " ) {
Task {
await MainActor . run ( ) {
fetchPlayers . nsPredicate = _pastePredicate ( pasteField : searchField , mostRecentDate : tournament . rankSourceDate )
pasteString = searchField
}
}
}
RowButtonView ( title : " D'accord " ) {
searchField = " "
presentSearch = false
}
}
}
ForEach ( teams ) { team in
Section {
TeamRowView ( team : team )
} header : {
HStack {
Text ( " Équipe " + team . formattedSeed ( in : unfilteredTeams ) )
Spacer ( )
Text ( team . weight . formatted ( ) )
}
} footer : {
HStack {
Spacer ( )
Menu {
Button ( " Éditer " ) {
editedTeam = team
team . unsortedPlayers ( ) . forEach { player in
createdPlayers . insert ( player )
createdPlayerIds . insert ( player . id )
}
}
Divider ( )
Button ( role : . destructive ) {
try ? dataStore . teamRegistrations . delete ( instance : team )
} label : {
LabelDelete ( )
}
} label : {
LabelOptions ( ) . labelStyle ( . titleOnly )
}
}
}
. headerProminence ( . increased )
}
}
. searchable ( text : $ searchField , isPresented : $ presentSearch , prompt : Text ( " Chercher parmi les équipes inscrites " ) )
. keyboardType ( . alphabet )
. autocorrectionDisabled ( )
}
var body : some View {
VStack ( spacing : 0 ) {
_managementView ( )
if createdPlayerIds . isEmpty = = false || pasteString != nil || editedTeam != nil {
if _isEditingTeam ( ) {
_buildingTeamView ( )
} else if tournament . unsortedTeams ( ) . isEmpty {
_inscriptionTipsView ( )
@ -345,6 +69,9 @@ struct InscriptionManagerView: View {
_teamRegisteredView ( )
}
}
. sheet ( isPresented : $ isLearningMore ) {
LearnMoreSheetView ( tournament : tournament )
}
. sheet ( isPresented : $ presentPlayerSearch , onDismiss : {
selectionSearchField = nil
} ) {
@ -375,43 +102,46 @@ struct InscriptionManagerView: View {
FileImportView ( fileContent : nil )
}
}
. onChange ( of : tournament . prioritizeClubMembers ) {
_save ( )
}
. onChange ( of : tournament . teamSorting ) {
_save ( )
}
. onChange ( of : currentRankSourceDate ) {
// i f l e t c u r r e n t R a n k S o u r c e D a t e , t o u r n a m e n t . c u r r e n t R a n k S o u r c e D a t e ! = c u r r e n t R a n k S o u r c e D a t e {
// c o n f i r m U p d a t e R a n k = t r u e
// }
confirmUpdateRank = true
if let currentRankSourceDate , tournament . rankSourceDate != currentRankSourceDate {
confirmUpdateRank = true
}
}
. sheet ( isPresented : $ confirmUpdateRank , onDismiss : {
currentRankSourceDate = tournament . rankSourceDate
} ) {
UpdateSourceRankDateView ( currentRankSourceDate : $ currentRankSourceDate , confirmUpdateRank : $ confirmUpdateRank , tournament : tournament )
}
. toolbar {
if createdPlayerIds . isEmpty = = false {
if _isEditingTeam ( ) {
ToolbarItem ( placement : . cancellationAction ) {
Button ( " Annuler " , role : . cancel ) {
pasteString = nil
createdPlayers . removeAll ( )
createdPlayerIds . removeAll ( )
}
}
}
if editedTeam = = nil {
} else {
ToolbarItem ( placement : . navigationBarTrailing ) {
Menu {
if tournament . inscriptionClosed ( ) = = false {
Menu {
// s o r t i n g T y p e P i c k e r V i e w
_sortingTypePickerView ( )
} label : {
Text ( " Méthode de sélection " )
Text ( tournament . teamSortingType . localizedLabel ( ) )
Text ( tournament . teamSorting . localizedLabel ( ) )
}
Divider ( )
rankingDateSourcePickerView ( showDateInLabel : true )
if tournament . teamSortingType = = . inscriptionDate {
if tournament . teamSorting = = . inscriptionDate {
Divider ( )
// p r i o r i t i z e C l u b M e m b e r s B u t t o n
_prioritizeClubMembersButton ( )
}
Divider ( )
Button {
@ -425,19 +155,16 @@ struct InscriptionManagerView: View {
Label ( " Clôturer " , systemImage : " lock " )
}
Divider ( )
// S h a r e L i n k ( i t e m : t o u r n a m e n t . p a s t e D a t a F o r I m p o r t i n g ) {
// T e x t ( " E x p o r t e r l e s p a i r e s " )
// }
ShareLink ( item : tournament . pasteDataForImporting ( ) ) {
Text ( " Exporter les paires " )
}
Button {
presentImportView = true
} label : {
Label ( " Importer beach-padel " , systemImage : " square.and.arrow.down " )
}
if let url = URL ( string : " beach-padel.app.fft.fr " ) {
Link ( destination : url ) {
Label ( " beach-padel.app.fft.fr " , systemImage : " safari " )
}
Link ( destination : SourceFileManager . beachPadel ) {
Label ( " beach-padel.app.fft.fr " , systemImage : " safari " )
}
} else {
Button {
@ -457,12 +184,89 @@ struct InscriptionManagerView: View {
}
}
}
. navigationBarBackButtonHidden ( createdPlayerIds . isEmpty = = false )
. toolbarBackground ( . visible , for : . navigationBar )
. navigationTitle ( " Inscriptions " )
. navigationBarTitleDisplayMode ( . inline )
. navigationBarBackButtonHidden ( _isEditingTeam ( ) )
. toolbarBackground ( . visible , for : . navigationBar )
. navigationTitle ( " Inscriptions " )
. navigationBarTitleDisplayMode ( . inline )
}
private func _isEditingTeam ( ) -> Bool {
createdPlayerIds . isEmpty = = false || editedTeam != nil || pasteString != nil
}
private func _teamRegisteredView ( ) -> some View {
List {
Section {
_rankHandlerView ( )
let duplicates = tournament . duplicates ( )
DisclosureGroup {
if duplicates . isEmpty = = false {
ForEach ( duplicates ) { player in
PlayerView ( player : player )
}
}
} label : {
LabeledContent {
Text ( duplicates . count . formatted ( ) )
} label : {
Text ( " Doublons " )
}
}
} header : {
Text ( " Informations " )
}
_relatedTips ( )
let unfilteredTeams = tournament . sortedTeams ( )
let teams = searchField . isEmpty ? unfilteredTeams : unfilteredTeams . filter ( { $0 . contains ( searchField . canonicalVersion ) } )
if teams . isEmpty && searchField . isEmpty = = false {
ContentUnavailableView {
Label ( " Aucun résultat " , systemImage : " person.2.slash " )
} description : {
Text ( " \( searchField ) est introuvable dans les équipes inscrites. " )
} actions : {
RowButtonView ( title : " Modifier la recherche " ) {
searchField = " "
presentSearch = true
}
RowButtonView ( title : " Créer une équipe " ) {
Task {
await MainActor . run ( ) {
fetchPlayers . nsPredicate = _pastePredicate ( pasteField : searchField , mostRecentDate : SourceFileManager . shared . mostRecentDateAvailable )
pasteString = searchField
}
}
}
RowButtonView ( title : " D'accord " ) {
searchField = " "
presentSearch = false
}
}
}
ForEach ( teams ) { team in
let teamIndex = team . index ( in : unfilteredTeams )
Section {
TeamRowView ( team : team )
} header : {
_teamHeaderView ( team , teamIndex : teamIndex )
} footer : {
_teamFooterView ( team )
}
. headerProminence ( . increased )
}
}
. searchable ( text : $ searchField , isPresented : $ presentSearch , prompt : Text ( " Chercher parmi les équipes inscrites " ) )
. keyboardType ( . alphabet )
. autocorrectionDisabled ( )
}
@ MainActor
private func _managementView ( ) -> some View {
HStack {
Button {
@ -481,12 +285,8 @@ struct InscriptionManagerView: View {
PasteButton ( payloadType : String . self ) { strings in
guard let first = strings . first else { return }
Task {
await MainActor . run ( ) {
fetchPlayers . nsPredicate = _pastePredicate ( pasteField : first , mostRecentDate : tournament . rankSourceDate )
pasteString = first
}
}
fetchPlayers . nsPredicate = _pastePredicate ( pasteField : first , mostRecentDate : SourceFileManager . shared . mostRecentDateAvailable )
pasteString = first
}
Button {
@ -517,9 +317,6 @@ struct InscriptionManagerView: View {
if currentRankSourceDate = = nil {
Text ( " inconnu " ) . tag ( nil as Date ? )
}
let dates = Array ( Set ( SourceFileManager . shared . allFilesSortedByDate ( tournament . tournamentCategory . rankingDataSourceMale ) . map ( { $0 . dateFromPath } ) ) ) . sorted ( ) . reversed ( )
ForEach ( dates , id : \ . self ) { date in
Text ( date . monthYearFormatted ) . tag ( date as Date ? )
}
@ -562,13 +359,15 @@ struct InscriptionManagerView: View {
}
@ ViewBuilder
func _inscriptionTipsView ( ) -> some View {
private func _inscriptionTipsView ( ) -> some View {
List {
Section {
TipView ( fileTip ) { action in
if action . id = = " website " {
UIApplication . shared . open ( SourceFileManager . beachPadel )
} else if action . id = = " add-team-file " {
presentImportView = true
}
}
. tipStyle ( tint : nil )
@ -609,7 +408,7 @@ struct InscriptionManagerView: View {
}
@ ViewBuilder
func _rankHandlerView ( ) -> some View {
private func _rankHandlerView ( ) -> some View {
if let mostRecentDate = SourceFileManager . shared . lastDataSourceDate ( ) , let currentRankSourceDate , currentRankSourceDate < mostRecentDate , tournament . hasEnded ( ) = = false {
Section {
TipView ( rankUpdateTip ) { action in
@ -623,7 +422,359 @@ struct InscriptionManagerView: View {
}
}
func _save ( ) {
@ ViewBuilder
private func _relatedTips ( ) -> some View {
if pasteString ? . isEmpty = = true
&& createdPlayerIds . isEmpty
&& tournament . unsortedTeams ( ) . count >= tournament . teamCount
&& tournament . unsortedPlayers ( ) . filter ( { $0 . source = = . beachPadel } ) . isEmpty {
Section {
TipView ( padelBeachExportTip ) { action in
if action . id = = " more-info-export " {
isLearningMore = true
}
if action . id = = " padel-beach " {
UIApplication . shared . open ( SourceFileManager . beachPadel )
}
}
. tipStyle ( tint : nil )
}
Section {
TipView ( padelBeachImportTip ) { action in
if action . id = = " more-info-import " {
presentImportView = true
}
}
. tipStyle ( tint : nil )
}
}
if tournament . tournamentCategory = = . men && tournament . femalePlayers ( ) . isEmpty = = false {
Section {
TipView ( inscriptionManagerWomanRankTip )
. tipStyle ( tint : nil )
}
}
Section {
TipView ( slideToDeleteTip )
. tipStyle ( tint : nil )
}
}
private func _searchSource ( ) -> String ? {
selectionSearchField ? ? pasteString
}
private func _pastePredicate ( pasteField : String , mostRecentDate : Date ? ) -> NSPredicate ? {
let text = pasteField . canonicalVersion
let nameComponents = text . components ( separatedBy : . whitespacesAndNewlines ) . compactMap { $0 . isEmpty ? nil : $0 } . filter ( { $0 != " de " && $0 != " la " && $0 != " le " && $0 . count > 1 } )
var andPredicates = [ NSPredicate ] ( )
var orPredicates = [ NSPredicate ] ( )
// s e l f . w o r d s C o u n t = n a m e C o m p o n e n t s . c o u n t
if _filterOption ( ) = = . male {
andPredicates . append ( NSPredicate ( format : " male == YES " ) )
} else if _filterOption ( ) = = . female {
andPredicates . append ( NSPredicate ( format : " male == NO " ) )
}
if let mostRecentDate {
andPredicates . append ( NSPredicate ( format : " importDate == %@ " , mostRecentDate as CVarArg ) )
}
if nameComponents . count > 1 {
orPredicates = nameComponents . pairs ( ) . map {
return NSPredicate ( format : " (firstName contains[cd] %@ AND lastName contains[cd] %@) OR (firstName contains[cd] %@ AND lastName contains[cd] %@) " , $0 , $1 , $1 , $0 ) }
} else {
orPredicates = nameComponents . map { NSPredicate ( format : " firstName contains[cd] %@ OR lastName contains[cd] %@ " , $0 , $0 ) }
}
let matches = text . licencesFound ( )
let licensesPredicates = matches . map { NSPredicate ( format : " license contains[cd] %@ " , $0 ) }
orPredicates = orPredicates + licensesPredicates
var predicate = NSCompoundPredicate ( andPredicateWithSubpredicates : andPredicates )
if orPredicates . isEmpty = = false {
predicate = NSCompoundPredicate ( andPredicateWithSubpredicates : [ predicate , NSCompoundPredicate ( orPredicateWithSubpredicates : orPredicates ) ] )
}
return predicate
}
private func _currentSelection ( ) -> Set < PlayerRegistration > {
var currentSelection = Set < PlayerRegistration > ( )
createdPlayerIds . compactMap { id in
fetchPlayers . first ( where : { id = = $0 . license } )
} . forEach { player in
let player = PlayerRegistration ( importedPlayer : player )
player . setWeight ( in : tournament )
currentSelection . insert ( player )
}
createdPlayerIds . compactMap { id in
createdPlayers . first ( where : { id = = $0 . id } )
} . forEach {
currentSelection . insert ( $0 )
}
return currentSelection
}
private func _createTeam ( ) {
let players = _currentSelection ( )
let team = tournament . addTeam ( players )
try ? dataStore . teamRegistrations . addOrUpdate ( instance : team )
try ? dataStore . playerRegistrations . addOrUpdate ( contentOfs : players )
createdPlayers . removeAll ( )
createdPlayerIds . removeAll ( )
pasteString = nil
}
private func _updateTeam ( ) {
guard let editedTeam else { return }
let players = _currentSelection ( )
editedTeam . updatePlayers ( players )
try ? dataStore . teamRegistrations . addOrUpdate ( instance : editedTeam )
try ? dataStore . playerRegistrations . addOrUpdate ( contentOfs : players )
createdPlayers . removeAll ( )
createdPlayerIds . removeAll ( )
pasteString = nil
self . editedTeam = nil
}
private func _buildingTeamView ( ) -> some View {
List ( selection : $ createdPlayerIds ) {
Section {
ForEach ( createdPlayerIds . sorted ( ) , id : \ . self ) { id in
if let p = createdPlayers . first ( where : { $0 . id = = id } ) {
PlayerView ( player : p ) . tag ( p . id )
}
if let p = fetchPlayers . first ( where : { $0 . license = = id } ) {
ImportedPlayerView ( player : p ) . tag ( p . license ! )
}
}
}
if editedTeam = = nil {
if createdPlayerIds . isEmpty {
RowButtonView ( title : " Bloquer une place " ) {
_createTeam ( )
}
} else {
RowButtonView ( title : " Ajouter l'équipe " ) {
_createTeam ( )
}
}
} else {
RowButtonView ( title : " Modifier l'équipe " ) {
_updateTeam ( )
}
}
if let pasteString {
Section {
Text ( pasteString )
} footer : {
HStack {
Text ( " contenu du presse-papier " )
Spacer ( )
Button ( " effacer " , role : . destructive ) {
self . pasteString = nil
self . createdPlayers . removeAll ( )
self . createdPlayerIds . removeAll ( )
}
. buttonStyle ( . borderless )
}
}
if fetchPlayers . isEmpty {
ContentUnavailableView {
Label ( " Aucun résultat " , systemImage : " person.2.slash " )
} description : {
Text ( " Aucun joueur classé n'a été trouvé dans ce message. " )
} actions : {
RowButtonView ( title : " Créer un joueur non classé " ) {
presentPlayerCreation = true
}
RowButtonView ( title : " Effacer cette recherche " ) {
self . pasteString = nil
}
}
} else {
Section {
ForEach ( fetchPlayers . sorted ( by : { $0 . hitForSearch ( pasteString ) > $1 . hitForSearch ( pasteString ) } ) ) { player in
ImportedPlayerView ( player : player ) . tag ( player . license ! )
}
} header : {
Text ( fetchPlayers . count . formatted ( ) + " résultat " + fetchPlayers . count . pluralSuffix )
}
}
}
}
. onReceive ( fetchPlayers . publisher . count ( ) ) { _ in // < - - h e r e
if let pasteString , count = = 2 {
fetchPlayers . filter { $0 . hitForSearch ( pasteString ) >= hitTarget } . sorted ( by : { $0 . hitForSearch ( pasteString ) > $1 . hitForSearch ( pasteString ) } ) . forEach { player in
createdPlayerIds . insert ( player . license ! )
}
}
}
. environment ( \ . editMode , Binding . constant ( EditMode . active ) )
}
private var count : Int {
return fetchPlayers . filter { $0 . hitForSearch ( pasteString ? ? " " ) >= hitTarget } . count
}
private var hitTarget : Int {
if ( pasteString ? . matches ( of : / [ 1 - 9 ] [ 0 - 9 ] { 5 , 7 } / ) . count ? ? 0 ) > 1 {
if fetchPlayers . filter ( { $0 . hitForSearch ( pasteString ? ? " " ) = = 100 } ) . count = = 2 { return 100 }
} else {
return 2
}
return 1
}
@ ViewBuilder
private func _sortingTypePickerView ( ) -> some View {
@ Bindable var tournament = tournament
Picker ( selection : $ tournament . teamSorting ) {
ForEach ( TeamSortingType . allCases ) {
Text ( $0 . localizedLabel ( ) ) . tag ( $0 )
}
} label : {
}
}
@ ViewBuilder
private func _prioritizeClubMembersButton ( ) -> some View {
@ Bindable var tournament = tournament
if let federalClub = tournament . club ( ) {
Menu {
Picker ( selection : $ tournament . prioritizeClubMembers ) {
Text ( " Oui " ) . tag ( true )
Text ( " Non " ) . tag ( false )
} label : {
}
. labelsHidden ( )
} label : {
Text ( " Membres prioritaires " )
Text ( federalClub . acronym )
}
Divider ( )
} else if let event = tournament . eventObject {
NavigationLink {
ClubSearchView ( )
} label : {
Text ( " Identifier le club " )
}
Divider ( )
}
}
private func _teamHeaderView ( _ team : TeamRegistration , teamIndex : Int ? ) -> some View {
HStack {
if let teamIndex {
Text ( " # " + ( teamIndex + 1 ) . formatted ( ) )
}
if team . unsortedPlayers ( ) . isEmpty = = false {
Text ( team . weight . formatted ( ) )
}
if team . isWildCard ( ) {
Text ( " wildcard " ) . italic ( ) . font ( . caption )
}
Spacer ( )
if team . walkOut {
Text ( " WO " )
} else if let teamIndex {
Text ( tournament . cutLabel ( index : teamIndex ) )
}
}
}
private func _teamFooterView ( _ team : TeamRegistration ) -> some View {
HStack {
if let formattedRegistrationDate = team . formattedInscriptionDate ( ) {
Text ( formattedRegistrationDate ) . font ( . caption ) . foregroundStyle ( . secondary )
}
Spacer ( )
_teamMenuOptionView ( team )
}
}
private func _teamMenuOptionView ( _ team : TeamRegistration ) -> some View {
Menu {
Section {
Button ( " Éditer les joueurs " ) {
editedTeam = team
team . unsortedPlayers ( ) . forEach { player in
createdPlayers . insert ( player )
createdPlayerIds . insert ( player . id )
}
}
Divider ( )
Toggle ( isOn : . init ( get : {
return team . wildCardBracket
} , set : { value in
team . resetPositions ( )
team . wildCardGroupStage = false
team . walkOut = false
team . wildCardBracket = value
try ? dataStore . teamRegistrations . addOrUpdate ( instance : team )
} ) ) {
Label ( " Wildcard Tableau " , systemImage : team . wildCardBracket ? " circle.inset.filled " : " circle " )
}
Toggle ( isOn : . init ( get : {
return team . wildCardGroupStage
} , set : { value in
team . resetPositions ( )
team . wildCardBracket = false
team . walkOut = false
team . wildCardGroupStage = value
try ? dataStore . teamRegistrations . addOrUpdate ( instance : team )
} ) ) {
Label ( " Wildcard Poule " , systemImage : team . wildCardGroupStage ? " circle.inset.filled " : " circle " )
}
Divider ( )
Toggle ( isOn : . init ( get : {
return team . walkOut
} , set : { value in
team . resetPositions ( )
team . wildCardBracket = false
team . wildCardGroupStage = false
team . walkOut = value
try ? dataStore . teamRegistrations . addOrUpdate ( instance : team )
} ) ) {
Label ( " WO " , systemImage : team . walkOut ? " circle.inset.filled " : " circle " )
}
Divider ( )
Button ( role : . destructive ) {
try ? dataStore . teamRegistrations . delete ( instance : team )
} label : {
LabelDelete ( )
}
} header : {
Text ( team . teamLabel ( . short ) )
}
} label : {
LabelOptions ( ) . labelStyle ( . titleOnly )
. font ( . caption )
}
}
private func _save ( ) {
try ? dataStore . tournaments . addOrUpdate ( instance : tournament )
}
}