diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index a3d49d0..dd2798e 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -653,6 +653,9 @@ FF82CFC52B911F5B00B0CAF2 /* OrganizedTournamentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF82CFC42B911F5B00B0CAF2 /* OrganizedTournamentView.swift */; }; FF82CFC92B9132AF00B0CAF2 /* ActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF82CFC82B9132AF00B0CAF2 /* ActivityView.swift */; }; FF8E1CE62C006E0200184680 /* Alphabet.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8E1CE52C006E0200184680 /* Alphabet.swift */; }; + FF8E52342DF01D6100099B75 /* EventStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8E52332DF01D6100099B75 /* EventStatusView.swift */; }; + FF8E52352DF01D6100099B75 /* EventStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8E52332DF01D6100099B75 /* EventStatusView.swift */; }; + FF8E52362DF01D6100099B75 /* EventStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8E52332DF01D6100099B75 /* EventStatusView.swift */; }; FF8F263B2BAD528600650388 /* EventCreationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F263A2BAD528600650388 /* EventCreationView.swift */; }; FF8F263D2BAD627A00650388 /* TournamentConfiguratorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F263C2BAD627A00650388 /* TournamentConfiguratorView.swift */; }; FF8F26412BADFC8700650388 /* TournamentInitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF8F26402BADFC8700650388 /* TournamentInitView.swift */; }; @@ -1058,6 +1061,7 @@ FF82CFC42B911F5B00B0CAF2 /* OrganizedTournamentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrganizedTournamentView.swift; sourceTree = ""; }; FF82CFC82B9132AF00B0CAF2 /* ActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityView.swift; sourceTree = ""; }; FF8E1CE52C006E0200184680 /* Alphabet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alphabet.swift; sourceTree = ""; }; + FF8E52332DF01D6100099B75 /* EventStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventStatusView.swift; sourceTree = ""; }; FF8F263A2BAD528600650388 /* EventCreationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventCreationView.swift; sourceTree = ""; }; FF8F263C2BAD627A00650388 /* TournamentConfiguratorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentConfiguratorView.swift; sourceTree = ""; }; FF8F26402BADFC8700650388 /* TournamentInitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentInitView.swift; sourceTree = ""; }; @@ -1843,6 +1847,7 @@ FF8F263A2BAD528600650388 /* EventCreationView.swift */, FFE103072C353B7600684FC9 /* EventClubSettingsView.swift */, FF8F263C2BAD627A00650388 /* TournamentConfiguratorView.swift */, + FF8E52332DF01D6100099B75 /* EventStatusView.swift */, ); name = Event; path = Cashier/Event; @@ -2381,6 +2386,7 @@ FF4C7F022BBBD7150031B6A3 /* TabItemModifier.swift in Sources */, C49772042DC260D3005CD239 /* Round+Extensions.swift in Sources */, FFDDD40C2B93B2BB00C91A49 /* DeferredViewModifier.swift in Sources */, + FF8E52342DF01D6100099B75 /* EventStatusView.swift in Sources */, FFE8B63C2DACEAED00BDE966 /* ConfigurationService.swift in Sources */, FF0E0B6D2BC254C6005F00A9 /* TournamentScheduleView.swift in Sources */, FF025AF12BD1AEBD00A86CF8 /* MatchFormatStorageView.swift in Sources */, @@ -2643,6 +2649,7 @@ FF4CBFF82C996C0600151637 /* TabItemModifier.swift in Sources */, FF4CBFF92C996C0600151637 /* DeferredViewModifier.swift in Sources */, FF4CBFFA2C996C0600151637 /* TournamentScheduleView.swift in Sources */, + FF8E52362DF01D6100099B75 /* EventStatusView.swift in Sources */, FF4CBFFB2C996C0600151637 /* MatchFormatStorageView.swift in Sources */, FF4CBFFC2C996C0600151637 /* UmpireView.swift in Sources */, FF4CBFFE2C996C0600151637 /* MatchSummaryView.swift in Sources */, @@ -2883,6 +2890,7 @@ FF70FB772C90584900129CC2 /* TabItemModifier.swift in Sources */, FF70FB782C90584900129CC2 /* DeferredViewModifier.swift in Sources */, FF70FB792C90584900129CC2 /* TournamentScheduleView.swift in Sources */, + FF8E52352DF01D6100099B75 /* EventStatusView.swift in Sources */, FF70FB7A2C90584900129CC2 /* MatchFormatStorageView.swift in Sources */, FF70FB7B2C90584900129CC2 /* UmpireView.swift in Sources */, FF70FB7D2C90584900129CC2 /* MatchSummaryView.swift in Sources */, @@ -3120,7 +3128,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.33; + MARKETING_VERSION = 1.2.35; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3166,7 +3174,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.33; + MARKETING_VERSION = 1.2.35; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3285,7 +3293,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.33; + MARKETING_VERSION = 1.2.34; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3330,7 +3338,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.33; + MARKETING_VERSION = 1.2.34; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/PadelClub/Views/Cashier/Event/EventStatusView.swift b/PadelClub/Views/Cashier/Event/EventStatusView.swift new file mode 100644 index 0000000..2cf1680 --- /dev/null +++ b/PadelClub/Views/Cashier/Event/EventStatusView.swift @@ -0,0 +1,107 @@ +// +// EventStatusView.swift +// PadelClub +// +// Created by Razmig Sarkissian on 04/06/2025. +// + +import SwiftUI +import PadelClubData + +struct EventStatusView: View { + @State private var teamsCount: Int? + + let tournaments: [Tournament] + + init(tournament: Tournament) { + self.tournaments = [tournament] + } + + init(event: Event) { + self.tournaments = event.confirmedTournaments() + } + + init(tournaments: [Tournament]) { + self.tournaments = tournaments + } + + private func _calculateTeamsCount() async { + Task { + self.teamsCount = tournaments.map({ $0.selectedSortedTeams().count }).reduce(0, +) + } + } + + private func _valueView(value: T, value2: T? = nil) -> some View { + if let value2 { + Text("\(value.description)/\(value2.description)") + .font(.title3) + } else { + Text("\(value.description)") + .font(.title3) + } + } + + private func _currencyView(value: Double, value2: Double? = nil) -> some View { + + let maps = [value, value2].compactMap({ $0 }).map { + $0.formatted(.currency(code: Locale.defaultCurrency()).precision(.fractionLength(0))) + } + + let string = maps.joined(separator: " / ") + return Text(string) + } + + + var body: some View { + List { + Section { + LabeledContent { + _valueView(value: tournaments.count) + } label: { + Text("Tournois") + } + + LabeledContent { + if let teamsCount { + _valueView(value: teamsCount, value2: tournaments.map({ $0.teamCount }).reduce(0, +)) + } else { + ProgressView() + } + } label: { + Text("Inscriptions") + } + + LabeledContent { + _currencyView(value: tournaments.map({ $0.earnings() }).reduce(0, +), value2: tournaments.map({ $0.totalIncome() }).reduce(0, +)) + } label: { + Text("Recettes") + } + } header: { + if let tournament = tournaments.first, tournaments.count == 1 { + Text(tournament.tournamentTitle()) + } else { + Text("Statistiques globales") + } + } + + if tournaments.count > 1 { + Section { + ForEach(tournaments) { tournament in + NavigationLink(destination: EventStatusView(tournament: tournament)) { + LabeledContent { + _valueView(value: tournament.selectedSortedTeams().count, value2: tournament.teamCount) + } label: { + Text(tournament.tournamentTitle()) + } + } + } + } + } + } + .task { + await self._calculateTeamsCount() + } + .toolbarBackground(.visible, for: .navigationBar) + .navigationBarTitleDisplayMode(.inline) + } +} diff --git a/PadelClub/Views/Cashier/Event/EventView.swift b/PadelClub/Views/Cashier/Event/EventView.swift index 365442d..e04154a 100644 --- a/PadelClub/Views/Cashier/Event/EventView.swift +++ b/PadelClub/Views/Cashier/Event/EventView.swift @@ -19,6 +19,7 @@ enum EventDestination: Identifiable, Selectable, Equatable { case tournaments(Event) case cashier case eventPlanning + case eventStatus var id: String { return String(describing: self) @@ -36,12 +37,14 @@ enum EventDestination: Identifiable, Selectable, Equatable { return "Finance" case .eventPlanning: return "Planning" + case .eventStatus: + return "Statut" } } func badgeValue() -> Int? { switch self { - case .links, .club: + case .links, .club, .eventStatus: return nil case .tournaments(let event): return event.tournaments.count @@ -80,7 +83,7 @@ struct EventView: View { } func allDestinations() -> [EventDestination] { - [.club(event), .eventPlanning, .tournaments(event), .cashier] + [.club(event), .eventStatus, .eventPlanning, .tournaments(event), .cashier] } var body: some View { @@ -91,6 +94,8 @@ struct EventView: View { EventSettingsView(event: event) case .some(let selectedEventDestination): switch selectedEventDestination { + case .eventStatus: + EventStatusView(event: event) case .club(let event): EventClubSettingsView(event: event) case .eventPlanning: diff --git a/PadelClub/Views/GroupStage/Components/GroupStageTeamView.swift b/PadelClub/Views/GroupStage/Components/GroupStageTeamView.swift index cb93030..cbb84c0 100644 --- a/PadelClub/Views/GroupStage/Components/GroupStageTeamView.swift +++ b/PadelClub/Views/GroupStage/Components/GroupStageTeamView.swift @@ -106,6 +106,29 @@ struct GroupStageTeamView: View { groupStage.matches().forEach({ $0.updateTeamScores() }) } } + + if let groupStagePosition = team.groupStagePosition { + Section { + RowButtonView("Forfait", role: .destructive) { + team.walkOut = true + let matches = groupStage.matches(forGroupStagePosition: groupStagePosition) + .filter({ + $0.hasStarted() == false && $0.hasEnded() == false + }) + + matches + .forEach({ + if let teamPosition = $0.teamPosition(for: team) { + $0.setWalkOut(teamPosition) + } + }) + tournamentStore?.matches.addOrUpdate(contentOfs: matches) + _save() + } + } footer: { + Text("En indiquant cette équipe forfaite, le résultat des matchs restant dans la poule seront mis à \(defaultScore) pour leur adversaire.") + } + } } } } @@ -184,6 +207,10 @@ struct GroupStageTeamView: View { .navigationTitle("Détail de l'équipe") } + private var defaultScore: String { + groupStage.matchFormat.defaultWalkOutScore(false).compactMap({ String($0) + "/0" }).joined(separator: " ") + } + private var _networkErrorMessage: String { ContactManagerError.getNetworkErrorMessage(sentError: sentError, networkMonitorConnected: networkMonitor.connected) } diff --git a/PadelClub/Views/Tournament/Screen/BroadcastView.swift b/PadelClub/Views/Tournament/Screen/BroadcastView.swift index 5382d73..33ef854 100644 --- a/PadelClub/Views/Tournament/Screen/BroadcastView.swift +++ b/PadelClub/Views/Tournament/Screen/BroadcastView.swift @@ -318,6 +318,14 @@ struct BroadcastView: View { Text("Lien du tournoi à partager") } + #if DEBUG + Section { + actionForURL(title: "La Boutique", url: URLs.main.url.appending(path: "shop")) + } header: { + Text("Lien de la boutique") + } + #endif + Section { let club = tournament.club() actionForURL(title: (club == nil) ? "Aucun club indiqué pour ce tournoi" : club!.clubTitle(), description: "Page du club", url: club?.shareURL()) diff --git a/PadelClub/Views/Tournament/TournamentView.swift b/PadelClub/Views/Tournament/TournamentView.swift index 761d5e2..c87755a 100644 --- a/PadelClub/Views/Tournament/TournamentView.swift +++ b/PadelClub/Views/Tournament/TournamentView.swift @@ -150,6 +150,9 @@ struct TournamentView: View { TournamentScheduleView(tournament: tournament) case .cashier: TournamentCashierView(tournament: tournament) + case .statistics: + EventStatusView(tournament: tournament) + .navigationTitle("Statistiques") case .call: TournamentCallView(tournament: tournament) case .rankings: @@ -265,6 +268,11 @@ struct TournamentView: View { Text(tournament.isFree() ? "Présence" : "Encaissement") } + NavigationLink(value: Screen.statistics) { + Text("Statistiques") + } + + NavigationLink(value: Screen.rankings) { LabeledContent { if tournament.publishRankings == false {