Merge branch 'main' into sync3

sync3
Laurent 5 months ago
commit 892db419fe
  1. 4
      PadelClub.xcodeproj/project.pbxproj
  2. 2
      PadelClub/Extensions/Tournament+Extensions.swift
  3. 20
      PadelClub/Views/Cashier/CashierDetailView.swift
  4. 8
      PadelClub/Views/Cashier/CashierSettingsView.swift
  5. 6
      PadelClub/Views/Cashier/Event/EventStatusView.swift
  6. 10
      PadelClub/Views/Match/Components/MatchDateView.swift
  7. 77
      PadelClub/Views/Match/EditSharingView.swift
  8. 6
      PadelClub/Views/Match/MatchDetailView.swift
  9. 11
      PadelClub/Views/Navigation/Agenda/EventListView.swift
  10. 70
      PadelClub/Views/Planning/CourtAvailabilitySettingsView.swift
  11. 149
      PadelClub/Views/Planning/PlanningView.swift
  12. 14
      PadelClub/Views/Score/EditScoreView.swift
  13. 159
      PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift
  14. 3
      PadelClubTests/ServerDataTests.swift

@ -3128,7 +3128,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.2.37; MARKETING_VERSION = 1.2.40;
PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@ -3174,7 +3174,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.2.37; MARKETING_VERSION = 1.2.40;
PRODUCT_BUNDLE_IDENTIFIER = app.padelclub; PRODUCT_BUNDLE_IDENTIFIER = app.padelclub;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";

@ -333,7 +333,7 @@ extension Tournament {
} }
let rankSourceDate = _mostRecentDateAvailable let rankSourceDate = _mostRecentDateAvailable
return Tournament(rankSourceDate: rankSourceDate) return Tournament(rankSourceDate: rankSourceDate, currencyCode: Locale.defaultCurrency())
} }
} }

@ -22,13 +22,17 @@ struct CashierDetailView: View {
self.tournaments = [tournament] self.tournaments = [tournament]
} }
func defaultCurrency() -> String {
tournaments.first?.currencyCode ?? Locale.defaultCurrency()
}
var body: some View { var body: some View {
List { List {
if tournaments.count > 1 { if tournaments.count > 1 {
Section { Section {
LabeledContent { LabeledContent {
if let remainingAmount { if let remainingAmount {
Text(remainingAmount.formatted(.currency(code: Locale.defaultCurrency()).precision(.fractionLength(0)))) Text(remainingAmount.formatted(.currency(code: defaultCurrency()).precision(.fractionLength(0))))
} else { } else {
ProgressView() ProgressView()
} }
@ -38,7 +42,7 @@ struct CashierDetailView: View {
LabeledContent { LabeledContent {
if let earnings { if let earnings {
Text(earnings.formatted(.currency(code: Locale.defaultCurrency()).precision(.fractionLength(0)))) Text(earnings.formatted(.currency(code: defaultCurrency()).precision(.fractionLength(0))))
} else { } else {
ProgressView() ProgressView()
} }
@ -116,7 +120,7 @@ struct CashierDetailView: View {
Section { Section {
LabeledContent { LabeledContent {
if let remainingAmount { if let remainingAmount {
Text(remainingAmount.formatted(.currency(code: Locale.defaultCurrency()).precision(.fractionLength(0)))) Text(remainingAmount.formatted(.currency(code: tournament.defaultCurrency()).precision(.fractionLength(0))))
} else { } else {
ProgressView() ProgressView()
} }
@ -126,7 +130,7 @@ struct CashierDetailView: View {
LabeledContent { LabeledContent {
if let earnings { if let earnings {
Text(earnings.formatted(.currency(code: Locale.defaultCurrency()).precision(.fractionLength(0)))) Text(earnings.formatted(.currency(code: tournament.defaultCurrency()).precision(.fractionLength(0))))
} else { } else {
ProgressView() ProgressView()
} }
@ -176,10 +180,14 @@ struct CashierDetailView: View {
@State private var value: Double? @State private var value: Double?
func defaultCurrency() -> String {
tournaments.first?.currencyCode ?? Locale.defaultCurrency()
}
var body: some View { var body: some View {
LabeledContent { LabeledContent {
if let value { if let value {
Text(value.formatted(.currency(code: Locale.defaultCurrency()))) Text(value.formatted(.currency(code: defaultCurrency())))
} else { } else {
ProgressView() ProgressView()
} }
@ -207,7 +215,7 @@ struct CashierDetailView: View {
if players.count > 0 { if players.count > 0 {
LabeledContent { LabeledContent {
let sum = players.compactMap({ $0.paidAmount(tournament) }).reduce(0.0, +) let sum = players.compactMap({ $0.paidAmount(tournament) }).reduce(0.0, +)
Text(sum.formatted(.currency(code: Locale.defaultCurrency()))) Text(sum.formatted(.currency(code: tournament.defaultCurrency())))
} label: { } label: {
Text(type.localizedLabel()) Text(type.localizedLabel())
Text(players.count.formatted()) Text(players.count.formatted())

@ -29,7 +29,7 @@ struct CashierSettingsView: View {
List { List {
Section { Section {
LabeledContent { LabeledContent {
TextField(tournament.isFree() ? "Gratuite" : "Inscription", value: $entryFee, format: .currency(code: Locale.defaultCurrency())) TextField(tournament.isFree() ? "Gratuite" : "Inscription", value: $entryFee, format: .currency(code: tournament.defaultCurrency()))
.keyboardType(.decimalPad) .keyboardType(.decimalPad)
.multilineTextAlignment(.trailing) .multilineTextAlignment(.trailing)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
@ -38,7 +38,7 @@ struct CashierSettingsView: View {
Text("Frais d'inscription") Text("Frais d'inscription")
} }
LabeledContent { LabeledContent {
TextField("Réduction", value: $clubMemberFeeDeduction, format: .currency(code: Locale.defaultCurrency())) TextField("Réduction", value: $clubMemberFeeDeduction, format: .currency(code: tournament.defaultCurrency()))
.keyboardType(.decimalPad) .keyboardType(.decimalPad)
.multilineTextAlignment(.trailing) .multilineTextAlignment(.trailing)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
@ -132,7 +132,7 @@ struct CashierSettingsView: View {
if focusedField == ._entryFee { if focusedField == ._entryFee {
if tournament.isFree() { if tournament.isFree() {
ForEach(priceTags, id: \.self) { priceTag in ForEach(priceTags, id: \.self) { priceTag in
Button(priceTag.formatted(.currency(code: Locale.defaultCurrency()))) { Button(priceTag.formatted(.currency(code: tournament.defaultCurrency()))) {
entryFee = priceTag entryFee = priceTag
tournament.entryFee = priceTag tournament.entryFee = priceTag
focusedField = nil focusedField = nil
@ -150,7 +150,7 @@ struct CashierSettingsView: View {
} }
} else if focusedField == ._clubMemberFeeDeduction { } else if focusedField == ._clubMemberFeeDeduction {
ForEach(deductionTags, id: \.self) { deductionTag in ForEach(deductionTags, id: \.self) { deductionTag in
Button(deductionTag.formatted(.currency(code: Locale.defaultCurrency()).precision(.fractionLength(0)))) { Button(deductionTag.formatted(.currency(code: tournament.defaultCurrency()).precision(.fractionLength(0)))) {
clubMemberFeeDeduction = deductionTag clubMemberFeeDeduction = deductionTag
tournament.clubMemberFeeDeduction = deductionTag tournament.clubMemberFeeDeduction = deductionTag
focusedField = nil focusedField = nil

@ -36,6 +36,10 @@ struct EventStatusView: View {
self.tournaments = tournaments self.tournaments = tournaments
} }
func defaultCurrency() -> String {
tournaments.first?.currencyCode ?? Locale.defaultCurrency()
}
private func _calculateTeamsCount() async { private func _calculateTeamsCount() async {
Task { Task {
self.teamsCount = tournaments.map({ $0.selectedSortedTeams().count }).reduce(0, +) self.teamsCount = tournaments.map({ $0.selectedSortedTeams().count }).reduce(0, +)
@ -55,7 +59,7 @@ struct EventStatusView: View {
private func _currencyView(value: Double, value2: Double? = nil) -> some View { private func _currencyView(value: Double, value2: Double? = nil) -> some View {
let maps = [value, value2].compactMap({ $0 }).map { let maps = [value, value2].compactMap({ $0 }).map {
$0.formatted(.currency(code: Locale.defaultCurrency()).precision(.fractionLength(0))) $0.formatted(.currency(code: defaultCurrency()).precision(.fractionLength(0)))
} }
let string = maps.joined(separator: " / ") let string = maps.joined(separator: " / ")

@ -46,7 +46,7 @@ struct MatchDateView: View {
if let updatedField { if let updatedField {
match.setCourt(updatedField) match.setCourt(updatedField)
} }
match.startDate = currentDate match.updateStartDate(currentDate, keepPlannedStartDate: true)
match.endDate = nil match.endDate = nil
match.confirmed = true match.confirmed = true
_save() _save()
@ -55,7 +55,7 @@ struct MatchDateView: View {
if let updatedField { if let updatedField {
match.setCourt(updatedField) match.setCourt(updatedField)
} }
match.startDate = Calendar.current.date(byAdding: .minute, value: 5, to: currentDate) match.updateStartDate(Calendar.current.date(byAdding: .minute, value: 5, to: currentDate), keepPlannedStartDate: true)
match.endDate = nil match.endDate = nil
match.confirmed = true match.confirmed = true
_save() _save()
@ -64,7 +64,7 @@ struct MatchDateView: View {
if let updatedField { if let updatedField {
match.setCourt(updatedField) match.setCourt(updatedField)
} }
match.startDate = Calendar.current.date(byAdding: .minute, value: 15, to: currentDate) match.updateStartDate(Calendar.current.date(byAdding: .minute, value: 15, to: currentDate), keepPlannedStartDate: true)
match.endDate = nil match.endDate = nil
match.confirmed = true match.confirmed = true
_save() _save()
@ -73,7 +73,7 @@ struct MatchDateView: View {
if let updatedField { if let updatedField {
match.setCourt(updatedField) match.setCourt(updatedField)
} }
match.startDate = Calendar.current.date(byAdding: .minute, value: estimatedDuration, to: currentDate) match.updateStartDate(Calendar.current.date(byAdding: .minute, value: estimatedDuration, to: currentDate), keepPlannedStartDate: true)
match.endDate = nil match.endDate = nil
match.confirmed = true match.confirmed = true
_save() _save()
@ -96,7 +96,7 @@ struct MatchDateView: View {
} }
Divider() Divider()
Button("Retirer l'horaire") { Button("Retirer l'horaire") {
match.cleanScheduleAndSave() match.updateStartDate(nil, keepPlannedStartDate: true)
} }
} label: { } label: {
label label

@ -5,10 +5,11 @@
// Created by Razmig Sarkissian on 03/02/2024. // Created by Razmig Sarkissian on 03/02/2024.
// //
import SwiftUI
import TipKit
import CoreTransferable import CoreTransferable
import PadelClubData import PadelClubData
import SwiftUI
import TipKit
import AVFoundation
struct EditSharingView: View { struct EditSharingView: View {
var match: Match var match: Match
@ -17,6 +18,8 @@ struct EditSharingView: View {
@State private var showCamera: Bool = false @State private var showCamera: Bool = false
@State private var newImage: UIImage? @State private var newImage: UIImage?
@State private var copied: Bool = false @State private var copied: Bool = false
@State private var cameraAuthorizationStatus: AVAuthorizationStatus = .notDetermined
@State private var showCameraAccessAlert: Bool = false
var shareMessage: String { var shareMessage: String {
shareMessage(displayRank: displayRank, displayTeamName: displayTeamTitle) shareMessage(displayRank: displayRank, displayTeamName: displayTeamTitle)
@ -32,7 +35,8 @@ struct EditSharingView: View {
} }
if let startDate = match.startDate { if let startDate = match.startDate {
locAndTime = [locAndTime, startDate.formattedAsHourMinute()].compactMap({ $0 }).joined(separator: " à ") locAndTime = [locAndTime, startDate.formattedAsHourMinute()].compactMap({ $0 })
.joined(separator: " à ")
} }
if let locAndTime, locAndTime.isEmpty == false { if let locAndTime, locAndTime.isEmpty == false {
@ -44,11 +48,21 @@ struct EditSharingView: View {
messageData.append(tournament.tournamentTitle()) messageData.append(tournament.tournamentTitle())
} }
let message = [match.isLoserBracket ? "Classement" : nil, match.roundTitle(), match.isLoserBracket ? nil : ((match.index > 0 || match.isGroupStage()) ? match.matchTitle(.short) : nil)].compactMap({ $0 }).joined(separator: " ") let message = [
match.isLoserBracket ? "Classement" : nil, match.roundTitle(),
match.isLoserBracket
? nil
: ((match.index > 0 || match.isGroupStage()) ? match.matchTitle(.short) : nil),
].compactMap({ $0 }).joined(separator: " ")
messageData.append(message) messageData.append(message)
guard let labelOne = match.team(.one)?.teamLabelRanked(displayRank: displayRank, displayTeamName: displayTeamName), let labelTwo = match.team(.two)?.teamLabelRanked(displayRank: displayRank, displayTeamName: displayTeamName) else { guard
let labelOne = match.team(.one)?.teamLabelRanked(
displayRank: displayRank, displayTeamName: displayTeamName),
let labelTwo = match.team(.two)?.teamLabelRanked(
displayRank: displayRank, displayTeamName: displayTeamName)
else {
return messageData.joined(separator: "\n") return messageData.joined(separator: "\n")
} }
@ -60,8 +74,6 @@ struct EditSharingView: View {
return messageData.joined(separator: "\n") return messageData.joined(separator: "\n")
} }
var body: some View { var body: some View {
List { List {
if let newImage { if let newImage {
@ -93,7 +105,7 @@ struct EditSharingView: View {
} else { } else {
Section { Section {
RowButtonView("Prendre une photo", systemImage: "camera") { RowButtonView("Prendre une photo", systemImage: "camera") {
showCamera = true checkCameraAuthorization()
} }
} }
} }
@ -124,12 +136,13 @@ struct EditSharingView: View {
.toolbar { .toolbar {
ToolbarItem(placement: .topBarTrailing) { ToolbarItem(placement: .topBarTrailing) {
if let newImage { if let newImage {
let photo = Photo(image: Image(uiImage:newImage), caption: shareMessage) let photo = Photo(image: Image(uiImage: newImage), caption: shareMessage)
ShareLink( ShareLink(
item: photo, item: photo,
preview: SharePreview( preview: SharePreview(
photo.caption, photo.caption,
image: photo.image)) { image: photo.image)
) {
Text("Partager") Text("Partager")
} }
.onAppear { .onAppear {
@ -141,6 +154,22 @@ struct EditSharingView: View {
} }
} }
} }
.onChange(of: displayTeamTitle) {
copied = false
}
.alert("Accès à l'appareil photo requis", isPresented: $showCameraAccessAlert) {
Button("Annuler", role: .cancel) {}
Button("Paramètres") {
if let settingsURL = URL(string: UIApplication.openSettingsURLString) {
UIApplication.shared.open(settingsURL)
}
}
} message: {
Text(
"Pour prendre des photos, autorisez l'accès à l'appareil photo dans les paramètres de l'application."
)
}
.navigationTitle("Préparation") .navigationTitle("Préparation")
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.visible, for: .navigationBar) .toolbarBackground(.visible, for: .navigationBar)
@ -155,6 +184,34 @@ struct EditSharingView: View {
copied = false copied = false
} }
} }
func checkCameraAuthorization() {
let status = AVCaptureDevice.authorizationStatus(for: .video)
self.cameraAuthorizationStatus = status
switch status {
case .authorized:
// Camera access already granted, show camera
self.showCamera = true
case .notDetermined:
// Request camera access
AVCaptureDevice.requestAccess(for: .video) { granted in
DispatchQueue.main.async {
if granted {
self.showCamera = true
} else {
self.showCameraAccessAlert = true
}
}
}
case .denied, .restricted:
// Camera access was previously denied or restricted
self.showCameraAccessAlert = true
@unknown default:
// Handle future cases
self.showCameraAccessAlert = true
}
}
} }
struct Photo: Transferable { struct Photo: Transferable {

@ -336,10 +336,7 @@ struct MatchDetailView: View {
} }
} }
Button(role: .destructive) { Button(role: .destructive) {
match.startDate = nil match.updateStartDate(nil, keepPlannedStartDate: true)
match.endDate = nil
match.confirmed = false
save()
} label: { } label: {
Text("Supprimer l'horaire") Text("Supprimer l'horaire")
} }
@ -355,6 +352,7 @@ struct MatchDetailView: View {
} }
Divider() Divider()
Button(role: .destructive) { Button(role: .destructive) {
match.updateStartDate(nil, keepPlannedStartDate: false)
match.resetTeamScores(outsideOf: []) match.resetTeamScores(outsideOf: [])
match.resetMatch() match.resetMatch()
match.confirmed = false match.confirmed = false

@ -427,12 +427,11 @@ struct EventListView: View {
LabelDelete() LabelDelete()
} }
} }
// Button() { Button() {
// self.showUserSearch = true dataStore.deleteTournament(tournament, noSync: true)
// } label: { } label: {
// ShareLabel().tint(.orange) Label("Soft delete", systemImage: "trash")
// } }
} }
#endif #endif
} }

@ -262,20 +262,29 @@ struct DateAdjusterView: View {
var body: some View { var body: some View {
HStack(spacing: 4) { HStack(spacing: 4) {
if let matchFormat { if let matchFormat {
_createButton(label: "-\(matchFormat.defaultEstimatedDuration)m", timeOffset: -matchFormat.defaultEstimatedDuration, component: .minute) _createButton(label: "-\(matchFormat.getEstimatedDuration())m", timeOffset: -matchFormat.getEstimatedDuration(), component: .minute)
_createButton(label: "+\(matchFormat.defaultEstimatedDuration)m", timeOffset: +matchFormat.defaultEstimatedDuration, component: .minute) Divider()
_createButton(label: "+\(matchFormat.getEstimatedDuration())m", timeOffset: +matchFormat.getEstimatedDuration(), component: .minute)
Divider()
_createButton(label: "-\(matchFormat.estimatedTimeWithBreak)m", timeOffset: -matchFormat.estimatedTimeWithBreak, component: .minute) _createButton(label: "-\(matchFormat.estimatedTimeWithBreak)m", timeOffset: -matchFormat.estimatedTimeWithBreak, component: .minute)
Divider()
_createButton(label: "+\(matchFormat.estimatedTimeWithBreak)m", timeOffset: +matchFormat.estimatedTimeWithBreak, component: .minute) _createButton(label: "+\(matchFormat.estimatedTimeWithBreak)m", timeOffset: +matchFormat.estimatedTimeWithBreak, component: .minute)
} else if let time { } else if let time {
_createButton(label: "-\(time)m", timeOffset: -time, component: .minute) _createButton(label: "-\(time)m", timeOffset: -time, component: .minute)
Divider()
_createButton(label: "-\(time/2)m", timeOffset: -time/2, component: .minute) _createButton(label: "-\(time/2)m", timeOffset: -time/2, component: .minute)
Divider()
_createButton(label: "+\(time/2)m", timeOffset: time/2, component: .minute) _createButton(label: "+\(time/2)m", timeOffset: time/2, component: .minute)
Divider()
_createButton(label: "+\(time)m", timeOffset: time, component: .minute) _createButton(label: "+\(time)m", timeOffset: time, component: .minute)
} else { } else {
_createButton(label: "-1h", timeOffset: -1, component: .hour) _createButton(label: "-1h", timeOffset: -60, component: .minute)
Divider()
_createButton(label: "-30m", timeOffset: -30, component: .minute) _createButton(label: "-30m", timeOffset: -30, component: .minute)
Divider()
_createButton(label: "+30m", timeOffset: 30, component: .minute) _createButton(label: "+30m", timeOffset: 30, component: .minute)
_createButton(label: "+1h", timeOffset: 1, component: .hour) Divider()
_createButton(label: "+1h", timeOffset: 60, component: .minute)
} }
} }
.font(.headline) .font(.headline)
@ -287,11 +296,58 @@ struct DateAdjusterView: View {
}) { }) {
Text(label) Text(label)
.lineLimit(1) .lineLimit(1)
.font(.footnote)
.underline()
.frame(maxWidth: .infinity) // Make buttons take equal space .frame(maxWidth: .infinity) // Make buttons take equal space
} }
.buttonStyle(.borderedProminent) .buttonStyle(.borderless)
.tint(.master)
}
}
struct StepAdjusterView: View {
@Binding var step: Int
var time: Int?
var matchFormat: MatchFormat?
var body: some View {
HStack(spacing: 4) {
if let matchFormat {
_createButton(label: "-\(matchFormat.getEstimatedDuration())m", timeOffset: -matchFormat.getEstimatedDuration(), component: .minute)
Divider()
_createButton(label: "+\(matchFormat.getEstimatedDuration())m", timeOffset: +matchFormat.getEstimatedDuration(), component: .minute)
Divider()
_createButton(label: "-\(matchFormat.estimatedTimeWithBreak)m", timeOffset: -matchFormat.estimatedTimeWithBreak, component: .minute)
Divider()
_createButton(label: "+\(matchFormat.estimatedTimeWithBreak)m", timeOffset: +matchFormat.estimatedTimeWithBreak, component: .minute)
} else if let time {
_createButton(label: "-\(time)m", timeOffset: -time, component: .minute)
Divider()
_createButton(label: "-\(time/2)m", timeOffset: -time/2, component: .minute)
Divider()
_createButton(label: "+\(time/2)m", timeOffset: time/2, component: .minute)
Divider()
_createButton(label: "+\(time)m", timeOffset: time, component: .minute)
} else {
_createButton(label: "-1h", timeOffset: -60, component: .minute)
Divider()
_createButton(label: "-30m", timeOffset: -30, component: .minute)
Divider()
_createButton(label: "+30m", timeOffset: 30, component: .minute)
Divider()
_createButton(label: "+1h", timeOffset: 60, component: .minute)
}
}
.font(.headline)
}
private func _createButton(label: String, timeOffset: Int, component: Calendar.Component) -> some View {
Button(action: {
step += timeOffset
}) {
Text(label)
.lineLimit(1)
.frame(maxWidth: .infinity) // Make buttons take equal space
}
.buttonStyle(.borderless)
.tint(.master) .tint(.master)
} }
} }

@ -110,7 +110,7 @@ struct PlanningView: View {
if _confirmationMode() { if _confirmationMode() {
ToolbarItem(placement: .topBarLeading) { ToolbarItem(placement: .topBarLeading) {
Button("Annuler") { Button(enableMove ? "Annuler" : "Terminer") {
enableMove = false enableMove = false
enableEditionBinding.wrappedValue = false enableEditionBinding.wrappedValue = false
} }
@ -130,29 +130,27 @@ struct PlanningView: View {
} }
} }
} else { } else {
ToolbarItemGroup(placement: .topBarTrailing) {
Menu {
if notSlots == false { if notSlots == false {
ToolbarItemGroup(placement: .bottomBar) {
HStack {
CourtOptionsView(timeSlots: timeSlots, underlined: false) CourtOptionsView(timeSlots: timeSlots, underlined: false)
Spacer()
Toggle(isOn: $enableMove) { Toggle(isOn: $enableMove) {
Label { Label {
Text("Déplacer") Text("Déplacer un créneau")
} icon: { } icon: {
Image(systemName: "rectangle.2.swap") Image(systemName: "rectangle.2.swap")
} }
} }
.popoverTip(timeSlotMoveOptionTip) .popoverTip(timeSlotMoveOptionTip)
.disabled(_confirmationMode()) .disabled(_confirmationMode())
Spacer()
Toggle(isOn: enableEditionBinding) { Toggle(isOn: enableEditionBinding) {
Text("Modifier") Text("Modifier un horaire")
} }
.disabled(_confirmationMode()) .disabled(_confirmationMode())
} }
}
} Divider()
ToolbarItemGroup(placement: .topBarTrailing) {
Menu { Menu {
Section { Section {
Picker(selection: $showFinishedMatches) { Picker(selection: $showFinishedMatches) {
@ -189,6 +187,7 @@ struct PlanningView: View {
filterOption == .byCourt || showFinishedMatches ? .fill : .none) filterOption == .byCourt || showFinishedMatches ? .fill : .none)
} }
Divider()
Button("Mettre à jour", systemImage: "arrow.trianglehead.2.clockwise.rotate.90.circle") { Button("Mettre à jour", systemImage: "arrow.trianglehead.2.clockwise.rotate.90.circle") {
let now = Date() let now = Date()
@ -206,6 +205,11 @@ struct PlanningView: View {
} }
} }
.popoverTip(updatePlannedDatesTip) .popoverTip(updatePlannedDatesTip)
} label: {
LabelOptions()
}
} }
} }
}) })
@ -235,7 +239,7 @@ struct PlanningView: View {
@Environment(\.editMode) private var editMode @Environment(\.editMode) private var editMode
@State private var selectedIds = Set<String>() @State private var selectedIds = Set<String>()
@State private var showDateUpdateView: Bool = false @State private var showDateUpdateView: Bool = false
@State private var dateToUpdate: Date = Date() @State private var matchesToUpdate: [Match] = []
let days: [Date] let days: [Date]
let keys: [Date] let keys: [Date]
@ -258,28 +262,34 @@ struct PlanningView: View {
day: day, day: day,
keys: keys.filter({ $0.dayInt == day.dayInt }), keys: keys.filter({ $0.dayInt == day.dayInt }),
timeSlots: timeSlots, timeSlots: timeSlots,
selectedDay: selectedDay selectedDay: selectedDay,
selectedIds: $selectedIds,
matchesForUpdateSheet: $matchesToUpdate
) )
} }
} }
} }
.toolbar(content: { .toolbar(content: {
if editMode?.wrappedValue == .active { if editMode?.wrappedValue == .active {
ToolbarItem(placement: .bottomBar) { ToolbarItem(placement: .topBarTrailing) {
Button { Button {
showDateUpdateView = true matchesToUpdate = matches.filter({ selectedIds.contains($0.stringId) })
} label: { } label: {
Text("Modifier la date des matchs sélectionnés") Text("Modifier")
} }
.buttonStyle(.borderless)
.disabled(selectedIds.isEmpty) .disabled(selectedIds.isEmpty)
} }
} }
}) })
.onChange(of: matchesToUpdate, { oldValue, newValue in
showDateUpdateView = matchesToUpdate.count > 0
})
.sheet(isPresented: $showDateUpdateView, onDismiss: { .sheet(isPresented: $showDateUpdateView, onDismiss: {
selectedIds.removeAll() selectedIds.removeAll()
matchesToUpdate = []
}) { }) {
let selectedMatches = matches.filter({ selectedIds.contains($0.stringId) }) DateUpdateView(selectedMatches: matchesToUpdate)
DateUpdateView(selectedMatches: selectedMatches)
} }
} }
} }
@ -290,6 +300,7 @@ struct PlanningView: View {
let selectedMatches: [Match] let selectedMatches: [Match]
let selectedFormats: [MatchFormat] let selectedFormats: [MatchFormat]
@State private var dateToUpdate: Date @State private var dateToUpdate: Date
@State private var updateStep: Int = 0
init(selectedMatches: [Match]) { init(selectedMatches: [Match]) {
self.selectedMatches = selectedMatches self.selectedMatches = selectedMatches
@ -306,13 +317,37 @@ struct PlanningView: View {
DatePicker(selection: $dateToUpdate) { DatePicker(selection: $dateToUpdate) {
Text(dateToUpdate.formatted(.dateTime.weekday(.wide))).font(.headline) Text(dateToUpdate.formatted(.dateTime.weekday(.wide))).font(.headline)
} }
RowButtonView("Définir le nouvel horaire", role: .destructive) {
_setDate()
}
} footer: {
Text("Choisir un nouvel horaire pour tous les matchs sélectionnés")
} }
Section { Section {
DateAdjusterView(date: $dateToUpdate) LabeledContent {
DateAdjusterView(date: $dateToUpdate, time: 10) StepperView(title: "minutes", count: $updateStep, step: 5)
} label: {
Text("Décalage")
}
RowButtonView("Décaler les horaires", role: .destructive) {
_updateDate()
}
} footer: {
Text("décale CHAQUE horaire du nombre de minutes indiqué")
.foregroundStyle(.logoRed)
}
VStack {
StepAdjusterView(step: $updateStep)
Divider()
StepAdjusterView(step: $updateStep, time: 10)
ForEach(selectedFormats, id: \.self) { matchFormat in ForEach(selectedFormats, id: \.self) { matchFormat in
DateAdjusterView(date: $dateToUpdate, matchFormat: matchFormat) Divider()
StepAdjusterView(step: $updateStep, matchFormat: matchFormat)
} }
} }
@ -325,7 +360,10 @@ struct PlanningView: View {
} }
} }
.navigationTitle("Modification de la date") .onChange(of: updateStep, { oldValue, newValue in
dateToUpdate.addTimeInterval(TimeInterval((newValue - oldValue) * 60))
})
.navigationTitle("Modifier l'horaire")
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.visible, for: .navigationBar) .toolbarBackground(.visible, for: .navigationBar)
.toolbar(content: { .toolbar(content: {
@ -334,25 +372,34 @@ struct PlanningView: View {
dismiss() dismiss()
} }
} }
ToolbarItem(placement: .topBarTrailing) {
Button("Valider") {
_updateDate()
}
}
}) })
} }
} }
private func _updateDate() { private func _updateDate() {
selectedMatches.forEach { match in
if match.hasStarted() || match.hasEnded() {
match.plannedStartDate?.addTimeInterval(TimeInterval(updateStep * 60))
} else {
match.startDate?.addTimeInterval(TimeInterval(updateStep * 60))
}
}
let groupByTournaments = selectedMatches.grouped { match in
match.currentTournament()
}
groupByTournaments.forEach { tournament, matches in
tournament?.tournamentStore?.matches.addOrUpdate(contentOfs: matches)
}
dismiss()
}
private func _setDate() {
selectedMatches.forEach { match in selectedMatches.forEach { match in
if match.hasStarted() || match.hasEnded() { if match.hasStarted() || match.hasEnded() {
match.plannedStartDate = dateToUpdate match.plannedStartDate = dateToUpdate
} else { } else {
let hasStarted = match.currentTournament()?.hasStarted() == true
match.startDate = dateToUpdate match.startDate = dateToUpdate
if hasStarted {
match.plannedStartDate = dateToUpdate
}
} }
} }
@ -378,6 +425,9 @@ struct PlanningView: View {
let keys: [Date] let keys: [Date]
let timeSlots: [Date: [Match]] let timeSlots: [Date: [Match]]
let selectedDay: Date? let selectedDay: Date?
@Binding var selectedIds: Set<String>
@State private var selectAll: Bool = false
@Binding var matchesForUpdateSheet: [Match]
var body: some View { var body: some View {
Section { Section {
@ -386,12 +436,22 @@ struct PlanningView: View {
key: key, key: key,
matches: timeSlots[key]?.sorted( matches: timeSlots[key]?.sorted(
by: filterOption == .byDefault by: filterOption == .byDefault
? \.computedOrder : \.courtIndexForSorting) ?? [] ? \.computedOrder : \.courtIndexForSorting) ?? [], matchesForUpdateSheet: $matchesForUpdateSheet
) )
} }
.onMove(perform: enableMove ? moveSection : nil) .onMove(perform: enableMove ? moveSection : nil)
} header: { } header: {
if editMode?.wrappedValue == .active {
HStack {
Spacer()
FooterButtonView(selectAll ? "Tout desélectionner" : "Tout sélectionner") {
selectAll.toggle()
}
.textCase(nil)
}
} else {
HeaderView(day: day, timeSlots: timeSlots) HeaderView(day: day, timeSlots: timeSlots)
}
} footer: { } footer: {
VStack(alignment: .leading) { VStack(alignment: .leading) {
if day.monthYearFormatted == Date.distantFuture.monthYearFormatted { if day.monthYearFormatted == Date.distantFuture.monthYearFormatted {
@ -403,6 +463,16 @@ struct PlanningView: View {
CourtOptionsView(timeSlots: timeSlots, underlined: true) CourtOptionsView(timeSlots: timeSlots, underlined: true)
} }
} }
.onChange(of: selectAll, { oldValue, newValue in
if oldValue == false, newValue == true {
selectedIds = Set(timeSlots.filter({ keys.contains($0.key) }).values.flatMap({ values in values.compactMap({ match in match.stringId }) }))
} else if oldValue == true, newValue == false {
selectedIds.removeAll()
}
})
.onChange(of: editMode?.wrappedValue) { oldValue, newValue in
selectAll = false
}
} }
func moveSection(from source: IndexSet, to destination: Int) { func moveSection(from source: IndexSet, to destination: Int) {
@ -450,7 +520,7 @@ struct PlanningView: View {
let matches: [Match] let matches: [Match]
@State private var isExpanded: Bool = false @State private var isExpanded: Bool = false
@State private var showDateUpdateView: Bool = false @Binding var matchesForUpdateSheet: [Match]
var body: some View { var body: some View {
if !matches.isEmpty { if !matches.isEmpty {
@ -462,21 +532,23 @@ struct PlanningView: View {
} label: { } label: {
TimeSlotHeaderView(key: key, matches: matches) TimeSlotHeaderView(key: key, matches: matches)
} }
.onChange(of: editMode?.wrappedValue, { oldValue, newValue in
if oldValue == .inactive, newValue == .active, isExpanded == false {
isExpanded = true
} else if oldValue == .active, newValue == .inactive, isExpanded == true {
isExpanded = false
}
})
.contextMenu { .contextMenu {
PlanningView.CourtOptionsView(timeSlots: [key: matches], underlined: false) PlanningView.CourtOptionsView(timeSlots: [key: matches], underlined: false)
Button { Button {
showDateUpdateView = true matchesForUpdateSheet = matches
} label: { } label: {
Text("Modifier la date") Text("Modifier la date")
} }
} }
.sheet(isPresented: $showDateUpdateView, onDismiss: {
}) {
PlanningView.DateUpdateView(selectedMatches: matches)
}
// .onChange(of: editMode?.wrappedValue) { // .onChange(of: editMode?.wrappedValue) {
// if editMode?.wrappedValue == .active, isExpanded == false { // if editMode?.wrappedValue == .active, isExpanded == false {
// isExpanded = true // isExpanded = true
@ -500,6 +572,7 @@ struct PlanningView: View {
} label: { } label: {
MatchRowView(match: match) MatchRowView(match: match)
} }
.listRowView(isActive: match.hasStarted(), color: .green, hideColorVariation: true)
} }
} }
} }

@ -19,6 +19,7 @@ struct EditScoreView: View {
@Environment(\.dismiss) private var dismiss @Environment(\.dismiss) private var dismiss
@State private var firstTeamIsFirstScoreToEnter: Bool = true @State private var firstTeamIsFirstScoreToEnter: Bool = true
@State private var shouldEndMatch: Bool = false @State private var shouldEndMatch: Bool = false
@State private var walkoutPosition: TeamPosition?
init(match: Match, confirmScoreEdition: Binding<Bool>) { init(match: Match, confirmScoreEdition: Binding<Bool>) {
let matchDescriptor = MatchDescriptor(match: match) let matchDescriptor = MatchDescriptor(match: match)
@ -196,9 +197,20 @@ struct EditScoreView: View {
Text("Terminer le match") Text("Terminer le match")
} }
if shouldEndMatch {
Picker(selection: $walkoutPosition) {
Text("Non").tag(nil as TeamPosition?)
Text(matchDescriptor.teamLabelOne).tag(TeamPosition.one)
Text(matchDescriptor.teamLabelTwo).tag(TeamPosition.two)
} label: {
Text("Abandon")
}
}
RowButtonView("Confirmer") { RowButtonView("Confirmer") {
if shouldEndMatch { if shouldEndMatch {
matchDescriptor.match?.setUnfinishedScore(fromMatchDescriptor: matchDescriptor) matchDescriptor.match?.setUnfinishedScore(fromMatchDescriptor: matchDescriptor, walkoutPosition: walkoutPosition)
} else { } else {
matchDescriptor.match?.updateScore(fromMatchDescriptor: matchDescriptor) matchDescriptor.match?.updateScore(fromMatchDescriptor: matchDescriptor)
} }

@ -22,6 +22,7 @@ struct TournamentGeneralSettingsView: View {
@State private var umpireCustomContact: String @State private var umpireCustomContact: String
@State private var umpireCustomMailIsInvalid: Bool = false @State private var umpireCustomMailIsInvalid: Bool = false
@State private var umpireCustomPhoneIsInvalid: Bool = false @State private var umpireCustomPhoneIsInvalid: Bool = false
@State private var showCurrencyPicker: Bool = false // New state for action sheet
@FocusState private var focusedField: Tournament.CodingKeys? @FocusState private var focusedField: Tournament.CodingKeys?
let priceTags: [Double] = [15.0, 20.0, 25.0] let priceTags: [Double] = [15.0, 20.0, 25.0]
@ -45,7 +46,7 @@ struct TournamentGeneralSettingsView: View {
TournamentDatePickerView() TournamentDatePickerView()
TournamentDurationManagerView() TournamentDurationManagerView()
LabeledContent { LabeledContent {
TextField(tournament.isFree() ? "Gratuite" : "Inscription", value: $entryFee, format: .currency(code: Locale.defaultCurrency())) TextField(tournament.isFree() ? "Gratuite" : "Inscription", value: $entryFee, format: .currency(code: tournament.defaultCurrency()))
.keyboardType(.decimalPad) .keyboardType(.decimalPad)
.multilineTextAlignment(.trailing) .multilineTextAlignment(.trailing)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
@ -60,10 +61,14 @@ struct TournamentGeneralSettingsView: View {
} label: { } label: {
Text("Inscription") Text("Inscription")
FooterButtonView("modifier la devise") {
showCurrencyPicker = true
}
.font(.footnote)
} }
LabeledContent { LabeledContent {
TextField("Réduction", value: $clubMemberFeeDeduction, format: .currency(code: Locale.defaultCurrency())) TextField("Réduction", value: $clubMemberFeeDeduction, format: .currency(code: tournament.defaultCurrency()))
.keyboardType(.decimalPad) .keyboardType(.decimalPad)
.multilineTextAlignment(.trailing) .multilineTextAlignment(.trailing)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
@ -84,27 +89,62 @@ struct TournamentGeneralSettingsView: View {
} }
if tournament.onlineRegistrationCanBeEnabled() { if tournament.onlineRegistrationCanBeEnabled() {
let canEnableOnlinePayment = dataStore.user.canEnableOnlinePayment()
Section { Section {
NavigationLink { // MARK: - Online Registration Row
RegistrationSetupView(tournament: tournament)
} label: {
LabeledContent { LabeledContent {
if tournament.enableOnlineRegistration { if tournament.enableOnlineRegistration {
Text("activée").foregroundStyle(.green) Text("Activée")
.foregroundStyle(.green)
.font(.headline) .font(.headline)
} else { } else {
Text("désactivée").foregroundStyle(.logoRed) Text("Désactivée")
.foregroundStyle(.logoRed)
.font(.headline) .font(.headline)
} }
} label: { } label: {
Text("Accéder aux paramètres") Text("Inscription en ligne")
Text(tournament.getOnlineRegistrationStatus().statusLocalized()) Text(tournament.getOnlineRegistrationStatus().statusLocalized())
} }
// MARK: - Online Payment Row (Conditionally Visible)
if canEnableOnlinePayment {
LabeledContent {
if tournament.enableOnlinePayment {
Text("Activé")
.foregroundStyle(.green)
.font(.headline)
} else {
Text("Désactivé")
.foregroundStyle(.logoRed)
.font(.headline)
}
} label: {
Text("Paiement en ligne")
Text(tournament.getPaymentStatus().statusLocalized())
}
}
// MARK: - Access Settings Row
NavigationLink {
RegistrationSetupView(tournament: tournament)
} label: {
Text("Accès aux réglages")
} }
} header: { } header: {
if canEnableOnlinePayment {
Text("Inscription et paiement en ligne")
} else {
Text("Inscription en ligne") Text("Inscription en ligne")
}
} footer: { } footer: {
if canEnableOnlinePayment {
Text("Paramétrez les possibilités d'inscription en ligne à votre tournoi via Padel Club") Text("Paramétrez les possibilités d'inscription en ligne à votre tournoi via Padel Club")
} else {
Text("Paramétrez les possibilités d'inscription et paiement en ligne à votre tournoi via Padel Club")
}
} }
} }
@ -156,9 +196,14 @@ struct TournamentGeneralSettingsView: View {
} footer: { } footer: {
FooterButtonView("Ajouter le prix de l'inscription") { FooterButtonView("Ajouter le prix de l'inscription") {
tournamentInformation.append("\n" + tournament.entryFeeMessage) tournamentInformation.append("\n" + tournament.entryFeeMessage)
_save()
} }
} }
} }
.sheet(isPresented: $showCurrencyPicker, content: {
CurrencySelectorView()
.environment(tournament)
})
.navigationBarBackButtonHidden(focusedField != nil) .navigationBarBackButtonHidden(focusedField != nil)
.toolbar(content: { .toolbar(content: {
if focusedField != nil { if focusedField != nil {
@ -177,7 +222,7 @@ struct TournamentGeneralSettingsView: View {
if focusedField == ._entryFee { if focusedField == ._entryFee {
if tournament.isFree() { if tournament.isFree() {
ForEach(priceTags, id: \.self) { priceTag in ForEach(priceTags, id: \.self) { priceTag in
Button(priceTag.formatted(.currency(code: Locale.defaultCurrency()).precision(.fractionLength(0)))) { Button(priceTag.formatted(.currency(code: tournament.defaultCurrency()).precision(.fractionLength(0)))) {
entryFee = priceTag entryFee = priceTag
tournament.entryFee = priceTag tournament.entryFee = priceTag
focusedField = nil focusedField = nil
@ -195,7 +240,7 @@ struct TournamentGeneralSettingsView: View {
} }
} else if focusedField == ._clubMemberFeeDeduction { } else if focusedField == ._clubMemberFeeDeduction {
ForEach(deductionTags, id: \.self) { deductionTag in ForEach(deductionTags, id: \.self) { deductionTag in
Button(deductionTag.formatted(.currency(code: Locale.defaultCurrency()).precision(.fractionLength(0)))) { Button(deductionTag.formatted(.currency(code: tournament.defaultCurrency()).precision(.fractionLength(0)))) {
clubMemberFeeDeduction = deductionTag clubMemberFeeDeduction = deductionTag
tournament.clubMemberFeeDeduction = deductionTag tournament.clubMemberFeeDeduction = deductionTag
focusedField = nil focusedField = nil
@ -291,6 +336,100 @@ struct TournamentGeneralSettingsView: View {
} }
} }
struct CurrencySelectorView: View {
@EnvironmentObject var dataStore: DataStore
@Environment(Tournament.self) var tournament
@Environment(\.dismiss) var dismiss
@State private var currencySearchText: String = ""
func formatter(forCurrencyCode currencyCode: String) -> NumberFormatter {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.currencyCode = currencyCode
return formatter
}
struct CurrencyData: Identifiable {
let id: String
let name: String
let symbol: String
init?(code: String) {
if let name = Locale.current.localizedString(forCurrencyCode: code) {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.currencyCode = code
self.symbol = formatter.currencySymbol ?? code
self.id = code
self.name = name
} else {
return nil
}
}
}
let currencies : [CurrencyData] = Locale.Currency.isoCurrencies.sorted(by: { Locale.current.localizedString(forCurrencyCode: $0.identifier) ?? $0.identifier < Locale.current.localizedString(forCurrencyCode: $1.identifier) ?? $1.identifier }).compactMap { currency in
CurrencyData(code: currency.identifier)
}
var currencyCode: Binding<String?> {
Binding {
tournament.defaultCurrency()
} set: { currency in
tournament.currencyCode = currency
dataStore.tournaments.addOrUpdate(instance: tournament)
dismiss()
}
}
var filteredCurrencies: [CurrencyData] {
if currencySearchText.isEmpty {
return currencies
} else {
return currencies.filter {
$0.name.lowercased().contains(currencySearchText.lowercased())
}
}
}
var body: some View {
NavigationStack {
List(selection: currencyCode) {
Section {
LabeledContent {
Text(tournament.defaultCurrency())
} label: {
Text("Devise utilisée du tournoi")
}
} header: {
Text("")
}
Section {
ForEach(filteredCurrencies) { currency in
LabeledContent {
Text(currency.symbol)
} label: {
Text(currency.name)
}
.tag(currency.id)
}
}
}
.navigationBarTitle("Choisir une devise")
.searchable(text: $currencySearchText, placement: .navigationBarDrawer(displayMode: .always))
.toolbar {
ToolbarItem(placement: .topBarLeading) {
Button("Retour", role: .cancel) {
self.dismiss()
}
}
}
.toolbarBackground(.visible, for: .navigationBar)
}
}
}
private func _confirmUmpireMail() { private func _confirmUmpireMail() {
umpireCustomMailIsInvalid = false umpireCustomMailIsInvalid = false
if umpireCustomMail.isEmpty { if umpireCustomMail.isEmpty {

@ -115,7 +115,7 @@ final class ServerDataTests: XCTestCase {
let tournament = Tournament(event: eventId, name: "RG Homme", startDate: Date(), endDate: nil, creationDate: Date(), isPrivate: false, groupStageFormat: MatchFormat.megaTie, roundFormat: MatchFormat.nineGames, loserRoundFormat: MatchFormat.nineGamesDecisivePoint, groupStageSortMode: GroupStageOrderingMode.snake, groupStageCount: 2, rankSourceDate: Date(), dayDuration: 5, teamCount: 3, teamSorting: TeamSortingType.rank, federalCategory: TournamentCategory.mix, federalLevelCategory: TournamentLevel.p1000, federalAgeCategory: FederalTournamentAge.a45, closedRegistrationDate: Date(), groupStageAdditionalQualified: 4, courtCount: 9, prioritizeClubMembers: true, qualifiedPerGroupStage: 1, teamsPerGroupStage: 2, entryFee: 30.0, additionalEstimationDuration: 5, isDeleted: true, publishTeams: true, publishSummons: true, publishGroupStages: true, publishBrackets: true, shouldVerifyGroupStage: true, shouldVerifyBracket: true, hideTeamsWeight: true, publishTournament: true, hidePointsEarned: true, publishRankings: true, loserBracketMode: .manual, initialSeedRound: 8, initialSeedCount: 4, accountIsRequired: false, licenseIsRequired: false, minimumPlayerPerTeam: 3, maximumPlayerPerTeam: 5, information: "Super", umpireCustomMail: "razmig@padelclub.app", umpireCustomContact: "Raz", umpireCustomPhone: "+33681598193", hideUmpireMail: true, hideUmpirePhone: true, disableRankingFederalRuling: true, teamCountLimit: false, enableOnlinePayment: false, onlinePaymentIsMandatory: false, enableOnlinePaymentRefund: false, refundDateLimit: nil, stripeAccountId: nil, enableTimeToConfirm: false, isCorporateTournament: false, isTemplate: false, let tournament = Tournament(event: eventId, name: "RG Homme", startDate: Date(), endDate: nil, creationDate: Date(), isPrivate: false, groupStageFormat: MatchFormat.megaTie, roundFormat: MatchFormat.nineGames, loserRoundFormat: MatchFormat.nineGamesDecisivePoint, groupStageSortMode: GroupStageOrderingMode.snake, groupStageCount: 2, rankSourceDate: Date(), dayDuration: 5, teamCount: 3, teamSorting: TeamSortingType.rank, federalCategory: TournamentCategory.mix, federalLevelCategory: TournamentLevel.p1000, federalAgeCategory: FederalTournamentAge.a45, closedRegistrationDate: Date(), groupStageAdditionalQualified: 4, courtCount: 9, prioritizeClubMembers: true, qualifiedPerGroupStage: 1, teamsPerGroupStage: 2, entryFee: 30.0, additionalEstimationDuration: 5, isDeleted: true, publishTeams: true, publishSummons: true, publishGroupStages: true, publishBrackets: true, shouldVerifyGroupStage: true, shouldVerifyBracket: true, hideTeamsWeight: true, publishTournament: true, hidePointsEarned: true, publishRankings: true, loserBracketMode: .manual, initialSeedRound: 8, initialSeedCount: 4, accountIsRequired: false, licenseIsRequired: false, minimumPlayerPerTeam: 3, maximumPlayerPerTeam: 5, information: "Super", umpireCustomMail: "razmig@padelclub.app", umpireCustomContact: "Raz", umpireCustomPhone: "+33681598193", hideUmpireMail: true, hideUmpirePhone: true, disableRankingFederalRuling: true, teamCountLimit: false, enableOnlinePayment: false, onlinePaymentIsMandatory: false, enableOnlinePaymentRefund: false, refundDateLimit: nil, stripeAccountId: nil, enableTimeToConfirm: false, isCorporateTournament: false, isTemplate: false,
publishProg: true, publishProg: true,
showTeamsInProg: true showTeamsInProg: true, currencyCode: "USD")
) )
@ -190,6 +190,7 @@ final class ServerDataTests: XCTestCase {
assert(t.isTemplate == tournament.isTemplate) assert(t.isTemplate == tournament.isTemplate)
assert(t.publishProg == tournament.publishProg) assert(t.publishProg == tournament.publishProg)
assert(t.showTeamsInProg == tournament.showTeamsInProg) assert(t.showTeamsInProg == tournament.showTeamsInProg)
assert(t.currencyCode == tournament.currencyCode)
} else { } else {
XCTFail("missing data") XCTFail("missing data")
} }

Loading…
Cancel
Save