clean up cashier

multistore
Razmig Sarkissian 2 years ago
parent 5f4c995fb8
commit 455074c155
  1. 16
      PadelClub.xcodeproj/project.pbxproj
  2. 2
      PadelClub/Data/Federal/FederalPlayer.swift
  3. 20
      PadelClub/Data/PlayerRegistration.swift
  4. 14
      PadelClub/Data/Tournament.swift
  5. 45
      PadelClub/Views/Cashier/CashierDetailView.swift
  6. 56
      PadelClub/Views/Cashier/CashierSettingsView.swift
  7. 592
      PadelClub/Views/Cashier/CashierView.swift
  8. 18
      PadelClub/Views/Cashier/PlayerListView.swift
  9. 100
      PadelClub/Views/Player/Components/EditablePlayerView.swift
  10. 3
      PadelClub/Views/Shared/ImportedPlayerView.swift
  11. 2
      PadelClub/Views/Tournament/Screen/Components/InscriptionInfoView.swift
  12. 100
      PadelClub/Views/Tournament/Screen/TournamentCashierView.swift
  13. 2
      PadelClub/Views/Tournament/TournamentView.swift

@ -86,6 +86,10 @@
FF0EC5762BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-04-2023.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC5452BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-04-2023.csv */; };
FF0EC5772BB195E20056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-10-2022.csv in Resources */ = {isa = PBXBuildFile; fileRef = FF0EC54A2BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-10-2022.csv */; };
FF11627A2BCF8109000C4809 /* CallMessageCustomizationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1162792BCF8109000C4809 /* CallMessageCustomizationView.swift */; };
FF11627D2BCF941A000C4809 /* CashierSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF11627C2BCF941A000C4809 /* CashierSettingsView.swift */; };
FF11627F2BCF9432000C4809 /* PlayerListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF11627E2BCF9432000C4809 /* PlayerListView.swift */; };
FF1162812BCF945C000C4809 /* TournamentCashierView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1162802BCF945C000C4809 /* TournamentCashierView.swift */; };
FF1162832BCFBE4E000C4809 /* EditablePlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1162822BCFBE4E000C4809 /* EditablePlayerView.swift */; };
FF1CBC1B2BB53D1F0036DAAB /* FederalTournament.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1CBC182BB53D1F0036DAAB /* FederalTournament.swift */; };
FF1CBC1D2BB53DC10036DAAB /* Calendar+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1CBC1C2BB53DC10036DAAB /* Calendar+Extensions.swift */; };
FF1CBC1F2BB53E0C0036DAAB /* FederalTournamentSearchScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF1CBC1E2BB53E0C0036DAAB /* FederalTournamentSearchScope.swift */; };
@ -356,6 +360,10 @@
FF0EC54B2BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-11-2022.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT-PADEL-MESSIEURS-11-2022.csv"; sourceTree = "<group>"; };
FF0EC54C2BB195CA0056B6D1 /* CLASSEMENT-PADEL-MESSIEURS-12-2022.csv */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CLASSEMENT-PADEL-MESSIEURS-12-2022.csv"; sourceTree = "<group>"; };
FF1162792BCF8109000C4809 /* CallMessageCustomizationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallMessageCustomizationView.swift; sourceTree = "<group>"; };
FF11627C2BCF941A000C4809 /* CashierSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CashierSettingsView.swift; sourceTree = "<group>"; };
FF11627E2BCF9432000C4809 /* PlayerListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerListView.swift; sourceTree = "<group>"; };
FF1162802BCF945C000C4809 /* TournamentCashierView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentCashierView.swift; sourceTree = "<group>"; };
FF1162822BCFBE4E000C4809 /* EditablePlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditablePlayerView.swift; sourceTree = "<group>"; };
FF1CBC182BB53D1F0036DAAB /* FederalTournament.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FederalTournament.swift; sourceTree = "<group>"; };
FF1CBC1C2BB53DC10036DAAB /* Calendar+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Calendar+Extensions.swift"; sourceTree = "<group>"; };
FF1CBC1E2BB53E0C0036DAAB /* FederalTournamentSearchScope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FederalTournamentSearchScope.swift; sourceTree = "<group>"; };
@ -707,6 +715,7 @@
FF089EB32BB0020000F0AEC7 /* PlayerSexPickerView.swift */,
FF089EBA2BB0120700F0AEC7 /* PlayerPopoverView.swift */,
FF9267FB2BCE84870080F940 /* PlayerPayView.swift */,
FF1162822BCFBE4E000C4809 /* EditablePlayerView.swift */,
);
path = Components;
sourceTree = "<group>";
@ -774,6 +783,8 @@
children = (
FF9267F92BCE78EB0080F940 /* CashierDetailView.swift */,
FF9267F72BCE78C70080F940 /* CashierView.swift */,
FF11627E2BCF9432000C4809 /* PlayerListView.swift */,
FF11627C2BCF941A000C4809 /* CashierSettingsView.swift */,
);
path = Cashier;
sourceTree = "<group>";
@ -837,6 +848,7 @@
FF8F26532BAE1E4400650388 /* TableStructureView.swift */,
FF0E0B6C2BC254C6005F00A9 /* TournamentScheduleView.swift */,
FF9268062BCE94D90080F940 /* TournamentCallView.swift */,
FF1162802BCF945C000C4809 /* TournamentCashierView.swift */,
FF8F26522BAE0E4E00650388 /* Components */,
);
path = Screen;
@ -1325,6 +1337,7 @@
C4A47DB32B86387500ADC637 /* AccountView.swift in Sources */,
FF1CBC1D2BB53DC10036DAAB /* Calendar+Extensions.swift in Sources */,
FF967CF22BAECC0B00A9A3BD /* TeamScore.swift in Sources */,
FF1162832BCFBE4E000C4809 /* EditablePlayerView.swift in Sources */,
FF5D0D762BB428B2005CB568 /* ListRowViewModifier.swift in Sources */,
FF6EC9002B94794700EA7F5A /* PresentationContext.swift in Sources */,
FFDB1C6D2BB2A02000F1E467 /* AppSettings.swift in Sources */,
@ -1351,8 +1364,10 @@
FF967D042BAEF1C300A9A3BD /* MatchRowView.swift in Sources */,
C44B79112BBDA63A00906534 /* Locale+Extensions.swift in Sources */,
FF967CEA2BAEC70100A9A3BD /* GroupStage.swift in Sources */,
FF1162812BCF945C000C4809 /* TournamentCashierView.swift in Sources */,
C4A47D742B72881F00ADC637 /* ClubView.swift in Sources */,
C4A47D902B7BBBEC00ADC637 /* StoreManager.swift in Sources */,
FF11627F2BCF9432000C4809 /* PlayerListView.swift in Sources */,
FF4AB6BB2B9256D50002987F /* SearchViewModel.swift in Sources */,
FF967CF32BAECC0B00A9A3BD /* PlayerRegistration.swift in Sources */,
FF4AB6BF2B92577A0002987F /* ImportedPlayerView.swift in Sources */,
@ -1385,6 +1400,7 @@
FF5D0D892BB4935C005CB568 /* ClubRowView.swift in Sources */,
FF1DC5512BAB351300FD8220 /* ClubDetailView.swift in Sources */,
FF9268032BCE94A30080F940 /* GroupStageCallingView.swift in Sources */,
FF11627D2BCF941A000C4809 /* CashierSettingsView.swift in Sources */,
FFFCDE0E2BCC833600317DEF /* LoserRoundScheduleEditorView.swift in Sources */,
C4A47D632B6D3D6500ADC637 /* Club.swift in Sources */,
FF6EC90B2B947AC000EA7F5A /* Array+Extensions.swift in Sources */,

@ -21,6 +21,7 @@ protocol PlayerHolder {
var clubName: String? { get }
var ligueName: String? { get }
var assimilation: String? { get }
var computedAge: Int? { get }
}
extension PlayerHolder {
@ -31,6 +32,7 @@ extension PlayerHolder {
extension ImportedPlayer: PlayerHolder {
var computedAge: Int? { nil }
var tournamentPlayed: Int? {
Int(tournamentCount)

@ -87,6 +87,26 @@ class PlayerRegistration: ModelObject, Storable {
}
}
var computedAge: Int? {
if let birthdate {
let components = birthdate.components(separatedBy: "/")
if components.count == 3 {
if let year = Calendar.current.dateComponents([.year], from: Date()).year, let age = components.last, let ageInt = Int(age) {
if age.count == 2 { //si l'année est sur 2 chiffres dans le fichier
if ageInt < 23 {
return year - 2000 - ageInt
} else {
return year - 2000 + 100 - ageInt
}
} else { //si l'année est représenté sur 4 chiffres
return year - ageInt
}
}
}
}
return nil
}
func pasteData() -> String {
[firstName.capitalized, lastName.capitalized, licenceId].compactMap({ $0 }).joined(separator: " ")
}

@ -320,9 +320,19 @@ class Tournament : ModelObject, Storable {
return groupStages.filter({ $0.hasStarted() && $0.hasEnded() == false }).sorted(by: \.index).first ?? groupStages.first
}
func getActiveRound() -> Round? {
func getActiveRound(withSeeds: Bool = false) -> Round? {
let rounds = rounds()
return rounds.filter({ $0.hasStarted() && $0.hasEnded() == false }).sorted(by: \.index).reversed().first ?? rounds.first
let round = rounds.filter({ $0.hasStarted() && $0.hasEnded() == false }).sorted(by: \.index).reversed().first ?? rounds.first
if withSeeds {
if round?.seeds().isEmpty == false {
return round
} else {
return nil
}
} else {
return round
}
}
func allMatches() -> [Match] {

@ -10,18 +10,38 @@ import SwiftUI
struct CashierDetailView: View {
var tournaments : [Tournament]
init(tournaments: [Tournament]) {
self.tournaments = tournaments
}
init(tournament: Tournament) {
self.tournaments = [tournament]
}
var body: some View {
List {
ForEach(tournaments) { tournament in
_tournamentCashierDetailView(tournament)
Section {
LabeledContent {
Text(tournament.earnings().formatted(.currency(code: "EUR").precision(.fractionLength(0))))
} label: {
Text("Encaissement")
Text(tournament.paidCompletion().formatted(.percent.precision(.fractionLength(0)))).foregroundStyle(.secondary)
}
_tournamentCashierDetailView(tournament)
} header: {
if tournaments.count > 1 {
Text(tournament.tournamentTitle())
}
}
}
}
.headerProminence(.increased)
.navigationTitle("Résumé")
.navigationTitle("Bilan")
}
private func _tournamentCashierDetailView(_ tournament: Tournament) -> some View {
Section {
DisclosureGroup {
ForEach(PlayerRegistration.PaymentType.allCases) { type in
let count = tournament.selectedPlayers().filter({ $0.registrationType == type }).count
LabeledContent {
@ -34,8 +54,23 @@ struct CashierDetailView: View {
Text(count.formatted())
}
}
} header: {
Text(tournament.tournamentTitle())
} label: {
Text("Voir le détail")
}
//
// Section {
// ForEach(tournaments) { tournament in
// }
//// HStack {
//// Text("Total")
//// Spacer()
//// Text(event.earnings.formatted(.currency(code: "EUR").precision(.fractionLength(0))))
//// Text(event.paidCompletion.formatted(.percent.precision(.fractionLength(0)))).foregroundStyle(.secondary)
//// }
// } header: {
// Text("Encaissement")
// }
}
}

@ -0,0 +1,56 @@
//
// CashierSettingsView.swift
// PadelClub
//
// Created by Razmig Sarkissian on 17/04/2024.
//
import SwiftUI
struct CashierSettingsView: View {
@EnvironmentObject var dataStore: DataStore
var tournaments: [Tournament]
init(tournaments: [Tournament]) {
self.tournaments = tournaments
}
init(tournament: Tournament) {
self.tournaments = [tournament]
}
var body: some View {
List {
Section {
RowButtonView("Tout le monde a réglé", role: .destructive) {
let players = tournaments.flatMap({ $0.selectedPlayers() })
players.forEach { player in
if player.hasPaid() == false {
player.registrationType = .gift
}
}
try? dataStore.playerRegistrations.addOrUpdate(contentOfs: players)
}
} footer: {
Text("Passe tous les joueurs qui n'ont pas réglé en offert")
}
Section {
RowButtonView("Personne n'a réglé", role: .destructive) {
let players = tournaments.flatMap({ $0.selectedPlayers() })
players.forEach { player in
player.registrationType = nil
}
try? dataStore.playerRegistrations.addOrUpdate(contentOfs: players)
}
} footer: {
Text("Remet à zéro le type d'encaissement de tous les joueurs")
}
}
}
}
#Preview {
CashierSettingsView(tournaments: [])
}

@ -11,60 +11,55 @@ import Combine
struct CashierView: View {
@EnvironmentObject var dataStore: DataStore
var tournaments : [Tournament]
@Environment(\.dismiss) private var dismiss
@State var licenseCheck: Bool?
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 licenseToEdit = ""
@State private var editPlayer: PlayerRegistration?
@State private var isSearching: Bool = false
let licenseMode: Bool
init(event: Event, licenseCheck: Bool? = nil, sortOption: SortOption = .callDate) {
_licenseCheck = State(wrappedValue: licenseCheck)
_sortOption = State(wrappedValue: sortOption)
licenseMode = licenseCheck != nil
init(event: Event) {
self.tournaments = event.tournaments
self.teams = []
}
init(tournament: Tournament, licenseCheck: Bool? = nil, sortOption: SortOption = .callDate) {
_licenseCheck = State(wrappedValue: licenseCheck)
_sortOption = State(wrappedValue: sortOption)
licenseMode = licenseCheck != nil
init(tournament: Tournament, teams: [TeamRegistration]) {
self.tournaments = [tournament]
self.teams = teams
}
func somePlayerToEdit() -> Binding<Bool> {
Binding {
editPlayer != nil
} set: { _ in
}
private func _sharedData() -> String {
let players = teams
.flatMap({ $0.players() })
.map {
[$0.pasteData()]
.compacted()
.joined(separator: "\n")
}
.joined(separator: "\n\n")
return players
}
enum SortOption: Int, Identifiable, CaseIterable {
case round
case team
case teamRank
case alphabeticalLastName
case alphabeticalFirstName
case rank
case playerRank
case age
case callDate
var id: Int { self.rawValue }
func localizedLabel() -> String {
switch self {
case .round, .callDate:
case .callDate:
return "Convocation"
case .team:
return "Équipe"
case .teamRank:
return "Poids d'équipe"
case .alphabeticalLastName:
return "Nom"
case .alphabeticalFirstName:
return "Prénom"
case .rank:
case .playerRank:
return "Rang"
case .age:
return "Âge"
@ -103,190 +98,9 @@ struct CashierView: View {
}
}
var orderedPlayers: [PlayerRegistration] {
if sortOrder == .ascending {
return sortedPlayers
} else {
return sortedPlayers.reversed()
}
}
var orderedTeams: [TeamRegistration] {
if sortOrder == .ascending {
return sortedTeams
} else {
return sortedTeams.reversed()
}
}
var sortedTeams: [TeamRegistration] {
tournaments.flatMap({ $0.selectedSortedTeams() })
}
func playerHasValidLicense(_ player: PlayerRegistration) -> Bool {
return true
//todo
// if player.isImported() {
// if let licenseCheck, let licenseYearValidity = event.licenseYearValidity {
// return player.isValidLicenseNumber(year: licenseYearValidity) == licenseCheck
// } else {
// return true
// }
// } else {
// return true
// }
}
var searchedPlayers: [PlayerRegistration] {
tournaments.flatMap({ $0.unsortedPlayers() })
// if searchText.trimmed.isEmpty {
// return event.orderedPlayers.filter { playerHasValidLicense($0) }
// } else {
// let search = searchText.trimmed.folding(options: .diacriticInsensitive, locale: .current).lowercased()
// return event.orderedPlayers.filter { $0.canonicalName.contains(search) && playerHasValidLicense($0) }
// }
}
var filteredPlayers: [PlayerRegistration] {
if licenseMode == false {
return searchedPlayers.filter { filterOption.shouldDisplayPlayer($0) }
} else {
return searchedPlayers
}
}
var sortedPlayers: [PlayerRegistration] {
switch sortOption {
case .callDate, .team, .round:
return filteredPlayers.sorted(using: .keyPath(\.lastName), .keyPath(\.firstName))
case .alphabeticalFirstName:
return filteredPlayers.sorted(using: .keyPath(\.firstName), .keyPath(\.lastName))
case .alphabeticalLastName:
return filteredPlayers.sorted(using: .keyPath(\.lastName), .keyPath(\.firstName))
case .rank:
return filteredPlayers.sorted(using: .keyPath(\.weight), .keyPath(\.lastName), .keyPath(\.firstName))
case .age:
return filteredPlayers.sorted(using: .keyPath(\.weight), .keyPath(\.lastName), .keyPath(\.firstName))
}
}
func save() {
// do {
// event.objectWillChange.send()
// try viewContext.save()
// } catch {
// // Replace this implementation with code to handle the error appropriately.
// // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
// let nsError = error as NSError
// fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
// }
}
func bracketPlayers(in tournament: Tournament) -> [PlayerRegistration] {
tournament.selectedSortedTeams().filter { $0.groupStagePosition != nil }.flatMap { $0.unsortedPlayers() }.filter { orderedPlayers.contains($0) }
}
func playersForRound(in round: Int, tournament: Tournament) -> [PlayerRegistration] {
tournament.selectedSortedTeams().filter { RoundRule.roundIndex(fromMatchIndex: 0) == round && $0.groupStagePosition == nil }.flatMap { $0.unsortedPlayers() }.filter { orderedPlayers.contains($0) }
}
var displayOptionView: some View {
DisclosureGroup {
HStack {
Text("Voir")
Spacer()
Picker(selection: $licenseCheck) {
Text("Tous les joueurs").tag(nil as Bool?)
Text("Avec licence valide").tag(true as Bool?)
Text("Sans licence valide").tag(false as Bool?)
} label: {
}
}
HStack {
Text("Filtre")
Spacer()
Picker(selection: $filterOption) {
ForEach(FilterOption.allCases) { filterOption in
Text(filterOption.localizedLabel()).tag(filterOption)
}
} label: {
}
}
HStack {
Text("Tri")
Spacer()
Picker(selection: $sortOption) {
ForEach(SortOption.allCases) { sortOption in
Text(sortOption.localizedLabel()).tag(sortOption)
}
} label: {
}
}
HStack {
Text("Ordre")
Spacer()
Picker(selection: $sortOrder) {
Text("Croissant").tag(SortOrder.ascending)
Text("Décroissant").tag(SortOrder.descending)
} label: {
}
}
} label: {
Text("Options d'affichage")
}
}
@ViewBuilder
var sortedPlayersView: some View {
if orderedPlayers.isEmpty {
ContentUnavailableView.search(text: searchText)
} else {
Section {
ForEach(orderedPlayers) { player in
computedPlayerView(player)
}
} header: {
HStack {
Text(orderedPlayers.count.formatted() + " joueurs")
}
}
}
}
var body: some View {
List {
Section {
NavigationLink {
CashierDetailView(tournaments: tournaments)
} label: {
Text("Résumé")
}
}
Section {
ForEach(tournaments) { tournament in
HStack {
Text(tournament.tournamentTitle())
Spacer()
Text(tournament.earnings().formatted(.currency(code: "EUR").precision(.fractionLength(0))))
Text(tournament.paidCompletion().formatted(.percent.precision(.fractionLength(0)))).foregroundStyle(.secondary)
}
}
// HStack {
// Text("Total")
// Spacer()
// Text(event.earnings.formatted(.currency(code: "EUR").precision(.fractionLength(0))))
// Text(event.paidCompletion.formatted(.percent.precision(.fractionLength(0)))).foregroundStyle(.secondary)
// }
} header: {
Text("Encaissement")
}
if licenseMode == false {
if isSearching == false {
Section {
Picker(selection: $filterOption) {
ForEach(FilterOption.allCases) { filterOption in
@ -304,236 +118,194 @@ struct CashierView: View {
Text("Affichage par")
}
if sortOption != .round {
Picker(selection: $sortOrder) {
Text("Croissant").tag(SortOrder.ascending)
Text("Décroissant").tag(SortOrder.descending)
} label: {
Text(sortOption == .team ? "Tri par rang" : "Tri")
}
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 searchText.isEmpty == false {
sortedPlayersView
} else {
if sortOption == .team && licenseMode == false {
if orderedTeams.isEmpty {
ContentUnavailableView.search(text: searchText)
} else {
ForEach(orderedTeams) { team in
Section {
ForEach(team.players()) { player in
computedPlayerView(player)
}
}
}
}
} else if sortOption == .round && licenseMode == false {
if orderedPlayers.isEmpty {
ContentUnavailableView.search(text: searchText)
} else {
ForEach(tournaments) { tournament in
let bracketPlayers = bracketPlayers(in: tournament)
Section {
DisclosureGroup {
ForEach(bracketPlayers) { player in
computedPlayerView(player)
}
} label: {
HStack {
Text("Poules")
Spacer()
Text(bracketPlayers.count.formatted())
}
}
} header: {
Text(tournament.tournamentTitle())
}
}
// ForEach(1...event.rounds) { round in
// ForEach(tournaments) { tournament in
// if tournament.isRoundHidden(round) == false {
// let players = playersForRound(in: round, tournament: tournament)
//
// if players.isEmpty == false {
// Section {
// DisclosureGroup {
// ForEach(players) { player in
// computedPlayerView(player)
// }
// } label: {
// HStack {
// Text(RoundLabel.labels[tournament.rounds - round])
// Spacer()
// Text(players.count.formatted())
// }
// }
// } header: {
// Text(tournament.localizedTitle)
// }
// }
// }
// }
// }
}
} else if sortOption == .callDate && licenseMode == false {
_byCallDateView()
} else {
sortedPlayersView
}
if _isContentUnavailable() {
_contentUnavailableView()
}
switch sortOption {
case .teamRank:
_byTeamRankView()
case .alphabeticalLastName:
_byPlayerLastName()
case .alphabeticalFirstName:
_byPlayerFirstName()
case .playerRank:
_byPlayerRank()
case .age:
_byPlayerAge()
case .callDate:
_byCallDateView()
}
}
// .alert("Licence", isPresented: somePlayerToEdit(), actions: {
// TextField("Licence", text: $licenseToEdit)
// .keyboardType(.asciiCapable)
// .autocorrectionDisabled(true)
// .textContentType(.init(rawValue: ""))
// Button("OK") {
// editPlayer?.license = licenseToEdit
// licenseToEdit = ""
// editPlayer?.objectWillChange.send()
// editPlayer = nil
// save()
// }
// Button("Annuler", role : .cancel) {
// licenseToEdit = ""
// editPlayer = nil
// }
// })
.headerProminence(.increased)
.searchable(text: $searchText, isPresented: $isSearching, prompt: Text("Chercher un joueur"))
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Menu {
// Button {
// event.orderedPlayers.forEach { player in
// if let registration = player.orderedRegistrations.first(where: { $0.tournament?.tournamentID == player.tournament?.tournamentID }) {
// if registration.paymentType == .notPaid {
// registration.paymentType = .gift
// }
// } else {
// let registration = Registration(context: viewContext)
// registration.paymentType = .gift
// registration.tournament = player.tournament
// player.addToRegistrations(registration)
// }
// save()
// }
// } label: {
// Text("Tout le monde a réglé")
// }
//
// Button {
// event.orderedPlayers.forEach { player in
// if let registration = player.orderedRegistrations.first(where: { $0.tournament?.tournamentID == player.tournament?.tournamentID }) {
// registration.paymentType = .notPaid
// } else {
// let registration = Registration(context: viewContext)
// registration.paymentType = .notPaid
// registration.tournament = player.tournament
// player.addToRegistrations(registration)
// }
// save()
// }
// } label: {
// Text("Personne n'a réglé")
// }
//
} label: {
LabelOptions()
}
ShareLink(item: _sharedData())
}
}
.navigationTitle("Joueurs")
.navigationBarTitleDisplayMode(.large)
.searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always), prompt: Text("Chercher un joueur"))
}
@ViewBuilder
func computedPlayerView(_ player: PlayerRegistration) -> some View {
VStack(alignment: .leading) {
ImportedPlayerView(player: player)
HStack {
Menu {
if let number = player.phoneNumber?.replacingOccurrences(of: " ", with: ""), let url = URL(string: "tel:\(number)") {
Link(destination: url) {
Label("Appeler", systemImage: "phone")
}
}
if let number = player.phoneNumber?.replacingOccurrences(of: " ", with: ""), let url = URL(string: "sms:\(number)") {
Link(destination: url) {
Label("SMS", systemImage: "message")
}
}
Divider()
if let licenseCheck, let licenseYearValidity = player.tournament()?.licenseYearValidity(), licenseCheck == false {
Button {
player.validateLicenceId(licenseYearValidity)
save()
if filteredPlayers.isEmpty {
dismiss()
}
} label: {
Text("Valider la licence \(licenseYearValidity)")
}
EditablePlayerView(player: player, editingOptions: [.licenceId, .payment])
}
private func _shouldDisplayTeam(_ team: TeamRegistration) -> Bool {
team.players().allSatisfy({
_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())
}
if let license = player.licenceId?.strippedLicense {
Button {
let pasteboard = UIPasteboard.general
pasteboard.string = license
} label: {
Label("Copier la licence", systemImage: "doc.on.doc")
Spacer()
Text(player.weight.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(\.weight)).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())
}
Section {
Button {
licenseToEdit = player.licenceId ?? ""
editPlayer = player
} label: {
if player.licenceId == nil {
Text("Ajouter la licence")
} else {
Text("Modifier la licence")
}
}
PasteButton(payloadType: String.self) { strings in
guard let first = strings.first else { return }
player.licenceId = first
}
} header: {
Text("Modification de licence")
} footer: {
if tournaments.count > 1, let tournamentTitle = team.tournamentObject()?.tournamentTitle() {
Text(tournamentTitle)
}
} label: {
Text("Options")
}
Spacer()
PlayerPayView(player: player)
}
}
}
@ViewBuilder
private func _byCallDateView() -> some View {
ForEach(tournaments) { tournament in
let teams = tournament.selectedSortedTeams()
let players = teams.filter({ $0.callDate != nil }).sorted(using: .keyPath(\.callDate!)).flatMap({ $0.players() }) + teams.filter({ $0.callDate == nil }).flatMap({ $0.players() })
Section {
ForEach(players) { player in
computedPlayerView(player)
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)
}
}
}
}
} header: {
Text(tournament.tournamentTitle())
}
.headerProminence(.increased)
}
}
@ViewBuilder
private func _cashierPlayersView(_ players: [PlayerRegistration]) -> some View {
ForEach(players) { player in
if _shouldDisplayPlayer(player) {
computedPlayerView(player)
}
}
}
private func _isContentUnavailable() -> Bool {
switch sortOption {
case .teamRank, .callDate:
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())
}
}
}

@ -0,0 +1,18 @@
//
// PlayerListView.swift
// PadelClub
//
// Created by Razmig Sarkissian on 17/04/2024.
//
import SwiftUI
struct PlayerListView: View {
var body: some View {
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
}
}
#Preview {
PlayerListView()
}

@ -0,0 +1,100 @@
//
// EditablePlayerView.swift
// PadelClub
//
// Created by Razmig Sarkissian on 17/04/2024.
//
import SwiftUI
struct EditablePlayerView: View {
enum PlayerEditingOption {
case payment
case licenceId
}
@EnvironmentObject var dataStore: DataStore
var player: PlayerRegistration
var editingOptions: [PlayerEditingOption]
@State private var editedLicenceId = ""
@State private var shouldPresentLicenceIdEdition: Bool = false
var body: some View {
computedPlayerView(player)
.alert("Numéro de licence", isPresented: $shouldPresentLicenceIdEdition) {
TextField("Numéro de licence", text: $editedLicenceId)
.onSubmit {
player.licenceId = editedLicenceId
editedLicenceId = ""
try? dataStore.playerRegistrations.addOrUpdate(instance: player)
}
}
}
@ViewBuilder
func computedPlayerView(_ player: PlayerRegistration) -> some View {
VStack(alignment: .leading) {
ImportedPlayerView(player: player)
HStack {
Menu {
if let number = player.phoneNumber?.replacingOccurrences(of: " ", with: ""), let url = URL(string: "tel:\(number)") {
Link(destination: url) {
Label("Appeler", systemImage: "phone")
}
}
if let number = player.phoneNumber?.replacingOccurrences(of: " ", with: ""), let url = URL(string: "sms:\(number)") {
Link(destination: url) {
Label("SMS", systemImage: "message")
}
}
if editingOptions.contains(.licenceId) {
Divider()
if let licenseYearValidity = player.tournament()?.licenseYearValidity(), player.isValidLicenseNumber(year: licenseYearValidity) == false, player.licenceId != nil {
Button {
player.validateLicenceId(licenseYearValidity)
} label: {
Text("Valider la licence \(licenseYearValidity)")
}
}
}
if let license = player.licenceId?.strippedLicense {
Button {
let pasteboard = UIPasteboard.general
pasteboard.string = license
} label: {
Label("Copier la licence", systemImage: "doc.on.doc")
}
}
Section {
Button {
editedLicenceId = player.licenceId ?? ""
shouldPresentLicenceIdEdition = true
} label: {
if player.licenceId == nil {
Text("Ajouter la licence")
} else {
Text("Modifier la licence")
}
}
PasteButton(payloadType: String.self) { strings in
guard let first = strings.first else { return }
player.licenceId = first
}
} header: {
Text("Modification de licence")
}
} label: {
Text("Options")
}
if editingOptions.contains(.payment) {
Spacer()
PlayerPayView(player: player)
}
}
}
}
}

@ -29,6 +29,9 @@ struct ImportedPlayerView: View {
.foregroundStyle(.secondary)
.font(.caption)
}
} else if let computedAge = player.computedAge {
Text(computedAge.formatted() + " ans")
.foregroundStyle(.secondary)
}
}
.font(.title3)

@ -134,7 +134,7 @@ struct InscriptionInfoView: View {
Section {
DisclosureGroup {
ForEach(playersWithoutValidLicense) {
ImportedPlayerView(player: $0)
EditablePlayerView(player: $0, editingOptions: [.licenceId])
}
} label: {
LabeledContent {

@ -0,0 +1,100 @@
//
// TournamentCashierView.swift
// PadelClub
//
// Created by Razmig Sarkissian on 17/04/2024.
//
import SwiftUI
enum CashierDestination: Identifiable, Selectable {
case summary
case groupStage(GroupStage)
case bracket(Round)
case all
var id: String {
switch self {
case .summary, .all:
return String(describing: self)
case .groupStage(let groupStage):
return groupStage.id
case .bracket(let round):
return round.id
}
}
func selectionLabel() -> String {
switch self {
case .summary:
return "Bilan"
case .groupStage(let groupStage):
return groupStage.selectionLabel()
case .bracket(let round):
return round.selectionLabel()
case .all:
return "Tous"
}
}
func badgeValue() -> Int? {
nil
}
}
struct TournamentCashierView: View {
var tournament: Tournament
@State private var selectedDestination: CashierDestination?
func allDestinations() -> [CashierDestination] {
var allDestinations : [CashierDestination] = [.summary, .all]
let destinations : [CashierDestination] = tournament.groupStages().map { CashierDestination.groupStage($0) }
allDestinations.append(contentsOf: destinations)
tournament.rounds().forEach { round in
if round.seeds().isEmpty == false {
allDestinations.append(CashierDestination.bracket(round))
}
}
return allDestinations
}
init(tournament: Tournament) {
self.tournament = tournament
let gs = tournament.getActiveGroupStage()
if let gs {
_selectedDestination = State(wrappedValue: .groupStage(gs))
} else if let rs = tournament.getActiveRound(withSeeds: true) {
_selectedDestination = State(wrappedValue: .bracket(rs))
}
}
var body: some View {
VStack(spacing: 0) {
GenericDestinationPickerView(selectedDestination: $selectedDestination, destinations: allDestinations(), nilDestinationIsValid: true)
switch selectedDestination {
case .none:
CashierSettingsView(tournament: tournament)
.navigationTitle("Réglages")
case .some(let selectedCall):
switch selectedCall {
case .summary:
CashierDetailView(tournament: tournament)
case .groupStage(let groupStage):
CashierView(tournament: tournament, teams: groupStage.teams())
case .bracket(let round):
CashierView(tournament: tournament, teams: round.seeds())
case .all:
CashierView(tournament: tournament, teams: tournament.selectedSortedTeams())
}
}
}
.environment(tournament)
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.visible, for: .navigationBar)
.navigationTitle("Encaissement")
}
}
#Preview {
TournamentCashierView(tournament: Tournament.mock())
}

@ -81,7 +81,7 @@ struct TournamentView: View {
case .schedule:
TournamentScheduleView(tournament: tournament)
case .cashier:
CashierView(tournament: tournament)
TournamentCashierView(tournament: tournament)
case .call:
TournamentCallView(tournament: tournament)
}

Loading…
Cancel
Save