You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
316 lines
10 KiB
316 lines
10 KiB
//
|
|
// CashierView.swift
|
|
// Padel Tournament
|
|
//
|
|
// Created by Razmig Sarkissian on 04/03/2023.
|
|
//
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
private func _sharedData() -> String {
|
|
let players = teams.filter({ _shouldDisplayTeam($0) })
|
|
.flatMap({ $0.players().filter({ _shouldDisplayPlayer($0) }) })
|
|
.map {
|
|
[$0.pasteData()]
|
|
.compacted()
|
|
.joined(separator: "\n")
|
|
}
|
|
.joined(separator: "\n\n")
|
|
return players
|
|
}
|
|
|
|
enum SortOption: Int, Identifiable, CaseIterable {
|
|
case teamRank
|
|
case alphabeticalLastName
|
|
case alphabeticalFirstName
|
|
case playerRank
|
|
case age
|
|
case callDate
|
|
|
|
var id: Int { self.rawValue }
|
|
func localizedLabel() -> String {
|
|
switch self {
|
|
case .callDate:
|
|
return "Convocation"
|
|
case .teamRank:
|
|
return "Poids d'équipe"
|
|
case .alphabeticalLastName:
|
|
return "Nom"
|
|
case .alphabeticalFirstName:
|
|
return "Prénom"
|
|
case .playerRank:
|
|
return "Rang"
|
|
case .age:
|
|
return "Âge"
|
|
}
|
|
}
|
|
}
|
|
|
|
enum FilterOption: Int, Identifiable, CaseIterable {
|
|
case all
|
|
case didPay
|
|
case didNotPay
|
|
|
|
var id: Int { self.rawValue }
|
|
|
|
func localizedLabel() -> String {
|
|
switch self {
|
|
case .all:
|
|
return "Tous"
|
|
case .didPay:
|
|
return "Réglé"
|
|
case .didNotPay:
|
|
return "Non réglé"
|
|
}
|
|
}
|
|
|
|
func shouldDisplayPlayer(_ player: PlayerRegistration) -> Bool {
|
|
switch self {
|
|
case .all:
|
|
return true
|
|
case .didPay:
|
|
return player.hasPaid()
|
|
case .didNotPay:
|
|
return player.hasPaid() == false
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
var body: some View {
|
|
List {
|
|
if isSearching == false {
|
|
Section {
|
|
Picker(selection: $filterOption) {
|
|
ForEach(FilterOption.allCases) { filterOption in
|
|
Text(filterOption.localizedLabel()).tag(filterOption)
|
|
}
|
|
} label: {
|
|
Text("Statut du règlement")
|
|
}
|
|
|
|
Picker(selection: $sortOption) {
|
|
ForEach(SortOption.allCases) { sortOption in
|
|
Text(sortOption.localizedLabel()).tag(sortOption)
|
|
}
|
|
} label: {
|
|
Text("Affichage par")
|
|
}
|
|
|
|
Picker(selection: $sortOrder) {
|
|
Text("Croissant").tag(SortOrder.ascending)
|
|
Text("Décroissant").tag(SortOrder.descending)
|
|
} label: {
|
|
Text("Trier par ordre")
|
|
}
|
|
} header: {
|
|
Text("Options d'affichage")
|
|
}
|
|
}
|
|
|
|
if _isContentUnavailable() {
|
|
_contentUnavailableView()
|
|
}
|
|
|
|
switch sortOption {
|
|
case .teamRank:
|
|
_byTeamRankView()
|
|
case .alphabeticalLastName:
|
|
_byPlayerLastName()
|
|
case .alphabeticalFirstName:
|
|
_byPlayerFirstName()
|
|
case .playerRank:
|
|
_byPlayerRank()
|
|
case .age:
|
|
_byPlayerAge()
|
|
case .callDate:
|
|
_byCallDateView()
|
|
}
|
|
}
|
|
.headerProminence(.increased)
|
|
.searchable(text: $searchText, isPresented: $isSearching, prompt: Text("Chercher un joueur"))
|
|
.toolbar {
|
|
ToolbarItem(placement: .topBarTrailing) {
|
|
ShareLink(item: _sharedData().createTxtFile("bilan"))
|
|
}
|
|
}
|
|
}
|
|
|
|
@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())
|
|
}
|
|
Spacer()
|
|
Text(player.computedRank.formatted())
|
|
}
|
|
} footer: {
|
|
if tournaments.count > 1, let tournamentTitle = player.tournament()?.tournamentTitle() {
|
|
Text(tournamentTitle)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@ViewBuilder
|
|
private func _byPlayerRank() -> some View {
|
|
let players = teams.flatMap({ $0.players() }).sorted(using: .keyPath(\.computedRank)).filter({ _shouldDisplayPlayer($0) })
|
|
_byPlayer(players)
|
|
}
|
|
|
|
@ViewBuilder
|
|
private func _byPlayerAge() -> some View {
|
|
let players = teams.flatMap({ $0.players() }).filter({ $0.computedAge != nil }).sorted(using: .keyPath(\.computedAge!)).filter({ _shouldDisplayPlayer($0) })
|
|
_byPlayer(players)
|
|
}
|
|
|
|
@ViewBuilder
|
|
private func _byPlayerLastName() -> some View {
|
|
let players = teams.flatMap({ $0.players() }).sorted(using: .keyPath(\.lastName)).filter({ _shouldDisplayPlayer($0) })
|
|
_byPlayer(players)
|
|
}
|
|
|
|
@ViewBuilder
|
|
private func _byPlayerFirstName() -> some View {
|
|
let players = teams.flatMap({ $0.players() }).sorted(using: .keyPath(\.firstName)).filter({ _shouldDisplayPlayer($0) })
|
|
_byPlayer(players)
|
|
}
|
|
|
|
@ViewBuilder
|
|
private func _byTeamRankView() -> some View {
|
|
let _teams = sortOrder == .ascending ? teams : teams.reversed()
|
|
ForEach(_teams) { team in
|
|
if _shouldDisplayTeam(team) {
|
|
Section {
|
|
_cashierPlayersView(team.players())
|
|
} header: {
|
|
HStack {
|
|
if let callDate = team.callDate {
|
|
Text(callDate.localizedDate())
|
|
}
|
|
Spacer()
|
|
Text(team.weight.formatted())
|
|
}
|
|
} footer: {
|
|
if tournaments.count > 1, let tournamentTitle = team.tournamentObject()?.tournamentTitle() {
|
|
Text(tournamentTitle)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@ViewBuilder
|
|
private func _byCallDateView() -> some View {
|
|
let groupedTeams = Dictionary(grouping: teams) { team in
|
|
team.callDate
|
|
}
|
|
let keys = 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)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@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 {
|
|
case .teamRank, .callDate:
|
|
return "person.2.slash.fill"
|
|
default:
|
|
return "person.slash.fill"
|
|
}
|
|
}
|
|
|
|
@ViewBuilder
|
|
private func _contentUnavailableView() -> some View {
|
|
if isSearching {
|
|
ContentUnavailableView.search(text: searchText)
|
|
} else {
|
|
ContentUnavailableView("Aucun résultat", systemImage: _unavailableIcon())
|
|
}
|
|
}
|
|
}
|
|
|