diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index f4a784b..29262c3 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -943,9 +943,13 @@ class Tournament : ModelObject, Storable { //return qualifiedTeams().count == qualifiedFromGroupStage() + groupStageAdditionalQualified } + func paymentMethodMessage() -> String? { + DataStore.shared.user.summonsAvailablePaymentMethods ?? ContactType.defaultAvailablePaymentMethods + } + var entryFeeMessage: String { if let entryFee { - return "Inscription: " + entryFee.formatted(.currency(code: "EUR")) + "." + return ["Inscription: " + entryFee.formatted(.currency(code: "EUR")) + " par joueur.", paymentMethodMessage()].compactMap { $0 }.joined(separator: "\n") } else { return "Inscription: gratuite." } diff --git a/PadelClub/Data/User.swift b/PadelClub/Data/User.swift index d945539..c83f2cd 100644 --- a/PadelClub/Data/User.swift +++ b/PadelClub/Data/User.swift @@ -35,6 +35,7 @@ class User: ModelObject, UserBase, Storable { var summonsMessageBody : String? = nil var summonsMessageSignature: String? = nil + var summonsAvailablePaymentMethods: String? = nil var summonsDisplayFormat: Bool = false var summonsDisplayEntryFee: Bool = false var summonsUseFullCustomMessage: Bool = false @@ -68,6 +69,10 @@ class User: ModelObject, UserBase, Storable { return try? federalContext.fetch(fetchRequest).first } + func defaultSignature() -> String { + return "Sportivement,\n\(firstName) \(lastName), votre JAP." + } + func hasClubs() -> Bool { self.clubs.isEmpty == false } @@ -106,6 +111,7 @@ class User: ModelObject, UserBase, Storable { case _country = "country" case _summonsMessageBody = "summonsMessageBody" case _summonsMessageSignature = "summonsMessageSignature" + case _summonsAvailablePaymentMethods = "summonsAvailablePaymentMethods" case _summonsDisplayFormat = "summonsDisplayFormat" case _summonsDisplayEntryFee = "summonsDisplayEntryFee" case _summonsUseFullCustomMessage = "summonsUseFullCustomMessage" diff --git a/PadelClub/Extensions/Array+Extensions.swift b/PadelClub/Extensions/Array+Extensions.swift index c437d43..62b8d4b 100644 --- a/PadelClub/Extensions/Array+Extensions.swift +++ b/PadelClub/Extensions/Array+Extensions.swift @@ -31,3 +31,21 @@ extension Array where Element: Equatable { } } } + +extension Array where Element: CustomStringConvertible { + func customJoined(separator: String, lastSeparator: String) -> String { + switch count { + case 0: + return "" + case 1: + return "\(self[0])" + case 2: + return "\(self[0]) \(lastSeparator) \(self[1])" + default: + let firstPart = dropLast().map { "\($0)" }.joined(separator: ", ") + let lastPart = "\(lastSeparator) \(last!)" + return "\(firstPart) \(lastPart)" + } + } +} + diff --git a/PadelClub/Utils/ContactManager.swift b/PadelClub/Utils/ContactManager.swift index d319183..077b0b2 100644 --- a/PadelClub/Utils/ContactManager.swift +++ b/PadelClub/Utils/ContactManager.swift @@ -30,11 +30,11 @@ enum ContactType: Identifiable { } extension ContactType { - static let defaultCustomMessage = "Il est conseillé de vous présenter 10 minutes avant de jouer.\nMerci de confirmer en répondant à ce message et de prévenir votre partenaire." - static let defaultSignature = "" + static let defaultCustomMessage = "Il est conseillé de vous présenter 10 minutes avant de jouer.\nMerci de me confirmer votre présence avec votre nom et de prévenir votre partenaire." + static let defaultAvailablePaymentMethods = "Règlement possible par chèque ou espèce." - static func callingGroupStageCustomMessage(tournament: Tournament?, startDate: Date?, roundLabel: String) -> String { - let tournamentCustomMessage = DataStore.shared.user.summonsMessageBody ?? defaultCustomMessage + static func callingCustomMessage(source: String? = nil, tournament: Tournament?, startDate: Date?, roundLabel: String) -> String { + let tournamentCustomMessage = source ?? DataStore.shared.user.summonsMessageBody ?? defaultCustomMessage let clubName = tournament?.clubName ?? "" var text = tournamentCustomMessage @@ -42,45 +42,42 @@ extension ContactType { if let tournament { text = text.replacingOccurrences(of: "#titre", with: tournament.tournamentTitle()) + text = text.replacingOccurrences(of: "#prix", with: tournament.entryFeeMessage) } text = text.replacingOccurrences(of: "#club", with: clubName) - text = text.replacingOccurrences(of: "#round", with: roundLabel) + text = text.replacingOccurrences(of: "#manche", with: roundLabel.lowercased()) text = text.replacingOccurrences(of: "#jour", with: "\(date.formatted(Date.FormatStyle().weekday(.wide).day().month(.wide)))") text = text.replacingOccurrences(of: "#horaire", with: "\(date.formatted(Date.FormatStyle().hour().minute()))") - let signature = DataStore.shared.user.summonsMessageSignature ?? defaultSignature + let signature = DataStore.shared.user.summonsMessageSignature ?? DataStore.shared.user.defaultSignature() text = text.replacingOccurrences(of: "#signature", with: signature) return text } - static func callingGroupStageMessage(tournament: Tournament?, startDate: Date?, roundLabel: String, matchFormat: MatchFormat?) -> String { + static func callingMessage(tournament: Tournament?, startDate: Date?, roundLabel: String, matchFormat: MatchFormat?) -> String { let useFullCustomMessage = DataStore.shared.user.summonsUseFullCustomMessage if useFullCustomMessage { - return callingGroupStageCustomMessage(tournament: tournament, startDate: startDate, roundLabel: roundLabel) + return callingCustomMessage(tournament: tournament, startDate: startDate, roundLabel: roundLabel) } let date = startDate ?? tournament?.startDate ?? Date() let clubName = tournament?.clubName ?? "" let message = DataStore.shared.user.summonsMessageBody ?? defaultCustomMessage - let signature = DataStore.shared.user.summonsMessageSignature ?? defaultSignature + let signature = DataStore.shared.user.summonsMessageSignature ?? DataStore.shared.user.defaultSignature() let localizedCalled = "convoqué" + (tournament?.tournamentCategory == .women ? "e" : "") + "s" - - var formatMessage: String? { - (DataStore.shared.user.summonsDisplayFormat) ? matchFormat?.computedLongLabel.appending(".") : nil - } - + var entryFeeMessage: String? { (DataStore.shared.user.summonsDisplayEntryFee) ? tournament?.entryFeeMessage : nil } var computedMessage: String { - [formatMessage, entryFeeMessage, message].compacted().map { $0.trimmed }.joined(separator: "\n") + [entryFeeMessage, message].compacted().map { $0.trimmed }.joined(separator: "\n") } if let tournament { diff --git a/PadelClub/Views/Calling/CallMessageCustomizationView.swift b/PadelClub/Views/Calling/CallMessageCustomizationView.swift index b2eaacd..acaeb17 100644 --- a/PadelClub/Views/Calling/CallMessageCustomizationView.swift +++ b/PadelClub/Views/Calling/CallMessageCustomizationView.swift @@ -11,33 +11,37 @@ import LeStorage struct CallMessageCustomizationView: View { @EnvironmentObject var dataStore: DataStore var tournament: Tournament - - @FocusState private var textEditor: Bool + enum Field { + case signature + case body + case paymentMethods + case clubName + } + + @FocusState var focusedField: CallMessageCustomizationView.Field? @State private var customClubName: String = "" @State private var customCallMessageBody: String = "" @State private var customCallMessageSignature: String = "" - + @State private var summonsAvailablePaymentMethods: String = "" + init(tournament: Tournament) { self.tournament = tournament - _customCallMessageBody = State(wrappedValue: DataStore.shared.user.summonsMessageBody ?? "") - _customCallMessageSignature = State(wrappedValue: DataStore.shared.user.summonsMessageSignature ?? "") - _customClubName = State(wrappedValue: tournament.clubName ?? "") + _customCallMessageBody = State(wrappedValue: DataStore.shared.user.summonsMessageBody ?? (DataStore.shared.user.summonsUseFullCustomMessage ? "" : ContactType.defaultCustomMessage)) + _customCallMessageSignature = State(wrappedValue: DataStore.shared.user.summonsMessageSignature ?? DataStore.shared.user.defaultSignature()) + _customClubName = State(wrappedValue: tournament.clubName ?? "Lieu du tournoi") + _summonsAvailablePaymentMethods = State(wrappedValue: DataStore.shared.user.summonsAvailablePaymentMethods ?? ContactType.defaultAvailablePaymentMethods) } var clubName: String { customClubName } - var formatMessage: String? { - dataStore.user.summonsDisplayFormat ? tournament.matchFormat.computedLongLabel + "." : nil - } - var entryFeeMessage: String? { dataStore.user.summonsDisplayEntryFee ? tournament.entryFeeMessage : nil } var computedMessage: String { - [formatMessage, entryFeeMessage, customCallMessageBody].compacted().map { $0.trimmed }.joined(separator: "\n") + [entryFeeMessage, customCallMessageBody].compacted().map { $0.trimmed }.joined(separator: "\n") } var finalMessage: String? { @@ -48,16 +52,12 @@ struct CallMessageCustomizationView: View { var body: some View { @Bindable var user = dataStore.user List { - Section { - ZStack { - Text(customCallMessageBody).hidden() - .padding(.vertical, 20) - TextEditor(text: $customCallMessageBody) - .autocorrectionDisabled() - .focused($textEditor) - } - } header: { - Text("Personnalisation du message de convocation") + _renderingView() + _optionsView() + _editorView() + + if user.summonsUseFullCustomMessage { + _paymentMethodsView() } Section { @@ -65,100 +65,54 @@ struct CallMessageCustomizationView: View { Text(customCallMessageSignature).hidden() TextEditor(text: $customCallMessageSignature) .autocorrectionDisabled() - .focused($textEditor) + .focused($focusedField, equals: .signature) } } header: { Text("Signature du message") - } - - _clubNameView() - - Section { - if user.summonsUseFullCustomMessage { - Text(self.computedFullCustomMessage()) - .contextMenu { - Button("Coller dans le presse-papier") { - UIPasteboard.general.string = self.computedFullCustomMessage() - } - } - } - else if let finalMessage { - Text(finalMessage) - .contextMenu { - Button("Coller dans le presse-papier") { - UIPasteboard.general.string = finalMessage - } - } - } - } header: { - Text("Exemple") - } - - Section { - LabeledContent { - Toggle(isOn: $user.summonsUseFullCustomMessage) { - + } footer: { + HStack { + Spacer() + FooterButtonView("éditer") { + focusedField = .signature } - } label: { - Text("contrôle complet du message") } - } header: { - Text("Personnalisation complète") - } footer: { - Text("Utilisez ces balises dans votre texte : #titre, #jour, #horaire, #club, #signature") } + + _clubNameView() } + .headerProminence(.increased) + .navigationBarTitleDisplayMode(.inline) + .toolbarBackground(.visible, for: .navigationBar) .navigationTitle("Message de convocation") .toolbar { - ToolbarItem(placement: .topBarTrailing) { - Menu { - Picker(selection: $user.summonsDisplayFormat) { - Text("Afficher le format").tag(true) - Text("Masquer le format").tag(false) - } label: { - - } - Picker(selection: $user.summonsDisplayEntryFee) { - Text("Afficher le prix d'inscription").tag(true) - Text("Masquer le prix d'inscription").tag(false) - } label: { - - } - } label: { - LabelOptions() - } - } ToolbarItemGroup(placement: .keyboard) { - if textEditor { + if focusedField != .clubName { Spacer() Button { - textEditor = false + focusedField = nil + user.summonsMessageBody = customCallMessageBody + user.summonsMessageSignature = customCallMessageSignature + user.summonsAvailablePaymentMethods = summonsAvailablePaymentMethods + _save() } label: { - Label("Fermer", systemImage: "xmark") + Text("Valider") } + .buttonStyle(.bordered) } } } .onChange(of: user.summonsUseFullCustomMessage) { + user.summonsMessageBody = nil if user.summonsUseFullCustomMessage == false { - user.summonsMessageBody = ContactType.defaultCustomMessage + customCallMessageBody = ContactType.defaultCustomMessage + } else { + customCallMessageBody = "" } _save() } - .onChange(of: customCallMessageBody) { - user.summonsMessageBody = customCallMessageBody - _save() - } - .onChange(of: customCallMessageSignature) { - user.summonsMessageSignature = customCallMessageSignature - _save() - } .onChange(of: user.summonsDisplayEntryFee) { _save() - } - .onChange(of: user.summonsDisplayFormat) { - _save() - } + } } private func _save() { @@ -169,6 +123,68 @@ struct CallMessageCustomizationView: View { } } + @ViewBuilder + private func _editorView() -> some View { + @Bindable var user = dataStore.user + + Section { + ZStack { + Text(customCallMessageBody).hidden() + .padding(.vertical, 20) + TextEditor(text: $customCallMessageBody) + .autocorrectionDisabled() + .focused($focusedField, equals: .body) + } + } header: { + if user.summonsUseFullCustomMessage { + Text("Message de convocation") + } else { + Text("Information supplémentaire") + } + } footer: { + if user.summonsUseFullCustomMessage { + LazyVStack(alignment: .leading) { + Text("Utilisez ces balises") + FooterButtonView("#titre") { + customCallMessageBody.append("#titre") + focusedField = .body + } + FooterButtonView("#manche") { + customCallMessageBody.append("#manche") + focusedField = .body + } + FooterButtonView("#prix") { + customCallMessageBody.append("#prix") + focusedField = .body + } + FooterButtonView("#jour") { + customCallMessageBody.append("#jour") + focusedField = .body + } + FooterButtonView("#horaire") { + customCallMessageBody.append("#horaire") + focusedField = .body + } + FooterButtonView("#club") { + customCallMessageBody.append("#club") + focusedField = .body + } + FooterButtonView("#signature") { + customCallMessageBody.append("#signature") + focusedField = .body + } + } + } else { + HStack { + Spacer() + FooterButtonView("éditer") { + focusedField = .body + } + } + } + } + } + @ViewBuilder private func _clubNameView() -> some View { if let eventClub = tournament.eventObject()?.clubObject() { @@ -176,6 +192,7 @@ struct CallMessageCustomizationView: View { Section { TextField("Nom du club", text: $customClubName) .autocorrectionDisabled() + .focused($focusedField, equals: .clubName) .onSubmit { eventClub.name = customClubName do { @@ -190,17 +207,79 @@ struct CallMessageCustomizationView: View { } footer: { if hasBeenCreated == false { Text("Édition impossible, vous n'êtes pas le créateur de ce club.").foregroundStyle(.logoRed) + } else { + HStack { + Spacer() + FooterButtonView("éditer") { + focusedField = .clubName + } + } } } } } + + @ViewBuilder + private func _renderingView() -> some View { + @Bindable var user = dataStore.user + Section { + if user.summonsUseFullCustomMessage { + Text(self.computedFullCustomMessage()) + } + else if let finalMessage { + Text(finalMessage) + } + } header: { + Text("Rendu généré automatiquement") + } + } + + @ViewBuilder + private func _optionsView() -> some View { + @Bindable var user = dataStore.user + Section { + Toggle(isOn: $user.summonsUseFullCustomMessage) { + Text("Tout personnaliser") + Text("Écrivez votre propre message.") + } + if user.summonsUseFullCustomMessage == false { + Toggle(isOn: $user.summonsDisplayEntryFee) { + Text("Afficher le prix d'inscription") + Text("et les informations sur le paiement") + } + } + } header: { + Text("Options") + } + if user.summonsDisplayEntryFee && user.summonsUseFullCustomMessage == false { + _paymentMethodsView() + } + } + + @ViewBuilder + private func _paymentMethodsView() -> some View { + Section { + ZStack { + Text(summonsAvailablePaymentMethods).hidden() + .padding(.vertical, 20) + TextEditor(text: $summonsAvailablePaymentMethods) + .autocorrectionDisabled() + .focused($focusedField, equals: .paymentMethods) + } + } header: { + Text("Information sur le paiement") + } footer: { + HStack { + Spacer() + FooterButtonView("éditer") { + focusedField = .paymentMethods + } + } + } + } + func computedFullCustomMessage() -> String { - var text = customCallMessageBody.replacingOccurrences(of: "#titre", with: tournament.tournamentTitle()) - text = text.replacingOccurrences(of: "#club", with: clubName) - text = text.replacingOccurrences(of: "#jour", with: "\(Date().formatted(Date.FormatStyle().weekday(.wide).day().month(.wide)))") - text = text.replacingOccurrences(of: "#horaire", with: "\(Date().formatted(Date.FormatStyle().hour().minute()))") - text = text.replacingOccurrences(of: "#signature", with: customCallMessageSignature) - return text + return ContactType.callingCustomMessage(source: customCallMessageBody, tournament: tournament, startDate: Date(), roundLabel: RoundRule.roundName(fromRoundIndex: 2)) } } diff --git a/PadelClub/Views/Calling/CallView.swift b/PadelClub/Views/Calling/CallView.swift index 9d21c2b..292c0c2 100644 --- a/PadelClub/Views/Calling/CallView.swift +++ b/PadelClub/Views/Calling/CallView.swift @@ -79,7 +79,7 @@ struct CallView: View { } var finalMessage: String { - ContactType.callingGroupStageMessage(tournament: tournament, startDate: callDate, roundLabel: roundLabel, matchFormat: matchFormat) + ContactType.callingMessage(tournament: tournament, startDate: callDate, roundLabel: roundLabel, matchFormat: matchFormat) } // TODO: Guard