multistore
Razmig Sarkissian 2 years ago
parent a4ac2b8c8f
commit d131eae629
  1. 12
      PadelClub.xcodeproj/project.pbxproj
  2. 7
      PadelClub/Data/MockData.swift
  3. 11
      PadelClub/Data/PlayerRegistration.swift
  4. 16
      PadelClub/Data/TeamRegistration.swift
  5. 97
      PadelClub/Data/Tournament.swift
  6. 33
      PadelClub/Extensions/Color+Extensions.swift
  7. 30
      PadelClub/Extensions/Date+Extensions.swift
  8. 36
      PadelClub/Manager/Tips.swift
  9. 3
      PadelClub/PadelClubApp.swift
  10. 57
      PadelClub/Views/Event/EventCreationView.swift
  11. 17
      PadelClub/Views/Player/Components/PlayerSexPickerView.swift
  12. 102
      PadelClub/Views/Tournament/FileImportView.swift
  13. 191
      PadelClub/Views/Tournament/Screen/Components/InscriptionInfoView.swift
  14. 62
      PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift
  15. 2
      PadelClub/Views/Tournament/Shared/TournamentCellView.swift
  16. 47
      PadelClub/Views/Tournament/TournamentView.swift
  17. 33
      PadelClub/Views/ViewModifiers/ListRowViewModifier.swift

@ -101,6 +101,9 @@
FF59FFB72B90EFBF0061EFF9 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF59FFB62B90EFBF0061EFF9 /* MainView.swift */; }; FF59FFB72B90EFBF0061EFF9 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF59FFB62B90EFBF0061EFF9 /* MainView.swift */; };
FF59FFB92B90EFD70061EFF9 /* ToolboxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF59FFB82B90EFD70061EFF9 /* ToolboxView.swift */; }; FF59FFB92B90EFD70061EFF9 /* ToolboxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF59FFB82B90EFD70061EFF9 /* ToolboxView.swift */; };
FF5D0D722BB3EFA5005CB568 /* LearnMoreSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D6F2BB3EFA5005CB568 /* LearnMoreSheetView.swift */; }; FF5D0D722BB3EFA5005CB568 /* LearnMoreSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D6F2BB3EFA5005CB568 /* LearnMoreSheetView.swift */; };
FF5D0D742BB41DF8005CB568 /* Color+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D732BB41DF8005CB568 /* Color+Extensions.swift */; };
FF5D0D762BB428B2005CB568 /* ListRowViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D752BB428B2005CB568 /* ListRowViewModifier.swift */; };
FF5D0D782BB42C5B005CB568 /* InscriptionInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5D0D772BB42C5B005CB568 /* InscriptionInfoView.swift */; };
FF6EC8F72B94773200EA7F5A /* RowButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8F62B94773100EA7F5A /* RowButtonView.swift */; }; FF6EC8F72B94773200EA7F5A /* RowButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8F62B94773100EA7F5A /* RowButtonView.swift */; };
FF6EC8FB2B94788600EA7F5A /* TournamentButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8FA2B94788600EA7F5A /* TournamentButtonView.swift */; }; FF6EC8FB2B94788600EA7F5A /* TournamentButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8FA2B94788600EA7F5A /* TournamentButtonView.swift */; };
FF6EC8FE2B94792300EA7F5A /* Screen.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8FD2B94792300EA7F5A /* Screen.swift */; }; FF6EC8FE2B94792300EA7F5A /* Screen.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8FD2B94792300EA7F5A /* Screen.swift */; };
@ -311,6 +314,9 @@
FF59FFB62B90EFBF0061EFF9 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = "<group>"; }; FF59FFB62B90EFBF0061EFF9 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = "<group>"; };
FF59FFB82B90EFD70061EFF9 /* ToolboxView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolboxView.swift; sourceTree = "<group>"; }; FF59FFB82B90EFD70061EFF9 /* ToolboxView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolboxView.swift; sourceTree = "<group>"; };
FF5D0D6F2BB3EFA5005CB568 /* LearnMoreSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LearnMoreSheetView.swift; sourceTree = "<group>"; }; FF5D0D6F2BB3EFA5005CB568 /* LearnMoreSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LearnMoreSheetView.swift; sourceTree = "<group>"; };
FF5D0D732BB41DF8005CB568 /* Color+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Extensions.swift"; sourceTree = "<group>"; };
FF5D0D752BB428B2005CB568 /* ListRowViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRowViewModifier.swift; sourceTree = "<group>"; };
FF5D0D772BB42C5B005CB568 /* InscriptionInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InscriptionInfoView.swift; sourceTree = "<group>"; };
FF6EC8F62B94773100EA7F5A /* RowButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RowButtonView.swift; sourceTree = "<group>"; }; FF6EC8F62B94773100EA7F5A /* RowButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RowButtonView.swift; sourceTree = "<group>"; };
FF6EC8FA2B94788600EA7F5A /* TournamentButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentButtonView.swift; sourceTree = "<group>"; }; FF6EC8FA2B94788600EA7F5A /* TournamentButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentButtonView.swift; sourceTree = "<group>"; };
FF6EC8FD2B94792300EA7F5A /* Screen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Screen.swift; sourceTree = "<group>"; }; FF6EC8FD2B94792300EA7F5A /* Screen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Screen.swift; sourceTree = "<group>"; };
@ -796,6 +802,7 @@
FF8F26482BAE0B4100650388 /* TournamentFormatSelectionView.swift */, FF8F26482BAE0B4100650388 /* TournamentFormatSelectionView.swift */,
FF8F26492BAE0B4100650388 /* TournamentLevelPickerView.swift */, FF8F26492BAE0B4100650388 /* TournamentLevelPickerView.swift */,
FF0EC5212BB173E70056B6D1 /* UpdateSourceRankDateView.swift */, FF0EC5212BB173E70056B6D1 /* UpdateSourceRankDateView.swift */,
FF5D0D772BB42C5B005CB568 /* InscriptionInfoView.swift */,
); );
path = Components; path = Components;
sourceTree = "<group>"; sourceTree = "<group>";
@ -848,6 +855,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
FFDDD40B2B93B2BB00C91A49 /* DeferredViewModifier.swift */, FFDDD40B2B93B2BB00C91A49 /* DeferredViewModifier.swift */,
FF5D0D752BB428B2005CB568 /* ListRowViewModifier.swift */,
); );
path = ViewModifiers; path = ViewModifiers;
sourceTree = "<group>"; sourceTree = "<group>";
@ -877,6 +885,7 @@
FF6EC9032B9479F500EA7F5A /* Sequence+Extensions.swift */, FF6EC9032B9479F500EA7F5A /* Sequence+Extensions.swift */,
FF6EC9082B947A5300EA7F5A /* FixedWidthInteger+Extensions.swift */, FF6EC9082B947A5300EA7F5A /* FixedWidthInteger+Extensions.swift */,
FF6EC90A2B947AC000EA7F5A /* Array+Extensions.swift */, FF6EC90A2B947AC000EA7F5A /* Array+Extensions.swift */,
FF5D0D732BB41DF8005CB568 /* Color+Extensions.swift */,
FFDB1C722BB2CFE900F1E467 /* MySortDescriptor.swift */, FFDB1C722BB2CFE900F1E467 /* MySortDescriptor.swift */,
); );
path = Extensions; path = Extensions;
@ -1089,11 +1098,13 @@
FF8F264F2BAE0B9600650388 /* MatchTypeSelectionView.swift in Sources */, FF8F264F2BAE0B9600650388 /* MatchTypeSelectionView.swift in Sources */,
FF967D062BAF3C4200A9A3BD /* MatchSetupView.swift in Sources */, FF967D062BAF3C4200A9A3BD /* MatchSetupView.swift in Sources */,
FF4AB6B52B9248200002987F /* NetworkManager.swift in Sources */, FF4AB6B52B9248200002987F /* NetworkManager.swift in Sources */,
FF5D0D742BB41DF8005CB568 /* Color+Extensions.swift in Sources */,
C4A47DB12B86375E00ADC637 /* MainUserView.swift in Sources */, C4A47DB12B86375E00ADC637 /* MainUserView.swift in Sources */,
FF7091682B90F79F00AB08DA /* TournamentCellView.swift in Sources */, FF7091682B90F79F00AB08DA /* TournamentCellView.swift in Sources */,
FF6EC9042B9479F500EA7F5A /* Sequence+Extensions.swift in Sources */, FF6EC9042B9479F500EA7F5A /* Sequence+Extensions.swift in Sources */,
C4A47DB32B86387500ADC637 /* AccountView.swift in Sources */, C4A47DB32B86387500ADC637 /* AccountView.swift in Sources */,
FF967CF22BAECC0B00A9A3BD /* TeamScore.swift in Sources */, FF967CF22BAECC0B00A9A3BD /* TeamScore.swift in Sources */,
FF5D0D762BB428B2005CB568 /* ListRowViewModifier.swift in Sources */,
FFD783FD2B91B9ED000F62A6 /* AgendaDestinationPickerView.swift in Sources */, FFD783FD2B91B9ED000F62A6 /* AgendaDestinationPickerView.swift in Sources */,
FF6EC9002B94794700EA7F5A /* PresentationContext.swift in Sources */, FF6EC9002B94794700EA7F5A /* PresentationContext.swift in Sources */,
FFDB1C6D2BB2A02000F1E467 /* AppSettings.swift in Sources */, FFDB1C6D2BB2A02000F1E467 /* AppSettings.swift in Sources */,
@ -1179,6 +1190,7 @@
FF967D032BAEF0C000A9A3BD /* MatchDetailView.swift in Sources */, FF967D032BAEF0C000A9A3BD /* MatchDetailView.swift in Sources */,
FF967D0F2BAF63B000A9A3BD /* PlayerBlockView.swift in Sources */, FF967D0F2BAF63B000A9A3BD /* PlayerBlockView.swift in Sources */,
C4A47D922B7BBBEC00ADC637 /* StoreItem.swift in Sources */, C4A47D922B7BBBEC00ADC637 /* StoreItem.swift in Sources */,
FF5D0D782BB42C5B005CB568 /* InscriptionInfoView.swift in Sources */,
FF4AB6BD2B9256E10002987F /* SelectablePlayerListView.swift in Sources */, FF4AB6BD2B9256E10002987F /* SelectablePlayerListView.swift in Sources */,
FF8F26512BAE0BAD00650388 /* MatchFormatPickerView.swift in Sources */, FF8F26512BAE0BAD00650388 /* MatchFormatPickerView.swift in Sources */,
C4A47DA62B83948E00ADC637 /* LoginView.swift in Sources */, C4A47DA62B83948E00ADC637 /* LoginView.swift in Sources */,

@ -36,6 +36,13 @@ extension Tournament {
let femaleUnrankedValue : Int? = lastDataSourceFemaleUnranked == 0 ? nil : lastDataSourceMaleUnranked let femaleUnrankedValue : Int? = lastDataSourceFemaleUnranked == 0 ? nil : lastDataSourceMaleUnranked
let rankSourceDate = _mostRecentDateAvailable let rankSourceDate = _mostRecentDateAvailable
//todo
/*
tournament.tournamentLevel = TournamentLevel.mostUsed(tournaments: tournaments)
tournament.tournamentCategory = TournamentCategory.mostUsed(tournaments: tournaments)
tournament.federalTournamentAge = FederalTournamentAge.mostUsed(tournaments: tournaments)
*/
return Tournament(groupStageSortMode: .snake, rankSourceDate: rankSourceDate, teamSorting: .inscriptionDate, federalCategory: .men, federalLevelCategory: .p100, federalAgeCategory: .senior, maleUnrankedValue: maleUnrankedValue, femaleUnrankedValue: femaleUnrankedValue) return Tournament(groupStageSortMode: .snake, rankSourceDate: rankSourceDate, teamSorting: .inscriptionDate, federalCategory: .men, federalLevelCategory: .p100, federalAgeCategory: .senior, maleUnrankedValue: maleUnrankedValue, femaleUnrankedValue: femaleUnrankedValue)
} }
} }

@ -130,6 +130,17 @@ class PlayerRegistration: ModelObject, Storable {
} }
} }
func isImported() -> Bool {
source == .beachPadel
}
func isValidLicenseNumber(year: Int) -> Bool {
guard let licenceId else { return false }
guard licenceId.isLicenseNumber else { return false }
guard licenceId.suffix(6) == "(\(year))" else { return false }
return true
}
@objc @objc
var canonicalName: String { var canonicalName: String {
playerLabel().folding(options: .diacriticInsensitive, locale: .current).lowercased() playerLabel().folding(options: .diacriticInsensitive, locale: .current).lowercased()

@ -31,6 +31,7 @@ class TeamRegistration: ModelObject, Storable {
var category: Int? var category: Int?
var weight: Int = 0 var weight: Int = 0
var lockWeight: Int? var lockWeight: Int?
var confirmationDate: Date?
internal init(tournament: String, groupStage: String? = nil, registrationDate: Date? = nil, callDate: Date? = nil, bracketPosition: Int? = nil, groupStagePosition: Int? = nil, comment: String? = nil, source: String? = nil, sourceValue: String? = nil, logo: String? = nil, name: String? = nil, category: Int? = nil) { internal init(tournament: String, groupStage: String? = nil, registrationDate: Date? = nil, callDate: Date? = nil, bracketPosition: Int? = nil, groupStagePosition: Int? = nil, comment: String? = nil, source: String? = nil, sourceValue: String? = nil, logo: String? = nil, name: String? = nil, category: Int? = nil) {
self.tournament = tournament self.tournament = tournament
@ -51,8 +52,16 @@ class TeamRegistration: ModelObject, Storable {
lockWeight ?? weight lockWeight ?? weight
} }
func called() -> Bool {
callDate != nil
}
func confirmed() -> Bool {
confirmationDate != nil
}
func isImported() -> Bool { func isImported() -> Bool {
unsortedPlayers().allSatisfy({ $0.source == .beachPadel }) unsortedPlayers().allSatisfy({ $0.isImported() })
} }
func isWildCard() -> Bool { func isWildCard() -> Bool {
@ -79,6 +88,10 @@ class TeamRegistration: ModelObject, Storable {
$0.clubName?.contains(codeClubOrClubName) == true || $0.clubName?.contains(codeClubOrClubName) == true $0.clubName?.contains(codeClubOrClubName) == true || $0.clubName?.contains(codeClubOrClubName) == true
}) })
} }
func updateWeight() {
setWeight(from: self.players())
}
func teamLabel(_ displayStyle: DisplayStyle = .wide) -> String { func teamLabel(_ displayStyle: DisplayStyle = .wide) -> String {
switch displayStyle { switch displayStyle {
@ -248,6 +261,7 @@ class TeamRegistration: ModelObject, Storable {
case _weight = "weight" case _weight = "weight"
case _walkOut = "walkOut" case _walkOut = "walkOut"
case _lockWeight = "lockWeight" case _lockWeight = "lockWeight"
case _confirmationDate = "confirmationDate"
} }
} }

@ -89,6 +89,10 @@ class Tournament : ModelObject, Storable {
case build case build
} }
func hasStarted() -> Bool {
startDate <= Date()
}
var eventObject: Event? { var eventObject: Event? {
guard let event else { return nil } guard let event else { return nil }
return Store.main.findById(event) return Store.main.findById(event)
@ -122,10 +126,6 @@ class Tournament : ModelObject, Storable {
Store.main.filter { $0.tournament == self.id }.sorted(by: \.index) Store.main.filter { $0.tournament == self.id }.sorted(by: \.index)
} }
var clubName: String? {
nil
}
func sortedTeams() -> [TeamRegistration] { func sortedTeams() -> [TeamRegistration] {
let teams = selectedSortedTeams() let teams = selectedSortedTeams()
return teams + waitingListTeams(in: teams) return teams + waitingListTeams(in: teams)
@ -169,7 +169,8 @@ class Tournament : ModelObject, Storable {
} }
func waitingListTeams(in teams: [TeamRegistration]) -> [TeamRegistration] { func waitingListTeams(in teams: [TeamRegistration]) -> [TeamRegistration] {
Set(unsortedTeams()).subtracting(teams).sorted(using: _defaultSorting(), order: .ascending) let waitingList = Set(unsortedTeams()).subtracting(teams)
return waitingList.filter { $0.walkOut == false }.sorted(using: _defaultSorting(), order: .ascending) + waitingList.filter { $0.walkOut == true }.sorted(using: _defaultSorting(), order: .ascending)
} }
func bracketCut() -> Int { func bracketCut() -> Int {
@ -193,32 +194,9 @@ class Tournament : ModelObject, Storable {
func unsortedTeams() -> [TeamRegistration] { func unsortedTeams() -> [TeamRegistration] {
Store.main.filter { $0.tournament == self.id } Store.main.filter { $0.tournament == self.id }
} }
typealias TeamRegistrationCompare = (TeamRegistration, TeamRegistration) -> Bool
func teams() -> [TeamRegistration] {
Store.main.filter { $0.tournament == self.id }
.sorted { (lhs, rhs) in
let predicates: [TeamRegistrationCompare] = [
{ $0.weight < $1.weight },
{ $0.registrationDate ?? .distantPast < $1.registrationDate ?? .distantPast },
]
for predicate in predicates {
if !predicate(lhs, rhs) && !predicate(rhs, lhs) {
continue
}
return predicate(lhs, rhs)
}
return false
}
}
func duplicates() -> [PlayerRegistration] { func duplicates(in players: [PlayerRegistration]) -> [PlayerRegistration] {
var duplicates = [PlayerRegistration]() var duplicates = [PlayerRegistration]()
let players = unsortedPlayers()
Set(players.compactMap({ $0.licenceId })).forEach { licenceId in Set(players.compactMap({ $0.licenceId })).forEach { licenceId in
let found = players.filter({ $0.licenceId == licenceId }) let found = players.filter({ $0.licenceId == licenceId })
if found.count > 1 { if found.count > 1 {
@ -250,15 +228,62 @@ class Tournament : ModelObject, Storable {
return malePlayer ? maleUnrankedValue : femaleUnrankedValue return malePlayer ? maleUnrankedValue : femaleUnrankedValue
} }
} }
//todo
var clubName: String? {
nil
}
//todo
var rounds: Int { var rounds: Int {
4 4
} }
//todo
func significantPlayerCount() -> Int { func significantPlayerCount() -> Int {
2 2
} }
func inadequatePlayers(in players: [PlayerRegistration]) -> [PlayerRegistration] {
if startDate.isInCurrentYear() == false {
return []
}
return players.filter { player in
if player.rank == nil { return false }
if player.weight <= tournamentLevel.minimumPlayerRank(category: tournamentCategory, ageCategory: federalTournamentAge) {
return true
} else {
return false
}
}
}
func mandatoryRegistrationCloseDate() -> Date? {
switch tournamentLevel {
case .p500, .p1000, .p1500, .p2000:
if let date = Calendar.current.date(byAdding: .day, value: -6, to: startDate) {
let startOfDay = Calendar.current.startOfDay(for: date)
return Calendar.current.date(byAdding: .minute, value: -1, to: startOfDay)
}
default:
break
}
return nil
}
func licenseYearValidity() -> Int {
if startDate.get(.month) > 8 {
return startDate.get(.year) + 1
} else {
return startDate.get(.year)
}
}
func playersWithoutValidLicense(in players: [PlayerRegistration]) -> [PlayerRegistration] {
let licenseYearValidity = licenseYearValidity()
return players.filter({ ($0.isImported() && $0.isValidLicenseNumber(year: licenseYearValidity) == false) || ($0.isImported() == false && ($0.licenceId == nil || $0.licenceId?.isLicenseNumber == false || $0.licenceId?.isEmpty == true)) })
}
func importTeams(_ teams: [FileImportManager.TeamHolder]) { func importTeams(_ teams: [FileImportManager.TeamHolder]) {
var teamsToImport = [TeamRegistration]() var teamsToImport = [TeamRegistration]()
teams.forEach { team in teams.forEach { team in
@ -278,6 +303,10 @@ class Tournament : ModelObject, Storable {
func lockRegistration() { func lockRegistration() {
closedRegistrationDate = Date() closedRegistrationDate = Date()
let count = selectedSortedTeams().count
if teamCount != count {
teamCount = count
}
let teams = unsortedTeams() let teams = unsortedTeams()
teams.forEach { team in teams.forEach { team in
team.lockWeight = team.weight team.lockWeight = team.weight
@ -314,12 +343,6 @@ class Tournament : ModelObject, Storable {
await MainActor.run { await MainActor.run {
self.maleUnrankedValue = lastRankMan self.maleUnrankedValue = lastRankMan
self.femaleUnrankedValue = lastRankWoman self.femaleUnrankedValue = lastRankWoman
// if inscriptionClosed == false {
// orderedEntries.forEach { entrant in
// entrant.weightAtRegistration = entrant.updatedRank
// }
// }
} }
} }
@ -460,7 +483,7 @@ class Tournament : ModelObject, Storable {
return return
} }
let max = groupStages.map { $0.size }.reduce(0,+) let max = groupStages.map { $0.size }.reduce(0,+)
var chunks = teams().filter { $0.wildCardBracket == false }.suffix(max).chunked(into: numberOfBracketsAsInt) var chunks = selectedSortedTeams().suffix(max).chunked(into: numberOfBracketsAsInt)
for (index, _) in chunks.enumerated() { for (index, _) in chunks.enumerated() {
if randomize { if randomize {
chunks[index].shuffle() chunks[index].shuffle()

@ -0,0 +1,33 @@
//
// Color+Extensions.swift
// PadelClub
//
// Created by Razmig Sarkissian on 27/03/2024.
//
import SwiftUI
extension Color {
func variation(withHueOffset hueOffset: Double = 0, saturationFactor: Double = 0.4, brightnessFactor: Double = 0.8, opacity: Double = 0.5) -> Color {
var hue: CGFloat = 0
var saturation: CGFloat = 0
var brightness: CGFloat = 0
var alpha: CGFloat = 0
UIColor(self).getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha)
// Apply adjustments
hue += CGFloat(hueOffset)
saturation *= CGFloat(saturationFactor)
brightness *= CGFloat(brightnessFactor)
alpha *= CGFloat(opacity)
// Clamp values
hue = max(0, min(hue, 1))
saturation = max(0, min(saturation, 1))
brightness = max(0, min(brightness, 1))
alpha = max(0, min(alpha, 1))
return Color(hue: Double(hue), saturation: Double(saturation), brightness: Double(brightness), opacity: Double(alpha))
}
}

@ -12,3 +12,33 @@ extension Date {
formatted(.dateTime.month(.wide).year(.defaultDigits)) formatted(.dateTime.month(.wide).year(.defaultDigits))
} }
} }
extension Date {
func isInCurrentYear() -> Bool {
let calendar = Calendar.current
let currentYear = calendar.component(.year, from: Date())
let yearOfDate = calendar.component(.year, from: self)
return currentYear == yearOfDate
}
func get(_ components: Calendar.Component..., calendar: Calendar = Calendar.current) -> DateComponents {
return calendar.dateComponents(Set(components), from: self)
}
func get(_ component: Calendar.Component, calendar: Calendar = Calendar.current) -> Int {
return calendar.component(component, from: self)
}
var tomorrowAtNine: Date {
let currentHour = Calendar.current.component(.hour, from: self)
let startOfDay = Calendar.current.startOfDay(for: self)
if currentHour < 8 {
return Calendar.current.date(byAdding: .hour, value: 9, to: startOfDay)!
} else {
let date = Calendar.current.date(byAdding: .day, value: 1, to: startOfDay)
return Calendar.current.date(byAdding: .hour, value: 9, to: date!)!
}
}
}

@ -298,6 +298,42 @@ struct SlideToDeleteTip: Tip {
} }
struct MultiTournamentsEventTip: Tip {
var title: Text {
Text("Plusieurs tournois le même week-end ?")
}
var message: Text? {
Text("Padel Club permet de gérer plusieurs tournois ayant lieu en même temps. Un P100 homme et dame par le même week-end par exemple.")
}
var image: Image? {
Image(systemName: "trophy.circle")
}
var actions: [Action] {
Action(id: ActionKey.addEvent.rawValue, title: "Ajoutez une épreuve")
}
enum ActionKey: String {
case addEvent = "add-event"
}
}
struct NotFoundAreWalkOutTip: Tip {
var title: Text {
Text("Gestion des équipes manquantes")
}
var message: Text? {
Text("Si une équipe déjà présente dans votre liste d'attente n'est pas dans le fichier, elle sera mise WO")
}
var image: Image? {
Image(systemName: "person.2.slash.fill")
}
}
struct TipStyleModifier: ViewModifier { struct TipStyleModifier: ViewModifier {
@Environment(\.colorScheme) var colorScheme @Environment(\.colorScheme) var colorScheme
var tint: Color? var tint: Color?

@ -20,6 +20,9 @@ struct PadelClubApp: App {
self._onAppear() self._onAppear()
} }
.task { .task {
//try? Tips.resetDatastore()
try? Tips.configure([ try? Tips.configure([
.displayFrequency(.immediate), .displayFrequency(.immediate),
.datastoreLocation(.applicationDefault) .datastoreLocation(.applicationDefault)

@ -6,16 +6,18 @@
// //
import SwiftUI import SwiftUI
import TipKit
struct EventCreationView: View { struct EventCreationView: View {
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
@EnvironmentObject var dataStore: DataStore @EnvironmentObject var dataStore: DataStore
@State private var eventType: EventType = .approvedTournament @State private var eventType: EventType = .approvedTournament
@State private var animationType: AnimationType = .upAndDown @State private var animationType: AnimationType = .upAndDown
@State private var startingDate: Date = Date() @State private var startingDate: Date = Date().tomorrowAtNine
@State private var duration: Int = 3 @State private var duration: Int = 3
@State private var eventName: String = "" @State private var eventName: String = ""
@State var tournaments: [Tournament] = [] @State var tournaments: [Tournament] = []
let multiTournamentsEventTip = MultiTournamentsEventTip()
var body: some View { var body: some View {
NavigationStack { NavigationStack {
@ -34,6 +36,14 @@ struct EventCreationView: View {
TextField("Nom de l'événement", text: $eventName) TextField("Nom de l'événement", text: $eventName)
} }
Section {
TipView(multiTournamentsEventTip) { action in
let tournament = Tournament.newEmptyInstance()
self.tournaments.append(tournament)
}
.tipStyle(tint: .orange)
}
Section { Section {
DatePicker(selection: $startingDate) { DatePicker(selection: $startingDate) {
Text(startingDate.formatted(.dateTime.weekday(.wide)).capitalized) Text(startingDate.formatted(.dateTime.weekday(.wide)).capitalized)
@ -64,15 +74,27 @@ struct EventCreationView: View {
animationEditorView animationEditorView
} }
} }
.navigationBarTitleDisplayMode(.large)
.toolbar { .toolbar {
ToolbarItem(placement: .cancellationAction) { ToolbarItem(placement: .cancellationAction) {
Button("Annuler", role: .cancel) { Button("Annuler", role: .cancel) {
dismiss() dismiss()
} }
} }
ToolbarItem(placement: .confirmationAction) { ToolbarItem(placement: .topBarTrailing) {
Button("Valider") { Button {
let tournament = Tournament.newEmptyInstance()
self.tournaments.append(tournament)
} label: {
Label("épreuve", systemImage: "plus.circle.fill").labelStyle(.titleAndIcon)
}
.clipShape(Capsule())
.buttonStyle(.bordered)
}
ToolbarItem(placement: .bottomBar) {
Button {
if tournaments.count > 1 || eventName.trimmed.isEmpty == false { if tournaments.count > 1 || eventName.trimmed.isEmpty == false {
let event = Event(name: eventName) let event = Event(name: eventName)
tournaments.forEach { tournament in tournaments.forEach { tournament in
@ -80,11 +102,21 @@ struct EventCreationView: View {
} }
try? dataStore.events.addOrUpdate(instance: event) try? dataStore.events.addOrUpdate(instance: event)
} }
tournaments.forEach { tournament in
tournament.startDate = startingDate
tournament.dayDuration = duration
}
try? dataStore.tournaments.addOrUpdate(contentOfs: tournaments) try? dataStore.tournaments.addOrUpdate(contentOfs: tournaments)
dismiss() dismiss()
} label: {
Text("Valider")
.frame(maxWidth: .infinity)
} }
.clipShape(Capsule()) .font(.headline)
.buttonStyle(.bordered) .buttonStyle(.borderedProminent)
.tint(.launchScreenBackground)
} }
} }
@ -97,26 +129,21 @@ struct EventCreationView: View {
ForEach(tournaments) { tournament in ForEach(tournaments) { tournament in
Section { Section {
TournamentConfigurationView(tournament: tournament) TournamentConfigurationView(tournament: tournament)
} header: { } footer: {
if tournaments.count > 1 { if tournaments.count > 1 {
HStack { HStack {
Spacer() Spacer()
Button { Button(role: .destructive) {
tournaments.removeAll(where: { $0 == tournament }) tournaments.removeAll(where: { $0 == tournament })
// viewContext.delete(tournament)
} label: { } label: {
Text("effacer") LabelDelete()
} }
.textCase(nil) .textCase(nil)
.font(.caption)
} }
} }
} }
} }
RowButtonView(title: "Ajouter une \((tournaments.count + 1).ordinalFormatted()) épreuve") {
let tournament = Tournament.newEmptyInstance()
self.tournaments.append(tournament)
}
} }
@ViewBuilder @ViewBuilder

@ -8,6 +8,8 @@
import SwiftUI import SwiftUI
struct PlayerSexPickerView: View { struct PlayerSexPickerView: View {
@EnvironmentObject var dataStore: DataStore
@Environment(Tournament.self) var tournament: Tournament
@Bindable var player: PlayerRegistration @Bindable var player: PlayerRegistration
var body: some View { var body: some View {
@ -23,16 +25,20 @@ struct PlayerSexPickerView: View {
.pickerStyle(.segmented) .pickerStyle(.segmented)
.fixedSize() .fixedSize()
.onChange(of: player.sex) { .onChange(of: player.sex) {
save() _save()
} }
} }
} }
func save() { private func _save() {
do { do {
// player.objectWillChange.send() player.setWeight(in: tournament)
// player.team?.entrant?.tournament?.objectWillChange.send() try dataStore.playerRegistrations.addOrUpdate(instance: player)
// try viewContext.save() if let team = player.team() {
team.updateWeight()
try dataStore.teamRegistrations.addOrUpdate(instance: team)
}
} catch { } catch {
// Replace this implementation with code to handle the error appropriately. // 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. // 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.
@ -45,4 +51,5 @@ struct PlayerSexPickerView: View {
#Preview { #Preview {
PlayerSexPickerView(player: PlayerRegistration.mock()) PlayerSexPickerView(player: PlayerRegistration.mock())
.environment(Tournament.mock())
} }

@ -6,6 +6,7 @@
// //
import SwiftUI import SwiftUI
import TipKit
struct FileImportView: View { struct FileImportView: View {
@EnvironmentObject var dataStore: DataStore @EnvironmentObject var dataStore: DataStore
@ -13,7 +14,8 @@ struct FileImportView: View {
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
let fileContent: String? let fileContent: String?
let notFoundAreWalkOutTip = NotFoundAreWalkOutTip()
@State private var teams: [FileImportManager.TeamHolder] = [] @State private var teams: [FileImportManager.TeamHolder] = []
@State private var isShowing = false @State private var isShowing = false
@State private var didImport = false @State private var didImport = false
@ -24,7 +26,6 @@ struct FileImportView: View {
@State private var selectedOptions: Set<TeamImportStrategy> = Set() @State private var selectedOptions: Set<TeamImportStrategy> = Set()
@State private var fileProvider: FileImportManager.FileProvider = .frenchFederation @State private var fileProvider: FileImportManager.FileProvider = .frenchFederation
let federalLink = URL(string: "https://beach-padel.app.fft.fr/beachja/index/")!
private var filteredTeams: [FileImportManager.TeamHolder] { private var filteredTeams: [FileImportManager.TeamHolder] {
return teams.filter { $0.tournamentCategory == tournament.tournamentCategory }.sorted(by: \.weight) return teams.filter { $0.tournamentCategory == tournament.tournamentCategory }.sorted(by: \.weight)
@ -35,8 +36,8 @@ struct FileImportView: View {
if teams.isEmpty { if teams.isEmpty {
Section { Section {
Link(destination: federalLink) { Link(destination: SourceFileManager.beachPadel) {
Label("Ouvrir beach-padel.app.fft.fr", systemImage: "tennisball") Label("beach-padel.app.fft.fr", systemImage: "tennisball")
} }
Button { Button {
@ -48,37 +49,34 @@ struct FileImportView: View {
} header: { } header: {
} footer: { } footer: {
VStack(alignment: .leading) { Text("Fichier provenant de beach-padel.app.fft.fr")
Text("Fichier provenant de beach-padel.app.fft.fr")
Text("Format XLS ou CSV, onglet inscriptions ou joueurs")
}
} }
} }
if filteredTeams.isEmpty == false && tournament.unsortedTeams().isEmpty == false { // if filteredTeams.isEmpty == false && tournament.unsortedTeams().isEmpty == false {
Section { // Section {
ForEach(TeamImportStrategy.allCases, id: \.self) { strategy in // ForEach(TeamImportStrategy.allCases, id: \.self) { strategy in
LabeledContent { // LabeledContent {
Toggle(isOn: .init(get: { // Toggle(isOn: .init(get: {
selectedOptions.contains(strategy) // selectedOptions.contains(strategy)
}, set: { selected in // }, set: { selected in
if selected { // if selected {
selectedOptions.insert(strategy) // selectedOptions.insert(strategy)
} else { // } else {
selectedOptions.remove(strategy) // selectedOptions.remove(strategy)
//
} // }
})) {} // })) {}
} label: { // } label: {
Text(strategy.titleLabel()) // Text(strategy.titleLabel())
Text(strategy.descriptionLabel()) // Text(strategy.descriptionLabel())
} // }
//
} // }
} header: { // } header: {
Text("Stratégie d'importation") // Text("Stratégie d'importation")
} // }
} // }
if convertingFile { if convertingFile {
Section { Section {
@ -125,7 +123,7 @@ struct FileImportView: View {
Text("Modifier la catégorie du tournoi ?") Text("Modifier la catégorie du tournoi ?")
} }
.onChange(of: tournament.tournamentCategory) { .onChange(of: tournament.tournamentCategory) {
save() _save()
} }
} }
} else if teams.isEmpty && didImport == true { } else if teams.isEmpty && didImport == true {
@ -133,16 +131,24 @@ struct FileImportView: View {
ContentUnavailableView("Aucune équipe détectée", systemImage: "person.2.slash") ContentUnavailableView("Aucune équipe détectée", systemImage: "person.2.slash")
} }
} else if didImport { } else if didImport {
let _filteredTeams = filteredTeams
let previousTeams = tournament.sortedTeams()
if previousTeams.isEmpty == false {
Section {
TipView(notFoundAreWalkOutTip)
.tipStyle(tint: nil)
}
}
Section { Section {
let previousTeams = tournament.teams() ForEach(_filteredTeams) { team in
ForEach(filteredTeams) { team in
LabeledContent { LabeledContent {
HStack { HStack {
if let previousTeam = team.previousTeam { if let previousTeam = team.previousTeam {
Text(previousTeam.formattedSeed(in: previousTeams)) Text(previousTeam.formattedSeed(in: previousTeams))
Image(systemName: "arrowshape.forward.fill")
} }
Text("->") Text(team.formattedSeed(in: _filteredTeams))
Text(team.formattedSeed(in: filteredTeams))
} }
} label: { } label: {
VStack(alignment: .leading) { VStack(alignment: .leading) {
@ -153,9 +159,9 @@ struct FileImportView: View {
} }
} header: { } header: {
HStack { HStack {
Text("Équipes \(tournament.tournamentCategory.importingRawValue) détectées dans ce fichier") Text("Équipe\(_filteredTeams.count.pluralSuffix) \(tournament.tournamentCategory.importingRawValue) détectée\(_filteredTeams.count.pluralSuffix)")
Spacer() Spacer()
Text(filteredTeams.count.formatted()) Text(_filteredTeams.count.formatted())
} }
} }
} }
@ -211,11 +217,8 @@ struct FileImportView: View {
errorMessage = error.localizedDescription errorMessage = error.localizedDescription
} }
} }
.navigationTitle("Import") .navigationTitle("Importation")
.navigationBarTitleDisplayMode(.large) .navigationBarTitleDisplayMode(.large)
.onDisappear {
//viewContext.rollback()
}
.toolbar { .toolbar {
ToolbarItem(placement: .cancellationAction) { ToolbarItem(placement: .cancellationAction) {
Button("Annuler", role: .cancel) { Button("Annuler", role: .cancel) {
@ -223,13 +226,13 @@ struct FileImportView: View {
} }
} }
ToolbarItem(placement: .bottomBar) { ToolbarItem(placement: .topBarTrailing) {
Button { Button {
if selectedOptions.contains(.deleteBeforeImport) { // remove all previous teams if false { //selectedOptions.contains(.deleteBeforeImport)
try? dataStore.teamRegistrations.delete(contentOfs: tournament.unsortedTeams()) try? dataStore.teamRegistrations.delete(contentOfs: tournament.unsortedTeams())
} }
if selectedOptions.contains(.notFoundAreWalkOut) { if true { //selectedOptions.contains(.notFoundAreWalkOut)
let previousTeams = filteredTeams.compactMap({ $0.previousTeam }) let previousTeams = filteredTeams.compactMap({ $0.previousTeam })
let unfound = Set(tournament.unsortedTeams()).subtracting(Set(previousTeams)) let unfound = Set(tournament.unsortedTeams()).subtracting(Set(previousTeams))
@ -249,7 +252,8 @@ struct FileImportView: View {
} label: { } label: {
Text("Valider") Text("Valider")
} }
.buttonStyle(.borderedProminent) .buttonStyle(.bordered)
.clipShape(Capsule())
.disabled(teams.isEmpty) .disabled(teams.isEmpty)
} }
} }
@ -267,8 +271,8 @@ struct FileImportView: View {
} }
} }
func save() { private func _save() {
try? dataStore.tournaments.addOrUpdate(instance: tournament)
} }
} }

@ -0,0 +1,191 @@
//
// InscriptionInfoView.swift
// PadelClub
//
// Created by Razmig Sarkissian on 27/03/2024.
//
import SwiftUI
struct InscriptionInfoView: View {
@Environment(Tournament.self) var tournament
@State private var duplicates = [PlayerRegistration]()
@State private var problematicPlayers = [PlayerRegistration]()
@State private var inadequatePlayers = [PlayerRegistration]()
@State private var playersWithoutValidLicense = [PlayerRegistration]()
@State private var entriesNotFromBeachPadel = [TeamRegistration]()
@State private var playersMissing = [TeamRegistration]()
@State private var waitingList = [TeamRegistration]()
@State private var selectedTeams = [TeamRegistration]()
var body: some View {
List {
let waitingListInBracket = waitingList.filter({ $0.bracketPosition != nil })
let waitingListInGroupStage = waitingList.filter({ $0.groupStage != nil })
Section {
DisclosureGroup {
ForEach(waitingListInBracket) { team in
TeamRowView(team: team)
}
} label: {
LabeledContent {
Text(waitingListInBracket.count.formatted())
} label: {
Text("Dans le tableau")
}
}
.listRowView(color: .red)
DisclosureGroup {
ForEach(waitingListInGroupStage) { team in
TeamRowView(team: team)
}
} label: {
LabeledContent {
Text(waitingListInGroupStage.count.formatted())
} label: {
Text("En poule")
}
}
.listRowView(color: .red)
} header: {
Text("Équipes non sélectionnées")
} footer: {
Text("Il s'agit des équipes déjà placé en poule ou tableau qui sont actuellement en attente à cause de l'arrivée d'une nouvelle équipe ou une modification de classement.")
}
Section {
DisclosureGroup {
ForEach(duplicates) { player in
ImportedPlayerView(player: player)
}
} label: {
LabeledContent {
Text(duplicates.count.formatted())
} label: {
Text("Doublons")
}
}
.listRowView(color: .red)
}
Section {
DisclosureGroup {
ForEach(problematicPlayers) { player in
PlayerSexPickerView(player: player)
}
} label: {
LabeledContent {
Text(problematicPlayers.count.formatted())
} label: {
Text("Joueurs problématiques")
}
}
.listRowView(color: .purple)
} footer: {
Text("Il s'agit des joueurs ou joueuses dont le sexe n'a pas pu être déterminé")
}
Section {
DisclosureGroup {
ForEach(inadequatePlayers) { player in
ImportedPlayerView(player: player)
}
} label: {
LabeledContent {
Text(inadequatePlayers.count.formatted())
} label: {
let playerLabel : String = tournament.tournamentCategory == .women ? "joueuse" : "joueur"
let grammarSuffix : String = tournament.tournamentCategory == .women ? "e" + inadequatePlayers.count.pluralSuffix : inadequatePlayers.count.pluralSuffix
Text(playerLabel.capitalized + inadequatePlayers.count.pluralSuffix + " trop bien classé" + grammarSuffix)
}
}
.listRowView(color: .red)
} footer: {
Text("Il s'agit des joueurs ou joueuses dont le rang est inférieur à la limite fédérale.")
}
Section {
DisclosureGroup {
ForEach(playersWithoutValidLicense) {
ImportedPlayerView(player: $0)
}
} label: {
LabeledContent {
Text(playersWithoutValidLicense.count.formatted())
} label: {
Text("Joueurs sans licence valide")
}
}
.listRowView(color: .orange)
} footer: {
Text("importé du fichier beach-padel sans licence valide ou créé sans licence")
}
Section {
DisclosureGroup {
ForEach(playersMissing) {
TeamRowView(team: $0)
}
} label: {
LabeledContent {
Text(playersMissing.count.formatted())
} label: {
Text("Paires incomplètes")
}
}
.listRowView(color: .pink)
}
Section {
LabeledContent {
Text(entriesNotFromBeachPadel.count.formatted())
} label: {
Text("Paires importées")
Text(SourceFileManager.beachPadel.absoluteString)
}
.listRowView(color: .indigo)
LabeledContent {
Text(selectedTeams.filter { $0.called() }.count.formatted())
} label: {
Text("Paires convoquées")
Text("Vous avez envoyé une convocation par sms ou email")
}
.listRowView(color: .cyan)
LabeledContent {
Text(selectedTeams.filter { $0.confirmed() }.count.formatted())
} label: {
Text("Paires ayant confirmées")
Text("Vous avez noté la confirmation de l'équipe")
}
.listRowView(color: .green)
}
}
.navigationTitle("Synthèse")
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.visible, for: .navigationBar)
.onAppear {
_initData()
}
}
private func _initData() {
let players = tournament.unsortedPlayers()
selectedTeams = tournament.selectedSortedTeams()
waitingList = tournament.waitingListTeams(in: selectedTeams)
duplicates = tournament.duplicates(in: players)
problematicPlayers = players.filter({ $0.sex == -1 })
inadequatePlayers = tournament.inadequatePlayers(in: players)
playersWithoutValidLicense = tournament.playersWithoutValidLicense(in: players)
entriesNotFromBeachPadel = selectedTeams.filter({ $0.isImported() })
playersMissing = selectedTeams.filter({ $0.unsortedPlayers().count < 2 })
}
}
#Preview {
InscriptionInfoView()
.environment(Tournament.mock())
}

@ -145,18 +145,14 @@ struct InscriptionManagerView: View {
} }
Divider() Divider()
Button { Button {
let count = tournament.unsortedTeams().count tournament.lockRegistration()
if tournament.teamCount > count {
tournament.teamCount = count
}
tournament.closedRegistrationDate = Date()
_save() _save()
} label: { } label: {
Label("Clôturer", systemImage: "lock") Label("Clôturer", systemImage: "lock")
} }
Divider() Divider()
ShareLink(item: tournament.pasteDataForImporting()) { ShareLink(item: tournament.pasteDataForImporting()) {
Text("Exporter les paires") Label("Exporter les paires", systemImage: "square.and.arrow.up")
} }
Button { Button {
presentImportView = true presentImportView = true
@ -196,30 +192,14 @@ struct InscriptionManagerView: View {
private func _teamRegisteredView() -> some View { private func _teamRegisteredView() -> some View {
List { List {
Section { let unfilteredTeams = tournament.sortedTeams()
_rankHandlerView()
let duplicates = tournament.duplicates() if presentSearch == false {
DisclosureGroup { _rankHandlerView()
if duplicates.isEmpty == false { _relatedTips()
ForEach(duplicates) { player in _informationView(count: unfilteredTeams.count)
PlayerView(player: player)
}
}
} label: {
LabeledContent {
Text(duplicates.count.formatted())
} label: {
Text("Doublons")
}
}
} header: {
Text("Informations")
} }
_relatedTips()
let unfilteredTeams = tournament.sortedTeams()
let teams = searchField.isEmpty ? unfilteredTeams : unfilteredTeams.filter({ $0.contains(searchField.canonicalVersion) }) let teams = searchField.isEmpty ? unfilteredTeams : unfilteredTeams.filter({ $0.contains(searchField.canonicalVersion) })
if teams.isEmpty && searchField.isEmpty == false { if teams.isEmpty && searchField.isEmpty == false {
@ -285,8 +265,12 @@ struct InscriptionManagerView: View {
PasteButton(payloadType: String.self) { strings in PasteButton(payloadType: String.self) { strings in
guard let first = strings.first else { return } guard let first = strings.first else { return }
fetchPlayers.nsPredicate = _pastePredicate(pasteField: first, mostRecentDate: SourceFileManager.shared.mostRecentDateAvailable) Task {
pasteString = first await MainActor.run {
fetchPlayers.nsPredicate = _pastePredicate(pasteField: first, mostRecentDate: SourceFileManager.shared.mostRecentDateAvailable)
pasteString = first
}
}
} }
Button { Button {
@ -422,9 +406,27 @@ struct InscriptionManagerView: View {
} }
} }
private func _informationView(count: Int) -> some View {
Section {
NavigationLink {
InscriptionInfoView()
.environment(tournament)
} label: {
LabeledContent {
Text(count.formatted() + "/" + tournament.teamCount.formatted())
} label: {
Text("Analyse des inscriptions")
if let closedRegistrationDate = tournament.closedRegistrationDate {
Text("clôturé le " + closedRegistrationDate.formatted())
}
}
}
}
}
@ViewBuilder @ViewBuilder
private func _relatedTips() -> some View { private func _relatedTips() -> some View {
if pasteString?.isEmpty == true if pasteString == nil
&& createdPlayerIds.isEmpty && createdPlayerIds.isEmpty
&& tournament.unsortedTeams().count >= tournament.teamCount && tournament.unsortedTeams().count >= tournament.teamCount
&& tournament.unsortedPlayers().filter({ $0.source == .beachPadel }).isEmpty { && tournament.unsortedPlayers().filter({ $0.source == .beachPadel }).isEmpty {

@ -14,7 +14,7 @@ struct TournamentCellView: View {
var body: some View { var body: some View {
HStack(alignment: .top) { HStack(alignment: .top) {
DateBoxView(date: Date()) DateBoxView(date: tournament.startDate)
Rectangle() Rectangle()
.fill(color) .fill(color)
.frame(width: 2) .frame(width: 2)

@ -30,13 +30,31 @@ struct TournamentView: View {
} }
NavigationLink(value: Screen.inscription) { Section {
LabeledContent { NavigationLink(value: Screen.inscription) {
Text(tournament.unsortedTeams().count.formatted()) LabeledContent {
} label: { Text(tournament.unsortedTeams().count.formatted() + "/" + tournament.teamCount.formatted())
Text("Inscriptions") } label: {
Text("Gestion des inscriptions")
if let closedRegistrationDate = tournament.closedRegistrationDate {
Text("clôturé le " + closedRegistrationDate.formatted(date: .abbreviated, time: .shortened))
}
}
}
if let endOfInscriptionDate = tournament.mandatoryRegistrationCloseDate(), tournament.inscriptionClosed() == false && tournament.hasStarted() == false {
LabeledContent {
Text(endOfInscriptionDate.formatted(date: .abbreviated, time: .shortened))
} label: {
Text("Date limite")
}
if endOfInscriptionDate < Date() {
RowButtonView(title: "Clôturer les inscriptions") {
tournament.lockRegistration()
_save()
}
}
} }
} }
switch tournament.state() { switch tournament.state() {
@ -45,11 +63,6 @@ struct TournamentView: View {
case .build: case .build:
TournamentRunningView() TournamentRunningView()
} }
// InscriptionManagerRowView(tournament: tournament)
// NavigationLink(value: Screen.groupStage) {
// Text("Poules")
// .badge(2)
// }
} }
.toolbarBackground(.visible, for: .navigationBar) .toolbarBackground(.visible, for: .navigationBar)
.navigationDestination(for: Screen.self, destination: { screen in .navigationDestination(for: Screen.self, destination: { screen in
@ -102,15 +115,9 @@ struct TournamentView: View {
} }
} }
} }
struct InscriptionManagerRowView: View { private func _save() {
let tournament: Tournament try? dataStore.tournaments.addOrUpdate(instance: tournament)
var body: some View {
NavigationLink(value: Screen.inscription) {
Text("Inscriptions")
.badge(24)
}
}
} }
} }

@ -0,0 +1,33 @@
//
// ListRowViewModifier.swift
// PadelClub
//
// Created by Razmig Sarkissian on 27/03/2024.
//
import SwiftUI
struct ListRowViewModifier: ViewModifier {
@State private var isActived = true
let color: Color
func body(content: Content) -> some View {
if isActived {
content
.listRowBackground(
color.variation()
.overlay(alignment: .leading, content: {
color.frame(width: 8)
})
)
} else {
content
}
}
}
extension View {
func listRowView(color: Color) -> some View {
modifier(ListRowViewModifier(color: color))
}
}
Loading…
Cancel
Save