multistore
Razmig Sarkissian 1 year ago
parent 689d7e6d8d
commit 3cfd6cdf81
  1. 4
      PadelClub.xcodeproj/project.pbxproj
  2. 4
      PadelClub/Utils/HtmlGenerator.swift
  3. 2
      PadelClub/Utils/HtmlService.swift
  4. 28
      PadelClub/Utils/Tips.swift
  5. 5
      PadelClub/ViewModel/SearchViewModel.swift
  6. 7
      PadelClub/ViewModel/Selectable.swift
  7. 260
      PadelClub/Views/Cashier/CashierView.swift
  8. 5
      PadelClub/Views/Club/ClubSearchView.swift
  9. 59
      PadelClub/Views/Components/GenericDestinationPickerView.swift
  10. 22
      PadelClub/Views/Tournament/Screen/BroadcastView.swift
  11. 205
      PadelClub/Views/Tournament/Screen/PrintSettingsView.swift
  12. 20
      PadelClub/Views/Tournament/Screen/TournamentCashierView.swift
  13. 18
      PadelClub/Views/Tournament/TournamentBuildView.swift
  14. 15
      PadelClub/Views/Tournament/TournamentInitView.swift
  15. 1
      PadelClub/Views/Tournament/TournamentView.swift

@ -1939,7 +1939,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 33;
CURRENT_PROJECT_VERSION = 34;
DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
DEVELOPMENT_TEAM = BQ3Y44M3Q6;
@ -1977,7 +1977,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 33;
CURRENT_PROJECT_VERSION = 34;
DEFINES_MODULE = YES;
DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\"";
DEVELOPMENT_TEAM = BQ3Y44M3Q6;

@ -58,6 +58,10 @@ class HtmlGenerator: ObservableObject {
self.width = width as! CGFloat
})
}
if self.completionHandler != nil {
self.buildPDF()
}
})
}

@ -183,7 +183,7 @@ enum HtmlService {
var template = ""
var bracket = ""
if let round = tournament.rounds().first(where: { $0.index == roundIndex }) {
for (_, match) in round.playedMatches().enumerated() {
for (_, match) in round._matches().enumerated() {
template = template.appending(HtmlService.match(match: match).html(headName: headName, withRank: withRank, withScore: withScore))
}
bracket = html.replacingOccurrences(of: "{{match-template}}", with: template)

@ -412,6 +412,34 @@ struct TournamentRunningTip: Tip {
}
}
struct CreateAccountTip: Tip {
var title: Text {
Text("Créer votre compte Padel Club")
}
var message: Text? {
let message = "Un compte est nécessaire pour publier le tournoi sur [Padel Club](\(URLs.main.rawValue)) et profiter de toutes du site, comme le mode TV pour transformer l'expérience de vos tournois !"
return Text(.init(message))
}
var image: Image? {
Image(systemName: "person.crop.circle")
}
var actions: [Action] {
Action(id: ActionKey.createAccount.rawValue, title: "Créer votre compte")
//todo
//Action(id: ActionKey.learnMore.rawValue, title: "En savoir plus")
Action(id: ActionKey.accessPadelClubWebPage.rawValue, title: "Jeter un oeil au site Padel Club")
}
enum ActionKey: String {
case createAccount = "createAccount"
case learnMore = "learnMore"
case accessPadelClubWebPage = "accessPadelClubWebPage"
}
}
struct TipStyleModifier: ViewModifier {
@Environment(\.colorScheme) var colorScheme
var tint: Color?

@ -7,6 +7,11 @@
import SwiftUI
class DebouncableViewModel: ObservableObject {
@Published var debouncableText: String = ""
var debounceTrigger: Double = 0.15
}
class SearchViewModel: ObservableObject, Identifiable {
let id: UUID = UUID()
var allowSelection : Int = 0

@ -13,6 +13,13 @@ protocol Selectable {
func badgeValue() -> Int?
func badgeImage() -> Badge?
func badgeValueColor() -> Color?
func displayImageIfValueZero() -> Bool
}
extension Selectable {
func displayImageIfValueZero() -> Bool {
return false
}
}
enum Badge {

@ -8,39 +8,62 @@
import SwiftUI
import Combine
struct CashierView: View {
@EnvironmentObject var dataStore: DataStore
var tournaments : [Tournament]
var teams: [TeamRegistration]
@State private var sortOption: SortOption = .callDate
@State private var filterOption: FilterOption = .all
@State private var sortOrder: SortOrder = .ascending
@State private var searchText = ""
@State private var isSearching: Bool = false
struct ShareableObject {
init(tournament: Tournament, teams: [TeamRegistration]) {
self.tournaments = [tournament]
self.teams = teams
if tournament.hasEnded(), tournament.players().anySatisfy({ $0.hasPaid() == false }) {
_filterOption = .init(wrappedValue: .didNotPay)
}
if teams.filter({ $0.callDate != nil }).isEmpty {
_sortOption = .init(wrappedValue: .teamRank)
} else {
_sortOption = .init(wrappedValue: .callDate)
}
}
let cashierViewModel: CashierViewModel
let teams: [TeamRegistration]
let fileName: String
private func _sharedData() -> String {
let players = teams.filter({ _shouldDisplayTeam($0) })
.flatMap({ $0.players().filter({ _shouldDisplayPlayer($0) }) })
func sharedData() async -> Data? {
let players = teams.filter({ cashierViewModel._shouldDisplayTeam($0) })
.flatMap({ $0.players().filter({ cashierViewModel._shouldDisplayPlayer($0) }) })
.map {
[$0.pasteData()]
.compacted()
.joined(separator: "\n")
}
.joined(separator: "\n\n")
return players
return players.data(using: .utf8)
}
}
extension ShareableObject: Transferable {
enum ShareError: Error {
case failed
}
static var transferRepresentation: some TransferRepresentation {
let rep = DataRepresentation<ShareableObject>(exportedContentType: .plainText) { object in
guard let data = await object.sharedData() else {
throw ShareError.failed
}
return data
}
return rep.suggestedFileName { object in object.fileName }
}
}
class CashierViewModel: ObservableObject {
let id: UUID = UUID()
@Published var sortOption: SortOption = .callDate
@Published var filterOption: FilterOption = .all
@Published var sortOrder: SortOrder = .ascending
@Published var searchText: String = ""
@Published var isSearching: Bool = false
func _shouldDisplayTeam(_ team: TeamRegistration) -> Bool {
team.unsortedPlayers().anySatisfy({
_shouldDisplayPlayer($0)
})
}
func _shouldDisplayPlayer(_ player: PlayerRegistration) -> Bool {
if searchText.isEmpty == false {
filterOption.shouldDisplayPlayer(player) && player.contains(searchText)
} else {
filterOption.shouldDisplayPlayer(player)
}
}
enum SortOption: Int, Identifiable, CaseIterable {
@ -101,27 +124,42 @@ struct CashierView: View {
}
}
}
struct CashierView: View {
@EnvironmentObject var dataStore: DataStore
@EnvironmentObject var cashierViewModel: CashierViewModel
var tournaments : [Tournament]
var teams: [TeamRegistration]
@State private var shareableObject: ShareableObject?
init(tournament: Tournament, teams: [TeamRegistration]) {
self.tournaments = [tournament]
self.teams = teams
}
var body: some View {
List {
if isSearching == false {
if cashierViewModel.isSearching == false {
Section {
Picker(selection: $filterOption) {
ForEach(FilterOption.allCases) { filterOption in
Picker(selection: $cashierViewModel.filterOption) {
ForEach(CashierViewModel.FilterOption.allCases) { filterOption in
Text(filterOption.localizedLabel()).tag(filterOption)
}
} label: {
Text("Statut du règlement")
}
Picker(selection: $sortOption) {
ForEach(SortOption.allCases) { sortOption in
Picker(selection: $cashierViewModel.sortOption) {
ForEach(CashierViewModel.SortOption.allCases) { sortOption in
Text(sortOption.localizedLabel()).tag(sortOption)
}
} label: {
Text("Affichage par")
}
Picker(selection: $sortOrder) {
Picker(selection: $cashierViewModel.sortOrder) {
Text("Croissant").tag(SortOrder.ascending)
Text("Décroissant").tag(SortOrder.descending)
} label: {
@ -132,11 +170,7 @@ struct CashierView: View {
}
}
if _isContentUnavailable() {
_contentUnavailableView()
}
switch sortOption {
switch cashierViewModel.sortOption {
case .teamRank:
_byTeamRankView()
case .alphabeticalLastName:
@ -151,51 +185,53 @@ struct CashierView: View {
_byCallDateView()
}
}
.searchable(text: $cashierViewModel.searchText, isPresented: $cashierViewModel.isSearching, placement: .navigationBarDrawer(displayMode: .always), prompt: Text("Chercher un joueur"))
.onAppear {
cashierViewModel.searchText = ""
// if tournaments.count == 1 {
// if tournaments.first!.hasEnded() == true, tournaments.first!.players().anySatisfy({ $0.hasPaid() == false }) {
// filterOption = .didNotPay
// }
// }
if cashierViewModel.sortOption == .callDate && teams.first(where: { $0.callDate != nil }) == nil {
cashierViewModel.sortOption = .teamRank
}
self.shareableObject = ShareableObject(cashierViewModel: cashierViewModel, teams: teams, fileName: "Encaissement.txt")
}
.headerProminence(.increased)
.searchable(text: $searchText, isPresented: $isSearching, prompt: Text("Chercher un joueur"))
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
ShareLink(item: _sharedData().createTxtFile("bilan"))
if let shareableObject {
ShareLink(
item: shareableObject,
preview: SharePreview(shareableObject.fileName)
)
}
}
}
}
@ViewBuilder
func computedPlayerView(_ player: PlayerRegistration) -> some View {
EditablePlayerView(player: player, editingOptions: [.licenceId, .name, .payment])
}
private func _shouldDisplayTeam(_ team: TeamRegistration) -> Bool {
team.players().anySatisfy({
_shouldDisplayPlayer($0)
})
}
private func _shouldDisplayPlayer(_ player: PlayerRegistration) -> Bool {
if searchText.isEmpty == false {
filterOption.shouldDisplayPlayer(player) && player.contains(searchText)
} else {
filterOption.shouldDisplayPlayer(player)
}
}
@ViewBuilder
private func _byPlayer(_ players: [PlayerRegistration]) -> some View {
let _players = sortOrder == .ascending ? players : players.reversed()
ForEach(_players) { player in
Section {
computedPlayerView(player)
} header: {
HStack {
if let teamCallDate = player.team()?.callDate {
Text(teamCallDate.localizedDate())
let _players = cashierViewModel.sortOrder == .ascending ? players : players.reversed()
if _players.isEmpty {
_contentUnavailableView()
} else {
ForEach(_players) { player in
Section {
EditablePlayerView(player: player, editingOptions: [.licenceId, .name, .payment])
} header: {
HStack {
if let teamCallDate = player.team()?.callDate {
Text(teamCallDate.localizedDate())
}
Spacer()
Text(player.formattedRank())
}
} footer: {
if tournaments.count > 1, let tournamentTitle = player.tournament()?.tournamentTitle() {
Text(tournamentTitle)
}
Spacer()
Text(player.computedRank.formatted())
}
} footer: {
if tournaments.count > 1, let tournamentTitle = player.tournament()?.tournamentTitle() {
Text(tournamentTitle)
}
}
}
@ -203,42 +239,52 @@ struct CashierView: View {
@ViewBuilder
private func _byPlayerRank() -> some View {
let players = teams.flatMap({ $0.players() }).sorted(using: .keyPath(\.computedRank)).filter({ _shouldDisplayPlayer($0) })
let players = teams.flatMap({ $0.unsortedPlayers() }).sorted(using: .keyPath(\.computedRank)).filter({ cashierViewModel._shouldDisplayPlayer($0) })
_byPlayer(players)
}
@ViewBuilder
private func _byPlayerAge() -> some View {
let players = teams.flatMap({ $0.players() }).filter({ $0.computedAge != nil }).sorted(using: .keyPath(\.computedAge!)).filter({ _shouldDisplayPlayer($0) })
let players = teams.flatMap({ $0.unsortedPlayers() }).filter({ $0.computedAge != nil }).sorted(using: .keyPath(\.computedAge!)).filter({ cashierViewModel._shouldDisplayPlayer($0) })
_byPlayer(players)
}
@ViewBuilder
private func _byPlayerLastName() -> some View {
let players = teams.flatMap({ $0.players() }).sorted(using: .keyPath(\.lastName)).filter({ _shouldDisplayPlayer($0) })
let players = teams.flatMap({ $0.unsortedPlayers() }).sorted(using: .keyPath(\.lastName)).filter({ cashierViewModel._shouldDisplayPlayer($0) })
_byPlayer(players)
}
@ViewBuilder
private func _byPlayerFirstName() -> some View {
let players = teams.flatMap({ $0.players() }).sorted(using: .keyPath(\.firstName)).filter({ _shouldDisplayPlayer($0) })
let players = teams.flatMap({ $0.unsortedPlayers() }).sorted(using: .keyPath(\.firstName)).filter({ cashierViewModel._shouldDisplayPlayer($0) })
_byPlayer(players)
}
@ViewBuilder
private func _byTeamRankView() -> some View {
let _teams = sortOrder == .ascending ? teams : teams.reversed()
ForEach(_teams) { team in
if _shouldDisplayTeam(team) {
let _teams = cashierViewModel.sortOrder == .ascending ? teams : teams.reversed()
let _filteredTeams = _teams.filter({ cashierViewModel._shouldDisplayTeam($0) })
if _filteredTeams.isEmpty {
_contentUnavailableView()
} else {
ForEach(_filteredTeams) { team in
Section {
_cashierPlayersView(team.players())
ForEach(team.players()) { player in
if cashierViewModel._shouldDisplayPlayer(player) {
EditablePlayerView(player: player, editingOptions: [.licenceId, .name, .payment])
}
}
} header: {
HStack {
if let callDate = team.callDate {
Text(callDate.localizedDate())
}
Spacer()
Text(team.weight.formatted())
VStack(alignment: .trailing, spacing: 0) {
Text("Poids").font(.caption)
Text(team.weight.formatted())
}
}
} footer: {
if tournaments.count > 1, let tournamentTitle = team.tournamentObject()?.tournamentTitle() {
@ -252,52 +298,40 @@ struct CashierView: View {
@ViewBuilder
private func _byCallDateView() -> some View {
let groupedTeams = Dictionary(grouping: teams) { team in
let _teams = teams.filter({ $0.callDate != nil && cashierViewModel._shouldDisplayTeam($0) })
if _teams.isEmpty {
_contentUnavailableView()
}
let groupedTeams = Dictionary(grouping: _teams) { team in
team.callDate
}
let keys = sortOrder == .ascending ? groupedTeams.keys.compactMap { $0 }.sorted() : groupedTeams.keys.compactMap { $0 }.sorted().reversed()
let keys = cashierViewModel.sortOrder == .ascending ? groupedTeams.keys.compactMap { $0 }.sorted() : groupedTeams.keys.compactMap { $0 }.sorted().reversed()
ForEach(keys, id: \.self) { key in
if let _teams = groupedTeams[key] {
ForEach(_teams) { team in
if _shouldDisplayTeam(team) {
Section {
_cashierPlayersView(team.players())
} header: {
Text(key.localizedDate())
} footer: {
if tournaments.count > 1, let tournamentTitle = team.tournamentObject()?.tournamentTitle() {
Text(tournamentTitle)
Section {
ForEach(team.players()) { player in
if cashierViewModel._shouldDisplayPlayer(player) {
EditablePlayerView(player: player, editingOptions: [.licenceId, .name, .payment])
}
}
} header: {
Text(key.localizedDate())
} footer: {
if tournaments.count > 1, let tournamentTitle = team.tournamentObject()?.tournamentTitle() {
Text(tournamentTitle)
}
}
}
}
}
}
@ViewBuilder
private func _cashierPlayersView(_ players: [PlayerRegistration]) -> some View {
ForEach(players) { player in
if _shouldDisplayPlayer(player) {
computedPlayerView(player)
}
}
}
private func _isContentUnavailable() -> Bool {
switch sortOption {
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
}
}
private func _unavailableIcon() -> String {
switch sortOption {
switch cashierViewModel.sortOption {
case .teamRank, .callDate:
return "person.2.slash.fill"
default:
@ -307,8 +341,8 @@ struct CashierView: View {
@ViewBuilder
private func _contentUnavailableView() -> some View {
if isSearching {
ContentUnavailableView.search(text: searchText)
if cashierViewModel.isSearching {
ContentUnavailableView.search(text: cashierViewModel.searchText)
} else {
ContentUnavailableView("Aucun résultat", systemImage: _unavailableIcon())
}

@ -41,11 +41,6 @@ struct ClubSearchView: View {
var club: Club?
var selection: ((Club) -> ())? = nil
fileprivate class DebouncableViewModel: ObservableObject {
@Published var debouncableText: String = ""
var debounceTrigger: Double = 0.15
}
private var distanceLimit: Measurement<UnitLength> {
Measurement(value: radius, unit: .kilometers)
}

@ -48,24 +48,47 @@ struct GenericDestinationPickerView<T: Identifiable & Selectable & Equatable >:
.id(destination.id)
.buttonStyle(.plain)
.overlay(alignment: .bottomTrailing) {
if let badge = destination.badgeImage() {
Image(systemName: badge.systemName())
.foregroundColor(badge.color())
.imageScale(.medium)
.background (
Color(.systemBackground)
.clipShape(.circle)
)
.offset(x: 3, y: 3)
} else if let count = destination.badgeValue(), count > 0 {
Image(systemName: count <= 50 ? "\(count).circle.fill" : "plus.circle.fill")
.foregroundColor(destination.badgeValueColor() ?? .red)
.imageScale(.medium)
.background (
Color(.systemBackground)
.clipShape(.circle)
)
.offset(x: 3, y: 3)
if destination.displayImageIfValueZero() {
let count = destination.badgeValue()
if let count, count == 0, let badge = destination.badgeImage() {
Image(systemName: badge.systemName())
.foregroundColor(badge.color())
.imageScale(.medium)
.background (
Color(.systemBackground)
.clipShape(.circle)
)
.offset(x: 3, y: 3)
} else if let count, count > 0 {
Image(systemName: count <= 50 ? "\(count).circle.fill" : "plus.circle.fill")
.foregroundColor(destination.badgeValueColor() ?? .red)
.imageScale(.medium)
.background (
Color(.systemBackground)
.clipShape(.circle)
)
.offset(x: 3, y: 3)
}
} else {
if let badge = destination.badgeImage() {
Image(systemName: badge.systemName())
.foregroundColor(badge.color())
.imageScale(.medium)
.background (
Color(.systemBackground)
.clipShape(.circle)
)
.offset(x: 3, y: 3)
} else if let count = destination.badgeValue(), count > 0 {
Image(systemName: count <= 50 ? "\(count).circle.fill" : "plus.circle.fill")
.foregroundColor(destination.badgeValueColor() ?? .red)
.imageScale(.medium)
.background (
Color(.systemBackground)
.clipShape(.circle)
)
.offset(x: 3, y: 3)
}
}
}
}

@ -17,18 +17,40 @@ extension String : Identifiable {
struct BroadcastView: View {
@EnvironmentObject var dataStore: DataStore
@Environment(Tournament.self) var tournament: Tournament
@Environment(NavigationViewModel.self) var navigation: NavigationViewModel
let context = CIContext()
let filter = CIFilter.qrCodeGenerator()
@State private var urlToShow: String?
@State private var tvMode: Bool = false
@State private var pageLink: PageLink = .teams
let createAccountTip = CreateAccountTip()
let tournamentPublishingTip = TournamentPublishingTip()
let tournamentTVBroadcastTip = TournamentTVBroadcastTip()
var body: some View {
@Bindable var tournament = tournament
List {
if Store.main.userId == nil {
Section {
TipView(createAccountTip) { action in
switch action.id {
case CreateAccountTip.ActionKey.accessPadelClubWebPage.rawValue:
UIApplication.shared.open(URLs.main.url)
case CreateAccountTip.ActionKey.createAccount.rawValue:
navigation.selectedTab = .umpire
default:
break
//todo
// case CreateAccountTip.ActionKey.learnMore.rawValue:
// UIApplication.shared.open(URLs.padelClubLandingPage.url)
}
}
.tipStyle(tint: .master)
}
}
Section {
TipView(tournamentPublishingTip) { action in
UIApplication.shared.open(URLs.main.url)

@ -13,6 +13,9 @@ struct PrintSettingsView: View {
@StateObject var generator: HtmlGenerator
@State private var presentShareView: Bool = false
@State private var prepareGroupStage: Bool = false
@State private var generationId: UUID = UUID()
@State private var generationGroupStageId: UUID = UUID()
@State private var generating: Bool = false
init(tournament: Tournament) {
self.tournament = tournament
@ -63,26 +66,55 @@ struct PrintSettingsView: View {
} header: {
Text("Tableau principal")
}
if generating == false {
RowButtonView("Générer le PDF", systemImage: "printer") {
await MainActor.run() {
self.generating = true
}
generator.preparePDF { result in
switch result {
case .success(true):
if generator.includeGroupStage && generator.groupStageIsReady == false && tournament.groupStages().isEmpty == false {
self.prepareGroupStage = true
self.generationGroupStageId = UUID()
} else {
self.presentShareView = true
self.generating = false
}
case .success(false):
print("didn't save pdf")
break
case .failure(let error):
print(error)
break
}
}
self.prepareGroupStage = false
self.generationId = UUID()
}
.disabled(generator.includeBracket == false && generator.includeGroupStage == false && generator.includeLoserBracket == false)
} else {
LabeledContent {
ProgressView()
} label: {
Text("Préparation du PDF")
}
.id(generationId)
}
}
Section {
NavigationLink {
WebView(htmlRawData: generator.generateHtml(), loadStatusChanged: { loaded, error, webView in
})
WebViewPreview(bracket: true)
.environmentObject(generator)
} label: {
Text("Aperçu du tableau")
}
ForEach(tournament.groupStages()) { groupStage in
NavigationLink {
WebView(htmlRawData: HtmlService.groupstage(groupStage: groupStage).html(headName: generator.displayHeads, withRank: generator.displayRank, withScore: false), loadStatusChanged: { loaded, error, webView in
if let error {
print("preparePDF", error)
} else if loaded == false {
generator.generateGroupStage(webView: webView)
} else {
print("preparePDF", "is loading")
}
})
WebViewPreview(groupStage: groupStage)
.environmentObject(generator)
} label: {
Text("Aperçu de la \(groupStage.groupStageTitle())")
}
@ -90,16 +122,59 @@ struct PrintSettingsView: View {
}
}
.background {
WebView(htmlRawData: generator.generateHtml(), loadStatusChanged: { loaded, error, webView in
if let error {
print("preparePDF", error)
} else if loaded == false {
generator.generateWebView(webView: webView)
} else {
print("preparePDF", "is loading")
}
}).opacity(0)
if generating {
_backgroundGenerationWebView()
_backgroundGroupStageWebView()
}
}
.navigationTitle("Imprimer")
.toolbarBackground(.visible, for: .navigationBar)
.navigationBarTitleDisplayMode(.inline)
// .toolbar {
// ToolbarItem(placement: .topBarTrailing) {
// Menu {
// Section {
// ShareLink(item: generator.generateHtml()) {
// Text("Tableau")
// }
//
// if let groupStage = tournament.groupStages().first {
// ShareLink(item: HtmlService.groupstage(groupStage: groupStage).html(headName: generator.displayHeads, withRank: generator.displayRank, withScore: false)) {
// Text("Poule")
// }
// }
// } header: {
// Text("Partager le code source HTML")
// }
// } label: {
// Label("Options", systemImage: "ellipsis.circle")
// }
// }
// }
.sheet(isPresented: $presentShareView) {
if let pdfURL = generator.pdfURL {
ShareSheet(urls: [pdfURL])
}
}
}
@ViewBuilder
private func _backgroundGenerationWebView() -> some View {
WebView(htmlRawData: generator.generateHtml(), loadStatusChanged: { loaded, error, webView in
if let error {
print("preparePDF", error)
} else if loaded == false {
generator.generateWebView(webView: webView)
} else {
print("preparePDF", "is loading")
}
})
.opacity(0)
.id(generationId)
}
private func _backgroundGroupStageWebView() -> some View {
Group {
if prepareGroupStage {
ForEach(tournament.groupStages()) { groupStage in
WebView(htmlRawData: HtmlService.groupstage(groupStage: groupStage).html(headName: generator.displayHeads, withRank: generator.displayRank, withScore: false), loadStatusChanged: { loaded, error, webView in
@ -114,64 +189,7 @@ struct PrintSettingsView: View {
}
}
}
.navigationTitle("Imprimer")
.toolbarBackground(.visible, for: .navigationBar)
.toolbarBackground(.visible, for: .bottomBar)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .bottomBar) {
Button {
generator.preparePDF { result in
switch result {
case .success(true):
if generator.includeGroupStage && generator.groupStageIsReady == false {
self.prepareGroupStage = true
} else {
self.presentShareView = true
}
case .success(false):
print("didn't save pdf")
break
case .failure(let error):
print(error)
break
}
}
self.prepareGroupStage = false
self.generator.buildPDF()
} label: {
Text("Obtenir le PDF")
}
.disabled(generator.includeBracket == false && generator.includeGroupStage == false && generator.includeLoserBracket == false)
.buttonStyle(.borderedProminent)
}
ToolbarItem(placement: .topBarTrailing) {
Menu {
Section {
ShareLink(item: generator.generateHtml()) {
Text("Tableau")
}
if let groupStage = tournament.groupStages().first {
ShareLink(item: HtmlService.groupstage(groupStage: groupStage).html(headName: generator.displayHeads, withRank: generator.displayRank, withScore: false)) {
Text("Poule")
}
}
} header: {
Text("Partager le code source HTML")
}
} label: {
Label("Options", systemImage: "ellipsis.circle")
}
}
}
.sheet(isPresented: $presentShareView) {
if let pdfURL = generator.pdfURL {
ShareSheet(urls: [pdfURL])
}
}
.id(generationGroupStageId)
}
}
@ -239,3 +257,34 @@ struct WebView: UIViewRepresentable {
}
}
struct WebViewPreview: View {
@EnvironmentObject var generator: HtmlGenerator
let bracket: Bool
let groupStage: GroupStage?
@State private var html: String?
init(bracket: Bool = false, groupStage: GroupStage? = nil) {
self.bracket = bracket
self.groupStage = groupStage
}
var body: some View {
Group {
if let html {
WebView(htmlRawData: html, loadStatusChanged: { loaded, error, webView in
})
} else {
ProgressView()
.onAppear {
if let groupStage {
html = HtmlService.groupstage(groupStage: groupStage).html(headName: generator.displayHeads, withRank: generator.displayRank, withScore: false)
} else {
html = generator.generateHtml()
}
}
}
}
}
}

@ -42,6 +42,10 @@ enum CashierDestination: Identifiable, Selectable, Equatable {
}
}
func displayImageIfValueZero() -> Bool {
return true
}
func badgeValue() -> Int? {
switch self {
case .summary:
@ -51,7 +55,7 @@ enum CashierDestination: Identifiable, Selectable, Equatable {
case .bracket(let round):
return round.seeds().flatMap { $0.unsortedPlayers() }.filter({ $0.hasPaid() == false }).count
case .all(let tournament):
return tournament.selectedPlayers().filter({ $0.hasPaid() == false }).count
return nil
}
}
@ -63,12 +67,10 @@ enum CashierDestination: Identifiable, Selectable, Equatable {
switch self {
case .summary:
return nil
case .groupStage(let groupStage):
return groupStage.unsortedPlayers().allSatisfy({ $0.hasPaid() }) ? .checkmark : nil
case .bracket(let round):
return round.seeds().flatMap { $0.unsortedPlayers() }.allSatisfy({ $0.hasPaid() }) ? .checkmark : nil
case .all(let tournament):
return tournament.selectedPlayers().allSatisfy({ $0.hasPaid() }) ? .checkmark : nil
case .all:
return nil
default:
return .checkmark
}
}
@ -77,6 +79,7 @@ enum CashierDestination: Identifiable, Selectable, Equatable {
struct TournamentCashierView: View {
var tournament: Tournament
@State private var selectedDestination: CashierDestination?
@StateObject private var cashierViewModel: CashierViewModel = CashierViewModel()
func allDestinations() -> [CashierDestination] {
var allDestinations : [CashierDestination] = []
@ -132,10 +135,13 @@ struct TournamentCashierView: View {
CashierDetailView(tournament: tournament)
case .groupStage(let groupStage):
CashierView(tournament: tournament, teams: groupStage.teams())
.environmentObject(cashierViewModel)
case .bracket(let round):
CashierView(tournament: tournament, teams: round.seeds())
.environmentObject(cashierViewModel)
case .all(let tournament):
CashierView(tournament: tournament, teams: tournament.selectedSortedTeams())
.environmentObject(cashierViewModel)
}
}
}

@ -20,7 +20,7 @@ struct TournamentBuildView: View {
if tournament.hasEnded() {
Section {
NavigationLink(value: Screen.rankings) {
Text("Classement")
Text("Classement final des équipes")
}
}
}
@ -30,7 +30,7 @@ struct TournamentBuildView: View {
NavigationLink(value: Screen.groupStage) {
LabeledContent {
if let groupStageStatus {
Text(groupStageStatus)
Text(groupStageStatus).lineLimit(1)
.multilineTextAlignment(.trailing)
} else {
ProgressView()
@ -51,7 +51,7 @@ struct TournamentBuildView: View {
NavigationLink(value: Screen.round) {
LabeledContent {
if let bracketStatus {
Text(bracketStatus)
Text(bracketStatus).lineLimit(1)
.multilineTextAlignment(.trailing)
} else {
ProgressView()
@ -82,7 +82,9 @@ struct TournamentBuildView: View {
} label: {
Text("Horaires")
if let tournamentStatus {
Text(tournamentStatus.label)
Text(tournamentStatus.label).lineLimit(1)
} else {
Text(" ")
}
}
}
@ -101,7 +103,9 @@ struct TournamentBuildView: View {
} label: {
Text("Convocations")
if let tournamentStatus {
Text(tournamentStatus.label)
Text(tournamentStatus.label).lineLimit(1)
} else {
Text(" ")
}
}
}
@ -122,7 +126,9 @@ struct TournamentBuildView: View {
} label: {
Text("Encaissement")
if let tournamentStatus {
Text(tournamentStatus.label)
Text(tournamentStatus.label).lineLimit(1)
} else {
Text(" ")
}
}
}

@ -6,6 +6,7 @@
//
import SwiftUI
import LeStorage
struct TournamentInitView: View {
var tournament: Tournament
@ -43,13 +44,21 @@ struct TournamentInitView: View {
NavigationLink(value: Screen.broadcast) {
LabeledContent {
if tournament.isPrivate {
Text("tournoi privé").foregroundStyle(.logoRed)
if Store.main.userId == nil {
Image(systemName: "xmark.circle.fill")
.foregroundStyle(.logoRed)
} else {
Text("Automatique")
if tournament.isPrivate {
Text("tournoi privé").foregroundStyle(.logoRed)
} else {
Text("Automatique")
}
}
} label: {
Text("Publication")
if Store.main.userId == nil {
Text("Un compte Padel Club est nécessaire")
}
}
}

@ -93,6 +93,7 @@ struct TournamentView: View {
TournamentRankView()
case .broadcast:
BroadcastView()
.environment(navigation)
case .event:
if let event = tournament.eventObject() {
EventView(event: event)

Loading…
Cancel
Save