fix a lot of stuff post PBL

sync2
Raz 1 year ago
parent 8ca2193579
commit f691681e94
  1. 8
      PadelClub.xcodeproj/project.pbxproj
  2. 4
      PadelClub/Data/Coredata/ImportedPlayer+Extensions.swift
  3. 1
      PadelClub/Data/Federal/PlayerHolder.swift
  4. 10
      PadelClub/Data/GroupStage.swift
  5. 34
      PadelClub/Data/MatchScheduler.swift
  6. 10
      PadelClub/Data/PlayerRegistration.swift
  7. 1
      PadelClub/Data/TeamRegistration.swift
  8. 22
      PadelClub/Data/Tournament.swift
  9. 22
      PadelClub/Utils/PadelRule.swift
  10. 27
      PadelClub/ViewModel/FederalDataViewModel.swift
  11. 2
      PadelClub/Views/Calling/Components/MenuWarningView.swift
  12. 19
      PadelClub/Views/Cashier/CashierDetailView.swift
  13. 67
      PadelClub/Views/Cashier/CashierSettingsView.swift
  14. 105
      PadelClub/Views/Cashier/CashierView.swift
  15. 16
      PadelClub/Views/Components/FooterButtonView.swift
  16. 4
      PadelClub/Views/GroupStage/Components/GroupStageTeamView.swift
  17. 17
      PadelClub/Views/GroupStage/GroupStageView.swift
  18. 2
      PadelClub/Views/GroupStage/Shared/GroupStageTeamReplacementView.swift
  19. 2
      PadelClub/Views/Match/Components/MatchDateView.swift
  20. 16
      PadelClub/Views/Match/Components/PlayerBlockView.swift
  21. 43
      PadelClub/Views/Match/MatchDetailView.swift
  22. 8
      PadelClub/Views/Navigation/Agenda/CalendarView.swift
  23. 4
      PadelClub/Views/Navigation/Agenda/EventListView.swift
  24. 10
      PadelClub/Views/Navigation/Agenda/TournamentSubscriptionView.swift
  25. 4
      PadelClub/Views/Planning/PlanningSettingsView.swift
  26. 18
      PadelClub/Views/Player/Components/EditablePlayerView.swift
  27. 113
      PadelClub/Views/Shared/ImportedPlayerView.swift
  28. 21
      PadelClub/Views/Team/TeamRowView.swift
  29. 8
      PadelClub/Views/Tournament/Screen/TournamentCashierView.swift
  30. 17
      PadelClub/Views/Tournament/Shared/TournamentCellView.swift
  31. 2
      PadelClub/Views/Tournament/TournamentBuildView.swift

@ -3293,7 +3293,7 @@
CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 4; CURRENT_PROJECT_VERSION = 3;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
@ -3315,7 +3315,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.14; MARKETING_VERSION = 1.0.15;
PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@ -3335,7 +3335,7 @@
CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements; CODE_SIGN_ENTITLEMENTS = PadelClub/PadelClub.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 4; CURRENT_PROJECT_VERSION = 3;
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
DEVELOPMENT_TEAM = BQ3Y44M3Q6; DEVELOPMENT_TEAM = BQ3Y44M3Q6;
@ -3356,7 +3356,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.14; MARKETING_VERSION = 1.0.15;
PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";

@ -122,6 +122,10 @@ extension ImportedPlayer: PlayerHolder {
func getProgression() -> Int { func getProgression() -> Int {
return Int(progression) return Int(progression)
} }
func getComputedRank() -> Int? {
nil
}
} }
fileprivate extension Int { fileprivate extension Int {

@ -27,6 +27,7 @@ protocol PlayerHolder {
func isNotFromCurrentDate() -> Bool func isNotFromCurrentDate() -> Bool
func getBirthYear() -> Int? func getBirthYear() -> Int?
func getProgression() -> Int func getProgression() -> Int
func getComputedRank() -> Int?
} }
extension PlayerHolder { extension PlayerHolder {

@ -266,11 +266,11 @@ final class GroupStage: ModelObject, Storable {
case 4: case 4:
return [2, 3, 1, 4, 5, 0] return [2, 3, 1, 4, 5, 0]
case 5: case 5:
return [5, 8, 0, 7, 3, 4, 2, 6, 1, 9] // return [5, 8, 0, 7, 3, 4, 2, 6, 1, 9]
// return [3, 5, 8, 2, 6, 7, 1, 9, 4, 0] return [3, 5, 8, 2, 6, 1, 9, 4, 7, 0]
case 6: case 6:
return [1, 7, 13, 11, 3, 6, 10, 2, 8, 12, 5, 4, 9, 14, 0] //return [1, 7, 13, 11, 3, 6, 10, 2, 8, 12, 5, 4, 9, 14, 0]
//return [4, 7, 9, 3, 6, 11, 2, 8, 10, 1, 13, 5, 12, 14, 0] return [4, 7, 9, 3, 6, 11, 2, 8, 10, 1, 13, 5, 12, 14, 0]
default: default:
return [] return []
} }
@ -283,7 +283,7 @@ final class GroupStage: ModelObject, Storable {
func localizedMatchUpLabel(for matchIndex: Int) -> String { func localizedMatchUpLabel(for matchIndex: Int) -> String {
let matchUp = _matchUp(for: matchIndex) let matchUp = _matchUp(for: matchIndex)
if let index = matchUp.first, let index2 = matchUp.last { if let index = matchUp.first, let index2 = matchUp.last {
return "#\(index + 1) contre #\(index2 + 1)" return "#\(index + 1) vs #\(index2 + 1)"
} else { } else {
return "--" return "--"
} }

@ -199,31 +199,29 @@ final class MatchScheduler : ModelObject, Storable {
while slots.count < flattenedMatches.count { while slots.count < flattenedMatches.count {
teamsPerRotation[rotationIndex] = [] teamsPerRotation[rotationIndex] = []
freeCourtPerRotation[rotationIndex] = [] freeCourtPerRotation[rotationIndex] = []
let previousRotationBracketIndexes = slots.filter { $0.rotationIndex == rotationIndex - 1 }.map { ($0.groupIndex, 1) }
let counts = Dictionary(previousRotationBracketIndexes, uniquingKeysWith: +)
var rotationMatches = Array(availableMatchs.filter({ match in
teamsPerRotation[rotationIndex]!.allSatisfy({ match.containsTeamId($0) == false }) == true
}).prefix(numberOfCourtsAvailablePerRotation))
if rotationIndex > 0 {
rotationMatches = rotationMatches.sorted(by: {
if counts[$0.groupStageObject!.index] ?? 0 == counts[$1.groupStageObject!.index] ?? 0 {
return $0.groupStageObject!.index < $1.groupStageObject!.index
} else {
return counts[$0.groupStageObject!.index] ?? 0 < counts[$1.groupStageObject!.index] ?? 0
}
})
}
(0..<numberOfCourtsAvailablePerRotation).forEach { courtIndex in (0..<numberOfCourtsAvailablePerRotation).forEach { courtIndex in
//print(mt.map { ($0.bracket!.index.intValue, counts[$0.bracket!.index.intValue]) }) //print(mt.map { ($0.bracket!.index.intValue, counts[$0.bracket!.index.intValue]) })
let previousRotationBracketIndexes = slots.map { ($0.groupIndex, 1) }
let counts = Dictionary(previousRotationBracketIndexes, uniquingKeysWith: +)
var rotationMatches = Array(availableMatchs)
if rotationIndex > 0 {
rotationMatches = rotationMatches.sorted(by: {
if counts[$0.groupStageObject!.index] ?? 0 == counts[$1.groupStageObject!.index] ?? 0 {
return $0.groupStageObject!.index < $1.groupStageObject!.index
} else {
return counts[$0.groupStageObject!.index] ?? 0 < counts[$1.groupStageObject!.index] ?? 0
}
})
}
if let first = rotationMatches.first(where: { match in if let first = rotationMatches.first(where: { match in
let estimatedDuration = match.matchFormat.getEstimatedDuration(additionalEstimationDuration) let estimatedDuration = match.matchFormat.getEstimatedDuration(additionalEstimationDuration)
let timeIntervalToAdd = (Double(rotationIndex)) * Double(estimatedDuration) * 60 let timeIntervalToAdd = (Double(rotationIndex)) * Double(estimatedDuration) * 60
let rotationStartDate: Date = startingDate.addingTimeInterval(timeIntervalToAdd) let rotationStartDate: Date = startingDate.addingTimeInterval(timeIntervalToAdd)
let courtsUnavailable = courtsUnavailable(startDate: rotationStartDate, duration: match.matchFormat.getEstimatedDuration(additionalEstimationDuration), courtsUnavailability: courtsUnavailability) let courtsUnavailable = courtsUnavailable(startDate: rotationStartDate, duration: match.matchFormat.getEstimatedDuration(additionalEstimationDuration), courtsUnavailability: courtsUnavailability)
if courtIndex >= numberOfCourtsAvailablePerRotation - courtsUnavailable.count { if courtsUnavailable.contains(courtIndex) {
return false return false
} else { } else {
return teamsPerRotation[rotationIndex]!.allSatisfy({ match.containsTeamId($0) == false }) == true return teamsPerRotation[rotationIndex]!.allSatisfy({ match.containsTeamId($0) == false }) == true
@ -486,7 +484,7 @@ final class MatchScheduler : ModelObject, Storable {
let roundObject = match.roundObject! let roundObject = match.roundObject!
let courtsUnavailable = courtsUnavailable(startDate: rotationStartDate, duration: match.matchFormat.getEstimatedDuration(additionalEstimationDuration), courtsUnavailability: courtsUnavailability) let courtsUnavailable = courtsUnavailable(startDate: rotationStartDate, duration: match.matchFormat.getEstimatedDuration(additionalEstimationDuration), courtsUnavailability: courtsUnavailability)
print("courtsUnavailable \(courtsUnavailable)") print("courtsUnavailable \(courtsUnavailable)")
if courtPosition >= availableCourts - courtsUnavailable.count { if courtsUnavailable.contains(courtPosition) {
return false return false
} }

@ -220,11 +220,7 @@ final class PlayerRegistration: ModelObject, Storable {
return "non classé" + (isMalePlayer() ? "" : "e") return "non classé" + (isMalePlayer() ? "" : "e")
} }
} }
func getRank() -> Int {
computedRank
}
@MainActor @MainActor
func updateRank(from sources: [CSVParser], lastRank: Int) async throws { func updateRank(from sources: [CSVParser], lastRank: Int) async throws {
if let dataFound = try await history(from: sources) { if let dataFound = try await history(from: sources) {
@ -586,4 +582,8 @@ extension PlayerRegistration: PlayerHolder {
func getProgression() -> Int { func getProgression() -> Int {
0 0
} }
func getComputedRank() -> Int? {
computedRank
}
} }

@ -208,6 +208,7 @@ final class TeamRegistration: ModelObject, Storable {
} }
func teamLabel(_ displayStyle: DisplayStyle = .wide, twoLines: Bool = false) -> String { func teamLabel(_ displayStyle: DisplayStyle = .wide, twoLines: Bool = false) -> String {
if let name { return name }
return players().map { $0.playerLabel(displayStyle) }.joined(separator: twoLines ? "\n" : " & ") return players().map { $0.playerLabel(displayStyle) }.joined(separator: twoLines ? "\n" : " & ")
} }

@ -1522,13 +1522,27 @@ defer {
return Double(selectedPlayers.filter { $0.hasPaid() }.count) / Double(selectedPlayers.count) return Double(selectedPlayers.filter { $0.hasPaid() }.count) / Double(selectedPlayers.count)
} }
func presenceStatus() -> Double {
let selectedPlayers = selectedPlayers()
if selectedPlayers.isEmpty { return 0 }
return Double(selectedPlayers.filter { $0.hasArrived }.count) / Double(selectedPlayers.count)
}
typealias TournamentStatus = (label:String, completion: String) typealias TournamentStatus = (label:String, completion: String)
func cashierStatus() async -> TournamentStatus { func cashierStatus() async -> TournamentStatus {
let selectedPlayers = selectedPlayers() let selectedPlayers = selectedPlayers()
let paid = selectedPlayers.filter({ $0.hasPaid() }) var filteredPlayers = [PlayerRegistration]()
var wording = ""
if isFree() {
wording = "présent"
filteredPlayers = selectedPlayers.filter({ $0.hasArrived })
} else {
wording = "encaissé"
filteredPlayers = selectedPlayers.filter({ $0.hasPaid() })
}
// let label = paid.count.formatted() + " / " + selectedPlayers.count.formatted() + " joueurs encaissés" // let label = paid.count.formatted() + " / " + selectedPlayers.count.formatted() + " joueurs encaissés"
let label = "\(paid.count.formatted()) / \(selectedPlayers.count.formatted()) joueurs encaissés" let label = "\(filteredPlayers.count.formatted()) / \(selectedPlayers.count.formatted()) joueurs \(wording)\(filteredPlayers.count.pluralSuffix)"
let completion = (Double(paid.count) / Double(selectedPlayers.count)) let completion = (Double(filteredPlayers.count) / Double(selectedPlayers.count))
let completionLabel = completion.isNaN ? "" : completion.formatted(.percent.precision(.fractionLength(0))) let completionLabel = completion.isNaN ? "" : completion.formatted(.percent.precision(.fractionLength(0)))
return TournamentStatus(label: label, completion: completionLabel) return TournamentStatus(label: label, completion: completionLabel)
} }
@ -2178,7 +2192,7 @@ extension Tournament: FederalTournamentHolder {
} }
extension Tournament: TournamentBuildHolder { extension Tournament: TournamentBuildHolder {
func buildHolderTitle() -> String { func buildHolderTitle(_ displayStyle: DisplayStyle) -> String {
tournamentTitle(.short) tournamentTitle(.short)
} }

@ -30,7 +30,7 @@ protocol TournamentBuildHolder: Identifiable {
var category: TournamentCategory { get } var category: TournamentCategory { get }
var level: TournamentLevel { get } var level: TournamentLevel { get }
var age: FederalTournamentAge { get } var age: FederalTournamentAge { get }
func buildHolderTitle() -> String func buildHolderTitle(_ displayStyle: DisplayStyle) -> String
} }
struct TournamentBuild: TournamentBuildHolder, Hashable, Codable, Identifiable { struct TournamentBuild: TournamentBuildHolder, Hashable, Codable, Identifiable {
@ -43,29 +43,29 @@ struct TournamentBuild: TournamentBuildHolder, Hashable, Codable, Identifiable {
// var japFirstName: String? = nil // var japFirstName: String? = nil
// var japLastName: String? = nil // var japLastName: String? = nil
func buildHolderTitle() -> String { func buildHolderTitle(_ displayStyle: DisplayStyle) -> String {
computedLabel computedLabel(displayStyle)
} }
var identifier: String { var identifier: String {
level.localizedLevelLabel()+":"+category.localizedLabel()+":"+age.localizedLabel() level.localizedLevelLabel()+":"+category.localizedLabel()+":"+age.localizedLabel()
} }
var computedLabel: String { func computedLabel(_ displayStyle: DisplayStyle = .wide) -> String {
if age == .senior { return localizedLabel() } if age == .senior { return localizedLabel(displayStyle) }
return localizedLabel() + " " + localizedAge return localizedLabel(displayStyle) + " " + localizedAge(displayStyle)
} }
func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String { func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String {
level.localizedLevelLabel() + category.localizedLabel(.short) level.localizedLevelLabel(displayStyle) + " " + category.localizedLabel(displayStyle)
} }
var localizedTitle: String { func localizedTitle(_ displayStyle: DisplayStyle = .wide) -> String {
level.localizedLevelLabel() + " " + category.localizedLabel() level.localizedLevelLabel(displayStyle) + " " + category.localizedLabel(displayStyle)
} }
var localizedAge: String { func localizedAge(_ displayStyle: DisplayStyle = .wide) -> String {
age.tournamentDescriptionLabel age.localizedLabel(displayStyle)
} }
} }

@ -97,6 +97,33 @@ class FederalDataViewModel {
}) })
} }
func countForTournamentBuilds(from tournaments: [any FederalTournamentHolder]) -> Int {
tournaments.filter({ tournament in
(selectedClubs.isEmpty || selectedClubs.contains(tournament.codeClub!))
&&
(dayPeriod == .all || (dayPeriod != .all && dayPeriod == tournament.dayPeriod))
&&
(dayDuration == nil || (dayDuration != nil && dayDuration == tournament.dayDuration))
})
.flatMap { $0.tournaments }
.filter {
(levels.isEmpty || levels.contains($0.level))
&&
(categories.isEmpty || categories.contains($0.category))
&&
(ageCategories.isEmpty || ageCategories.contains($0.age))
}
.count
}
func buildIsValid(_ build: any TournamentBuildHolder) -> Bool {
(levels.isEmpty || levels.contains(build.level))
&&
(categories.isEmpty || categories.contains(build.category))
&&
(ageCategories.isEmpty || ageCategories.contains(build.age))
}
func isTournamentValidForFilters(_ tournament: Tournament) -> Bool { func isTournamentValidForFilters(_ tournament: Tournament) -> Bool {
if tournament.isDeleted { return false } if tournament.isDeleted { return false }
let firstPart = (levels.isEmpty || levels.contains(tournament.level)) let firstPart = (levels.isEmpty || levels.contains(tournament.level))

@ -124,7 +124,7 @@ struct MenuWarningView: View {
@ViewBuilder @ViewBuilder
func _teamActionView(_ team: TeamRegistration) -> some View { func _teamActionView(_ team: TeamRegistration) -> some View {
Menu("Toute l'équipe") { Menu(team.name ?? "Toute l'équipe") {
let players = team.players() let players = team.players()
_actionView(players: players) _actionView(players: players)
} }

@ -89,7 +89,8 @@ struct CashierDetailView: View {
let showTournamentTitle: Bool let showTournamentTitle: Bool
@State private var earnings: Double? = nil @State private var earnings: Double? = nil
@State private var paidCompletion: Double? = nil @State private var paidCompletion: Double? = nil
@State private var presence: Double? = nil
var body: some View { var body: some View {
Section { Section {
LabeledContent { LabeledContent {
@ -99,9 +100,15 @@ struct CashierDetailView: View {
ProgressView() ProgressView()
} }
} label: { } label: {
Text("Encaissement") Text(tournament.isFree() ? "Présence" : "Encaissement")
if let paidCompletion { if tournament.isFree() {
Text(paidCompletion.formatted(.percent.precision(.fractionLength(0)))).foregroundStyle(.secondary) if let presence {
Text(presence.formatted(.percent.precision(.fractionLength(0)))).foregroundStyle(.secondary)
}
} else {
if let paidCompletion {
Text(paidCompletion.formatted(.percent.precision(.fractionLength(0)))).foregroundStyle(.secondary)
}
} }
} }
CashierDetailDisclosureView(tournament: tournament) CashierDetailDisclosureView(tournament: tournament)
@ -119,6 +126,10 @@ struct CashierDetailView: View {
if paidCompletion == nil { if paidCompletion == nil {
paidCompletion = tournament.paidCompletion() paidCompletion = tournament.paidCompletion()
} }
if presence == nil {
presence = tournament.presenceStatus()
}
} }
} }
} }

@ -25,14 +25,12 @@ struct CashierSettingsView: View {
var body: some View { var body: some View {
List { List {
Section { Section {
RowButtonView("Tout le monde a réglé", role: .destructive) { RowButtonView("Tout le monde est arrivé", role: .destructive) {
for tournament in self.tournaments { for tournament in self.tournaments {
let players = tournament.selectedPlayers() // tournaments.flatMap({ $0.selectedPlayers() }) let players = tournament.selectedPlayers() // tournaments.flatMap({ $0.selectedPlayers() })
players.forEach { player in players.forEach { player in
if player.hasPaid() == false { player.hasArrived = true
player.paymentType = .gift
}
} }
do { do {
try tournament.tournamentStore.playerRegistrations.addOrUpdate(contentOfs: players) try tournament.tournamentStore.playerRegistrations.addOrUpdate(contentOfs: players)
@ -43,29 +41,72 @@ struct CashierSettingsView: View {
} }
} footer: { } footer: {
Text("Passe tous les joueurs qui n'ont pas réglé en offert") Text("Indique tous les joueurs sont là")
} }
Section { Section {
RowButtonView("Personne n'a réglé", role: .destructive) { RowButtonView("Personne n'est là", role: .destructive) {
for tournament in self.tournaments { for tournament in self.tournaments {
let store = tournament.tournamentStore let players = tournament.selectedPlayers() // tournaments.flatMap({ $0.selectedPlayers() })
let players = tournament.selectedPlayers()
players.forEach { player in players.forEach { player in
player.paymentType = nil player.hasArrived = false
} }
do { do {
try store.playerRegistrations.addOrUpdate(contentOfs: players) try tournament.tournamentStore.playerRegistrations.addOrUpdate(contentOfs: players)
} catch { } catch {
Logger.error(error) Logger.error(error)
} }
} }
} }
} footer: { } footer: {
Text("Remet à zéro le type d'encaissement de tous les joueurs") Text("Indique qu'aucun joueur n'est arrivé")
} }
if tournaments.count > 1 || tournaments.first?.isFree() == false {
Section {
RowButtonView("Tout le monde a réglé", role: .destructive) {
for tournament in self.tournaments {
let players = tournament.selectedPlayers() // tournaments.flatMap({ $0.selectedPlayers() })
players.forEach { player in
if player.hasPaid() == false {
player.paymentType = .gift
}
}
do {
try tournament.tournamentStore.playerRegistrations.addOrUpdate(contentOfs: players)
} catch {
Logger.error(error)
}
}
}
} footer: {
Text("Passe tous les joueurs qui n'ont pas réglé en offert")
}
Section {
RowButtonView("Personne n'a réglé", role: .destructive) {
for tournament in self.tournaments {
let store = tournament.tournamentStore
let players = tournament.selectedPlayers()
players.forEach { player in
player.paymentType = nil
}
do {
try store.playerRegistrations.addOrUpdate(contentOfs: players)
} catch {
Logger.error(error)
}
}
}
} footer: {
Text("Remet à zéro le type d'encaissement de tous les joueurs")
}
}
} }
} }
} }

@ -57,6 +57,7 @@ class CashierViewModel: ObservableObject {
let id: UUID = UUID() let id: UUID = UUID()
@Published var sortOption: SortOption = .callDate @Published var sortOption: SortOption = .callDate
@Published var filterOption: FilterOption = .all @Published var filterOption: FilterOption = .all
@Published var presenceFilterOption: PresenceFilterOption = .all
@Published var sortOrder: SortOrder = .ascending @Published var sortOrder: SortOrder = .ascending
@Published var searchText: String = "" @Published var searchText: String = ""
@Published var isSearching: Bool = false @Published var isSearching: Bool = false
@ -69,9 +70,14 @@ class CashierViewModel: ObservableObject {
func _shouldDisplayPlayer(_ player: PlayerRegistration) -> Bool { func _shouldDisplayPlayer(_ player: PlayerRegistration) -> Bool {
if searchText.isEmpty == false { if searchText.isEmpty == false {
sortOption.shouldDisplayPlayer(player) && filterOption.shouldDisplayPlayer(player) && player.contains(searchText) sortOption.shouldDisplayPlayer(player)
&& filterOption.shouldDisplayPlayer(player)
&& presenceFilterOption.shouldDisplayPlayer(player)
&& player.contains(searchText)
} else { } else {
sortOption.shouldDisplayPlayer(player) && filterOption.shouldDisplayPlayer(player) sortOption.shouldDisplayPlayer(player)
&& filterOption.shouldDisplayPlayer(player)
&& presenceFilterOption.shouldDisplayPlayer(player)
} }
} }
@ -183,6 +189,37 @@ class CashierViewModel: ObservableObject {
} }
} }
enum PresenceFilterOption: Int, Identifiable, CaseIterable {
case all
case hasArrived
case hasNotArrived
var id: Int { self.rawValue }
func localizedLabel() -> String {
switch self {
case .all:
return "Tous"
case .hasArrived:
return "Présent"
case .hasNotArrived:
return "Absent"
}
}
func shouldDisplayPlayer(_ player: PlayerRegistration) -> Bool {
switch self {
case .all:
return true
case .hasArrived:
return player.hasArrived
case .hasNotArrived:
return player.hasArrived == false
}
}
}
} }
struct CashierView: View { struct CashierView: View {
@ -201,16 +238,42 @@ struct CashierView: View {
_players = .init(wrappedValue: teams.flatMap({ $0.unsortedPlayers() })) _players = .init(wrappedValue: teams.flatMap({ $0.unsortedPlayers() }))
} }
private func _isFree() -> Bool {
if tournaments.count == 1 {
return tournaments.first?.isFree() == true
} else {
return false
}
}
private func _editingOptions() -> [EditablePlayerView.PlayerEditingOption] {
if _isFree() {
return [.licenceId, .name, .presence]
} else {
return [.licenceId, .name, .payment]
}
}
var body: some View { var body: some View {
List { List {
if cashierViewModel.isSearching == false { if cashierViewModel.isSearching == false {
Section { Section {
Picker(selection: $cashierViewModel.filterOption) { Picker(selection: $cashierViewModel.presenceFilterOption) {
ForEach(CashierViewModel.FilterOption.allCases) { filterOption in ForEach(CashierViewModel.PresenceFilterOption.allCases) { filterOption in
Text(filterOption.localizedLabel()).tag(filterOption) Text(filterOption.localizedLabel()).tag(filterOption)
} }
} label: { } label: {
Text("Statut du règlement") Text("Présence")
}
if _isFree() == false {
Picker(selection: $cashierViewModel.filterOption) {
ForEach(CashierViewModel.FilterOption.allCases) { filterOption in
Text(filterOption.localizedLabel()).tag(filterOption)
}
} label: {
Text("Statut du règlement")
}
} }
Picker(selection: $cashierViewModel.sortOption) { Picker(selection: $cashierViewModel.sortOption) {
@ -239,12 +302,12 @@ struct CashierView: View {
switch cashierViewModel.sortOption { switch cashierViewModel.sortOption {
case .teamRank: case .teamRank:
TeamRankView(teams: teams, displayTournamentTitle: tournaments.count > 1) TeamRankView(teams: teams, displayTournamentTitle: tournaments.count > 1, editingOptions: _editingOptions())
case .alphabeticalLastName, .alphabeticalFirstName, .playerRank, .age: case .alphabeticalLastName, .alphabeticalFirstName, .playerRank, .age:
PlayerCashierView(players: filteredPlayers, displayTournamentTitle: tournaments.count > 1) PlayerCashierView(players: filteredPlayers, displayTournamentTitle: tournaments.count > 1, editingOptions: _editingOptions())
case .callDate: case .callDate:
let _teams = teams.filter({ $0.callDate != nil }) let _teams = teams.filter({ $0.callDate != nil })
TeamCallDateView(teams: _teams, displayTournamentTitle: tournaments.count > 1) TeamCallDateView(teams: _teams, displayTournamentTitle: tournaments.count > 1, editingOptions: _editingOptions())
} }
} }
.onAppear { .onAppear {
@ -279,11 +342,12 @@ struct CashierView: View {
@EnvironmentObject var cashierViewModel: CashierViewModel @EnvironmentObject var cashierViewModel: CashierViewModel
let players: [PlayerRegistration] let players: [PlayerRegistration]
let displayTournamentTitle: Bool let displayTournamentTitle: Bool
let editingOptions: [EditablePlayerView.PlayerEditingOption]
var body: some View { 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: editingOptions)
} header: { } header: {
if displayTournamentTitle, let tournamentTitle = player.tournament()?.tournamentTitle() { if displayTournamentTitle, let tournamentTitle = player.tournament()?.tournamentTitle() {
Text(tournamentTitle) Text(tournamentTitle)
@ -301,6 +365,7 @@ struct CashierView: View {
@EnvironmentObject var cashierViewModel: CashierViewModel @EnvironmentObject var cashierViewModel: CashierViewModel
let teams: [TeamRegistration] let teams: [TeamRegistration]
let displayTournamentTitle: Bool let displayTournamentTitle: Bool
let editingOptions: [EditablePlayerView.PlayerEditingOption]
var body: some View { var body: some View {
ForEach(teams) { team in ForEach(teams) { team in
@ -308,11 +373,17 @@ struct CashierView: View {
if players.isEmpty == false { if players.isEmpty == false {
Section { Section {
ForEach(players) { player in ForEach(players) { player in
EditablePlayerView(player: player, editingOptions: [.licenceId, .name, .payment]) EditablePlayerView(player: player, editingOptions: editingOptions)
} }
} header: { } header: {
if displayTournamentTitle, let tournamentTitle = team.tournamentObject()?.tournamentTitle() { HStack {
Text(tournamentTitle) if let name = team.name {
Text(name)
}
if displayTournamentTitle, let tournamentTitle = team.tournamentObject()?.tournamentTitle() {
Spacer()
Text(tournamentTitle)
}
} }
} footer: { } footer: {
if let callDate = team.callDate { if let callDate = team.callDate {
@ -329,6 +400,7 @@ struct CashierView: View {
@EnvironmentObject var cashierViewModel: CashierViewModel @EnvironmentObject var cashierViewModel: CashierViewModel
let teams: [TeamRegistration] let teams: [TeamRegistration]
let displayTournamentTitle: Bool let displayTournamentTitle: Bool
let editingOptions: [EditablePlayerView.PlayerEditingOption]
var body: some View { var body: some View {
let groupedTeams = Dictionary(grouping: teams) { team in let groupedTeams = Dictionary(grouping: teams) { team in
@ -343,10 +415,15 @@ struct CashierView: View {
if players.isEmpty == false { if players.isEmpty == false {
Section { Section {
ForEach(players) { player in ForEach(players) { player in
EditablePlayerView(player: player, editingOptions: [.licenceId, .name, .payment]) EditablePlayerView(player: player, editingOptions: editingOptions)
} }
} header: { } header: {
if let name = team.name {
Text(name)
}
if displayTournamentTitle, let tournamentTitle = team.tournamentObject()?.tournamentTitle() { if displayTournamentTitle, let tournamentTitle = team.tournamentObject()?.tournamentTitle() {
Spacer()
Text(tournamentTitle) Text(tournamentTitle)
} }
} footer: { } footer: {

@ -11,13 +11,15 @@ fileprivate let defaultConfirmationMessage = "Êtes-vous sûr de vouloir faire c
struct FooterButtonView: View { struct FooterButtonView: View {
var role: ButtonRole? = nil var role: ButtonRole? = nil
var systemImage: String? = nil
let title: String let title: String
let confirmationMessage: String let confirmationMessage: String
let action: () -> () let action: () -> ()
@State private var askConfirmation: Bool = false @State private var askConfirmation: Bool = false
init(_ title: String, role: ButtonRole? = nil, confirmationMessage: String? = nil, action: @escaping () -> Void) { init(_ title: String, role: ButtonRole? = nil, systemImage: String? = nil, confirmationMessage: String? = nil, action: @escaping () -> Void) {
self.title = title self.title = title
self.systemImage = systemImage
self.action = action self.action = action
self.role = role self.role = role
self.confirmationMessage = confirmationMessage ?? defaultConfirmationMessage self.confirmationMessage = confirmationMessage ?? defaultConfirmationMessage
@ -31,8 +33,16 @@ struct FooterButtonView: View {
action() action()
} }
} label: { } label: {
Text(title) if let systemImage {
.underline() HStack {
Text(title)
.underline()
Image(systemName: systemImage).font(.caption)
}
} else {
Text(title)
.underline()
}
} }
.buttonStyle(.borderless) .buttonStyle(.borderless)
.confirmationDialog("Confirmation", .confirmationDialog("Confirmation",

@ -40,9 +40,11 @@ struct GroupStageTeamView: View {
var body: some View { var body: some View {
List { List {
Section { Section {
if let name = team.name {
Text(name).foregroundStyle(.secondary)
}
ForEach(team.players()) { player in ForEach(team.players()) { player in
EditablePlayerView(player: player, editingOptions: [.licenceId, .name, .payment]) EditablePlayerView(player: player, editingOptions: [.licenceId, .name, .payment])
.environmentObject(tournament.tournamentStore)
} }
} }

@ -125,15 +125,16 @@ struct GroupStageView: View {
HStack { HStack {
VStack(alignment: .leading) { VStack(alignment: .leading) {
if let teamName = team.name { if let teamName = team.name {
Text(teamName).foregroundStyle(.secondary) Text(teamName).font(.title3)
} } else {
ForEach(team.players()) { player in ForEach(team.players()) { player in
Text(player.playerLabel()).lineLimit(1) Text(player.playerLabel()).lineLimit(1)
.overlay { .overlay {
if player.hasArrived && team.isHere() == false { if player.hasArrived && team.isHere() == false {
Color.green.opacity(0.6) Color.green.opacity(0.6)
}
} }
} }
} }
} }
Spacer() Spacer()

@ -54,7 +54,7 @@ struct GroupStageTeamReplacementView: View {
Section { Section {
Picker(selection: $selectedPlayer) { Picker(selection: $selectedPlayer) {
HStack { HStack {
Text("Toute l'équipe") Text(team.name ?? "Toute l'équipe")
Spacer() Spacer()
Text(team.weight.formatted()).bold() Text(team.weight.formatted()).bold()
} }

@ -93,7 +93,7 @@ struct MatchDateView: View {
.foregroundStyle(Color.master) .foregroundStyle(Color.master)
.underline() .underline()
} else { } else {
Text("en attente") Text("démarrer")
.foregroundStyle(Color.master) .foregroundStyle(Color.master)
.underline() .underline()
} }

@ -57,19 +57,21 @@ struct PlayerBlockView: View {
} }
if let name = team?.name { if let name = team?.name {
Text(name).foregroundStyle(.secondary) Text(name).font(.title3)
} } else {
ForEach(names, id: \.self) { name in ForEach(names, id: \.self) { name in
Text(name).lineLimit(1) Text(name).lineLimit(1)
}
} }
} else { } else {
ZStack(alignment: .leading) { ZStack(alignment: .leading) {
VStack { VStack {
if let name = team?.name { if let name = team?.name {
Text(name).foregroundStyle(.secondary) Text(name).font(.title3)
} else {
Text("longLabelPlayerOne").lineLimit(1)
Text("longLabelPlayerTwo").lineLimit(1)
} }
Text("longLabelPlayerOne").lineLimit(1)
Text("longLabelPlayerTwo").lineLimit(1)
} }
.opacity(0) .opacity(0)
Text(_defaultLabel()).foregroundStyle(.secondary).lineLimit(1) Text(_defaultLabel()).foregroundStyle(.secondary).lineLimit(1)

@ -107,30 +107,31 @@ struct MatchDetailView: View {
} }
} }
let players = self.match.teams().flatMap { $0.players() } if self.match.currentTournament()?.isFree() == false {
let unpaid = players.filter({ $0.hasPaid() == false }) let players = self.match.teams().flatMap { $0.players() }
let unpaid = players.filter({ $0.hasPaid() == false })
if unpaid.isEmpty == false {
Section { if unpaid.isEmpty == false {
DisclosureGroup { Section {
ForEach(unpaid) { player in DisclosureGroup {
ForEach(unpaid) { player in
LabeledContent {
PlayerPayView(player: player)
.environmentObject(tournamentStore)
} label: {
Text(player.playerLabel())
}
}
} label: {
LabeledContent { LabeledContent {
PlayerPayView(player: player) Text(unpaid.count.formatted() + " / " + players.count.formatted())
.environmentObject(tournamentStore)
} label: { } label: {
Text(player.playerLabel()) Text("Encaissement manquant")
} }
} }
} label: {
LabeledContent {
Text(unpaid.count.formatted() + " / " + players.count.formatted())
} label: {
Text("Encaissement manquant")
}
} }
} }
} }
menuView menuView
} }
.sheet(isPresented: $showDetails) { .sheet(isPresented: $showDetails) {
@ -423,9 +424,9 @@ struct MatchDetailView: View {
let rotationDuration = match.getDuration() let rotationDuration = match.getDuration()
Picker(selection: $startDateSetup) { Picker(selection: $startDateSetup) {
if match.isReady() { if match.isReady() {
Text("Tout de suite").tag(MatchDateSetup.now)
Text("Dans 5 minutes").tag(MatchDateSetup.inMinutes(5)) Text("Dans 5 minutes").tag(MatchDateSetup.inMinutes(5))
Text("Dans 15 minutes").tag(MatchDateSetup.inMinutes(15)) Text("Dans 15 minutes").tag(MatchDateSetup.inMinutes(15))
Text("Tout de suite").tag(MatchDateSetup.now)
} }
Text("Précédente rotation").tag(MatchDateSetup.inMinutes(-rotationDuration)) Text("Précédente rotation").tag(MatchDateSetup.inMinutes(-rotationDuration))
Text("Prochaine rotation").tag(MatchDateSetup.inMinutes(rotationDuration)) Text("Prochaine rotation").tag(MatchDateSetup.inMinutes(rotationDuration))
@ -464,11 +465,7 @@ struct MatchDetailView: View {
Text("Au hasard parmi les libres").tag(MatchFieldSetup.random) Text("Au hasard parmi les libres").tag(MatchFieldSetup.random)
Text("Au hasard").tag(MatchFieldSetup.fullRandom) Text("Au hasard").tag(MatchFieldSetup.fullRandom)
//Text("Premier disponible").tag(MatchFieldSetup.firstAvailable) //Text("Premier disponible").tag(MatchFieldSetup.firstAvailable)
if let club = match.currentTournament()?.club() { if let tournament = match.currentTournament() {
ForEach(0..<club.courtCount, id: \.self) { courtIndex in
Text(club.courtName(atIndex: courtIndex)) .tag(MatchFieldSetup.field(courtIndex))
}
} else if let tournament = match.currentTournament() {
ForEach(0..<tournament.courtCount, id: \.self) { courtIndex in ForEach(0..<tournament.courtCount, id: \.self) { courtIndex in
Text(tournament.courtName(atIndex: courtIndex)) .tag(MatchFieldSetup.field(courtIndex)) Text(tournament.courtName(atIndex: courtIndex)) .tag(MatchFieldSetup.field(courtIndex))
} }

@ -93,11 +93,11 @@ struct CalendarView: View {
if federalDataViewModel.isFederalTournamentValidForFilters(tournament, build: build) { if federalDataViewModel.isFederalTournamentValidForFilters(tournament, build: build) {
if navigation.agendaDestination == .around { if navigation.agendaDestination == .around {
NavigationLink(build.buildHolderTitle()) { NavigationLink(build.buildHolderTitle(.wide)) {
TournamentSubscriptionView(federalTournament: tournament, build: build, user: dataStore.user) TournamentSubscriptionView(federalTournament: tournament, build: build, user: dataStore.user)
} }
} else { } else {
Button(build.buildHolderTitle()) { Button(build.buildHolderTitle(.wide)) {
_createOrShow(federalTournament: tournament, existingTournament: event(forTournament: tournament)?.existingBuild(build), build: build) _createOrShow(federalTournament: tournament, existingTournament: event(forTournament: tournament)?.existingBuild(build), build: build)
} }
} }
@ -144,7 +144,9 @@ struct CalendarView: View {
let filteredTournaments = tournaments let filteredTournaments = tournaments
let mappedItems = filteredTournaments.flatMap { tournamentHolder in let mappedItems = filteredTournaments.flatMap { tournamentHolder in
(0..<tournamentHolder.dayDuration).map({ dayDuration in (0..<tournamentHolder.dayDuration).map({ dayDuration in
(tournamentHolder.startDate.dayInt + dayDuration, tournamentHolder.tournaments.count) (tournamentHolder.startDate.dayInt + dayDuration, tournamentHolder.tournaments.filter({ build in
federalDataViewModel.buildIsValid(build)
}).count)
}) })
} }
counts = Dictionary(mappedItems, uniquingKeysWith: +) counts = Dictionary(mappedItems, uniquingKeysWith: +)

@ -33,7 +33,7 @@ struct EventListView: View {
HStack { HStack {
Text(section.monthYearFormatted) Text(section.monthYearFormatted)
Spacer() Spacer()
let count = _tournaments.map { $0.tournaments.count }.reduce(0,+) let count = federalDataViewModel.countForTournamentBuilds(from: _tournaments)
Text("\(count.formatted()) tournoi" + count.pluralSuffix) Text("\(count.formatted()) tournoi" + count.pluralSuffix)
} }
} }
@ -52,7 +52,7 @@ struct EventListView: View {
HStack { HStack {
Text(section.monthYearFormatted) Text(section.monthYearFormatted)
Spacer() Spacer()
let count = _tournaments.map { $0.tournaments.count }.reduce(0,+) let count = federalDataViewModel.countForTournamentBuilds(from: _tournaments)
Text("\(count.formatted()) tournoi" + count.pluralSuffix) Text("\(count.formatted()) tournoi" + count.pluralSuffix)
} }
} }

@ -96,7 +96,7 @@ struct TournamentSubscriptionView: View {
Text(federalTournament.clubLabel()) Text(federalTournament.clubLabel())
} }
LabeledContent("Épreuve") { LabeledContent("Épreuve") {
Text(build.buildHolderTitle()) Text(build.buildHolderTitle(.wide))
} }
LabeledContent("JAP") { LabeledContent("JAP") {
@ -292,24 +292,24 @@ struct TournamentSubscriptionView: View {
var messageBody: String { var messageBody: String {
let bonjourOuBonsoir = Date().timeOfDay.hello let bonjourOuBonsoir = Date().timeOfDay.hello
let bonneSoireeOuBonneJournee = Date().timeOfDay.goodbye let bonneSoireeOuBonneJournee = Date().timeOfDay.goodbye
let body = [["\(bonjourOuBonsoir),\n\nJe souhaiterais inscrire mon équipe au tournoi : ", build.buildHolderTitle(), "du", federalTournament.computedStartDate, "au", federalTournament.clubLabel() + ".\n"].compacted().joined(separator: " "), teamsString, "\nCordialement,\n", user.fullName() ?? bonneSoireeOuBonneJournee, "----------------------------------\nCe message a été préparé grâce à l'application Padel Club !\n\(URLs.appStore.rawValue)"].compactMap { $0 }.joined(separator: "\n") + "\n" let body = [["\(bonjourOuBonsoir),\n\nJe souhaiterais inscrire mon équipe au tournoi : ", build.buildHolderTitle(.wide), "du", federalTournament.computedStartDate, "au", federalTournament.clubLabel() + ".\n"].compacted().joined(separator: " "), teamsString, "\nCordialement,\n", user.fullName() ?? bonneSoireeOuBonneJournee, "----------------------------------\nCe message a été préparé grâce à l'application Padel Club !\n\(URLs.appStore.rawValue)"].compactMap { $0 }.joined(separator: "\n") + "\n"
return body return body
} }
var messageBodyShort: String { var messageBodyShort: String {
let bonjourOuBonsoir = Date().timeOfDay.hello let bonjourOuBonsoir = Date().timeOfDay.hello
let bonneSoireeOuBonneJournee = Date().timeOfDay.goodbye let bonneSoireeOuBonneJournee = Date().timeOfDay.goodbye
let body = [["\(bonjourOuBonsoir),\n\nJe souhaiterais inscrire mon équipe au tournoi : ", build.buildHolderTitle(), "du", federalTournament.computedStartDate, "au", federalTournament.clubLabel() + ".\n"].compacted().joined(separator: " "), teamsString, "\nCordialement,\n", user.fullName() ?? bonneSoireeOuBonneJournee, "----------------------------------\nCe message a été préparé grâce à l'application Padel Club !\n\(URLs.appStore.rawValue)"].compactMap { $0 }.joined(separator: "\n") + "\n" let body = [["\(bonjourOuBonsoir),\n\nJe souhaiterais inscrire mon équipe au tournoi : ", build.buildHolderTitle(.wide), "du", federalTournament.computedStartDate, "au", federalTournament.clubLabel() + ".\n"].compacted().joined(separator: " "), teamsString, "\nCordialement,\n", user.fullName() ?? bonneSoireeOuBonneJournee, "----------------------------------\nCe message a été préparé grâce à l'application Padel Club !\n\(URLs.appStore.rawValue)"].compactMap { $0 }.joined(separator: "\n") + "\n"
return body return body
} }
var noteCalendar: String { var noteCalendar: String {
let body = [[build.buildHolderTitle(), "du", federalTournament.computedStartDate, "au", federalTournament.clubLabel() + ".\n"].compacted().joined(separator: " "), teamsString, federalTournament.calendarNoteMessage()].compactMap { $0 }.joined(separator: "\n") + "\n" let body = [[build.buildHolderTitle(.wide), "du", federalTournament.computedStartDate, "au", federalTournament.clubLabel() + ".\n"].compacted().joined(separator: " "), teamsString, federalTournament.calendarNoteMessage()].compactMap { $0 }.joined(separator: "\n") + "\n"
return body return body
} }
var messageSubject: String { var messageSubject: String {
let subject = [build.buildHolderTitle(), federalTournament.clubLabel()].compacted().joined(separator: " ") let subject = [build.buildHolderTitle(.wide), federalTournament.clubLabel()].compacted().joined(separator: " ")
return subject return subject
} }

@ -240,9 +240,9 @@ struct PlanningSettingsView: View {
let value = tournament.getGroupStageChunkValue() let value = tournament.getGroupStageChunkValue()
if parallelType == false { if parallelType == false {
if value > 1 { if value > 1 {
Text("\(value.formatted()) poules commenceront en parallèle") Text("\(value.formatted()) poules en parallèle")
} else { } else {
Text("une poule sera jouer à la fois") Text("une poule sera jouée à la fois")
} }
} }
} }

@ -14,6 +14,7 @@ struct EditablePlayerView: View {
case payment case payment
case licenceId case licenceId
case name case name
case presence
} }
@EnvironmentObject var dataStore: DataStore @EnvironmentObject var dataStore: DataStore
@ -77,6 +78,13 @@ struct EditablePlayerView: View {
Logger.error(error) Logger.error(error)
} }
} }
.onChange(of: player.hasArrived) {
do {
try self.tournamentStore.playerRegistrations.addOrUpdate(instance: player)
} catch {
Logger.error(error)
}
}
} }
@ViewBuilder @ViewBuilder
@ -91,11 +99,6 @@ struct EditablePlayerView: View {
Menu { Menu {
Button { Button {
player.hasArrived.toggle() player.hasArrived.toggle()
do {
try self.tournamentStore.playerRegistrations.addOrUpdate(instance: player)
} catch {
Logger.error(error)
}
} label: { } label: {
Label("Présent", systemImage: player.hasArrived ? "checkmark.circle" : "circle") Label("Présent", systemImage: player.hasArrived ? "checkmark.circle" : "circle")
} }
@ -172,6 +175,11 @@ struct EditablePlayerView: View {
if editingOptions.contains(.payment) { if editingOptions.contains(.payment) {
Spacer() Spacer()
PlayerPayView(player: player) PlayerPayView(player: player)
} else if editingOptions.contains(.presence) {
Spacer()
FooterButtonView(player.hasArrived ? "Présent" : "Sur place ?", role: player.hasArrived ? nil : .cancel, systemImage: player.hasArrived ? "checkmark" : nil) {
player.hasArrived.toggle()
}
} }
} }
} }

@ -12,6 +12,9 @@ struct ImportedPlayerView: View {
var index: Int? = nil var index: Int? = nil
var showFemaleInMaleAssimilation: Bool = false var showFemaleInMaleAssimilation: Bool = false
var showProgression: Bool = false var showProgression: Bool = false
var isAnimation: Bool {
player.getComputedRank() == 0
}
var body: some View { var body: some View {
VStack(alignment: .leading) { VStack(alignment: .leading) {
@ -39,74 +42,76 @@ struct ImportedPlayerView: View {
} }
.font(.title3) .font(.title3)
.lineLimit(1) .lineLimit(1)
HStack { if isAnimation == false {
HStack(alignment: .top, spacing: 0) { HStack {
Text(player.formattedRank()).italic(player.isAssimilated) HStack(alignment: .top, spacing: 0) {
.font(.title3) Text(player.formattedRank()).italic(player.isAssimilated)
.background { .font(.title3)
if player.isNotFromCurrentDate() { .background {
UnderlineView() if player.isNotFromCurrentDate() {
UnderlineView()
}
} }
if let rank = player.getRank() {
Text(rank.ordinalFormattedSuffix()).italic(player.isAssimilated)
.font(.caption)
}
}
if showProgression, player.getProgression() != 0 {
HStack(alignment: .top, spacing: 2) {
Text("(")
Text(player.getProgression().formatted(.number.sign(strategy: .always())))
.foregroundStyle(player.getProgressionColor(progression: player.getProgression()))
Text(")")
}.font(.title3)
}
if let pts = player.getPoints(), pts > 0 {
HStack(alignment: .lastTextBaseline, spacing: 0) {
Text(pts.formatted()).font(.title3)
Text(" pts").font(.caption)
}
}
if let tournamentPlayed = player.tournamentPlayed, tournamentPlayed > 0 {
HStack(alignment: .lastTextBaseline, spacing: 0) {
Text(tournamentPlayed.formatted()).font(.title3)
Text(" tournoi" + tournamentPlayed.pluralSuffix).font(.caption)
} }
if let rank = player.getRank() {
Text(rank.ordinalFormattedSuffix()).italic(player.isAssimilated)
.font(.caption)
} }
} }
.lineLimit(1)
if showProgression, player.getProgression() != 0 { if showFemaleInMaleAssimilation, let assimilatedAsMaleRank = player.getAssimilatedAsMaleRank() {
HStack(alignment: .top, spacing: 2) { HStack(alignment: .top, spacing: 2) {
Text("(") Text("(")
Text(player.getProgression().formatted(.number.sign(strategy: .always()))) Text(assimilatedAsMaleRank.formatted())
.foregroundStyle(player.getProgressionColor(progression: player.getProgression())) VStack(alignment: .leading, spacing: 0) {
Text(")") Text("équivalence")
}.font(.title3) Text("messieurs")
} }
.font(.caption)
if let pts = player.getPoints(), pts > 0 { Text(")").font(.title3)
HStack(alignment: .lastTextBaseline, spacing: 0) {
Text(pts.formatted()).font(.title3)
Text(" pts").font(.caption)
} }
} }
if let tournamentPlayed = player.tournamentPlayed, tournamentPlayed > 0 { HStack {
HStack(alignment: .lastTextBaseline, spacing: 0) { Text(player.formattedLicense())
Text(tournamentPlayed.formatted()).font(.title3) if let computedAge = player.computedAge {
Text(" tournoi" + tournamentPlayed.pluralSuffix).font(.caption) Text(computedAge.formatted() + " ans")
} }
} }
} .font(.caption)
.lineLimit(1) if let clubName = player.clubName {
Text(clubName)
if showFemaleInMaleAssimilation, let assimilatedAsMaleRank = player.getAssimilatedAsMaleRank() { .font(.caption)
HStack(alignment: .top, spacing: 2) {
Text("(")
Text(assimilatedAsMaleRank.formatted())
VStack(alignment: .leading, spacing: 0) {
Text("équivalence")
Text("messieurs")
}
.font(.caption)
Text(")").font(.title3)
} }
} if let ligueName = player.ligueName {
Text(ligueName)
HStack { .font(.caption)
Text(player.formattedLicense())
if let computedAge = player.computedAge {
Text(computedAge.formatted() + " ans")
} }
} }
.font(.caption)
if let clubName = player.clubName {
Text(clubName)
.font(.caption)
}
if let ligueName = player.ligueName {
Text(ligueName)
.font(.caption)
}
} }
} }
} }

@ -17,10 +17,6 @@ struct TeamRowView: View {
TeamWeightView(team: team, teamPosition: teamPosition) TeamWeightView(team: team, teamPosition: teamPosition)
} label: { } label: {
VStack(alignment: .leading) { VStack(alignment: .leading) {
if let name = team.name {
Text(name).foregroundStyle(.secondary)
}
if let groupStage = team.groupStageObject() { if let groupStage = team.groupStageObject() {
HStack { HStack {
Text(groupStage.groupStageTitle()) Text(groupStage.groupStageTitle())
@ -32,13 +28,20 @@ struct TeamRowView: View {
Text(round.roundTitle(.wide)) Text(round.roundTitle(.wide))
} }
if team.players().isEmpty == false { if let name = team.name {
ForEach(team.players()) { player in Text(name).font(.title3)
Text(player.playerLabel()) if team.players().isEmpty {
Text("Aucun joueur")
} }
} else { } else {
Text("Place réservée") if team.players().isEmpty == false {
Text("Place réservée") ForEach(team.players()) { player in
Text(player.playerLabel())
}
} else {
Text("Place réservée")
Text("Place réservée")
}
} }
} }
if displayCallDate { if displayCallDate {

@ -54,9 +54,15 @@ enum CashierDestination: Identifiable, Selectable, Equatable {
case .summary: case .summary:
return nil return nil
case .groupStage(let groupStage): case .groupStage(let groupStage):
if groupStage.tournamentObject()?.isFree() == true {
return groupStage.unsortedPlayers().filter({ $0.hasArrived == false }).count
}
return groupStage.unsortedPlayers().filter({ $0.hasPaid() == false }).count return groupStage.unsortedPlayers().filter({ $0.hasPaid() == false }).count
case .bracket(let round): case .bracket(let round):
let playerRegistrations: [PlayerRegistration] = round.seeds().flatMap { $0.unsortedPlayers() } let playerRegistrations: [PlayerRegistration] = round.seeds().flatMap { $0.unsortedPlayers() }
if round.tournamentObject()?.isFree() == true {
return playerRegistrations.filter({ $0.hasArrived == false }).count
}
return playerRegistrations.filter({ $0.hasPaid() == false }).count return playerRegistrations.filter({ $0.hasPaid() == false }).count
case .all(_): case .all(_):
return nil return nil
@ -156,7 +162,7 @@ struct TournamentCashierView: View {
.environmentObject(cashierViewModel) .environmentObject(cashierViewModel)
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.visible, for: .navigationBar) .toolbarBackground(.visible, for: .navigationBar)
.navigationTitle("Encaissement") .navigationTitle(tournament.isFree() ? "Présence" : "Encaissement")
} }
} }

@ -11,6 +11,7 @@ import LeStorage
struct TournamentCellView: View { struct TournamentCellView: View {
@EnvironmentObject var dataStore: DataStore @EnvironmentObject var dataStore: DataStore
@Environment(NavigationViewModel.self) private var navigation @Environment(NavigationViewModel.self) private var navigation
@Environment(FederalDataViewModel.self) var federalDataViewModel: FederalDataViewModel
let tournament: FederalTournamentHolder let tournament: FederalTournamentHolder
// let color: Color = .black // let color: Color = .black
@ -23,11 +24,17 @@ struct TournamentCellView: View {
var body: some View { var body: some View {
ForEach(tournament.tournaments, id: \.id) { build in ForEach(tournament.tournaments, id: \.id) { build in
if navigation.agendaDestination == .around, let federalTournament = tournament as? FederalTournament { if let federalTournament = tournament as? FederalTournament {
NavigationLink { if federalDataViewModel.isFederalTournamentValidForFilters(federalTournament, build: build) {
TournamentSubscriptionView(federalTournament: federalTournament, build: build, user: dataStore.user) if navigation.agendaDestination == .around {
} label: { NavigationLink {
_buildView(build, existingTournament: event?.existingBuild(build)) TournamentSubscriptionView(federalTournament: federalTournament, build: build, user: dataStore.user)
} label: {
_buildView(build, existingTournament: event?.existingBuild(build))
}
} else {
_buildView(build, existingTournament: event?.existingBuild(build))
}
} }
} else { } else {
_buildView(build, existingTournament: event?.existingBuild(build)) _buildView(build, existingTournament: event?.existingBuild(build))

@ -185,7 +185,7 @@ struct TournamentBuildView: View {
ProgressView() ProgressView()
} }
} label: { } label: {
Text("Encaissement") Text(tournament.isFree() ? "Présence" : "Encaissement")
if let tournamentStatus { if let tournamentStatus {
Text(tournamentStatus.label).lineLimit(1) Text(tournamentStatus.label).lineLimit(1)
} else { } else {

Loading…
Cancel
Save