@ -11,19 +11,18 @@ import Combine
struct ShareableObject {
struct ShareableObject {
let cashierViewModel : CashierViewModel
let cashierViewModel : CashierViewModel
let teams : [ Team Registration]
let players : [ Player Registration]
let fileName : String
let fileName : String
func sharedData ( ) async -> Data ? {
func sharedData ( ) async -> Data ? {
let players = teams . filter ( { cashierViewModel . _shouldDisplayTeam ( $0 ) } )
let _players = players . filter ( { cashierViewModel . _shouldDisplayPlayer ( $0 ) } )
. flatMap ( { $0 . players ( ) . filter ( { cashierViewModel . _shouldDisplayPlayer ( $0 ) } ) } )
. map {
. map {
[ $0 . pasteData ( ) ]
[ $0 . pasteData ( ) ]
. compacted ( )
. compacted ( )
. joined ( separator : " \n " )
. joined ( separator : " \n " )
}
}
. joined ( separator : " \n \n " )
. joined ( separator : " \n \n " )
return players . data ( using : . utf8 )
return _ players. data ( using : . utf8 )
}
}
}
}
@ -43,6 +42,16 @@ extension ShareableObject: Transferable {
}
}
}
}
extension Array {
func sorted < T : Comparable > ( by keyPath : KeyPath < Element , T > , order : SortOrder ) -> [ Element ] {
switch order {
case . ascending :
return self . sorted { $0 [ keyPath : keyPath ] < $1 [ keyPath : keyPath ] }
case . descending :
return self . sorted { $0 [ keyPath : keyPath ] > $1 [ keyPath : keyPath ] }
}
}
}
class CashierViewModel : ObservableObject {
class CashierViewModel : ObservableObject {
let id : UUID = UUID ( )
let id : UUID = UUID ( )
@ -60,10 +69,16 @@ class CashierViewModel: ObservableObject {
func _shouldDisplayPlayer ( _ player : PlayerRegistration ) -> Bool {
func _shouldDisplayPlayer ( _ player : PlayerRegistration ) -> Bool {
if searchText . isEmpty = = false {
if searchText . isEmpty = = false {
filterOption . shouldDisplayPlayer ( player ) && player . contains ( searchText )
sortOption . shouldDisplayPlayer ( player ) && filterOption . shouldDisplayPlayer ( player ) && player . contains ( searchText )
} else {
} else {
filterOption . shouldDisplayPlayer ( player )
sortOption . shouldDisplayPlayer ( player ) && filterOption . shouldDisplayPlayer ( player )
}
}
}
func _sortPlayers ( _ players : [ PlayerRegistration ] ) -> [ PlayerRegistration ] {
players
. filter ( { _shouldDisplayPlayer ( $0 ) } )
. sorted { sortOption . compare ( $0 , $1 , order : sortOrder ) }
}
}
enum SortOption : Int , Identifiable , CaseIterable {
enum SortOption : Int , Identifiable , CaseIterable {
@ -74,6 +89,50 @@ class CashierViewModel: ObservableObject {
case age
case age
case callDate
case callDate
var sortingKeyPath : AnyKeyPath {
switch self {
case . alphabeticalLastName :
return \ PlayerRegistration . lastName
case . alphabeticalFirstName :
return \ PlayerRegistration . firstName
case . playerRank , . teamRank , . callDate :
return \ PlayerRegistration . computedRank
case . age :
return \ PlayerRegistration . computedAge !
}
}
func compare ( _ lhs : PlayerRegistration , _ rhs : PlayerRegistration , order : SortOrder ) -> Bool {
switch self {
case . alphabeticalLastName :
return compare ( lhs [ keyPath : \ . lastName ] , rhs [ keyPath : \ . lastName ] , order : order )
case . alphabeticalFirstName :
return compare ( lhs [ keyPath : \ . firstName ] , rhs [ keyPath : \ . firstName ] , order : order )
case . playerRank , . teamRank , . callDate :
return compare ( lhs [ keyPath : \ . computedRank ] , rhs [ keyPath : \ . computedRank ] , order : order )
case . age :
return compare ( lhs [ keyPath : \ . computedAge ! ] , rhs [ keyPath : \ . computedAge ! ] , order : order )
}
}
private func compare < T : Comparable > ( _ lhs : T , _ rhs : T , order : SortOrder ) -> Bool {
switch order {
case . ascending :
return lhs < rhs
case . descending :
return lhs > rhs
}
}
func shouldDisplayPlayer ( _ player : PlayerRegistration ) -> Bool {
switch self {
case . age :
player . computedAge != nil
default :
true
}
}
var id : Int { self . rawValue }
var id : Int { self . rawValue }
func localizedLabel ( ) -> String {
func localizedLabel ( ) -> String {
switch self {
switch self {
@ -131,12 +190,14 @@ struct CashierView: View {
@ EnvironmentObject var cashierViewModel : CashierViewModel
@ EnvironmentObject var cashierViewModel : CashierViewModel
var tournaments : [ Tournament ]
var tournaments : [ Tournament ]
var teams : [ TeamRegistration ]
@ State private var teams : [ TeamRegistration ]
@ State private var players : [ PlayerRegistration ]
@ State private var shareableObject : ShareableObject ?
@ State private var shareableObject : ShareableObject ?
init ( tournament : Tournament , teams : [ TeamRegistration ] ) {
init ( tournament : Tournament , teams : [ TeamRegistration ] ) {
self . tournaments = [ tournament ]
self . tournaments = [ tournament ]
self . teams = teams
_teams = . init ( wrappedValue : teams )
_players = . init ( wrappedValue : teams . flatMap ( { $0 . unsortedPlayers ( ) } ) )
}
}
var body : some View {
var body : some View {
@ -170,22 +231,21 @@ struct CashierView: View {
}
}
}
}
let filteredPlayers = cashierViewModel . _sortPlayers ( players )
if filteredPlayers . isEmpty {
_contentUnavailableView ( )
}
switch cashierViewModel . sortOption {
switch cashierViewModel . sortOption {
case . teamRank :
case . teamRank :
_byTeamRankView ( )
TeamRankView ( teams : teams , displayTournamentTitle : tournaments . count > 1 )
case . alphabeticalLastName :
case . alphabeticalLastName , . alphabeticalFirstName , . playerRank , . age :
_byPlayerLastName ( )
PlayerCashierView ( players : filteredPlayers , displayTournamentTitle : tournaments . count > 1 )
case . alphabeticalFirstName :
_byPlayerFirstName ( )
case . playerRank :
_byPlayerRank ( )
case . age :
_byPlayerAge ( )
case . callDate :
case . callDate :
_byCallDateView ( )
let _teams = teams . filter ( { $0 . callDate != nil } )
TeamCallDateView ( teams : _teams , displayTournamentTitle : tournaments . count > 1 )
}
}
}
}
. searchable ( text : $ cashierViewModel . searchText , isPresented : $ cashierViewModel . isSearching , placement : . navigationBarDrawer ( displayMode : . always ) , prompt : Text ( " Chercher un joueur " ) )
. onAppear {
. onAppear {
cashierViewModel . searchText = " "
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 . c o u n t = = 1 {
@ -196,7 +256,10 @@ struct CashierView: View {
if cashierViewModel . sortOption = = . callDate && teams . first ( where : { $0 . callDate != nil } ) = = nil {
if cashierViewModel . sortOption = = . callDate && teams . first ( where : { $0 . callDate != nil } ) = = nil {
cashierViewModel . sortOption = . teamRank
cashierViewModel . sortOption = . teamRank
}
}
self . shareableObject = ShareableObject ( cashierViewModel : cashierViewModel , teams : teams , fileName : " Encaissement.txt " )
self . shareableObject = ShareableObject ( cashierViewModel : cashierViewModel , players : players , fileName : " Encaissement " )
}
. onChange ( of : cashierViewModel . sortOrder ) {
teams = cashierViewModel . sortOrder = = . ascending ? teams : teams . reversed ( )
}
}
. headerProminence ( . increased )
. headerProminence ( . increased )
. toolbar {
. toolbar {
@ -211,84 +274,49 @@ struct CashierView: View {
}
}
}
}
@ ViewBuilder
struct PlayerCashierView : View {
private func _byPlayer ( _ players : [ PlayerRegistration ] ) -> some View {
@ EnvironmentObject var cashierViewModel : CashierViewModel
let _players = cashierViewModel . sortOrder = = . ascending ? players : players . reversed ( )
let players : [ PlayerRegistration ]
if _players . isEmpty {
let displayTournamentTitle : Bool
_contentUnavailableView ( )
} else {
var body : some View {
ForEach ( _ players) { player in
ForEach ( players ) { player in
Section {
Section {
EditablePlayerView ( player : player , editingOptions : [ . licenceId , . name , . payment ] )
EditablePlayerView ( player : player , editingOptions : [ . licenceId , . name , . payment ] )
} header : {
} header : {
HStack {
if displayTournamentTitle , let tournamentTitle = player . tournament ( ) ? . tournamentTitle ( ) {
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 )
Text ( tournamentTitle )
}
}
} footer : {
if let teamCallDate = player . team ( ) ? . callDate {
Text ( " équipe convoqué " ) + Text ( teamCallDate . localizedDate ( ) )
}
}
}
}
}
}
}
}
@ ViewBuilder
private func _byPlayerRank ( ) -> some View {
let players = teams . flatMap ( { $0 . unsortedPlayers ( ) } ) . sorted ( using : . keyPath ( \ . computedRank ) ) . filter ( { cashierViewModel . _shouldDisplayPlayer ( $0 ) } )
_byPlayer ( players )
}
}
@ ViewBuilder
struct TeamRankView : View {
private func _byPlayerAge ( ) -> some View {
@ EnvironmentObject var cashierViewModel : CashierViewModel
let players = teams . flatMap ( { $0 . unsortedPlayers ( ) } ) . filter ( { $0 . computedAge != nil } ) . sorted ( using : . keyPath ( \ . computedAge ! ) ) . filter ( { cashierViewModel . _shouldDisplayPlayer ( $0 ) } )
let teams : [ TeamRegistration ]
_byPlayer ( players )
let displayTournamentTitle : Bool
}
@ ViewBuilder
private func _byPlayerLastName ( ) -> some View {
let players = teams . flatMap ( { $0 . unsortedPlayers ( ) } ) . sorted ( using : . keyPath ( \ . lastName ) ) . filter ( { cashierViewModel . _shouldDisplayPlayer ( $0 ) } )
_byPlayer ( players )
}
@ ViewBuilder
private func _byPlayerFirstName ( ) -> some View {
let players = teams . flatMap ( { $0 . unsortedPlayers ( ) } ) . sorted ( using : . keyPath ( \ . firstName ) ) . filter ( { cashierViewModel . _shouldDisplayPlayer ( $0 ) } )
_byPlayer ( players )
}
@ ViewBuilder
var body : some View {
private func _byTeamRankView ( ) -> some View {
ForEach ( teams ) { team in
let _teams = cashierViewModel . sortOrder = = . ascending ? teams : teams . reversed ( )
let players = team . players ( ) . filter ( { cashierViewModel . _shouldDisplayPlayer ( $0 ) } )
let _filteredTeams = _teams . filter ( { cashierViewModel . _shouldDisplayTeam ( $0 ) } )
if players . isEmpty = = false {
if _filteredTeams . isEmpty {
_contentUnavailableView ( )
} else {
ForEach ( _filteredTeams ) { team in
Section {
Section {
ForEach ( team . players ( ) ) { player in
ForEach ( players ) { player in
if cashierViewModel . _shouldDisplayPlayer ( player ) {
EditablePlayerView ( player : player , editingOptions : [ . licenceId , . name , . payment ] )
EditablePlayerView ( player : player , editingOptions : [ . licenceId , . name , . payment ] )
}
}
}
} header : {
} header : {
HStack {
if displayTournamentTitle , let tournamentTitle = team . tournamentObject ( ) ? . tournamentTitle ( ) {
if let callDate = team . callDate {
Text ( tournamentTitle )
Text ( callDate . localizedDate ( ) )
}
Spacer ( )
VStack ( alignment : . trailing , spacing : 0 ) {
Text ( " Poids " ) . font ( . caption )
Text ( team . weight . formatted ( ) )
}
}
}
} footer : {
} footer : {
if tournaments . count > 1 , let tournamentTitle = team . tournamentObject ( ) ? . tournamentTitle ( ) {
if let callDate = team . callDate {
Text ( tournamentTitle )
Text ( " convoqué " ) + Text ( callDate . localizedDate ( ) )
}
}
}
}
}
}
}
@ -296,15 +324,13 @@ struct CashierView: View {
}
}
@ ViewBuilder
struct TeamCallDateView : View {
private func _byCallDateView ( ) -> some View {
@ EnvironmentObject var cashierViewModel : CashierViewModel
let _teams = teams . filter ( { $0 . callDate != nil && cashierViewModel . _shouldDisplayTeam ( $0 ) } )
let teams : [ TeamRegistration ]
let displayTournamentTitle : Bool
if _teams . isEmpty {
_contentUnavailableView ( )
}
let groupedTeams = Dictionary ( grouping : _teams ) { team in
var body : some View {
let groupedTeams = Dictionary ( grouping : teams ) { team in
team . callDate
team . callDate
}
}
let keys = cashierViewModel . 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 ( )
@ -312,16 +338,16 @@ struct CashierView: View {
ForEach ( keys , id : \ . self ) { key in
ForEach ( keys , id : \ . self ) { key in
if let _teams = groupedTeams [ key ] {
if let _teams = groupedTeams [ key ] {
ForEach ( _teams ) { team in
ForEach ( _teams ) { team in
let players = team . players ( ) . filter ( { cashierViewModel . _shouldDisplayPlayer ( $0 ) } )
if players . isEmpty = = false {
Section {
Section {
ForEach ( team . players ( ) ) { player in
ForEach ( players ) { player in
if cashierViewModel . _shouldDisplayPlayer ( player ) {
EditablePlayerView ( player : player , editingOptions : [ . licenceId , . name , . payment ] )
EditablePlayerView ( player : player , editingOptions : [ . licenceId , . name , . payment ] )
}
}
}
} header : {
} header : {
Text ( key . localizedDate ( ) )
Text ( key . localizedDate ( ) )
} footer : {
} footer : {
if tournaments . count > 1 , let tournamentTitle = team . tournamentObject ( ) ? . tournamentTitle ( ) {
if displayTournamentTitle , let tournamentTitle = team . tournamentObject ( ) ? . tournamentTitle ( ) {
Text ( tournamentTitle )
Text ( tournamentTitle )
}
}
}
}
@ -329,6 +355,8 @@ struct CashierView: View {
}
}
}
}
}
}
}
}
private func _unavailableIcon ( ) -> String {
private func _unavailableIcon ( ) -> String {
switch cashierViewModel . sortOption {
switch cashierViewModel . sortOption {