add player replacement stuff

multistore
Razmig Sarkissian 2 years ago
parent d6be8e5305
commit 0090e78325
  1. 22
      PadelClub.xcodeproj/project.pbxproj
  2. 5
      PadelClub/Data/GroupStage.swift
  3. 5
      PadelClub/Data/Round.swift
  4. 32
      PadelClub/Data/TeamRegistration.swift
  5. 10
      PadelClub/ViewModel/AgendaDestination.swift
  6. 54
      PadelClub/ViewModel/SearchViewModel.swift
  7. 1
      PadelClub/ViewModel/Selectable.swift
  8. 2
      PadelClub/Views/Components/GenericDestinationPickerView.swift
  9. 23
      PadelClub/Views/GroupStage/Components/GroupStageTeamView.swift
  10. 4
      PadelClub/Views/GroupStage/GroupStagesView.swift
  11. 140
      PadelClub/Views/GroupStage/Shared/GroupStageTeamReplacementView.swift
  12. 39
      PadelClub/Views/Match/Components/MatchDateView.swift
  13. 11
      PadelClub/Views/Match/Components/PlayerBlockView.swift
  14. 208
      PadelClub/Views/Match/MatchSummaryView.swift
  15. 10
      PadelClub/Views/Player/Components/EditablePlayerView.swift
  16. 4
      PadelClub/Views/Round/LoserRoundsView.swift
  17. 7
      PadelClub/Views/Shared/SelectablePlayerListView.swift
  18. 11
      PadelClub/Views/Team/Components/TeamWeightView.swift
  19. 4
      PadelClub/Views/Tournament/Screen/TournamentCallView.swift
  20. 4
      PadelClub/Views/Tournament/Screen/TournamentCashierView.swift
  21. 4
      PadelClub/Views/Tournament/Screen/TournamentScheduleView.swift
  22. 4
      PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift

@ -217,6 +217,7 @@
FF967D0B2BAF3D4C00A9A3BD /* TeamPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D0A2BAF3D4C00A9A3BD /* TeamPickerView.swift */; };
FF967D0D2BAF3EB300A9A3BD /* MatchDateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D0C2BAF3EB200A9A3BD /* MatchDateView.swift */; };
FF967D0F2BAF63B000A9A3BD /* PlayerBlockView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF967D0E2BAF63B000A9A3BD /* PlayerBlockView.swift */; };
FF9AC3952BE3627B00C2E883 /* GroupStageTeamReplacementView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9AC3942BE3627B00C2E883 /* GroupStageTeamReplacementView.swift */; };
FFA1B1292BB71773006CE248 /* PadelClubButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA1B1282BB71773006CE248 /* PadelClubButtonView.swift */; };
FFA6D7852BB0B795003A31F3 /* FileImportManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA6D7842BB0B795003A31F3 /* FileImportManager.swift */; };
FFA6D7872BB0B7A2003A31F3 /* CloudConvert.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA6D7862BB0B7A2003A31F3 /* CloudConvert.swift */; };
@ -513,6 +514,7 @@
FF967D0A2BAF3D4C00A9A3BD /* TeamPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamPickerView.swift; sourceTree = "<group>"; };
FF967D0C2BAF3EB200A9A3BD /* MatchDateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchDateView.swift; sourceTree = "<group>"; };
FF967D0E2BAF63B000A9A3BD /* PlayerBlockView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerBlockView.swift; sourceTree = "<group>"; };
FF9AC3942BE3627B00C2E883 /* GroupStageTeamReplacementView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupStageTeamReplacementView.swift; sourceTree = "<group>"; };
FFA1B1282BB71773006CE248 /* PadelClubButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PadelClubButtonView.swift; sourceTree = "<group>"; };
FFA6D7842BB0B795003A31F3 /* FileImportManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileImportManager.swift; sourceTree = "<group>"; };
FFA6D7862BB0B7A2003A31F3 /* CloudConvert.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CloudConvert.swift; sourceTree = "<group>"; };
@ -1099,7 +1101,8 @@
FF967CFA2BAEE13800A9A3BD /* GroupStageView.swift */,
FF967CFB2BAEE13900A9A3BD /* GroupStagesView.swift */,
FF5DA18E2BB9268800A33061 /* GroupStageSettingsView.swift */,
FFBF065B2BBD2657009D6715 /* GroupStageTeamView.swift */,
FF9AC3932BE3625D00C2E883 /* Components */,
FF9AC3922BE3625200C2E883 /* Shared */,
);
path = GroupStage;
sourceTree = "<group>";
@ -1128,6 +1131,22 @@
path = Team;
sourceTree = "<group>";
};
FF9AC3922BE3625200C2E883 /* Shared */ = {
isa = PBXGroup;
children = (
FF9AC3942BE3627B00C2E883 /* GroupStageTeamReplacementView.swift */,
);
path = Shared;
sourceTree = "<group>";
};
FF9AC3932BE3625D00C2E883 /* Components */ = {
isa = PBXGroup;
children = (
FFBF065B2BBD2657009D6715 /* GroupStageTeamView.swift */,
);
path = Components;
sourceTree = "<group>";
};
FFC83D4B2BB807C200750834 /* Round */ = {
isa = PBXGroup;
children = (
@ -1572,6 +1591,7 @@
C425D4012B6D249D002A7B48 /* PadelClubApp.swift in Sources */,
FF8F26432BADFE5B00650388 /* TournamentSettingsView.swift in Sources */,
C49EF03C2BE15AF80077B5AA /* String+Crypto.swift in Sources */,
FF9AC3952BE3627B00C2E883 /* GroupStageTeamReplacementView.swift in Sources */,
FF4C7F022BBBD7150031B6A3 /* TabItemModifier.swift in Sources */,
FFDDD40C2B93B2BB00C91A49 /* DeferredViewModifier.swift in Sources */,
FFD784042B91C280000F62A6 /* EmptyActivityView.swift in Sources */,

@ -8,6 +8,7 @@
import Foundation
import LeStorage
import Algorithms
import SwiftUI
@Observable
class GroupStage: ModelObject, Storable {
@ -309,6 +310,10 @@ extension GroupStage: Selectable {
runningMatches().count
}
func badgeValueColor() -> Color? {
return nil
}
func badgeImage() -> Badge? {
hasEnded() ? .checkmark : nil
}

@ -7,6 +7,7 @@
import Foundation
import LeStorage
import SwiftUI
@Observable
class Round: ModelObject, Storable {
@ -503,6 +504,10 @@ extension Round: Selectable {
}
}
func badgeValueColor() -> Color? {
return nil
}
func badgeImage() -> Badge? {
hasEnded() ? .checkmark : nil
}

@ -214,6 +214,38 @@ class TeamRegistration: ModelObject, Storable {
groupStagePosition != nil && bracketPosition != nil
}
typealias TeamRange = (left: TeamRegistration?, right: TeamRegistration?)
func replacementRange() -> TeamRange? {
guard let tournamentObject = tournamentObject() else { return nil }
guard let index = tournamentObject.indexOf(team: self) else { return nil }
let selectedSortedTeams = tournamentObject.selectedSortedTeams()
let left = selectedSortedTeams[safe: index - 1]
let right = selectedSortedTeams[safe: index + 1]
return (left: left, right: right)
}
func replacementRangeExtended() -> TeamRange? {
guard let tournamentObject = tournamentObject() else { return nil }
guard let groupStagePosition else { return nil }
let selectedSortedTeams = tournamentObject.selectedSortedTeams()
var left: TeamRegistration? = nil
if groupStagePosition == 0 {
left = tournamentObject.seeds().last
} else {
let previousHat = selectedSortedTeams.filter({ $0.groupStagePosition == groupStagePosition - 1 }).sorted(by: \.weight)
left = previousHat.last
}
var right: TeamRegistration? = nil
if groupStagePosition == tournamentObject.teamsPerGroupStage - 1 {
right = nil
} else {
let previousHat = selectedSortedTeams.filter({ $0.groupStagePosition == groupStagePosition + 1 }).sorted(by: \.weight)
right = previousHat.first
}
return (left: left, right: right)
}
typealias AreInIncreasingOrder = (PlayerRegistration, PlayerRegistration) -> Bool
func players() -> [PlayerRegistration] {

@ -6,6 +6,7 @@
//
import Foundation
import SwiftUI
enum AgendaDestination: CaseIterable, Identifiable, Selectable {
var id: Self { self }
@ -56,6 +57,15 @@ enum AgendaDestination: CaseIterable, Identifiable, Selectable {
}
}
func badgeValueColor() -> Color? {
switch self {
case .history:
return .green
default:
return nil
}
}
func badgeImage() -> Badge? {
switch self {
case .activity:

@ -15,6 +15,7 @@ class SearchViewModel: ObservableObject, Identifiable {
var clubName: String? = nil
var ligueName: String? = nil
var showFemaleInMaleAssimilation: Bool = false
var hidePlayers: [String]?
@Published var debouncableText: String = ""
@Published var searchText: String = ""
@ -67,6 +68,10 @@ class SearchViewModel: ObservableObject, Identifiable {
return message.joined(separator: "\n")
}
func codeClubs() -> [String] {
DataStore.shared.clubs.compactMap { $0.code }
}
func getCodeClub() -> String? {
if let codeClub { return codeClub }
// if let userCodeClub = user?.player?.codeClub { return userCodeClub }
@ -97,7 +102,7 @@ class SearchViewModel: ObservableObject, Identifiable {
return "dans cette ligue"
case .club:
return "dans ce club"
case .favorite:
case .favoriteClubs, .favoritePlayers:
return "dans mes favoris"
}
}
@ -110,8 +115,10 @@ class SearchViewModel: ObservableObject, Identifiable {
return (ligueName)?.capitalized ?? "Ma ligue"
case .club:
return (clubName)?.capitalized ?? "Mon club"
case .favorite:
return "Mes favoris"
case .favoriteClubs:
return "Clubs favoris"
case .favoritePlayers:
return "Joueurs favoris"
}
}
@ -135,10 +142,11 @@ class SearchViewModel: ObservableObject, Identifiable {
func orPredicate() -> NSPredicate? {
var predicates : [NSPredicate] = []
let searchText = searchText.canonicalVersion
let canonicalVersionWithoutPunctuation = searchText.canonicalVersion
let canonicalVersionWithPunctuation = searchText.canonicalVersionWithPunctuation
switch tokens.first {
case .none:
if searchText.isEmpty == false {
if canonicalVersionWithoutPunctuation.isEmpty == false {
let wordsPredicates = wordsPredicates()
if let wordsPredicates {
predicates.append(wordsPredicates)
@ -148,32 +156,32 @@ class SearchViewModel: ObservableObject, Identifiable {
}
}
case .ligue:
if searchText.isEmpty {
if canonicalVersionWithoutPunctuation.isEmpty {
predicates.append(NSPredicate(format: "ligueName == nil"))
} else {
predicates.append(NSPredicate(format: "ligueName contains[cd] %@", searchText))
predicates.append(NSPredicate(format: "ligueName contains[cd] %@", canonicalVersionWithoutPunctuation))
}
case .club:
if searchText.isEmpty {
if canonicalVersionWithoutPunctuation.isEmpty {
predicates.append(NSPredicate(format: "clubName == nil"))
} else {
predicates.append(NSPredicate(format: "clubName contains[cd] %@", searchText))
predicates.append(NSPredicate(format: "clubName contains[cd] %@", canonicalVersionWithoutPunctuation))
}
case .rankMoreThan:
if searchText.isEmpty || Int(searchText) == 0 {
if canonicalVersionWithoutPunctuation.isEmpty || Int(canonicalVersionWithoutPunctuation) == 0 {
predicates.append(NSPredicate(format: "rank == 0"))
} else {
predicates.append(NSPredicate(format: "rank >= %@", searchText))
predicates.append(NSPredicate(format: "rank >= %@", canonicalVersionWithoutPunctuation))
}
case .rankLessThan:
if searchText.isEmpty || Int(searchText) == 0 {
if canonicalVersionWithoutPunctuation.isEmpty || Int(canonicalVersionWithoutPunctuation) == 0 {
predicates.append(NSPredicate(format: "rank == 0"))
} else {
predicates.append(NSPredicate(format: "rank <= %@", searchText))
predicates.append(NSPredicate(format: "rank <= %@", canonicalVersionWithoutPunctuation))
}
case .rankBetween:
let values = searchText.components(separatedBy: ",")
if searchText.isEmpty || values.count != 2 {
let values = canonicalVersionWithPunctuation.components(separatedBy: ",")
if canonicalVersionWithPunctuation.isEmpty || values.count != 2 {
predicates.append(NSPredicate(format: "rank == 0"))
} else {
predicates.append(NSPredicate(format: "rank BETWEEN {%@,%@}", values.first!, values.last!))
@ -220,10 +228,17 @@ class SearchViewModel: ObservableObject, Identifiable {
} else {
predicates.append(NSPredicate(format: "clubCode == nil"))
}
case .favorite:
case .favoriteClubs:
predicates.append(NSPredicate(format: "clubCode IN %@", codeClubs()))
case .favoritePlayers:
//todo
predicates.append(NSPredicate(format: "license == nil"))
}
if hidePlayers?.isEmpty == false {
predicates.append(NSPredicate(format: "NOT (license IN %@)", hidePlayers!))
}
return NSCompoundPredicate(andPredicateWithSubpredicates: predicates.compactMap({ $0 }))
}
@ -341,7 +356,8 @@ enum DataSet: Int, CaseIterable, Identifiable {
case national
case ligue
case club
case favorite
case favoriteClubs
case favoritePlayers
var id: Int { rawValue }
func localizedLabel(_ displayStyle: DisplayStyle = .wide) -> String {
@ -352,7 +368,7 @@ enum DataSet: Int, CaseIterable, Identifiable {
return "Ligue"
case .club:
return "Club"
case .favorite:
case .favoritePlayers, .favoriteClubs:
return "Favori"
}
}
@ -365,7 +381,7 @@ enum DataSet: Int, CaseIterable, Identifiable {
return [.club, .rankMoreThan, .rankLessThan, .rankBetween]
case .club:
return [.rankMoreThan, .rankLessThan, .rankBetween]
case .favorite:
case .favoritePlayers, .favoriteClubs:
return [.rankMoreThan, .rankLessThan, .rankBetween]
}
}

@ -12,6 +12,7 @@ protocol Selectable {
func selectionLabel() -> String
func badgeValue() -> Int?
func badgeImage() -> Badge?
func badgeValueColor() -> Color?
}
enum Badge {

@ -56,7 +56,7 @@ struct GenericDestinationPickerView<T: Identifiable & Selectable>: View {
.offset(x: 3, y: 3)
} else if let count = destination.badgeValue(), count > 0 {
Image(systemName: count <= 50 ? "\(count).circle.fill" : "plus.circle.fill")
.foregroundColor(.red)
.foregroundColor(destination.badgeValueColor() ?? .red)
.imageScale(.medium)
.background (
Color(.systemBackground)

@ -22,17 +22,22 @@ struct GroupStageTeamView: View {
}
if groupStage.tournamentObject()?.hasEnded() == false {
Section {
NavigationLink {
GroupStageTeamReplacementView(team: team)
} label: {
Text("Chercher à remplacer")
}
}
Section {
if team.qualified == false {
RowButtonView("Qualifier l'équipe") {
RowButtonView("Qualifier l'équipe", role: .destructive) {
team.qualified = true
// team.bracketPosition = nil
//team.bracketPosition = nil
_save()
}
}
}
Section {
if team.qualified {
} else {
RowButtonView("Annuler la qualification", role: .destructive) {
team.qualified = false
team.bracketPosition = nil
@ -41,12 +46,6 @@ struct GroupStageTeamView: View {
}
}
Section {
RowButtonView("Remplacement") {
}
}
Section {
RowButtonView("Retirer de la poule", role: .destructive) {
team.groupStagePosition = nil

@ -42,6 +42,10 @@ struct GroupStagesView: View {
}
}
func badgeValueColor() -> Color? {
return nil
}
func badgeImage() -> Badge? {
nil
}

@ -0,0 +1,140 @@
//
// GroupStageTeamReplacementView.swift
// PadelClub
//
// Created by Razmig Sarkissian on 02/05/2024.
//
import SwiftUI
struct GroupStageTeamReplacementView: View {
var team: TeamRegistration
let teamRange: TeamRegistration.TeamRange?
let teamRangeExtended: TeamRegistration.TeamRange?
@State private var selectedPlayer: PlayerRegistration?
init(team: TeamRegistration) {
self.team = team
self.teamRange = team.replacementRange()
self.teamRangeExtended = team.replacementRangeExtended()
}
private func _getWeight() -> Int {
guard let selectedPlayer else { return 0 }
return team.weight - selectedPlayer.weight
}
private func _searchableRange(_ teamRange: TeamRegistration.TeamRange) -> String {
let left = teamRange.left?.weight
let right = teamRange.right?.weight
let sides = [left, right].compactMap({ $0 }).map { $0 - _getWeight() }
return sides.map({ String($0) }).joined(separator: ",")
}
private func _searchToken(_ teamRange: TeamRegistration.TeamRange) -> SearchToken {
if teamRange.left == nil { return .rankLessThan }
if teamRange.right == nil { return .rankMoreThan }
return .rankBetween
}
var body: some View {
List {
Section {
Picker(selection: $selectedPlayer) {
HStack {
Text("Toute l'équipe")
Spacer()
Text(team.weight.formatted()).bold()
}
.tag(nil as PlayerRegistration?)
ForEach(team.players()) { player in
HStack {
Text(player.playerLabel())
Spacer()
Text(player.rankLabel()).bold()
}
.tag(player as PlayerRegistration?)
}
} label: {
Text("Remplacement de l'équipe ou d'un joueur particulier")
}
.labelsHidden()
.pickerStyle(.inline)
} footer: {
Text("Remplacement de l'équipe ou d'un joueur particulier")
}
if let teamRange {
Section {
TeamRangeView(teamRange: teamRange, playerWeight: _getWeight())
if selectedPlayer != nil {
_searchLinkView(teamRange)
}
} header: {
Text("Même position en poule")
} footer: {
Text("Intervalle de poids d'équipe pour que le remplacement n'affecte pas les poules")
}
}
if let teamRangeExtended {
Section {
TeamRangeView(teamRange: teamRangeExtended, playerWeight: _getWeight())
if selectedPlayer != nil {
_searchLinkView(teamRangeExtended)
}
} header: {
Text("Même ligne en poule")
} footer: {
Text("Intervalle de poids d'équipe respecte le tirage au sort par chapeau")
}
}
}
.headerProminence(.increased)
.navigationTitle("Remplacement")
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.visible, for: .navigationBar)
}
private func _searchLinkView(_ teamRange: TeamRegistration.TeamRange) -> some View {
NavigationLink {
let tournament = team.tournamentObject()
SelectablePlayerListView(searchField: _searchableRange(teamRange), dataSet: .favoriteClubs, filterOption: tournament?.tournamentCategory.playerFilterOption ?? .all, sortOption: .rank, showFemaleInMaleAssimilation: true, tokens: [_searchToken(teamRange)], hidePlayers: tournament?.selectedPlayers().compactMap { $0.licenceId })
} label: {
Text("Chercher dans vos clubs favoris")
}
}
struct TeamRangeView: View {
let teamRange: TeamRegistration.TeamRange
let playerWeight: Int
@ViewBuilder
var body: some View {
HStack {
TeamRangeSideView(team: teamRange.left, playerWeight: -playerWeight)
Spacer()
Image(systemName: "arrowshape.forward.fill")
Spacer()
TeamRangeSideView(team: teamRange.right, playerWeight: -playerWeight)
}
}
}
struct TeamRangeSideView: View {
let team: TeamRegistration?
let playerWeight: Int
@ViewBuilder
var body: some View {
if let team {
Text((team.weight + playerWeight).formatted()).font(.largeTitle)
} else {
Text("Aucune limite")
}
}
}
}
#Preview {
GroupStageTeamReplacementView(team: TeamRegistration.mock())
}

@ -6,29 +6,41 @@
//
import SwiftUI
import LeStorage
struct MatchDateView: View {
@EnvironmentObject var dataStore: DataStore
var match: Match
var showPrefix: Bool = false
private var isReady: Bool
private var hasWalkoutTeam: Bool
private var hasEnded: Bool
init(match: Match, showPrefix: Bool) {
self.match = match
self.showPrefix = showPrefix
self.isReady = match.isReady()
self.hasWalkoutTeam = match.hasWalkoutTeam()
self.hasEnded = match.hasEnded()
}
var body: some View {
Menu {
if match.startDate == nil && match.isReady() {
if match.startDate == nil && isReady {
Button("Démarrer") {
match.startDate = Date()
save()
_save()
}
Button("Échauffement") {
match.startDate = Calendar.current.date(byAdding: .minute, value: 5, to: Date())
save()
_save()
}
} else {
if match.isReady() {
if isReady {
Button("Démarrer maintenant") {
match.startDate = Date()
match.endDate = nil
save()
_save()
}
} else {
let tournament = match.currentTournament()
@ -36,13 +48,13 @@ struct MatchDateView: View {
Button("Décaler de \(estimatedDuration) minutes") {
match.startDate = match.startDate?.addingTimeInterval(Double(estimatedDuration) * 60.0)
match.endDate = nil
save()
_save()
}
}
Button("Retirer l'horaire") {
match.startDate = nil
match.endDate = nil
save()
_save()
}
}
} label: {
@ -55,7 +67,7 @@ struct MatchDateView: View {
var label: some View {
HStack {
VStack(alignment: .trailing) {
if match.hasWalkoutTeam() == false {
if hasWalkoutTeam == false {
if let startDate = match.startDate, match.endDate == nil {
if startDate.timeIntervalSinceNow < 0 {
if showPrefix {
@ -110,7 +122,7 @@ struct MatchDateView: View {
.underline()
}
if match.startDate == nil && match.hasEnded() == false {
if match.startDate == nil && hasEnded == false {
Text("démarrage").font(.footnote).foregroundStyle(.secondary)
Text("non défini")
.foregroundStyle(Color.master)
@ -121,17 +133,12 @@ struct MatchDateView: View {
}
}
func save() {
private func _save() {
do {
try dataStore.matches.addOrUpdate(instance: match)
} 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)")
Logger.error(error)
}
}
}

@ -13,13 +13,16 @@ struct PlayerBlockView: View {
let team: TeamRegistration?
let color: Color
let width: CGFloat
let teamScore: TeamScore?
init(match: Match, teamPosition: TeamPosition, color: Color, width: CGFloat) {
self.match = match
self.teamPosition = teamPosition
self.team = match.team(teamPosition)
let theTeam = match.team(teamPosition)
self.team = theTeam
self.color = color
self.width = width
self.teamScore = match.teamScore(ofTeam: theTeam)
}
var names: [String]? {
@ -39,18 +42,18 @@ struct PlayerBlockView: View {
}
var scores: [String] {
match.teamScore(ofTeam: team)?.score?.components(separatedBy: ",") ?? []
teamScore?.score?.components(separatedBy: ",") ?? []
}
private func _defaultLabel() -> String {
return teamPosition.localizedLabel()
teamPosition.localizedLabel()
}
var body: some View {
HStack {
VStack(alignment: .leading) {
if let names {
if let teamScore = match.teamScore(ofTeam: team), teamScore.luckyLoser != nil {
if let teamScore, teamScore.luckyLoser != nil {
Text("Repêchée").italic().font(.caption)
}

@ -10,25 +10,39 @@ import SwiftUI
struct MatchSummaryView: View {
var match: Match
let matchViewStyle: MatchViewStyle
let matchTitle: String
let roundTitle: String?
let courtName: String?
let spacing: CGFloat
let padding: CGFloat
let color: Color
let width: CGFloat
var entrantLabelOne: String {
"match.longLabelTeamOne"
}
init(match: Match, matchViewStyle: MatchViewStyle) {
self.match = match
self.matchViewStyle = matchViewStyle
self.padding = matchViewStyle == .plainStyle ? 0 : 8
self.spacing = matchViewStyle == .plainStyle ? 8 : 0
self.width = matchViewStyle == .plainStyle ? 1 : 2
self.color = Color(white: 0.9)
var entrantLabelTwo: String {
"match.longLabelTeamTwo"
}
var color: Color {
return Color(white: 0.9)
//matchViewStyle == .plainStyle ? Color(uiColor: .tertiaryLabel) : Color(uiColor: .secondaryLabel)
if let groupStage = match.groupStageObject {
self.roundTitle = groupStage.groupStageTitle()
} else if let round = match.roundObject {
self.roundTitle = round.roundTitle()
} else {
self.roundTitle = nil
}
self.matchTitle = match.matchTitle()
if let court = match.courtName(), match.hasEnded() == false {
self.courtName = court
} else {
self.courtName = nil
}
}
var width: CGFloat {
// return 2.0
matchViewStyle == .plainStyle ? 1 : 2
}
var body: some View {
matchSummaryView
// .contextMenu {
@ -59,37 +73,33 @@ struct MatchSummaryView: View {
VStack(alignment: .leading) {
if matchViewStyle != .plainStyle {
if matchViewStyle == .feedStyle, let tournament = match.currentTournament() {
tournamentHeaderView(tournament)
Text(tournament.tournamentTitle())
}
HStack {
if match.isGroupStage() {
if let groupStage = match.groupStageObject {
Text(groupStage.groupStageTitle())
} else if let round = match.roundObject {
Text(round.roundTitle())
}
Text(match.matchTitle())
if let roundTitle {
Text(roundTitle)
}
Text(matchTitle)
Spacer()
if let court = match.courtName(), match.hasEnded() == false {
if let courtName {
Spacer()
Text(court)
Text(courtName)
}
}
.lineLimit(1)
}
HStack(spacing: 0) {
VStack(alignment: .leading, spacing: matchViewStyle == .plainStyle ? 8 : 0) {
VStack(alignment: .leading, spacing: spacing) {
PlayerBlockView(match: match, teamPosition: .one, color: color, width: width)
.padding(matchViewStyle == .plainStyle ? 0 : 8)
.padding(padding)
if width == 1 {
Divider()
} else {
Divider().frame(height: width).overlay(color)
}
PlayerBlockView(match: match, teamPosition: .two, color: color, width: width)
.padding(matchViewStyle == .plainStyle ? 0 : 8)
.padding(padding)
}
}
.overlay {
@ -106,149 +116,9 @@ struct MatchSummaryView: View {
}
}
}
.padding(.vertical, matchViewStyle != .plainStyle ? 8 : 0)
.padding(.vertical, padding)
.monospacedDigit()
}
@ViewBuilder
func tournamentHeaderView(_ currentTournament: Tournament) -> some View {
return Text(currentTournament.tournamentTitle())
VStack(alignment: .leading) {
// HStack {
// ZStack(alignment: .leading) {
// Text("Poule 9").font(.title3).bold().opacity(0)
// Text(currentTournament.tournamentLevel.localizedLabel)
// }
// if let tournamentTitle = currentTournament.title, tournamentTitle.isEmpty == false {
// Text(tournamentTitle)
// }
//
// Spacer()
// if let startDate = match.startDate {
// if let endDate = match.endDate {
// let duration = Duration(
// secondsComponent: Int64(endDate.timeIntervalSince(startDate)),
// attosecondsComponent: 0
// ).formatted(.units(allowed: [.hours, .minutes], width: .narrow))
// Text("durée")
// } else if startDate.timeIntervalSinceNow < 0 {
// Text("en cours")
// } else {
// Text("prévu à")
// }
// } else {
// if let endDate = match.endDate {
// Text("a fini à")
// }
// }
// }
//
// HStack {
// ZStack(alignment: .leading) {
// Text("Poule 9").opacity(0)
// Text(match.shortTitleLabel)
// }
// if let score = match.score, match.hasEnded() {
// Text(score.label)
// } else if match.fieldIndex > 0 {
// Text("terrain #\(match.fieldIndex)")
// }
//
// Spacer()
// if let startDate = match.startDate {
// if let endDate = match.endDate {
// let duration = Duration(
// secondsComponent: Int64(endDate.timeIntervalSince(startDate)),
// attosecondsComponent: 0
// ).formatted(.units(allowed: [.hours, .minutes], width: .narrow))
// Text(duration)
// } else if startDate.timeIntervalSinceNow < 0 {
// Text(startDate, style: .timer)
// } else {
// Text(startDate.formatted(date: .omitted, time: .shortened))
// }
// } else {
// if let endDate = match.endDate {
// Text(endDate.formattedAsHourMinute())
// }
// }
// }
// .font(.title3)
//
// HStack {
// ZStack(alignment: .leading) {
// Text("Poule 9").font(.title3).bold().opacity(0)
// VStack {
// Text(currentTournament.tournamentCategory.localizedLabel)
// }
// }
//
// if let winnerEntrant = match.winnerEntrant {
// Text(winnerEntrant.longLabelPlayerOne)
// } else if match.isBracketMatch {
// Text(match.subtitleLabel)
// } else if let entrantOne = match.entrantOne()?.team?.firstPairLastNames {
// Text(entrantOne)
// }
//
// Spacer()
// if let startDate = match.startDate {
// if let endDate = match.endDate {
// let duration = Duration(
// secondsComponent: Int64(endDate.timeIntervalSince(startDate)),
// attosecondsComponent: 0
// ).formatted(.units(allowed: [.hours, .minutes], width: .narrow))
// Text(startDate.formattedAsHourMinute())
// } else if startDate.timeIntervalSinceNow < 0 {
// Text(startDate.formattedAsHourMinute())
// } else {
// Text(startDate.formatted(.dateTime.day().month()))
// }
// } else {
// if let endDate = match.endDate {
// Text("début")
// }
// }
// }
// .font(.caption)
//
// HStack {
// ZStack(alignment: .leading) {
// Text("Poule 9").font(.title3).bold().opacity(0)
// VStack {
// Text(currentTournament.tournamentAgeLabel)
// }
// }
//
// if let winnerEntrant = match.winnerEntrant {
// Text(winnerEntrant.longLabelPlayerTwo)
// } else if match.isBracketMatch {
// } else if let entrant = match.entrantTwo()?.team?.firstPairLastNames {
// Text(entrant)
// }
//
// Spacer()
// if let startDate = match.startDate {
// if let endDate = match.endDate {
// let duration = Duration(
// secondsComponent: Int64(endDate.timeIntervalSince(startDate)),
// attosecondsComponent: 0
// ).formatted(.units(allowed: [.hours, .minutes], width: .narrow))
// Text(startDate.formatted(.dateTime.day().month().year()))
// } else if startDate.timeIntervalSinceNow < 0 {
// Text(startDate.formatted(.dateTime.day().month().year()))
// } else {
// Text(startDate.formatted(.dateTime.year()))
// }
// } else {
// if let endDate = match.endDate {
// Text("non défini")
// }
// }
// }
// .font(.caption)
}
}
}
#Preview {

@ -35,12 +35,12 @@ struct EditablePlayerView: View {
@ViewBuilder
func computedPlayerView(_ player: PlayerRegistration) -> some View {
VStack(alignment: .leading) {
VStack(alignment: .leading, spacing: 0.0) {
ImportedPlayerView(player: player)
HStack {
Text(player.isImported() ? "importé" : "non importé")
Text(player.formattedLicense().isLicenseNumber ? "valide" : "non valide")
}
// HStack {
// Text(player.isImported() ? "importé via beach padel" : "")
// Text(player.formattedLicense().isLicenseNumber ? "licence valide" : "non valide")
// }.font(.footnote)
HStack {
Menu {
if let number = player.phoneNumber?.replacingOccurrences(of: " ", with: ""), let url = URL(string: "tel:\(number)") {

@ -43,6 +43,10 @@ extension LoserRound {
return rounds.flatMap { $0.playedMatches() }.filter({ $0.isRunning() }).count
}
func badgeValueColor() -> Color? {
return nil
}
func badgeImage() -> Badge? {
return rounds.allSatisfy({ $0.hasEnded() }) ? .checkmark : nil
}

@ -34,12 +34,14 @@ struct SelectablePlayerListView: View {
return URL.importDateFormatter.date(from: lastDataSource)
}
init(allowSelection: Int = 0, searchField: String? = nil, user: User? = nil, dataSet: DataSet = .national, filterOption: PlayerFilterOption = .all, hideAssimilation: Bool = false, ascending: Bool = true, sortOption: SortOption = .rank, fromPlayer: FederalPlayer? = nil, codeClub: String? = nil, ligue: String? = nil, showFemaleInMaleAssimilation: Bool = false, playerSelectionAction: PlayerSelectionAction? = nil, contentUnavailableAction: ContentUnavailableAction? = nil) {
init(allowSelection: Int = 0, searchField: String? = nil, user: User? = nil, dataSet: DataSet = .national, filterOption: PlayerFilterOption = .all, hideAssimilation: Bool = false, ascending: Bool = true, sortOption: SortOption = .rank, fromPlayer: FederalPlayer? = nil, codeClub: String? = nil, ligue: String? = nil, showFemaleInMaleAssimilation: Bool = false, tokens: [SearchToken] = [], hidePlayers: [String]? = nil, playerSelectionAction: PlayerSelectionAction? = nil, contentUnavailableAction: ContentUnavailableAction? = nil) {
self.allowSelection = allowSelection
// self.searchText = searchField ?? ""
self.playerSelectionAction = playerSelectionAction
self.contentUnavailableAction = contentUnavailableAction
let searchViewModel = SearchViewModel()
searchViewModel.tokens = tokens
searchViewModel.searchText = searchField ?? ""
searchViewModel.debouncableText = searchField ?? ""
searchViewModel.showFemaleInMaleAssimilation = showFemaleInMaleAssimilation
searchViewModel.searchText = searchField ?? ""
searchViewModel.isPresented = allowSelection != 0
@ -53,6 +55,7 @@ struct SelectablePlayerListView: View {
searchViewModel.hideAssimilation = hideAssimilation
searchViewModel.ascending = ascending
searchViewModel.sortOption = sortOption
searchViewModel.hidePlayers = hidePlayers
_searchViewModel = StateObject(wrappedValue: searchViewModel)
}

@ -10,6 +10,13 @@ import SwiftUI
struct TeamWeightView: View {
var team: TeamRegistration
var teamPosition: TeamPosition? = nil
var teamIndex: Int?
init(team: TeamRegistration, teamPosition: TeamPosition? = nil) {
self.team = team
self.teamPosition = teamPosition
self.teamIndex = team.tournamentObject()?.indexOf(team: team)
}
var body: some View {
VStack(alignment: .trailing, spacing: 0) {
@ -18,8 +25,8 @@ struct TeamWeightView: View {
.monospacedDigit()
.font(.caption)
}
if let teams = team.tournamentObject()?.selectedSortedTeams(), let index = team.index(in: teams) {
Text("#" + (index + 1).formatted(.number.precision(.integerLength(2...3))))
if let teamIndex {
Text("#" + (teamIndex + 1).formatted(.number.precision(.integerLength(2...3))))
.monospacedDigit()
}
if teamPosition == .two {

@ -40,6 +40,10 @@ enum CallDestination: Identifiable, Selectable {
}
}
func badgeValueColor() -> Color? {
return nil
}
func badgeImage() -> Badge? {
switch self {
case .seeds(let tournament):

@ -50,6 +50,10 @@ enum CashierDestination: Identifiable, Selectable {
}
}
func badgeValueColor() -> Color? {
return nil
}
func badgeImage() -> Badge? {
switch self {
case .summary:

@ -42,6 +42,10 @@ enum ScheduleDestination: String, Identifiable, Selectable {
nil
}
func badgeValueColor() -> Color? {
return nil
}
func badgeImage() -> Badge? {
nil
}

@ -32,6 +32,10 @@ enum TournamentSettings: Identifiable, Selectable {
nil
}
func badgeValueColor() -> Color? {
return nil
}
func badgeImage() -> Badge? {
switch self {
case .club(let tournament):

Loading…
Cancel
Save