@ -8,39 +8,62 @@
import SwiftUI
import Combine
struct CashierView : View {
@ EnvironmentObject var dataStore : DataStore
var tournaments : [ Tournament ]
var teams : [ TeamRegistration ]
@ State private var sortOption : SortOption = . callDate
@ State private var filterOption : FilterOption = . all
@ State private var sortOrder : SortOrder = . ascending
@ State private var searchText = " "
@ State private var isSearching : Bool = false
init ( tournament : Tournament , teams : [ TeamRegistration ] ) {
self . tournaments = [ tournament ]
self . teams = teams
if tournament . hasEnded ( ) , tournament . players ( ) . anySatisfy ( { $0 . hasPaid ( ) = = false } ) {
_filterOption = . init ( wrappedValue : . didNotPay )
}
if teams . filter ( { $0 . callDate != nil } ) . isEmpty {
_sortOption = . init ( wrappedValue : . teamRank )
} else {
_sortOption = . init ( wrappedValue : . callDate )
}
}
struct ShareableObject {
private func _sharedData ( ) -> String {
let players = teams . filter ( { _shouldDisplayTeam ( $0 ) } )
. flatMap ( { $0 . players ( ) . filter ( { _shouldDisplayPlayer ( $0 ) } ) } )
let cashierViewModel : CashierViewModel
let teams : [ TeamRegistration ]
let fileName : String
func sharedData ( ) async -> Data ? {
let players = teams . filter ( { cashierViewModel . _shouldDisplayTeam ( $0 ) } )
. flatMap ( { $0 . players ( ) . filter ( { cashierViewModel . _shouldDisplayPlayer ( $0 ) } ) } )
. map {
[ $0 . pasteData ( ) ]
. compacted ( )
. joined ( separator : " \n " )
}
. joined ( separator : " \n \n " )
return players
return players . data ( using : . utf8 )
}
}
extension ShareableObject : Transferable {
enum ShareError : Error {
case failed
}
static var transferRepresentation : some TransferRepresentation {
let rep = DataRepresentation < ShareableObject > ( exportedContentType : . plainText ) { object in
guard let data = await object . sharedData ( ) else {
throw ShareError . failed
}
return data
}
return rep . suggestedFileName { object in object . fileName }
}
}
class CashierViewModel : ObservableObject {
let id : UUID = UUID ( )
@ Published var sortOption : SortOption = . callDate
@ Published var filterOption : FilterOption = . all
@ Published var sortOrder : SortOrder = . ascending
@ Published var searchText : String = " "
@ Published var isSearching : Bool = false
func _shouldDisplayTeam ( _ team : TeamRegistration ) -> Bool {
team . unsortedPlayers ( ) . anySatisfy ( {
_shouldDisplayPlayer ( $0 )
} )
}
func _shouldDisplayPlayer ( _ player : PlayerRegistration ) -> Bool {
if searchText . isEmpty = = false {
filterOption . shouldDisplayPlayer ( player ) && player . contains ( searchText )
} else {
filterOption . shouldDisplayPlayer ( player )
}
}
enum SortOption : Int , Identifiable , CaseIterable {
@ -100,28 +123,43 @@ struct CashierView: View {
}
}
}
}
struct CashierView : View {
@ EnvironmentObject var dataStore : DataStore
@ EnvironmentObject var cashierViewModel : CashierViewModel
var tournaments : [ Tournament ]
var teams : [ TeamRegistration ]
@ State private var shareableObject : ShareableObject ?
init ( tournament : Tournament , teams : [ TeamRegistration ] ) {
self . tournaments = [ tournament ]
self . teams = teams
}
var body : some View {
List {
if isSearching = = false {
if cashierViewModel . isSearching = = false {
Section {
Picker ( selection : $ filterOption ) {
ForEach ( FilterOption . allCases ) { filterOption in
Picker ( selection : $ cashierViewModel . filterOption ) {
ForEach ( CashierViewModel . FilterOption . allCases ) { filterOption in
Text ( filterOption . localizedLabel ( ) ) . tag ( filterOption )
}
} label : {
Text ( " Statut du règlement " )
}
Picker ( selection : $ sortOption ) {
ForEach ( SortOption . allCases ) { sortOption in
Picker ( selection : $ cashierViewModel . sortOption ) {
ForEach ( CashierViewModel . SortOption . allCases ) { sortOption in
Text ( sortOption . localizedLabel ( ) ) . tag ( sortOption )
}
} label : {
Text ( " Affichage par " )
}
Picker ( selection : $ sortOrder ) {
Picker ( selection : $ cashierViewModel . sortOrder ) {
Text ( " Croissant " ) . tag ( SortOrder . ascending )
Text ( " Décroissant " ) . tag ( SortOrder . descending )
} label : {
@ -131,12 +169,8 @@ struct CashierView: View {
Text ( " Options d'affichage " )
}
}
if _isContentUnavailable ( ) {
_contentUnavailableView ( )
}
switch sortOption {
switch cashierViewModel . sortOption {
case . teamRank :
_byTeamRankView ( )
case . alphabeticalLastName :
@ -151,51 +185,53 @@ struct CashierView: View {
_byCallDateView ( )
}
}
. searchable ( text : $ cashierViewModel . searchText , isPresented : $ cashierViewModel . isSearching , placement : . navigationBarDrawer ( displayMode : . always ) , prompt : Text ( " Chercher un joueur " ) )
. onAppear {
cashierViewModel . searchText = " "
// i f t o u r n a m e n t s . c o u n t = = 1 {
// i f t o u r n a m e n t s . f i r s t ! . h a s E n d e d ( ) = = t r u e , t o u r n a m e n t s . f i r s t ! . p l a y e r s ( ) . a n y S a t i s f y ( { $ 0 . h a s P a i d ( ) = = f a l s e } ) {
// f i l t e r O p t i o n = . d i d N o t P a y
// }
// }
if cashierViewModel . sortOption = = . callDate && teams . first ( where : { $0 . callDate != nil } ) = = nil {
cashierViewModel . sortOption = . teamRank
}
self . shareableObject = ShareableObject ( cashierViewModel : cashierViewModel , teams : teams , fileName : " Encaissement.txt " )
}
. headerProminence ( . increased )
. searchable ( text : $ searchText , isPresented : $ isSearching , prompt : Text ( " Chercher un joueur " ) )
. toolbar {
ToolbarItem ( placement : . topBarTrailing ) {
ShareLink ( item : _sharedData ( ) . createTxtFile ( " bilan " ) )
if let shareableObject {
ShareLink (
item : shareableObject ,
preview : SharePreview ( shareableObject . fileName )
)
}
}
}
}
@ ViewBuilder
func computedPlayerView ( _ player : PlayerRegistration ) -> some View {
EditablePlayerView ( player : player , editingOptions : [ . licenceId , . name , . payment ] )
}
private func _shouldDisplayTeam ( _ team : TeamRegistration ) -> Bool {
team . players ( ) . anySatisfy ( {
_shouldDisplayPlayer ( $0 )
} )
}
private func _shouldDisplayPlayer ( _ player : PlayerRegistration ) -> Bool {
if searchText . isEmpty = = false {
filterOption . shouldDisplayPlayer ( player ) && player . contains ( searchText )
} else {
filterOption . shouldDisplayPlayer ( player )
}
}
@ ViewBuilder
private func _byPlayer ( _ players : [ PlayerRegistration ] ) -> some View {
let _players = sortOrder = = . ascending ? players : players . reversed ( )
ForEach ( _players ) { player in
Section {
computedPlayerView ( player )
} header : {
HStack {
if let teamCallDate = player . team ( ) ? . callDate {
Text ( teamCallDate . localizedDate ( ) )
let _players = cashierViewModel . sortOrder = = . ascending ? players : players . reversed ( )
if _players . isEmpty {
_contentUnavailableView ( )
} else {
ForEach ( _players ) { player in
Section {
EditablePlayerView ( player : player , editingOptions : [ . licenceId , . name , . payment ] )
} header : {
HStack {
if let teamCallDate = player . team ( ) ? . callDate {
Text ( teamCallDate . localizedDate ( ) )
}
Spacer ( )
Text ( player . formattedRank ( ) )
}
} footer : {
if tournaments . count > 1 , let tournamentTitle = player . tournament ( ) ? . tournamentTitle ( ) {
Text ( tournamentTitle )
}
Spacer ( )
Text ( player . computedRank . formatted ( ) )
}
} footer : {
if tournaments . count > 1 , let tournamentTitle = player . tournament ( ) ? . tournamentTitle ( ) {
Text ( tournamentTitle )
}
}
}
@ -203,42 +239,52 @@ struct CashierView: View {
@ ViewBuilder
private func _byPlayerRank ( ) -> some View {
let players = teams . flatMap ( { $0 . p layers( ) } ) . sorted ( using : . keyPath ( \ . computedRank ) ) . filter ( { _shouldDisplayPlayer ( $0 ) } )
let players = teams . flatMap ( { $0 . unsortedP layers( ) } ) . sorted ( using : . keyPath ( \ . computedRank ) ) . filter ( { cashierViewModel . _shouldDisplayPlayer ( $0 ) } )
_byPlayer ( players )
}
@ ViewBuilder
private func _byPlayerAge ( ) -> some View {
let players = teams . flatMap ( { $0 . p layers( ) } ) . filter ( { $0 . computedAge != nil } ) . sorted ( using : . keyPath ( \ . computedAge ! ) ) . filter ( { _shouldDisplayPlayer ( $0 ) } )
let players = teams . flatMap ( { $0 . unsortedP layers( ) } ) . filter ( { $0 . computedAge != nil } ) . sorted ( using : . keyPath ( \ . computedAge ! ) ) . filter ( { cashierViewModel . _shouldDisplayPlayer ( $0 ) } )
_byPlayer ( players )
}
@ ViewBuilder
private func _byPlayerLastName ( ) -> some View {
let players = teams . flatMap ( { $0 . p layers( ) } ) . sorted ( using : . keyPath ( \ . lastName ) ) . filter ( { _shouldDisplayPlayer ( $0 ) } )
let players = teams . flatMap ( { $0 . unsortedP layers( ) } ) . sorted ( using : . keyPath ( \ . lastName ) ) . filter ( { cashierViewModel . _shouldDisplayPlayer ( $0 ) } )
_byPlayer ( players )
}
@ ViewBuilder
private func _byPlayerFirstName ( ) -> some View {
let players = teams . flatMap ( { $0 . p layers( ) } ) . sorted ( using : . keyPath ( \ . firstName ) ) . filter ( { _shouldDisplayPlayer ( $0 ) } )
let players = teams . flatMap ( { $0 . unsortedP layers( ) } ) . sorted ( using : . keyPath ( \ . firstName ) ) . filter ( { cashierViewModel . _shouldDisplayPlayer ( $0 ) } )
_byPlayer ( players )
}
@ ViewBuilder
private func _byTeamRankView ( ) -> some View {
let _teams = sortOrder = = . ascending ? teams : teams . reversed ( )
ForEach ( _teams ) { team in
if _shouldDisplayTeam ( team ) {
let _teams = cashierViewModel . sortOrder = = . ascending ? teams : teams . reversed ( )
let _filteredTeams = _teams . filter ( { cashierViewModel . _shouldDisplayTeam ( $0 ) } )
if _filteredTeams . isEmpty {
_contentUnavailableView ( )
} else {
ForEach ( _filteredTeams ) { team in
Section {
_cashierPlayersView ( team . players ( ) )
ForEach ( team . players ( ) ) { player in
if cashierViewModel . _shouldDisplayPlayer ( player ) {
EditablePlayerView ( player : player , editingOptions : [ . licenceId , . name , . payment ] )
}
}
} header : {
HStack {
if let callDate = team . callDate {
Text ( callDate . localizedDate ( ) )
}
Spacer ( )
Text ( team . weight . formatted ( ) )
VStack ( alignment : . trailing , spacing : 0 ) {
Text ( " Poids " ) . font ( . caption )
Text ( team . weight . formatted ( ) )
}
}
} footer : {
if tournaments . count > 1 , let tournamentTitle = team . tournamentObject ( ) ? . tournamentTitle ( ) {
@ -252,52 +298,40 @@ struct CashierView: View {
@ ViewBuilder
private func _byCallDateView ( ) -> some View {
let groupedTeams = Dictionary ( grouping : teams ) { team in
let _teams = teams . filter ( { $0 . callDate != nil && cashierViewModel . _shouldDisplayTeam ( $0 ) } )
if _teams . isEmpty {
_contentUnavailableView ( )
}
let groupedTeams = Dictionary ( grouping : _teams ) { team in
team . callDate
}
let keys = sortOrder = = . ascending ? groupedTeams . keys . compactMap { $0 } . sorted ( ) : groupedTeams . keys . compactMap { $0 } . sorted ( ) . reversed ( )
let keys = cashierViewModel . sortOrder = = . ascending ? groupedTeams . keys . compactMap { $0 } . sorted ( ) : groupedTeams . keys . compactMap { $0 } . sorted ( ) . reversed ( )
ForEach ( keys , id : \ . self ) { key in
if let _teams = groupedTeams [ key ] {
ForEach ( _teams ) { team in
if _shouldDisplayTeam ( team ) {
Section {
_cashierPlayersView ( team . players ( ) )
} header : {
Text ( key . localizedDate ( ) )
} footer : {
if tournaments . count > 1 , let tournamentTitle = team . tournamentObject ( ) ? . tournamentTitle ( ) {
Text ( tournamentTitle )
Section {
ForEach ( team . players ( ) ) { player in
if cashierViewModel . _shouldDisplayPlayer ( player ) {
EditablePlayerView ( player : player , editingOptions : [ . licenceId , . name , . payment ] )
}
}
} header : {
Text ( key . localizedDate ( ) )
} footer : {
if tournaments . count > 1 , let tournamentTitle = team . tournamentObject ( ) ? . tournamentTitle ( ) {
Text ( tournamentTitle )
}
}
}
}
}
}
@ ViewBuilder
private func _cashierPlayersView ( _ players : [ PlayerRegistration ] ) -> some View {
ForEach ( players ) { player in
if _shouldDisplayPlayer ( player ) {
computedPlayerView ( player )
}
}
}
private func _isContentUnavailable ( ) -> Bool {
switch sortOption {
case . callDate :
return teams . filter ( { $0 . callDate != nil && _shouldDisplayTeam ( $0 ) } ) . isEmpty
case . teamRank :
return teams . filter ( { _shouldDisplayTeam ( $0 ) } ) . isEmpty
default :
return teams . flatMap ( { $0 . players ( ) } ) . filter ( { _shouldDisplayPlayer ( $0 ) } ) . isEmpty
}
}
private func _unavailableIcon ( ) -> String {
switch sortOption {
switch cashierViewModel . sortOption {
case . teamRank , . callDate :
return " person.2.slash.fill "
default :
@ -307,8 +341,8 @@ struct CashierView: View {
@ ViewBuilder
private func _contentUnavailableView ( ) -> some View {
if isSearching {
ContentUnavailableView . search ( text : searchText )
if cashierViewModel . isSearching {
ContentUnavailableView . search ( text : cashierViewModel . searchText )
} else {
ContentUnavailableView ( " Aucun résultat " , systemImage : _unavailableIcon ( ) )
}