multistore
Laurent 2 years ago
commit 8a2197bbfd
  1. 36
      PadelClub.xcodeproj/project.pbxproj
  2. 36
      PadelClub/Data/GroupStage.swift
  3. 11
      PadelClub/Data/Match.swift
  4. 16
      PadelClub/Data/MockData.swift
  5. 17
      PadelClub/Data/Round.swift
  6. 69
      PadelClub/Data/Tournament.swift
  7. 0
      PadelClub/Utils/CloudConvert.swift
  8. 0
      PadelClub/Utils/ContactManager.swift
  9. 0
      PadelClub/Utils/DisplayContext.swift
  10. 0
      PadelClub/Utils/FileImportManager.swift
  11. 0
      PadelClub/Utils/Key.swift
  12. 0
      PadelClub/Utils/LocationManager.swift
  13. 0
      PadelClub/Utils/Network/NetworkFederalService.swift
  14. 0
      PadelClub/Utils/Network/NetworkManager.swift
  15. 0
      PadelClub/Utils/Network/NetworkManagerError.swift
  16. 0
      PadelClub/Utils/NetworkMonitor.swift
  17. 0
      PadelClub/Utils/PadelRule.swift
  18. 0
      PadelClub/Utils/SourceFileManager.swift
  19. 0
      PadelClub/Utils/SwiftParser.swift
  20. 0
      PadelClub/Utils/Tips.swift
  21. 0
      PadelClub/Utils/URLs.swift
  22. 2
      PadelClub/Views/Cashier/CashierDetailView.swift
  23. 16
      PadelClub/Views/Cashier/CashierView.swift
  24. 8
      PadelClub/Views/Components/GenericDestinationPickerView.swift
  25. 4
      PadelClub/Views/Components/MatchListView.swift
  26. 209
      PadelClub/Views/GroupStage/GroupStageSettingsView.swift
  27. 56
      PadelClub/Views/GroupStage/GroupStageTeamView.swift
  28. 292
      PadelClub/Views/GroupStage/GroupStageView.swift
  29. 8
      PadelClub/Views/GroupStage/GroupStagesView.swift
  30. 128
      PadelClub/Views/Match/MatchDetailView.swift
  31. 15
      PadelClub/Views/Navigation/Agenda/EventListView.swift
  32. 7
      PadelClub/Views/Player/Components/EditablePlayerView.swift
  33. 20
      PadelClub/Views/Round/RoundSettingsView.swift
  34. 7
      PadelClub/Views/Score/EditScoreView.swift
  35. 1
      PadelClub/Views/Team/Components/TeamWeightView.swift
  36. 22
      PadelClub/Views/Tournament/Screen/BroadcastView.swift
  37. 2
      PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift
  38. 66
      PadelClub/Views/Tournament/Screen/Components/TournamentStatusView.swift
  39. 2
      PadelClub/Views/Tournament/Screen/Screen.swift
  40. 14
      PadelClub/Views/Tournament/Screen/TournamentCashierView.swift
  41. 106
      PadelClub/Views/Tournament/Screen/TournamentRankView.swift
  42. 9
      PadelClub/Views/Tournament/Screen/TournamentSettingsView.swift
  43. 2
      PadelClub/Views/Tournament/TournamentRunningView.swift
  44. 23
      PadelClub/Views/Tournament/TournamentView.swift
  45. 8
      PadelClub/Views/ViewModifiers/ListRowViewModifier.swift

@ -22,8 +22,8 @@
C49EF0392BDFF4600077B5AA /* LeStorage.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C49EF0372BDFF3000077B5AA /* LeStorage.framework */; };
C49EF03A2BDFF4600077B5AA /* LeStorage.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C49EF0372BDFF3000077B5AA /* LeStorage.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
C49EF03C2BE15AF80077B5AA /* String+Crypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EF03B2BE15AF80077B5AA /* String+Crypto.swift */; };
C49EF03E2BE160720077B5AA /* Key.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EF03D2BE160720077B5AA /* Key.swift */; };
C49EF0422BE23BF50077B5AA /* PaymentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EF0412BE23BF50077B5AA /* PaymentTests.swift */; };
C49EF0442BE286780077B5AA /* Key.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49EF0432BE286780077B5AA /* Key.swift */; };
C4A47D5A2B6D383C00ADC637 /* Tournament.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D592B6D383C00ADC637 /* Tournament.swift */; };
C4A47D5E2B6D38EC00ADC637 /* DataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D5D2B6D38EC00ADC637 /* DataStore.swift */; };
C4A47D632B6D3D6500ADC637 /* Club.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D622B6D3D6500ADC637 /* Club.swift */; };
@ -140,6 +140,7 @@
FF59FFB72B90EFBF0061EFF9 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF59FFB62B90EFBF0061EFF9 /* MainView.swift */; };
FF59FFB92B90EFD70061EFF9 /* ToolboxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF59FFB82B90EFD70061EFF9 /* ToolboxView.swift */; };
FF5BAF6E2BE0B3C8008B4B7E /* FederalDataViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5BAF6D2BE0B3C8008B4B7E /* FederalDataViewModel.swift */; };
FF5BAF722BE19274008B4B7E /* TournamentRankView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5BAF712BE19274008B4B7E /* TournamentRankView.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 */; };
@ -155,6 +156,8 @@
FF5DA1932BB9279B00A33061 /* RoundSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA1922BB9279B00A33061 /* RoundSettingsView.swift */; };
FF5DA1952BB927E800A33061 /* GenericDestinationPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA1942BB927E800A33061 /* GenericDestinationPickerView.swift */; };
FF5DA19B2BB9662200A33061 /* TournamentSeedEditing.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF5DA19A2BB9662200A33061 /* TournamentSeedEditing.swift */; };
FF6087EA2BE25EF1004E1E47 /* TournamentStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6087E92BE25EF1004E1E47 /* TournamentStatusView.swift */; };
FF6087EC2BE26A2F004E1E47 /* BroadcastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6087EB2BE26A2F004E1E47 /* BroadcastView.swift */; };
FF663FBE2BE019EC0031AE83 /* TournamentFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF663FBD2BE019EC0031AE83 /* TournamentFilterView.swift */; };
FF6EC8F72B94773200EA7F5A /* RowButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8F62B94773100EA7F5A /* RowButtonView.swift */; };
FF6EC8FB2B94788600EA7F5A /* TournamentButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF6EC8FA2B94788600EA7F5A /* TournamentButtonView.swift */; };
@ -315,8 +318,8 @@
C49EF0252BD80AE80077B5AA /* SubscriptionInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionInfoView.swift; sourceTree = "<group>"; };
C49EF0372BDFF3000077B5AA /* LeStorage.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = LeStorage.framework; sourceTree = BUILT_PRODUCTS_DIR; };
C49EF03B2BE15AF80077B5AA /* String+Crypto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Crypto.swift"; sourceTree = "<group>"; };
C49EF03D2BE160720077B5AA /* Key.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Key.swift; sourceTree = "<group>"; };
C49EF0412BE23BF50077B5AA /* PaymentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentTests.swift; sourceTree = "<group>"; };
C49EF0432BE286780077B5AA /* Key.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Key.swift; sourceTree = "<group>"; };
C4A47D592B6D383C00ADC637 /* Tournament.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tournament.swift; sourceTree = "<group>"; };
C4A47D5D2B6D38EC00ADC637 /* DataStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataStore.swift; sourceTree = "<group>"; };
C4A47D622B6D3D6500ADC637 /* Club.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Club.swift; sourceTree = "<group>"; };
@ -433,6 +436,7 @@
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>"; };
FF5BAF6D2BE0B3C8008B4B7E /* FederalDataViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FederalDataViewModel.swift; sourceTree = "<group>"; };
FF5BAF712BE19274008B4B7E /* TournamentRankView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentRankView.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>"; };
@ -448,6 +452,8 @@
FF5DA1922BB9279B00A33061 /* RoundSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundSettingsView.swift; sourceTree = "<group>"; };
FF5DA1942BB927E800A33061 /* GenericDestinationPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericDestinationPickerView.swift; sourceTree = "<group>"; };
FF5DA19A2BB9662200A33061 /* TournamentSeedEditing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentSeedEditing.swift; sourceTree = "<group>"; };
FF6087E92BE25EF1004E1E47 /* TournamentStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentStatusView.swift; sourceTree = "<group>"; };
FF6087EB2BE26A2F004E1E47 /* BroadcastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BroadcastView.swift; sourceTree = "<group>"; };
FF663FBD2BE019EC0031AE83 /* TournamentFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentFilterView.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>"; };
@ -619,7 +625,7 @@
C4A47D722B72881500ADC637 /* Views */,
FF3F74FD2B91A087004CFE0E /* ViewModel */,
C4A47D5F2B6D3B2D00ADC637 /* Data */,
FFF8ACD02B9238A2008466FA /* Manager */,
FFF8ACD02B9238A2008466FA /* Utils */,
FFF8ACD72B923F26008466FA /* Extensions */,
C425D4042B6D249E002A7B48 /* Assets.xcassets */,
FF0EC54D2BB195CA0056B6D1 /* CSV */,
@ -935,6 +941,8 @@
FF0E0B6C2BC254C6005F00A9 /* TournamentScheduleView.swift */,
FF9268062BCE94D90080F940 /* TournamentCallView.swift */,
FF1162802BCF945C000C4809 /* TournamentCashierView.swift */,
FF5BAF712BE19274008B4B7E /* TournamentRankView.swift */,
FF6087EB2BE26A2F004E1E47 /* BroadcastView.swift */,
FF8F26522BAE0E4E00650388 /* Components */,
);
path = Screen;
@ -1066,6 +1074,7 @@
FF025AE02BD0EB9000A86CF8 /* TournamentClubSettingsView.swift */,
FF025AE22BD0EBA900A86CF8 /* TournamentMatchFormatsSettingsView.swift */,
FF025AE42BD0EBB800A86CF8 /* TournamentGeneralSettingsView.swift */,
FF6087E92BE25EF1004E1E47 /* TournamentStatusView.swift */,
);
path = Components;
sourceTree = "<group>";
@ -1173,15 +1182,14 @@
path = Components;
sourceTree = "<group>";
};
FFF8ACD02B9238A2008466FA /* Manager */ = {
FFF8ACD02B9238A2008466FA /* Utils */ = {
isa = PBXGroup;
children = (
FF6EC9072B947A1E00EA7F5A /* Network */,
FFA6D7862BB0B7A2003A31F3 /* CloudConvert.swift */,
FF92680A2BCEE3E10080F940 /* ContactManager.swift */,
FF1DC55A2BAB80C400FD8220 /* DisplayContext.swift */,
FFA6D7842BB0B795003A31F3 /* FileImportManager.swift */,
C49EF03D2BE160720077B5AA /* Key.swift */,
C49EF0432BE286780077B5AA /* Key.swift */,
FFC1E1072BAC29FC008D6F59 /* LocationManager.swift */,
FF92680C2BCEE5EA0080F940 /* NetworkMonitor.swift */,
FF8F26352BAD523300650388 /* PadelRule.swift */,
@ -1189,8 +1197,9 @@
FF0EC51D2BB16F680056B6D1 /* SwiftParser.swift */,
FF1DC5582BAB767000FD8220 /* Tips.swift */,
C49EF01A2BD6A1E80077B5AA /* URLs.swift */,
FF6EC9072B947A1E00EA7F5A /* Network */,
);
path = Manager;
path = Utils;
sourceTree = "<group>";
};
FFF8ACD72B923F26008466FA /* Extensions */ = {
@ -1453,6 +1462,7 @@
FF2EFBF02BDE295E0049CE3B /* SendToAllView.swift in Sources */,
FF8F263B2BAD528600650388 /* EventCreationView.swift in Sources */,
FFC1E1082BAC29FC008D6F59 /* LocationManager.swift in Sources */,
FF6087EC2BE26A2F004E1E47 /* BroadcastView.swift in Sources */,
FFF964552BC266CF00EEF017 /* SchedulerView.swift in Sources */,
FFA1B1292BB71773006CE248 /* PadelClubButtonView.swift in Sources */,
FF5DA19B2BB9662200A33061 /* TournamentSeedEditing.swift in Sources */,
@ -1503,6 +1513,7 @@
FFC1E10C2BAC7FB0008D6F59 /* ClubImportView.swift in Sources */,
FF3B60A32BC49BBC008C2E66 /* MatchScheduler.swift in Sources */,
FF11627A2BCF8109000C4809 /* CallMessageCustomizationView.swift in Sources */,
FF6087EA2BE25EF1004E1E47 /* TournamentStatusView.swift in Sources */,
FF025ADB2BD0C2D000A86CF8 /* MatchTeamDetailView.swift in Sources */,
FF5DA1952BB927E800A33061 /* GenericDestinationPickerView.swift in Sources */,
FFF116E12BD2A9B600A33B06 /* DateInterval.swift in Sources */,
@ -1518,6 +1529,7 @@
FF5D0D892BB4935C005CB568 /* ClubRowView.swift in Sources */,
FF1DC5512BAB351300FD8220 /* ClubDetailView.swift in Sources */,
FF9268032BCE94A30080F940 /* GroupStageCallingView.swift in Sources */,
C49EF0442BE286780077B5AA /* Key.swift in Sources */,
FF11627D2BCF941A000C4809 /* CashierSettingsView.swift in Sources */,
FFFCDE0E2BCC833600317DEF /* LoserRoundScheduleEditorView.swift in Sources */,
C4A47D632B6D3D6500ADC637 /* Club.swift in Sources */,
@ -1571,7 +1583,6 @@
FF967D012BAEF0B400A9A3BD /* MatchSummaryView.swift in Sources */,
FF8F26452BAE0A3400650388 /* TournamentDurationManagerView.swift in Sources */,
FF1DC5532BAB354A00FD8220 /* MockData.swift in Sources */,
C49EF03E2BE160720077B5AA /* Key.swift in Sources */,
FF967D092BAF3D4000A9A3BD /* TeamDetailView.swift in Sources */,
FF5DA18F2BB9268800A33061 /* GroupStageSettingsView.swift in Sources */,
FF663FBE2BE019EC0031AE83 /* TournamentFilterView.swift in Sources */,
@ -1597,6 +1608,7 @@
FF5D0D782BB42C5B005CB568 /* InscriptionInfoView.swift in Sources */,
FF4AB6BD2B9256E10002987F /* SelectablePlayerListView.swift in Sources */,
FF8F26512BAE0BAD00650388 /* MatchFormatPickerView.swift in Sources */,
FF5BAF722BE19274008B4B7E /* TournamentRankView.swift in Sources */,
FF5D0D872BB48AFD005CB568 /* NumberFormatter+Extensions.swift in Sources */,
FFCFC0182BBC5A6800B82851 /* SetLabelView.swift in Sources */,
FFF9645B2BC2D53B00EEF017 /* GroupStageScheduleEditorView.swift in Sources */,
@ -1783,6 +1795,8 @@
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = PadelClub/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "Padel Club";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.sports";
INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Padel Club a besoin de votre position pour rechercher les clubs autour de vous.";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
@ -1793,7 +1807,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
MARKETING_VERSION = 0.1;
PRODUCT_BUNDLE_IDENTIFIER = app.padelclub;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
@ -1815,6 +1829,8 @@
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = PadelClub/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "Padel Club";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.sports";
INFOPLIST_KEY_NSLocationWhenInUseUsageDescription = "Padel Club a besoin de votre position pour rechercher les clubs autour de vous.";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
@ -1825,7 +1841,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
MARKETING_VERSION = 0.1;
PRODUCT_BUNDLE_IDENTIFIER = app.padelclub;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;

@ -94,8 +94,25 @@ class GroupStage: ModelObject, Storable {
}
}
func scoreLabel(forGroupStagePosition groupStagePosition: Int) -> (wins: String, losses: String, setsDifference: String?, gamesDifference: String?)? {
if let scoreData = _score(forGroupStagePosition: groupStagePosition) {
func updateGroupStageState() {
if hasEnded(), let tournament = tournamentObject() {
do {
let teams = teams(true)
for (index, team) in teams.enumerated() {
team.qualified = index < tournament.qualifiedPerGroupStage
if team.bracketPosition != nil && team.qualified == false {
team.bracketPosition = nil
}
}
try DataStore.shared.teamRegistrations.addOrUpdate(contentOfs: teams)
} catch {
Logger.error(error)
}
}
}
func scoreLabel(forGroupStagePosition groupStagePosition: Int, score: TeamGroupStageScore? = nil) -> (wins: String, losses: String, setsDifference: String?, gamesDifference: String?)? {
if let scoreData = (score ?? _score(forGroupStagePosition: groupStagePosition, nilIfEmpty: true)) {
let hideSetDifference = matchFormat.setsToWin == 1
let setDifference = scoreData.setDifference.formatted(.number.sign(strategy: .always(includingZero: false)))
let gameDifference = scoreData.gameDifference.formatted(.number.sign(strategy: .always(includingZero: false)))
@ -106,10 +123,10 @@ class GroupStage: ModelObject, Storable {
}
}
fileprivate func _score(forGroupStagePosition groupStagePosition: Int) -> TeamGroupStageScore? {
func _score(forGroupStagePosition groupStagePosition: Int, nilIfEmpty: Bool = false) -> TeamGroupStageScore? {
guard let team = teamAt(groupStagePosition: groupStagePosition) else { return nil }
let matches = matches(forGroupStagePosition: groupStagePosition).filter({ $0.hasEnded() })
if matches.isEmpty { return nil }
if matches.isEmpty && nilIfEmpty { return nil }
let wins = matches.filter { $0.winningTeamId == team.id }.count
let loses = matches.filter { $0.losingTeamId == team.id }.count
let differences = matches.compactMap { $0.scoreDifference(groupStagePosition) }
@ -208,8 +225,9 @@ class GroupStage: ModelObject, Storable {
}
fileprivate typealias TeamScoreAreInIncreasingOrder = (TeamGroupStageScore, TeamGroupStageScore) -> Bool
fileprivate typealias TeamGroupStageScore = (team: TeamRegistration, wins: Int, loses: Int, setDifference: Int, gameDifference: Int)
typealias TeamGroupStageScore = (team: TeamRegistration, wins: Int, loses: Int, setDifference: Int, gameDifference: Int)
fileprivate func _headToHead(_ teamPosition: TeamRegistration, _ otherTeam: TeamRegistration) -> Bool {
let indexes = [teamPosition, otherTeam].compactMap({ $0.groupStagePosition }).sorted()
let combos = Array((0..<size).combinations(ofCount: 2))
@ -224,9 +242,11 @@ class GroupStage: ModelObject, Storable {
Store.main.filter { $0.groupStage == self.id && $0.groupStagePosition != nil }
}
func teams(_ sortedByScore: Bool = false) -> [TeamRegistration] {
func teams(_ sortedByScore: Bool = false, scores: [TeamGroupStageScore]? = nil) -> [TeamRegistration] {
if sortedByScore {
return unsortedTeams().compactMap({ _score(forGroupStagePosition: $0.groupStagePosition!) }).sorted { (lhs, rhs) in
return unsortedTeams().compactMap({ team in
scores?.first(where: { $0.team.id == team.id }) ?? _score(forGroupStagePosition: team.groupStagePosition!)
}).sorted { (lhs, rhs) in
let predicates: [TeamScoreAreInIncreasingOrder] = [
{ $0.wins < $1.wins },
{ $0.setDifference < $1.setDifference },

@ -348,10 +348,8 @@ class Match: ModelObject, Storable {
winningTeamId = teamScoreWinning.teamRegistration
losingTeamId = teamScoreWalkout.teamRegistration
// matchDescriptor.match?.tournament?.generateLoserBracket(for: matchDescriptor.match!.round, viewContext: viewContext, reset: false)
// matchDescriptor.match?.loserBracket?.generateLoserBracket(for: matchDescriptor.match!.round, viewContext: viewContext, reset: false)
// matchDescriptor.match?.currentTournament?.removeField(matchDescriptor.match?.fieldIndex)
groupStageObject?.updateGroupStageState()
roundObject?.updateTournamentState()
}
func setScore(fromMatchDescriptor matchDescriptor: MatchDescriptor) {
@ -359,11 +357,10 @@ class Match: ModelObject, Storable {
if endDate == nil {
endDate = Date()
}
// matchDescriptor.match?.tournament?.generateLoserBracket(for: matchDescriptor.match!.round, viewContext: viewContext, reset: false)
// matchDescriptor.match?.loserBracket?.generateLoserBracket(for: matchDescriptor.match!.round, viewContext: viewContext, reset: false)
// matchDescriptor.match?.currentTournament?.removeField(matchDescriptor.match?.fieldIndex)
winningTeamId = team(matchDescriptor.winner)?.id
losingTeamId = team(matchDescriptor.winner.otherTeam)?.id
groupStageObject?.updateGroupStageState()
roundObject?.updateTournamentState()
}
func updateScore(fromMatchDescriptor matchDescriptor: MatchDescriptor) {

@ -45,22 +45,6 @@ extension Tournament {
static func mock() -> Tournament {
Tournament(groupStageSortMode: .snake, teamSorting: .inscriptionDate, federalCategory: .men, federalLevelCategory: .p100, federalAgeCategory: .senior)
}
static func newEmptyInstance() -> Tournament {
let lastDataSource: String? = DataStore.shared.appSettings.lastDataSource
var _mostRecentDateAvailable: Date? {
guard let lastDataSource else { return nil }
return URL.importDateFormatter.date(from: lastDataSource)
}
let rankSourceDate = _mostRecentDateAvailable
let tournaments : [Tournament] = DataStore.shared.tournaments.filter { $0.endDate != nil }.sorted(by: \.startDate).reversed()
let tournamentLevel = TournamentLevel.mostUsed(inTournaments: tournaments)
let tournamentCategory = TournamentCategory.mostUsed(inTournaments: tournaments)
let federalTournamentAge = FederalTournamentAge.mostUsed(inTournaments: tournaments)
return Tournament(groupStageSortMode: .snake, rankSourceDate: rankSourceDate, teamSorting: tournamentLevel.defaultTeamSortingType, federalCategory: tournamentCategory, federalLevelCategory: tournamentLevel, federalAgeCategory: federalTournamentAge)
}
}
extension Match {

@ -337,6 +337,10 @@ class Round: ModelObject, Storable {
return seedInterval.localizedLabel(displayStyle)
}
func hasNextRound() -> Bool {
nextRound()?.isDisabled() == false
}
func seedInterval() -> SeedInterval? {
if parent == nil {
let numberOfMatches = RoundRule.numberOfMatches(forRoundIndex: index + 1)
@ -366,10 +370,23 @@ class Round: ModelObject, Storable {
}
return RoundRule.roundName(fromRoundIndex: index)
}
func updateTournamentState() {
if let tournamentObject = tournamentObject(), index == 0, isUpperBracket(), hasEnded() {
tournamentObject.endDate = Date()
do {
try DataStore.shared.tournaments.addOrUpdate(instance: tournamentObject)
} catch {
Logger.error(error)
}
}
}
func roundStatus() -> String {
if hasStarted() && hasEnded() == false {
return "en cours"
} else if hasEnded() {
return "terminée"
} else {
return "à démarrer"
}

@ -323,7 +323,7 @@ class Tournament : ModelObject, Storable, ObservableObject {
func getActiveRound(withSeeds: Bool = false) -> Round? {
let rounds = rounds()
let round = rounds.filter({ $0.hasStarted() && $0.hasEnded() == false }).sorted(by: \.index).reversed().first ?? rounds.first
let round = rounds.filter({ $0.hasStarted() && $0.hasEnded() == false }).sorted(by: \.index).reversed().first ?? rounds.last(where: { $0.hasEnded() }) ?? rounds.first
if withSeeds {
if round?.seeds().isEmpty == false {
@ -551,7 +551,7 @@ class Tournament : ModelObject, Storable, ObservableObject {
func registrationIssues() -> Int {
let players : [PlayerRegistration] = unsortedPlayers()
let selectedTeams : [TeamRegistration] = selectedSortedTeams()
let callDateIssue : [TeamRegistration] = selectedTeams.filter { isStartDateIsDifferentThanCallDate($0) }
let callDateIssue : [TeamRegistration] = selectedTeams.filter { $0.callDate != nil && isStartDateIsDifferentThanCallDate($0) }
let duplicates : [PlayerRegistration] = duplicates(in: players)
let problematicPlayers : [PlayerRegistration] = players.filter({ $0.sex == -1 })
let inadequatePlayers : [PlayerRegistration] = inadequatePlayers(in: players)
@ -592,6 +592,54 @@ class Tournament : ModelObject, Storable, ObservableObject {
return Array(allMatches.filter({ $0.hasEnded() }).sorted(by: \.computedEndDateForSorting).reversed().prefix(_limit))
}
func finalRanking() -> [Int: [String]] {
var teams: [Int: [String]] = [:]
let rounds = rounds()
let final = rounds.last?.playedMatches().last
if let winner = final?.winningTeamId {
teams[1] = [winner]
}
if let finalist = final?.losingTeamId {
teams[2] = [finalist]
}
let others : [Round] = rounds.flatMap { round in
round.loserRoundsAndChildren().filter { $0.isDisabled() == false && $0.hasNextRound() == false }
}.compactMap({ $0 })
others.forEach { round in
if let interval = round.seedInterval() {
let playedMatches = round.playedMatches().filter { $0.disabled == false }
let winners = playedMatches.compactMap({ $0.winningTeamId })
let losers = playedMatches.compactMap({ $0.losingTeamId })
teams[interval.first + winners.count - 1] = winners
teams[interval.last] = losers
}
}
let groupStages = groupStages()
let baseRank = teamCount - teamsPerGroupStage * groupStageCount + qualifiedPerGroupStage * groupStageCount + groupStageAdditionalQualified
groupStages.forEach { groupStage in
let groupStageTeams = groupStage.teams(true)
for (index, team) in groupStageTeams.enumerated() {
if team.qualified == false {
let groupStageWidth = max(((index == qualifiedPerGroupStage) ? teamsPerGroupStage - groupStageAdditionalQualified : teamsPerGroupStage) * (index - qualifiedPerGroupStage), 0)
let _index = baseRank + groupStageWidth + 1
if let existingTeams = teams[_index] {
teams[_index] = existingTeams + [team.id]
} else {
teams[_index] = [team.id]
}
}
}
}
return teams
}
func lockRegistration() {
closedRegistrationDate = Date()
@ -1291,7 +1339,22 @@ extension Tournament: TournamentBuildHolder {
}
extension Tournament {
static func newEmptyInstance() -> Tournament {
let lastDataSource: String? = DataStore.shared.appSettings.lastDataSource
var _mostRecentDateAvailable: Date? {
guard let lastDataSource else { return nil }
return URL.importDateFormatter.date(from: lastDataSource)
}
let rankSourceDate = _mostRecentDateAvailable
let tournaments : [Tournament] = DataStore.shared.tournaments.filter { $0.endDate != nil }.sorted(by: \.startDate).reversed()
let tournamentLevel = TournamentLevel.mostUsed(inTournaments: tournaments)
let tournamentCategory = TournamentCategory.mostUsed(inTournaments: tournaments)
let federalTournamentAge = FederalTournamentAge.mostUsed(inTournaments: tournaments)
return Tournament(creator: DataStore.shared.user?.id, groupStageSortMode: .snake, rankSourceDate: rankSourceDate, teamSorting: tournamentLevel.defaultTeamSortingType, federalCategory: tournamentCategory, federalLevelCategory: tournamentLevel, federalAgeCategory: federalTournamentAge)
}
static func fake() -> Tournament {
return Tournament(event: "Roland Garros", creator: "", name: "Magic P100", startDate: Date(), endDate: Date(), creationDate: Date(), isPrivate: false, groupStageFormat: .nineGames, roundFormat: nil, loserRoundFormat: nil, groupStageSortMode: .snake, groupStageCount: 4, rankSourceDate: nil, dayDuration: 2, teamCount: 24, teamSorting: .rank, federalCategory: .men, federalLevelCategory: .p100, federalAgeCategory: .a45, groupStageCourtCount: nil, seedCount: 8, closedRegistrationDate: nil, groupStageAdditionalQualified: 0, courtCount: 4, prioritizeClubMembers: false, qualifiedPerGroupStage: 2, teamsPerGroupStage: 4, entryFee: nil)
}

@ -54,7 +54,7 @@ struct CashierDetailView: View {
Text(count.formatted())
}
}
} label: {
} label: {
Text("Voir le détail")
}

@ -17,15 +17,15 @@ struct CashierView: View {
@State private var sortOrder: SortOrder = .ascending
@State private var searchText = ""
@State private var isSearching: Bool = false
init(event: Event) {
self.tournaments = event.tournaments
self.teams = []
}
init(tournament: Tournament, teams: [TeamRegistration]) {
self.tournaments = [tournament]
self.teams = teams
if teams.filter { $0.callDate != nil }.isEmpty {
_sortOption = .init(wrappedValue: .teamRank)
} else {
_sortOption = .init(wrappedValue: .callDate)
}
}
private func _sharedData() -> String {
@ -284,7 +284,9 @@ struct CashierView: View {
private func _isContentUnavailable() -> Bool {
switch sortOption {
case .teamRank, .callDate:
case .callDate:
return teams.filter({ $0.callDate != nil && _shouldDisplayTeam($0) }).isEmpty
case .teamRank:
return teams.filter({ _shouldDisplayTeam($0) }).isEmpty
default:
return teams.flatMap({ $0.players() }).filter({ _shouldDisplayPlayer($0) }).isEmpty

@ -21,13 +21,12 @@ struct GenericDestinationPickerView<T: Identifiable & Selectable>: View {
selectedDestination = nil
} label: {
Image(systemName: "wrench.and.screwdriver")
.foregroundColor(selectedDestination == nil ? .white : .black)
}
.foregroundStyle(selectedDestination == nil ? .white : .black)
.padding()
.background {
Circle()
.fill(Color.master)
.opacity(selectedDestination == nil ? 1.0 : 0.2)
.fill(selectedDestination == nil ? .master : .beige)
}
.buttonStyle(.plain)
}
@ -42,8 +41,7 @@ struct GenericDestinationPickerView<T: Identifiable & Selectable>: View {
.padding()
.background {
Capsule()
.fill(Color.master)
.opacity(selectedDestination?.id == destination.id ? 1.0 : 0.2)
.fill(selectedDestination?.id == destination.id ? .master : .beige)
}
.buttonStyle(.plain)
.overlay(alignment: .bottomTrailing) {

@ -13,7 +13,7 @@ struct MatchListView: View {
let matches: [Match]
var matchViewStyle: MatchViewStyle = .sectionedStandardStyle
@State private var isExpanded: Bool = true
@State var isExpanded: Bool = true
@ViewBuilder
var body: some View {
@ -22,7 +22,7 @@ struct MatchListView: View {
DisclosureGroup(isExpanded: $isExpanded) {
ForEach(matches) { match in
MatchRowView(match: match, matchViewStyle: matchViewStyle)
.listRowInsets(EdgeInsets())
.listRowInsets(EdgeInsets(top: 0, leading: -2, bottom: 0, trailing: 8))
}
} label: {
LabeledContent {

@ -6,6 +6,7 @@
//
import SwiftUI
import LeStorage
struct GroupStageSettingsView: View {
@EnvironmentObject var dataStore: DataStore
@ -25,17 +26,16 @@ struct GroupStageSettingsView: View {
var body: some View {
List {
Toggle(isOn: $nameAlphabetical) {
Text("Nommer les poules alphabétiquement")
}
Menu {
//menuAddGroupStage
Section {
menuBuildAllGroupStages
}
Section {
menuGenerateGroupStage(.random)
}
Section {
menuGenerateGroupStage(.snake)
//menuGenerateGroupStage(.swiss)
} label: {
LabelOptions()
}
if tournament.missingQualifiedFromGroupStages().isEmpty == false && tournament.qualifiedTeams().count >= tournament.qualifiedFromGroupStage() && tournament.groupStageAdditionalQualified > 0 {
@ -52,115 +52,9 @@ struct GroupStageSettingsView: View {
}
}
// if (tournament.groupStagesAreWrong || (tournament.emptySlotInGroupStages > 0 && tournament.entriesCount >= tournament.teamsFromGroupStages)) {
// Section {
// RowButtonView("Reconstruire les poules") {
// confirmGroupStageRebuild = true
// }
// .modify {
// if UIDevice.current.userInterfaceIdiom == .pad {
// $0.alert("Êtes-vous sûr ?", isPresented: $confirmGroupStageRebuild) {
//
// Button(role: .destructive) {
// tournament.refreshGroupStages()
// save()
// } label: {
// Text("Reconstruire")
// }
//
//
// Button(role: .cancel) {
//
// } label: {
// Text("Annuler")
// }
// } message: {
// Text("Attention, cela peut modifier les poules existants.")
//
// }
// } else {
// $0.confirmationDialog("Êtes-vous sûr ?", isPresented: $confirmGroupStageRebuild) {
// Button(role: .destructive) {
// tournament.refreshGroupStages()
// save()
// } label: {
// Text("Reconstruire")
// }
// } message: {
// Text("Attention, cela peut modifier les poules existants.")
// }
// }
// }
// } header: {
// Text("Erreur détectée")
// }
// }
//
// if tournament.isRoundSwissTournament() == false && (tournament.orderedGroupStages.allSatisfy({ $0.orderedMatches.count > 0 }) == false && tournament.groupStagesAreOver == false && tournament.groupStagesCount > 0) {
// Section {
// RowButtonView("Générer les matchs de poules") {
// startAllGroupStageConfirmation = true
// }
// .modify {
// if UIDevice.current.userInterfaceIdiom == .pad {
// $0.alert("Êtes-vous sûr ?", isPresented: $startAllGroupStageConfirmation) {
// Button("Générer") {
// tournament.orderedGroupStages.forEach {
// if $0.orderedMatches.isEmpty {
// $0.startGroupStage()
// }
// }
// save()
// }
// Button(role: .cancel) {
//
// } label: {
// Text("Annuler")
// }
// }
//
// } else {
// $0.confirmationDialog("Êtes-vous sûr ?", isPresented: $startAllGroupStageConfirmation) {
// Button("Générer") {
// tournament.orderedGroupStages.forEach {
// if $0.orderedMatches.isEmpty {
// $0.startGroupStage()
// }
// }
// save()
// }
// }
// }
// }
// }
// }
//
if tournament.groupStagesAreOver() == false {
// Section {
// GroupStageMatchAvailableToStartView(tournament: tournament, groupStageIndex: selectedGroupStageIndex)
// } header: {
// if selectedGroupStageIndex == -1 {
// Text("Matchs de poules prêt à démarrer")
// } else {
// Text("Matchs de la poule \(selectedGroupStageIndex) prêt à démarrer")
// }
// } footer: {
// Text("présence d'au moins 2 équipes d'une même poule ayant réglé.")
// }
Toggle(isOn: $nameAlphabetical) {
Text("Nommer les poules alphabétiquement")
}
// if tournament.teamsPerGroupStage == 3 && tournament.qualifiedPerGroupStage == 1 && tournament.numberOfGroupStages%2 == 0 && tournament.moreQualifiedFromGroupStages == 0 {
// Section {
// NavigationLink {
// GroupStageMissingMatchView(tournament: tournament)
// } label: {
// Text("Matchs de classement de poules")
// }
// }
// }
}
.onChange(of: nameAlphabetical) {
let groupStages = tournament.groupStages()
@ -178,71 +72,30 @@ struct GroupStageSettingsView: View {
}
var menuBuildAllGroupStages: some View {
Button(role: .destructive) {
tournament.deleteGroupStages()
tournament.buildGroupStages()
} label: {
Label("Refaire les poules", systemImage: "restart")
}
var menuBuildAllGroupStages: some View {
RowButtonView("Refaire les poules", role: .destructive) {
tournament.deleteGroupStages()
tournament.buildGroupStages()
_save()
}
@ViewBuilder
func menuGenerateGroupStage(_ mode: GroupStageOrderingMode) -> some View {
Button(role: .destructive) {
tournament.groupStageOrderingMode = mode
tournament.refreshGroupStages()
//save()
} label: {
Label("Poule \(mode.localizedLabel().lowercased())", systemImage: mode.systemImage)
}
}
@ViewBuilder
func menuGenerateGroupStage(_ mode: GroupStageOrderingMode) -> some View {
RowButtonView("Poule \(mode.localizedLabel().lowercased())", role: .destructive, systemImage: mode.systemImage) {
tournament.groupStageOrderingMode = mode
tournament.refreshGroupStages()
_save()
}
}
// func addGroupStage(_ size: Int64) {
// let groupStage = GroupStage(context: viewContext)
// groupStage.index = tournament.firstIndexToUseForNewGroupStage
// groupStage.size = Int64(size)
// groupStage.matchFormatRawValue = tournament.groupStageMatchFormatRawValue
// print("addGroupStage groupStagesCount", tournament.groupStagesCount)
// print("addGroupStage numberOfGroupStages", tournament.numberOfGroupStages)
// if tournament.groupStagesCount >= tournament.numberOfGroupStages {
// tournament.numberOfGroupStages += 1
// }
// tournament.addToGroupStages(groupStage)
// save()
// }
//
// var menuAddGroupStage: some View {
// Menu {
// ForEach(-1...1) { index in
// let i = tournament.teamsPerGroupStage + Int64(index)
// Button {
// addGroupStage(i)
// } label: {
// Text("Poule de \(i)")
// }
// .disabled(i < 2)
// }
// } label: {
// Label("Ajouter une poule", systemImage: "server.rack")
// }
//
// }
// func save() {
// do {
// tournament.objectWillChange.send()
// try viewContext.save()
// viewContext.refreshAllObjects()
// } 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)")
// }
// }
private func _save() {
do {
try dataStore.tournaments.addOrUpdate(instance: tournament)
} catch {
Logger.error(error)
}
}
}
#Preview {

@ -15,21 +15,9 @@ struct GroupStageTeamView: View {
var body: some View {
List {
ForEach(team.players()) { player in
Section {
ImportedPlayerView(player: player)
} footer: {
HStack {
Button("contacter") {
}
Spacer()
Button {
player.hasArrived.toggle()
try? dataStore.playerRegistrations.addOrUpdate(instance: player)
} label: {
Label("présent", systemImage: player.hasArrived ? "checkmark.circle" : "circle")
}
}
Section {
ForEach(team.players()) { player in
EditablePlayerView(player: player, editingOptions: [.licenceId, .payment])
}
}
@ -38,7 +26,7 @@ struct GroupStageTeamView: View {
if team.qualified == false {
RowButtonView("Qualifier l'équipe") {
team.qualified = true
team.bracketPosition = nil
// team.bracketPosition = nil
_save()
}
}
@ -54,41 +42,13 @@ struct GroupStageTeamView: View {
}
Section {
// if let deltaLabel = bracket.tournament?.deltaLabel(index, bracketIndex: bracket.index.intValue) {
// Section {
// Button(role: .destructive) {
// entrant.resetBracketPosition()
// save()
// } label: {
// Text(deltaLabel)
// }
// Divider()
// } header: {
// Text("Toute l'équipe, poids: " + entrant.updatedRank.formatted())
// }
// }
//
// ForEach(entrant.orderedPlayers) { player in
// if let deltaLabel = bracket.tournament?.playerDeltaLabel(rank: entrant.otherPlayer(player)?.currentRank, positionInBracket: index, bracketIndex: bracket.index.intValue) {
// Section {
// Button(role: .destructive) {
// entrant.team?.removeFromPlayers(player)
// save()
// } label: {
// Text(deltaLabel)
// }
// Divider()
// } header: {
// Text(player.longLabel + ", rang: " + player.formattedRank)
// }
// }
// }
} header: {
Text("Remplacement")
RowButtonView("Remplacement") {
}
}
Section {
RowButtonView("Retirer de la poule") {
RowButtonView("Retirer de la poule", role: .destructive) {
team.groupStagePosition = nil
team.groupStage = nil
_save()

@ -6,6 +6,7 @@
//
import SwiftUI
import LeStorage
struct GroupStageView: View {
@EnvironmentObject var dataStore: DataStore
@ -15,21 +16,7 @@ struct GroupStageView: View {
@State private var confirmRemoveAll: Bool = false
@State private var confirmResetMatch: Bool = false
@State private var groupStageName: String = ""
private enum GroupStageSortingMode {
case auto
case score
case weight
}
var sortByScore: Bool {
sortingMode == .auto ? groupStage.hasEnded() : sortingMode == .score
}
func teamAt(atIndex index: Int) -> TeamRegistration? {
sortByScore ? groupStage.teams(sortByScore)[safe: index] : groupStage.teamAt(groupStagePosition: index)
}
init(groupStage: GroupStage) {
self.groupStage = groupStage
_groupStageName = State(wrappedValue: groupStage.groupStageTitle())
@ -38,7 +25,7 @@ struct GroupStageView: View {
var body: some View {
List {
Section {
_groupStageView()
GroupStageScoreView(groupStage: groupStage, sortByScore: _sortByScore)
} header: {
if let startDate = groupStage.startDate {
Text(startDate.formatted(Date.FormatStyle().weekday(.wide)).capitalized + " à partir de " + startDate.formattedAsHourMinute())
@ -47,19 +34,9 @@ struct GroupStageView: View {
HStack {
Spacer()
Button {
if sortingMode == .auto {
if groupStage.hasEnded() {
sortingMode = .weight
} else {
sortingMode = .score
}
} else if sortingMode == .weight {
sortingMode = .score
} else {
sortingMode = .weight
}
_updateSortingMode()
} label: {
Label(sortByScore ? "tri par score" : "tri par poids", systemImage: "arrow.up.arrow.down").labelStyle(.titleOnly)
Label(_sortByScore ? "tri par score" : "tri par poids", systemImage: "arrow.up.arrow.down").labelStyle(.titleOnly)
.underline()
}
.buttonStyle(.borderless)
@ -70,11 +47,11 @@ struct GroupStageView: View {
MatchListView(section: "disponible", matches: groupStage.availableToStart()).id(UUID())
MatchListView(section: "en cours", matches: groupStage.runningMatches()).id(UUID())
MatchListView(section: "à lancer", matches: groupStage.readyMatches()).id(UUID())
MatchListView(section: "terminés", matches: groupStage.finishedMatches()).id(UUID())
MatchListView(section: "terminés", matches: groupStage.finishedMatches(), isExpanded: false).id(UUID())
}
.onChange(of: groupStageName) {
groupStage.name = groupStageName
try? dataStore.groupStages.addOrUpdate(instance: groupStage)
_save()
}
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
@ -84,65 +61,113 @@ struct GroupStageView: View {
.navigationTitle($groupStageName)
}
private func _groupStageView() -> some View {
ForEach(0..<(groupStage.size), id: \.self) { index in
if let team = teamAt(atIndex: index), let groupStagePosition = team.groupStagePosition {
NavigationLink {
GroupStageTeamView(groupStage: groupStage, team: team)
} label: {
HStack(alignment: .center) {
private enum GroupStageSortingMode {
case auto
case score
case weight
}
private var _sortByScore: Bool {
sortingMode == .auto ? groupStage.hasEnded() : sortingMode == .score
}
private func _updateSortingMode() {
if sortingMode == .auto {
if groupStage.hasEnded() {
sortingMode = .weight
} else {
sortingMode = .score
}
} else if sortingMode == .weight {
sortingMode = .score
} else {
sortingMode = .weight
}
}
struct GroupStageScoreView: View {
@EnvironmentObject var dataStore: DataStore
let groupStage: GroupStage
let sortByScore: Bool
let scores: [GroupStage.TeamGroupStageScore]?
let teams: [TeamRegistration]
init(groupStage: GroupStage, sortByScore: Bool) {
self.groupStage = groupStage
self.sortByScore = sortByScore
self.scores = sortByScore ? (0..<groupStage.size).compactMap({ groupStage._score(forGroupStagePosition: $0) }) : nil
self.teams = groupStage.teams(sortByScore, scores: scores)
}
private func _teamAt(atIndex index: Int) -> TeamRegistration? {
sortByScore ? teams[safe: index] : groupStage.teamAt(groupStagePosition: index)
}
var body: some View {
ForEach(0..<(groupStage.size), id: \.self) { index in
if let team = _teamAt(atIndex: index), let groupStagePosition = team.groupStagePosition {
NavigationLink {
GroupStageTeamView(groupStage: groupStage, team: team)
} label: {
VStack(alignment: .leading, spacing: 4.0) {
HStack(spacing: 6.0) {
Text("#\(groupStagePosition + 1)")
Text("Poids \(team.weight)")
Spacer()
if team.qualified {
Text("qualifié")
}
}
.font(.footnote)
HStack {
if let teamName = team.name {
Text(teamName)
Text(teamName).font(.title)
} else {
VStack(alignment: .leading) {
ForEach(team.players()) { player in
Text(player.playerLabel())
Text(player.playerLabel()).lineLimit(1)
}
}
}
if team.qualified {
Image(systemName: "checkmark.seal")
}
}
}
Spacer()
if let score = groupStage.scoreLabel(forGroupStagePosition: groupStagePosition) {
VStack(alignment: .center) {
HStack(spacing: 0.0) {
Text(score.wins)
Text("/")
Text(score.losses)
}.bold()
if let setsDifference = score.setsDifference {
Text(setsDifference + " sets")
}
if let gamesDifference = score.gamesDifference {
Text(gamesDifference + " jeux")
Spacer()
if let score = groupStage.scoreLabel(forGroupStagePosition: groupStagePosition, score: scores?.first(where: { $0.team.groupStagePosition == groupStagePosition })) {
VStack(alignment: .trailing) {
HStack(spacing: 0.0) {
Text(score.wins)
Text("/")
Text(score.losses)
}.font(.headline).monospacedDigit()
if let setsDifference = score.setsDifference {
HStack(spacing: 4.0) {
Text(setsDifference)
Text("sets")
}.font(.footnote)
}
if let gamesDifference = score.gamesDifference {
HStack(spacing: 4.0) {
Text(gamesDifference)
Text("jeux")
}.font(.footnote)
}
}
}
}
}
}
}
} else {
HStack(alignment: .center) {
.listRowView(isActive: team.qualified, color: .master)
} else {
VStack(alignment: .leading, spacing: 0) {
HStack {
Text("#\(index + 1)")
}
.font(.caption)
Text("#\(index + 1)")
.font(.caption)
TeamPickerView(teamPicked: { team in
print(team.pasteData())
team.groupStage = groupStage.id
team.groupStagePosition = index
try? dataStore.teamRegistrations.addOrUpdate(instance: team)
do {
try dataStore.teamRegistrations.addOrUpdate(instance: team)
} catch {
Logger.error(error)
}
})
}
}
@ -156,121 +181,15 @@ struct GroupStageView: View {
Button("Retirer le nom") {
groupStage.name = nil
groupStageName = groupStage.groupStageTitle()
try? dataStore.groupStages.addOrUpdate(instance: groupStage)
_save()
}
}
// if groupStage.playedMatches().isEmpty {
// Button {
// //groupStage.startGroupStage()
// //save()
// } label: {
// Text("Créer les matchs")
// }
// .buttonStyle(.borderless)
// }
Button("Retirer tout le monde", role: .destructive) {
confirmRemoveAll = true
}
Button("Recommencer tous les matchs", role: .destructive) {
confirmResetMatch = true
}
// Button {
// selectedMenuLink = .prepare
// } label: {
// Label("Préparer", systemImage: "calendar")
// }
//
// Menu {
// MenuWarnView(warningSender: groupStage)
// } label: {
// Label("Prévenir", systemImage: "person.crop.circle")
// }
//
// if groupStage.isBroadcasted() {
// Button {
// groupStage.refreshBroadcast()
// } label: {
// Label("Rafraîchir", systemImage: "arrow.up.circle.fill")
// }
// Button {
// groupStage.stopBroadcast()
// save()
// } label: {
// Label("Arrêter la diffusion", systemImage: "stop.circle.fill")
// }
// } else if groupStage.tournament?.canBroadcast() == true {
// Button {
// Task {
// try? await groupStage.broadcastGroupStage()
// save()
// }
// } label: {
// Label("Diffuser", systemImage: "airplayvideo")
// }
// }
//
// Divider()
// if groupStage.tournament?.canBroadcast() == true {
// Menu {
// Button {
// Task {
// try? await groupStage.broadcastGroupStageMatches()
// save()
// }
// } label: {
// Label("Diffuser", systemImage: "airplayvideo")
// }
//
// Button {
// groupStage.refreshBroadcastMatches()
// } label: {
// Label("Rafraîchir", systemImage: "arrow.up.circle.fill")
// }
// Button {
// groupStage.stopBroadcastMatches()
// save()
// } label: {
// Label("Arrêter la diffusion", systemImage: "stop.circle.fill")
// }
// } label: {
// Text("Diffusion des matchs")
// }
// }
//
// Divider()
// Menu {
// if groupStage.orderedMatches.isEmpty == false {
// Button(role: .destructive) {
// groupStage.startGroupStage()
// save()
// } label: {
// Label("Re-démarrer les matchs de la \(groupStage.titleLabel.lowercased())", systemImage: "trash")
// }
// }
//
// if groupStage.orderedMatches.isEmpty == false {
// Button(role: .destructive) {
// groupStage.removeMatches()
// save()
// } label: {
// Label("Supprimer les matchs de la \(groupStage.titleLabel.lowercased())", systemImage: "trash")
// }
// }
//
// Button(role: .destructive) {
// groupStage.tournament?.completeEntries.filter { $0.groupStagePosition == groupStage.index }.forEach { $0.resetGroupStagePosition() }
// groupStage.tournament?.removeFromGroupStages(groupStage)
// groupStage.tournament?.numberOfGroupStages -= 1
// save()
// } label: {
// Label("Supprimer la \(groupStage.titleLabel.lowercased())", systemImage: "trash")
// }
// } label: {
// Text("Éditer")
// }
} label: {
LabelOptions()
}
@ -281,7 +200,11 @@ struct GroupStageView: View {
team.groupStagePosition = nil
team.groupStage = nil
}
try? dataStore.teamRegistrations.addOrUpdate(contentOfs: teams)
do {
try dataStore.teamRegistrations.addOrUpdate(contentOfs: teams)
} catch {
Logger.error(error)
}
}
}
.confirmationDialog("Êtes-vous sûr de vouloir faire cela ?", isPresented: $confirmResetMatch, titleVisibility: .visible) {
@ -292,18 +215,11 @@ struct GroupStageView: View {
}
// func save() {
// do {
// groupStage.objectWillChange.send()
// groupStage.tournament?.orderedGroupStages.forEach { $0.objectWillChange.send() }
// groupStage.tournament?.objectWillChange.send()
//
// try viewContext.save()
// } catch {
// // Replace this implementation with code to handle the error appropriately.
// // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
// let nsError = error as NSError
// fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
// }
// }
private func _save() {
do {
try dataStore.groupStages.addOrUpdate(instance: groupStage)
} catch {
Logger.error(error)
}
}
}

@ -73,10 +73,10 @@ struct GroupStagesView: View {
let runningMatches = allGroupStages.flatMap({ $0.runningMatches() })
let readyMatches = allGroupStages.flatMap({ $0.readyMatches() })
let finishedMatches = allGroupStages.flatMap({ $0.finishedMatches() })
MatchListView(section: "disponible", matches: availableToStart, matchViewStyle: .standardStyle)
MatchListView(section: "en cours", matches: runningMatches, matchViewStyle: .standardStyle)
MatchListView(section: "à lancer", matches: readyMatches, matchViewStyle: .standardStyle)
MatchListView(section: "terminés", matches: finishedMatches, matchViewStyle: .standardStyle)
MatchListView(section: "disponible", matches: availableToStart, matchViewStyle: .standardStyle, isExpanded: false)
MatchListView(section: "en cours", matches: runningMatches, matchViewStyle: .standardStyle, isExpanded: false)
MatchListView(section: "à lancer", matches: readyMatches, matchViewStyle: .standardStyle, isExpanded: false)
MatchListView(section: "terminés", matches: finishedMatches, matchViewStyle: .standardStyle, isExpanded: false)
}
.navigationTitle("Toutes les poules")
case .groupStage(let groupStage):

@ -386,68 +386,67 @@ struct MatchDetailView: View {
}
}
@ViewBuilder
var startingOptionView: some View {
Section {
if match.hasEnded() == false {
let rotationDuration = match.getDuration()
Picker(selection: $startDateSetup) {
if match.isReady() {
Text("Dans 5 minutes").tag(MatchDateSetup.inMinutes(5))
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("Prochaine rotation").tag(MatchDateSetup.inMinutes(rotationDuration))
Text("À").tag(MatchDateSetup.customDate)
} label: {
Text("Horaire")
if match.hasEnded() == false {
let rotationDuration = match.getDuration()
Picker(selection: $startDateSetup) {
if match.isReady() {
Text("Dans 5 minutes").tag(MatchDateSetup.inMinutes(5))
Text("Dans 15 minutes").tag(MatchDateSetup.inMinutes(15))
Text("Tout de suite").tag(MatchDateSetup.now)
}
.onChange(of: startDateSetup, perform: { value in
switch startDateSetup {
case .customDate:
break
case .now:
startDate = Date()
case .inMinutes(let minutes):
startDate = Date().addingTimeInterval(Double(minutes) * 60)
}
})
Text("Précédente rotation").tag(MatchDateSetup.inMinutes(-rotationDuration))
Text("Prochaine rotation").tag(MatchDateSetup.inMinutes(rotationDuration))
Text("À").tag(MatchDateSetup.customDate)
} label: {
Text("Horaire")
}
if match.startDate != nil || startDateSetup == .customDate {
DatePicker(selection: $startDate) {
Label("Début", systemImage: "calendar").labelStyle(.titleOnly)
.onChange(of: startDateSetup, perform: { value in
switch startDateSetup {
case .customDate:
break
case .now:
startDate = Date()
case .inMinutes(let minutes):
startDate = Date().addingTimeInterval(Double(minutes) * 60)
}
.datePickerStyle(.compact)
})
}
if match.startDate != nil || startDateSetup == .customDate {
DatePicker(selection: $startDate) {
Label("Début", systemImage: "calendar").labelStyle(.titleOnly)
}
if match.endDate != nil {
DatePicker(selection: $endDate) {
Label("Fin", systemImage: "calendar").labelStyle(.titleOnly)
}
.datePickerStyle(.compact)
.datePickerStyle(.compact)
}
if match.endDate != nil {
DatePicker(selection: $endDate) {
Label("Fin", systemImage: "calendar").labelStyle(.titleOnly)
}
.datePickerStyle(.compact)
}
Picker(selection: $fieldSetup) {
Text("Au hasard").tag(MatchFieldSetup.random)
//Text("Premier disponible").tag(MatchFieldSetup.firstAvailable)
if let tournament = match.currentTournament() {
ForEach(0..<tournament.courtCount, id: \.self) { courtIndex in
Text(tournament.courtName(atIndex: courtIndex)) .tag(MatchFieldSetup.field(courtIndex))
}
Picker(selection: $fieldSetup) {
Text("Au hasard").tag(MatchFieldSetup.random)
//Text("Premier disponible").tag(MatchFieldSetup.firstAvailable)
if let tournament = match.currentTournament() {
ForEach(0..<tournament.courtCount, id: \.self) { courtIndex in
Text(tournament.courtName(atIndex: courtIndex)) .tag(MatchFieldSetup.field(courtIndex))
}
} label: {
Text("Choix du terrain")
}
.contextMenu {
NavigationLink {
//FieldDrawView(match: match)
} label: {
Text("Tirage au sort visuel")
}
} label: {
Text("Choix du terrain")
}
.contextMenu {
NavigationLink {
//FieldDrawView(match: match)
} label: {
Text("Tirage au sort visuel")
}
}
// if match.canBroadcast() == true {
// Picker(selection: $broadcasted) {
@ -457,21 +456,20 @@ struct MatchDetailView: View {
// Text("Diffuser automatiquement")
// }
// }
RowButtonView("Valider") {
match.validateMatch(fromStartDate: startDateSetup == .now ? Date() : startDate, toEndDate: endDate, fieldSetup: fieldSetup)
RowButtonView("Valider") {
match.validateMatch(fromStartDate: startDateSetup == .now ? Date() : startDate, toEndDate: endDate, fieldSetup: fieldSetup)
if broadcasted {
broadcastAndSave()
} else {
save()
}
isEditing.toggle()
if match.hasStarted() == false {
dismiss()
}
if broadcasted {
broadcastAndSave()
} else {
save()
}
isEditing.toggle()
if match.hasStarted() == false {
dismiss()
}
}
}

@ -100,21 +100,6 @@ struct EventListView: View {
NavigationLink(value: tournament) {
TournamentCellView(tournament: tournament)
}
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
Button(role: .destructive) {
tournament.isDeleted = true
do {
try dataStore.tournaments.addOrUpdate(instance: tournament)
} catch {
Logger.error(error)
}
// try? dataStore.tournaments.delete(instance: tournament)
} label: {
LabelDelete()
}
}
.contextMenu {
Button {

@ -75,6 +75,13 @@ struct EditablePlayerView: View {
}
Section {
Button {
player.hasArrived.toggle()
try? dataStore.playerRegistrations.addOrUpdate(instance: player)
} label: {
Label("présent", systemImage: player.hasArrived ? "checkmark.circle" : "circle")
}
Button {
editedLicenceId = player.licenceId ?? ""
shouldPresentLicenceIdEdition = true

@ -14,16 +14,16 @@ struct RoundSettingsView: View {
var body: some View {
List {
Section {
RowButtonView("Enabled", role: .destructive) {
let allMatches = tournament._allMatchesIncludingDisabled()
allMatches.forEach({
$0.disabled = false
$0.byeState = false
})
try? dataStore.matches.addOrUpdate(contentOfs: allMatches)
}
}
// Section {
// RowButtonView("Enabled", role: .destructive) {
// let allMatches = tournament._allMatchesIncludingDisabled()
// allMatches.forEach({
// $0.disabled = false
// $0.byeState = false
// })
// try? dataStore.matches.addOrUpdate(contentOfs: allMatches)
// }
// }
Section {
RowButtonView("Retirer toutes les têtes de séries", role: .destructive) {
tournament.unsortedTeams().forEach({ $0.bracketPosition = nil })

@ -6,6 +6,7 @@
//
import SwiftUI
import LeStorage
struct EditScoreView: View {
@EnvironmentObject var dataStore: DataStore
@ -102,7 +103,11 @@ struct EditScoreView: View {
func save() {
if let match = matchDescriptor.match {
try? dataStore.matches.addOrUpdate(instance: match)
do {
try dataStore.matches.addOrUpdate(instance: match)
} catch {
Logger.error(error)
}
}
}
}

@ -21,7 +21,6 @@ struct TeamWeightView: View {
if let teams = team.tournamentObject()?.selectedSortedTeams(), let index = team.index(in: teams) {
Text("#" + (index + 1).formatted(.number.precision(.integerLength(2...3))))
.monospacedDigit()
.font(.title)
}
if teamPosition == .two {
Text(team.weight.formatted())

@ -0,0 +1,22 @@
//
// BroadcastView.swift
// PadelClub
//
// Created by Razmig Sarkissian on 01/05/2024.
//
import SwiftUI
struct BroadcastView: View {
var body: some View {
List {
}
.navigationTitle("Diffusion")
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.visible, for: .navigationBar)
}
}
#Preview {
BroadcastView()
}

@ -17,7 +17,7 @@ struct TournamentGeneralSettingsView: View {
var body: some View {
@Bindable var tournament = tournament
Form {
Form {
Section {
TournamentDatePickerView()
TournamentDurationManagerView()

@ -0,0 +1,66 @@
//
// TournamentStatusView.swift
// PadelClub
//
// Created by Razmig Sarkissian on 01/05/2024.
//
import SwiftUI
import LeStorage
struct TournamentStatusView: View {
@Environment(Tournament.self) private var tournament: Tournament
@EnvironmentObject var dataStore: DataStore
var body: some View {
@Bindable var tournament = tournament
Form {
Section {
RowButtonView("Supprimer le tournoi", role: .destructive) {
tournament.isDeleted.toggle()
}
} footer: {
Text("todo: expliquer ce que ca fait")
}
Section {
if tournament.hasEnded() == false {
if tournament.isCanceled == false {
RowButtonView("Annuler le tournoi", role: .destructive) {
tournament.isCanceled.toggle()
}
} else {
RowButtonView("Reprendre le tournoi", role: .destructive) {
tournament.isCanceled.toggle()
}
}
}
} footer: {
Text("todo: expliquer ce que ca fait")
}
Section {
Toggle(isOn: $tournament.isPrivate) {
Text("Tournoi privée")
}
} footer: {
Text("Le tournoi sera masqué sur le site padelclub.app")
}
}
.toolbarBackground(.visible, for: .navigationBar)
.onChange(of: [tournament.isDeleted, tournament.isCanceled, tournament.isPrivate]) {
_save()
}
}
private func _save() {
do {
try dataStore.tournaments.addOrUpdate(instance: tournament)
} catch {
Logger.error(error)
}
}
}
#Preview {
TournamentStatusView()
}

@ -16,4 +16,6 @@ enum Screen: String, Codable {
case schedule
case cashier
case call
case rankings
case broadcast
}

@ -71,6 +71,12 @@ struct TournamentCashierView: View {
func allDestinations() -> [CashierDestination] {
var allDestinations : [CashierDestination] = []
let tournamentHasEnded = tournament.hasEnded()
if tournamentHasEnded {
allDestinations.append(.summary)
allDestinations.append(.all(tournament))
}
let destinations : [CashierDestination] = tournament.groupStages().map { CashierDestination.groupStage($0) }
allDestinations.append(contentsOf: destinations)
tournament.rounds().forEach { round in
@ -78,8 +84,12 @@ struct TournamentCashierView: View {
allDestinations.append(CashierDestination.bracket(round))
}
}
allDestinations.append(.all(tournament))
allDestinations.append(.summary)
if tournamentHasEnded == false {
allDestinations.append(.all(tournament))
allDestinations.append(.summary)
}
return allDestinations
}

@ -0,0 +1,106 @@
//
// TournamentRankView.swift
// PadelClub
//
// Created by Razmig Sarkissian on 30/04/2024.
//
import SwiftUI
import LeStorage
struct TournamentRankView: View {
@Environment(Tournament.self) var tournament: Tournament
@EnvironmentObject var dataStore: DataStore
@State private var rankings: [Int: [TeamRegistration]] = [:]
var body: some View {
List {
let keys = rankings.keys.sorted()
ForEach(keys, id: \.self) { key in
if let rankedTeams = rankings[key] {
ForEach(rankedTeams) { team in
HStack {
VStack(alignment: .trailing) {
VStack(alignment: .trailing, spacing: -8.0) {
ZStack(alignment: .trailing) {
Text(tournament.teamCount.formatted()).hidden()
Text(key.formatted())
}
.monospacedDigit()
.font(.largeTitle)
.fontWeight(.bold)
Text(key.ordinalFormattedSuffix()).font(.caption)
}
if let index = tournament.indexOf(team: team) {
let rankingDifference = index - (key - 1)
if rankingDifference > 0 {
HStack(spacing: 0.0) {
Text(rankingDifference.formatted(.number.sign(strategy: .always())))
.monospacedDigit()
Image(systemName: "arrowtriangle.up.fill")
.imageScale(.small)
}
.foregroundColor(.green)
} else if rankingDifference < 0 {
HStack(spacing: 0.0) {
Text(rankingDifference.formatted(.number.sign(strategy: .always())))
.monospacedDigit()
Image(systemName: "arrowtriangle.down.fill")
.imageScale(.small)
}
.foregroundColor(.red)
} else {
Text("--")
}
}
}
Divider()
VStack(alignment: .leading) {
ForEach(team.players()) { player in
VStack(alignment: .leading, spacing: -4.0) {
Text(player.playerLabel()).bold()
HStack(alignment: .firstTextBaseline, spacing: 0.0) {
Text(player.rankLabel())
if let rank = player.getRank() {
Text(rank.ordinalFormattedSuffix())
.font(.caption)
}
}
}
}
}
Spacer()
VStack(alignment: .trailing) {
HStack(alignment: .lastTextBaseline, spacing: 0.0) {
Text(tournament.tournamentLevel.points(for: key - 1, count: tournament.teamCount).formatted(.number.sign(strategy: .always())))
Text("pts").font(.caption)
}
}
}
}
}
}
}
.listStyle(.grouped)
.onAppear {
let finalRanks = tournament.finalRanking()
finalRanks.keys.sorted().forEach { rank in
if let rankedTeamIds = finalRanks[rank] {
rankings[rank] = rankedTeamIds.compactMap { Store.main.findById($0) }
}
}
}
.navigationTitle("Classement")
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.visible, for: .navigationBar)
}
}
#Preview {
TournamentRankView()
}

@ -8,6 +8,7 @@
import SwiftUI
enum TournamentSettings: Identifiable, Selectable {
case status
case general
case club(Tournament)
case matchFormats
@ -16,8 +17,10 @@ enum TournamentSettings: Identifiable, Selectable {
func selectionLabel() -> String {
switch self {
case .status:
return "Statut"
case .matchFormats:
return "Formats de jeu"
return "Formats"
case .general:
return "Général"
case .club:
@ -48,13 +51,15 @@ struct TournamentSettingsView: View {
@Environment(Tournament.self) var tournament: Tournament
private func destinations() -> [TournamentSettings] {
[.general, .club(tournament), .matchFormats]
[.status, .general, .club(tournament), .matchFormats]
}
var body: some View {
VStack(spacing: 0) {
GenericDestinationPickerView(selectedDestination: $selectedDestination, destinations: destinations(), nilDestinationIsValid: false)
switch selectedDestination! {
case .status:
TournamentStatusView()
case .matchFormats:
TournamentMatchFormatsSettingsView()
case .general:

@ -75,7 +75,7 @@ struct TournamentRunningView: View {
MatchListView(section: "en cours", matches: tournament.runningMatches(allMatches))
// MatchListView(section: "à lancer", matches: tournament.readyMatches(allMatches))
// MatchListView(section: "disponible", matches: tournament.availableToStart(allMatches))
MatchListView(section: "terminés", matches: tournament.finishedMatches(allMatches))
MatchListView(section: "terminés", matches: tournament.finishedMatches(allMatches), isExpanded: false)
}
}

@ -45,7 +45,7 @@ struct TournamentView: View {
}
}
} footer: {
if tournament.inscriptionClosed() == false {
if tournament.inscriptionClosed() == false && tournament.state() == .build && tournament.unsortedTeams().isEmpty == false && tournament.hasStarted() == false {
Button {
tournament.lockRegistration()
_save()
@ -60,8 +60,19 @@ struct TournamentView: View {
switch tournament.state() {
case .initial:
TournamentInitView()
NavigationLink(value: Screen.broadcast) {
Text("Diffusion")
}
case .build:
TournamentRunningView(tournament: tournament)
if tournament.hasEnded() {
NavigationLink(value: Screen.rankings) {
Text("Classement")
}
}
}
}
}
@ -85,6 +96,10 @@ struct TournamentView: View {
TournamentCashierView(tournament: tournament)
case .call:
TournamentCallView(tournament: tournament)
case .rankings:
TournamentRankView()
case .broadcast:
BroadcastView()
}
}
.environment(tournament)
@ -118,6 +133,12 @@ struct TournamentView: View {
NavigationLink(value: Screen.structure) {
LabelStructure()
}
NavigationLink(value: Screen.rankings) {
Text("Classement")
}
NavigationLink(value: Screen.broadcast) {
Text("Diffusion")
}
}
} label: {
LabelOptions()

@ -8,11 +8,11 @@
import SwiftUI
struct ListRowViewModifier: ViewModifier {
@State private var isActived = true
let isActive: Bool
let color: Color
func body(content: Content) -> some View {
if isActived {
if isActive {
content
.listRowBackground(
color.variation()
@ -27,7 +27,7 @@ struct ListRowViewModifier: ViewModifier {
}
extension View {
func listRowView(color: Color) -> some View {
modifier(ListRowViewModifier(color: color))
func listRowView(isActive: Bool = true, color: Color) -> some View {
modifier(ListRowViewModifier(isActive: isActive, color: color))
}
}

Loading…
Cancel
Save