newoffer2025
Razmig Sarkissian 5 months ago
parent 87c7c074d3
commit 69c0163ccb
  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. 14
      PadelClub/Views/Score/EditScoreView.swift
  7. 165
      PadelClub/Views/Tournament/Screen/Components/TournamentGeneralSettingsView.swift
  8. 3
      PadelClubTests/ServerDataTests.swift

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

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

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

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

@ -35,6 +35,10 @@ struct EventStatusView: View {
init(tournaments: [Tournament]) {
self.tournaments = tournaments
}
func defaultCurrency() -> String {
tournaments.first?.currencyCode ?? Locale.defaultCurrency()
}
private func _calculateTeamsCount() async {
Task {
@ -55,7 +59,7 @@ struct EventStatusView: View {
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)))
$0.formatted(.currency(code: defaultCurrency()).precision(.fractionLength(0)))
}
let string = maps.joined(separator: " / ")

@ -19,6 +19,7 @@ struct EditScoreView: View {
@Environment(\.dismiss) private var dismiss
@State private var firstTeamIsFirstScoreToEnter: Bool = true
@State private var shouldEndMatch: Bool = false
@State private var walkoutPosition: TeamPosition?
init(match: Match, confirmScoreEdition: Binding<Bool>) {
let matchDescriptor = MatchDescriptor(match: match)
@ -196,9 +197,20 @@ struct EditScoreView: View {
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") {
if shouldEndMatch {
matchDescriptor.match?.setUnfinishedScore(fromMatchDescriptor: matchDescriptor)
matchDescriptor.match?.setUnfinishedScore(fromMatchDescriptor: matchDescriptor, walkoutPosition: walkoutPosition)
} else {
matchDescriptor.match?.updateScore(fromMatchDescriptor: matchDescriptor)
}

@ -22,6 +22,7 @@ struct TournamentGeneralSettingsView: View {
@State private var umpireCustomContact: String
@State private var umpireCustomMailIsInvalid: 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?
let priceTags: [Double] = [15.0, 20.0, 25.0]
@ -45,7 +46,7 @@ struct TournamentGeneralSettingsView: View {
TournamentDatePickerView()
TournamentDurationManagerView()
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)
.multilineTextAlignment(.trailing)
.frame(maxWidth: .infinity)
@ -60,10 +61,14 @@ struct TournamentGeneralSettingsView: View {
} label: {
Text("Inscription")
FooterButtonView("modifier la devise") {
showCurrencyPicker = true
}
.font(.footnote)
}
LabeledContent {
TextField("Réduction", value: $clubMemberFeeDeduction, format: .currency(code: Locale.defaultCurrency()))
TextField("Réduction", value: $clubMemberFeeDeduction, format: .currency(code: tournament.defaultCurrency()))
.keyboardType(.decimalPad)
.multilineTextAlignment(.trailing)
.frame(maxWidth: .infinity)
@ -84,27 +89,62 @@ struct TournamentGeneralSettingsView: View {
}
if tournament.onlineRegistrationCanBeEnabled() {
let canEnableOnlinePayment = dataStore.user.canEnableOnlinePayment()
Section {
NavigationLink {
RegistrationSetupView(tournament: tournament)
// MARK: - Online Registration Row
LabeledContent {
if tournament.enableOnlineRegistration {
Text("Activée")
.foregroundStyle(.green)
.font(.headline)
} else {
Text("Désactivée")
.foregroundStyle(.logoRed)
.font(.headline)
}
} label: {
Text("Inscription en ligne")
Text(tournament.getOnlineRegistrationStatus().statusLocalized())
}
// MARK: - Online Payment Row (Conditionally Visible)
if canEnableOnlinePayment {
LabeledContent {
if tournament.enableOnlineRegistration {
Text("activée").foregroundStyle(.green)
if tournament.enableOnlinePayment {
Text("Activé")
.foregroundStyle(.green)
.font(.headline)
} else {
Text("désactivée").foregroundStyle(.logoRed)
Text("Désactivé")
.foregroundStyle(.logoRed)
.font(.headline)
}
} label: {
Text("Accéder aux paramètres")
Text(tournament.getOnlineRegistrationStatus().statusLocalized())
Text("Paiement en ligne")
Text(tournament.getPaymentStatus().statusLocalized())
}
}
// MARK: - Access Settings Row
NavigationLink {
RegistrationSetupView(tournament: tournament)
} label: {
Text("Accès aux réglages")
}
} header: {
Text("Inscription en ligne")
if canEnableOnlinePayment {
Text("Inscription et paiement en ligne")
} else {
Text("Inscription en ligne")
}
} footer: {
Text("Paramétrez les possibilités d'inscription en ligne à votre tournoi via Padel Club")
if canEnableOnlinePayment {
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: {
FooterButtonView("Ajouter le prix de l'inscription") {
tournamentInformation.append("\n" + tournament.entryFeeMessage)
_save()
}
}
}
.sheet(isPresented: $showCurrencyPicker, content: {
CurrencySelectorView()
.environment(tournament)
})
.navigationBarBackButtonHidden(focusedField != nil)
.toolbar(content: {
if focusedField != nil {
@ -177,7 +222,7 @@ struct TournamentGeneralSettingsView: View {
if focusedField == ._entryFee {
if tournament.isFree() {
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
tournament.entryFee = priceTag
focusedField = nil
@ -195,7 +240,7 @@ struct TournamentGeneralSettingsView: View {
}
} else if focusedField == ._clubMemberFeeDeduction {
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
tournament.clubMemberFeeDeduction = deductionTag
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() {
umpireCustomMailIsInvalid = false
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,
publishProg: true,
showTeamsInProg: true
showTeamsInProg: true, currencyCode: "USD")
)
@ -190,6 +190,7 @@ final class ServerDataTests: XCTestCase {
assert(t.isTemplate == tournament.isTemplate)
assert(t.publishProg == tournament.publishProg)
assert(t.showTeamsInProg == tournament.showTeamsInProg)
assert(t.currencyCode == tournament.currencyCode)
} else {
XCTFail("missing data")
}

Loading…
Cancel
Save