You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
620 lines
24 KiB
620 lines
24 KiB
//
|
|
// EditingTeamView.swift
|
|
// PadelClub
|
|
//
|
|
// Created by Razmig Sarkissian on 17/04/2024.
|
|
//
|
|
|
|
import SwiftUI
|
|
import LeStorage
|
|
import PadelClubData
|
|
|
|
struct EditingTeamView: View {
|
|
@EnvironmentObject var dataStore: DataStore
|
|
@EnvironmentObject var networkMonitor: NetworkMonitor
|
|
@Environment(Tournament.self) var tournament: Tournament
|
|
@Environment(\.dismiss) private var dismiss
|
|
|
|
var team: TeamRegistration
|
|
@State private var editedTeam: TeamRegistration?
|
|
@State private var contactType: ContactType? = nil
|
|
@State private var sentError: ContactManagerError? = nil
|
|
@State private var showSubscriptionView: Bool = false
|
|
@State private var registrationDate : Date
|
|
@State private var walkOut : Bool
|
|
@State private var wildCardBracket : Bool
|
|
@State private var wildCardGroupStage : Bool
|
|
@State private var name: String
|
|
@FocusState private var focusedField: TeamRegistration.CodingKeys?
|
|
@State private var isProcessingRefund = false
|
|
@State private var refundMessage: String?
|
|
@State private var registrationDateModified: Date
|
|
@State private var uniqueRandomIndex: Int
|
|
@State private var isDeleting: Bool = false
|
|
@State private var showPaymentLinkManager: Bool = false
|
|
|
|
var messageSentFailed: Binding<Bool> {
|
|
Binding {
|
|
sentError != nil
|
|
} set: { newValue in
|
|
if newValue == false {
|
|
sentError = nil
|
|
}
|
|
}
|
|
}
|
|
|
|
var hasChanged: Binding<Bool> {
|
|
Binding {
|
|
if isDeleting {
|
|
return false
|
|
}
|
|
|
|
if canSaveWithoutWarning() {
|
|
return false
|
|
}
|
|
|
|
return
|
|
registrationDate != team.registrationDate
|
|
|| uniqueRandomIndex != team.uniqueRandomIndex
|
|
|| walkOut != team.walkOut
|
|
|| wildCardBracket != team.wildCardBracket
|
|
|| wildCardGroupStage != team.wildCardGroupStage
|
|
|
|
} set: { _ in
|
|
}
|
|
}
|
|
|
|
var tournamentStore: TournamentStore? {
|
|
return self.tournament.tournamentStore
|
|
}
|
|
|
|
init(team: TeamRegistration) {
|
|
self.team = team
|
|
_name = .init(wrappedValue: team.name ?? "")
|
|
let date = Date()
|
|
_registrationDate = State(wrappedValue: team.registrationDate ?? date)
|
|
_registrationDateModified = .init(wrappedValue: team.registrationDate ?? date)
|
|
_walkOut = State(wrappedValue: team.walkOut)
|
|
_wildCardBracket = State(wrappedValue: team.wildCardBracket)
|
|
_wildCardGroupStage = State(wrappedValue: team.wildCardGroupStage)
|
|
_uniqueRandomIndex = .init(wrappedValue: team.uniqueRandomIndex)
|
|
}
|
|
|
|
private func _resetTeam() {
|
|
team.resetPositions()
|
|
team.qualified = false
|
|
team.wildCardGroupStage = false
|
|
team.walkOut = false
|
|
team.wildCardBracket = false
|
|
team.uniqueRandomIndex = 0
|
|
}
|
|
|
|
var hasRegisteredOnline: Binding<Bool> {
|
|
Binding {
|
|
team.hasRegisteredOnline()
|
|
} set: { hasRegisteredOnline in
|
|
let players = team.players()
|
|
players.forEach { player in
|
|
player.registeredOnline = hasRegisteredOnline
|
|
}
|
|
|
|
tournamentStore?.playerRegistrations.addOrUpdate(contentOfs: players)
|
|
}
|
|
}
|
|
|
|
var body: some View {
|
|
List {
|
|
#if PRODTEST
|
|
|
|
Section {
|
|
if let pid = team.players().first(where: { $0.paymentId != nil })?.paymentId {
|
|
Text(pid)
|
|
}
|
|
}
|
|
// } else {
|
|
// if let paste = UIPasteboard.general.string {
|
|
// RowButtonView("Coller le payment id de l'équipe", role: .destructive) {
|
|
// let p = team.players()
|
|
// p.forEach { player in
|
|
// player.paymentId = UIPasteboard.general.string
|
|
// player.paymentType = .creditCard
|
|
// }
|
|
// team.tournamentStore?.playerRegistrations.addOrUpdate(contentOfs: p)
|
|
// }
|
|
// }
|
|
// }
|
|
// } footer: {
|
|
// if let paste = UIPasteboard.general.string {
|
|
// Text(paste)
|
|
// }
|
|
// }
|
|
#endif
|
|
|
|
Section {
|
|
NavigationLink {
|
|
TeamMatchesView(team: team)
|
|
} label: {
|
|
Text("Voir les matchs de l'équipe")
|
|
}
|
|
}
|
|
|
|
Section {
|
|
RowButtonView("Modifier la composition de l'équipe", role: (team.hasRegisteredOnline() || team.hasPaidOnline()) ? .destructive : .none, confirmationMessage: "Vous êtes sur le point de modifier une équipe qui s'est inscrite en ligne.") {
|
|
editedTeam = team
|
|
}
|
|
TeamDetailView(team: team)
|
|
} header: {
|
|
if team.hasRegisteredOnline() {
|
|
Text("Inscription en ligne")
|
|
} else {
|
|
Text("Inscription par vous-même")
|
|
}
|
|
} footer: {
|
|
HStack {
|
|
CopyPasteButtonView(pasteValue: team.playersPasteData(type: .sharing))
|
|
Spacer()
|
|
if team.isWildCard(), team.unsortedPlayers().isEmpty {
|
|
TeamPickerView(pickTypeContext: .wildcard) { teamregistration in
|
|
teamregistration.wildCardBracket = team.wildCardBracket
|
|
teamregistration.wildCardGroupStage = team.wildCardGroupStage
|
|
tournament.tournamentStore?.teamRegistrations.addOrUpdate(instance: teamregistration)
|
|
tournament.tournamentStore?.teamRegistrations.delete(instance: team)
|
|
dismiss()
|
|
}
|
|
} else {
|
|
NavigationLink {
|
|
GroupStageTeamReplacementView(team: team)
|
|
.environment(tournament)
|
|
} label: {
|
|
Text("Chercher à remplacer")
|
|
.underline()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.headerProminence(.increased)
|
|
|
|
if team.hasRegisteredOnline() || team.hasPaidOnline() || tournament.enableOnlineRegistration {
|
|
Section {
|
|
|
|
Toggle(isOn: hasRegisteredOnline) {
|
|
Text("Inscrits en ligne")
|
|
}
|
|
|
|
if tournament.enableOnlinePayment || team.hasPaidOnline() {
|
|
LabeledContent {
|
|
Text(team.hasPaidOnline() ? "Oui" : "Non")
|
|
} label: {
|
|
Text("Payé en ligne")
|
|
}
|
|
|
|
if team.hasPaidOnline() == false {
|
|
#if PRODTEST
|
|
Button("Récupérer le lien de paiement") {
|
|
showPaymentLinkManager = true
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
if let refundMessage, refundMessage.isEmpty == false {
|
|
Text(refundMessage).foregroundStyle(.logoRed)
|
|
}
|
|
if team.hasPaidOnline() {
|
|
RowButtonView("Rembourser l'équipe", role: .destructive) {
|
|
await _processRefund()
|
|
}
|
|
} else if team.hasConfirmed() == false, tournament.enableTimeToConfirm {
|
|
RowButtonView("Confirmer l'inscription de l'équipe", role: .destructive) {
|
|
team.confirmRegistration()
|
|
}
|
|
.disabled(team.walkOut)
|
|
}
|
|
} header: {
|
|
Text("Statut de l'inscription en ligne")
|
|
} footer: {
|
|
if team.hasPaidOnline() {
|
|
Text("Le remboursement passe part le service de Stripe qui re-crédite le moyen de paiement utilisé du montant payé.")
|
|
} else {
|
|
PaymentRequestButton(teamRegistration: team)
|
|
}
|
|
}
|
|
}
|
|
|
|
Section {
|
|
if let callDate = team.callDate {
|
|
LabeledContent() {
|
|
Text(callDate.localizedDate())
|
|
} label: {
|
|
Text("Convocation")
|
|
}
|
|
|
|
Toggle(isOn: confirmationReceived) {
|
|
Text("Confirmation reçue")
|
|
Text("L'équipe vous a confirmé votre convocation")
|
|
}
|
|
|
|
} else {
|
|
Text("Cette équipe n'a pas été convoquée")
|
|
}
|
|
|
|
if team.unsortedPlayers().isEmpty == false {
|
|
Toggle(isOn: hasArrived) {
|
|
Text("Équipe sur place")
|
|
}
|
|
}
|
|
}
|
|
|
|
Section {
|
|
DatePicker(selection: $registrationDateModified) {
|
|
if registrationDate != registrationDateModified {
|
|
HStack {
|
|
Button("Valider", systemImage: "checkmark.circle") {
|
|
registrationDate = registrationDateModified
|
|
}
|
|
.tint(.green)
|
|
Divider()
|
|
Button("Annuler", systemImage: "xmark.circle", role: .cancel) {
|
|
registrationDateModified = registrationDate
|
|
}
|
|
.tint(.logoRed)
|
|
}
|
|
.labelStyle(.iconOnly)
|
|
.buttonStyle(.borderedProminent)
|
|
} else {
|
|
Text("Inscription")
|
|
Text(registrationDateModified.localizedWeekDay().capitalized)
|
|
}
|
|
}
|
|
|
|
Toggle(isOn: $wildCardBracket) {
|
|
Text("Wildcard Tableau")
|
|
}
|
|
|
|
Toggle(isOn: $wildCardGroupStage) {
|
|
Text("Wildcard Poule")
|
|
}.disabled(tournament.groupStageCount == 0)
|
|
|
|
Toggle(isOn: $walkOut) {
|
|
Text("Forfait")
|
|
}
|
|
}
|
|
|
|
Section {
|
|
LabeledContent {
|
|
StepperView(count: $uniqueRandomIndex, minimum: 0)
|
|
} label: {
|
|
Text("Ordre à poids de paire égal")
|
|
}
|
|
} footer: {
|
|
Text("Si plusieurs équipes ont le même poids et que leur position est tiré au sort, ce champ permet de les positionner correctement dans l'ordre croissant.")
|
|
}
|
|
|
|
Section {
|
|
HStack {
|
|
TextField("Nom de l'équipe", text: $name)
|
|
.autocorrectionDisabled()
|
|
.focused($focusedField, equals: ._name)
|
|
.keyboardType(.alphabet)
|
|
.frame(maxWidth: .infinity)
|
|
.submitLabel(.done)
|
|
.onSubmit(of: .text) {
|
|
let trimmed = name.prefixTrimmed(200)
|
|
if trimmed.isEmpty {
|
|
team.name = nil
|
|
} else {
|
|
team.name = trimmed
|
|
}
|
|
|
|
_save()
|
|
}
|
|
if name.isEmpty == false {
|
|
FooterButtonView("effacer", role: .destructive) {
|
|
name = ""
|
|
team.name = nil
|
|
_save()
|
|
}
|
|
}
|
|
}
|
|
} header: {
|
|
Text("Nom de l'équipe")
|
|
}
|
|
|
|
if tournament.coachingIsAuthorized() {
|
|
CoachListView(team: team)
|
|
}
|
|
|
|
Section {
|
|
RowButtonView("Retirer des poules", role: .destructive) {
|
|
team.resetGroupeStagePosition()
|
|
_save()
|
|
}
|
|
.disabled(team.inGroupStage() == false)
|
|
}
|
|
|
|
Section {
|
|
RowButtonView("Retirer du tableau", role: .destructive) {
|
|
team.resetBracketPosition()
|
|
_save()
|
|
}
|
|
.disabled(team.inRound() == false)
|
|
}
|
|
|
|
Section {
|
|
RowButtonView("Effacer l'équipe", role: .destructive, systemImage: "trash") {
|
|
isDeleting = true
|
|
_resetTeam()
|
|
team.deleteTeamScores()
|
|
do {
|
|
try tournamentStore?.teamRegistrations.delete(instance: team)
|
|
} catch {
|
|
Logger.error(error)
|
|
}
|
|
dismiss()
|
|
}
|
|
.disabled(team.hasPaidOnline())
|
|
} footer: {
|
|
if team.hasPaidOnline() {
|
|
Text("Il n'est pas possible de supprimer cette équipe, leur inscription a été payée en ligne. Vous pouvez les mettre forfait et/ou les rembourser.").foregroundStyle(.logoRed)
|
|
} else if team.hasRegisteredOnline() {
|
|
Text("Attention, supprimer cette équipe notifiera par email que leur inscription a été annulée.").foregroundStyle(.logoRed)
|
|
}
|
|
}
|
|
}
|
|
.ifAvailableiOS26 {
|
|
if #available(iOS 26.0, *) {
|
|
$0.navigationSubtitle(tournament.tournamentTitle())
|
|
}
|
|
}
|
|
.alert("Attention", isPresented: hasChanged, actions: {
|
|
Button("Confirmer") {
|
|
if walkOut == false && team.walkOut == true {
|
|
registrationDateModified = Date()
|
|
registrationDate = registrationDateModified
|
|
}
|
|
_resetTeam()
|
|
team.registrationDate = registrationDate
|
|
team.wildCardBracket = wildCardBracket
|
|
team.wildCardGroupStage = wildCardGroupStage
|
|
team.walkOut = walkOut
|
|
team.uniqueRandomIndex = uniqueRandomIndex
|
|
_save()
|
|
}
|
|
|
|
Button("Annuler", role: .cancel) {
|
|
registrationDate = team.registrationDate ?? Date()
|
|
walkOut = team.walkOut
|
|
wildCardBracket = team.wildCardBracket
|
|
wildCardGroupStage = team.wildCardGroupStage
|
|
uniqueRandomIndex = team.uniqueRandomIndex
|
|
}
|
|
}, message: {
|
|
Text("Ce changement peut entraîner l'entrée ou la sortie d'une équipe de votre sélection. Padel Club préviendra automatiquement une équipe inscrite en ligne de son nouveau statut.")
|
|
})
|
|
.navigationBarBackButtonHidden(focusedField != nil)
|
|
.toolbar(content: {
|
|
if focusedField != nil {
|
|
ToolbarItem(placement: .topBarLeading) {
|
|
Button("Annuler", role: .cancel) {
|
|
focusedField = nil
|
|
}
|
|
}
|
|
}
|
|
})
|
|
.alert("Un problème est survenu", isPresented: messageSentFailed) {
|
|
Button("OK") {
|
|
}
|
|
} message: {
|
|
Text(_networkErrorMessage)
|
|
}
|
|
.sheet(item: $contactType) { contactType in
|
|
Group {
|
|
switch contactType {
|
|
case .message(_, let recipients, let body, _):
|
|
MessageComposeView(recipients: recipients, body: body) { result in
|
|
switch result {
|
|
case .cancelled:
|
|
break
|
|
case .failed:
|
|
self.sentError = .messageFailed
|
|
case .sent:
|
|
if networkMonitor.connected == false {
|
|
self.contactType = nil
|
|
if team.getPhoneNumbers().isEmpty == false {
|
|
self.sentError = .uncalledTeams([team])
|
|
} else {
|
|
self.sentError = .messageNotSent
|
|
}
|
|
}
|
|
@unknown default:
|
|
break
|
|
}
|
|
}
|
|
case .mail(_, let recipients, let bccRecipients, let body, let subject, _):
|
|
MailComposeView(recipients: recipients, bccRecipients: bccRecipients, body: body, subject: subject) { result in
|
|
switch result {
|
|
case .cancelled, .saved:
|
|
self.contactType = nil
|
|
case .failed:
|
|
self.contactType = nil
|
|
self.sentError = .mailFailed
|
|
case .sent:
|
|
if networkMonitor.connected == false {
|
|
self.contactType = nil
|
|
if team.getMail().isEmpty == false {
|
|
self.sentError = .uncalledTeams([team])
|
|
} else {
|
|
self.sentError = .mailNotSent
|
|
}
|
|
}
|
|
@unknown default:
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.tint(.master)
|
|
}
|
|
.sheet(isPresented: $showPaymentLinkManager) {
|
|
NavigationStack {
|
|
PaymentLinkManagerView(teamRegistration: team)
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.toolbar {
|
|
ToolbarItem(placement: .navigationBarTrailing) {
|
|
Button("Fermer") {
|
|
showPaymentLinkManager = false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.fullScreenCover(item: $editedTeam) { editedTeam in
|
|
NavigationStack {
|
|
AddTeamView(tournament: tournament, editedTeam: editedTeam)
|
|
}
|
|
.tint(.master)
|
|
}
|
|
.toolbar {
|
|
ToolbarItem(placement: .topBarTrailing) {
|
|
MenuWarningView(tournament: tournament, teams: [team], contactType: $contactType)
|
|
}
|
|
}
|
|
.onChange(of: registrationDate) {
|
|
if canSaveWithoutWarning() {
|
|
if let tr = team.registrationDate, tr == registrationDate {
|
|
|
|
} else {
|
|
team.registrationDate = registrationDate
|
|
_save()
|
|
}
|
|
}
|
|
}
|
|
.onChange(of: uniqueRandomIndex) {
|
|
if canSaveWithoutWarning() {
|
|
team.uniqueRandomIndex = uniqueRandomIndex
|
|
_save()
|
|
}
|
|
}
|
|
.onChange(of: [walkOut, wildCardBracket, wildCardGroupStage]) {
|
|
if canSaveWithoutWarning() {
|
|
if walkOut == false && team.walkOut == true {
|
|
registrationDateModified = Date()
|
|
team.registrationDate = registrationDateModified
|
|
registrationDate = registrationDateModified
|
|
}
|
|
_resetTeam()
|
|
team.walkOut = walkOut
|
|
team.wildCardBracket = wildCardBracket
|
|
team.wildCardGroupStage = wildCardGroupStage
|
|
_save()
|
|
}
|
|
}
|
|
.toolbarBackground(.visible, for: .navigationBar)
|
|
.navigationTitle("Édition de l'équipe")
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
}
|
|
|
|
func canSaveWithoutWarning() -> Bool {
|
|
(tournament.shouldWarnOnlineRegistrationUpdates() && tournament.teamCount <= tournament.unsortedTeamsCount()) == false
|
|
}
|
|
|
|
private var confirmationReceived: Binding<Bool> {
|
|
Binding {
|
|
team.confirmed()
|
|
} set: { confirmed in
|
|
team.confirmationDate = confirmed ? Date() : nil
|
|
do {
|
|
try tournamentStore?.teamRegistrations.addOrUpdate(instance: team)
|
|
} catch {
|
|
Logger.error(error)
|
|
}
|
|
}
|
|
}
|
|
|
|
private var hasArrived: Binding<Bool> {
|
|
Binding {
|
|
team.isHere()
|
|
} set: { hasArrived in
|
|
team.unsortedPlayers().forEach {
|
|
$0.hasArrived = hasArrived
|
|
}
|
|
do {
|
|
try tournamentStore?.playerRegistrations.addOrUpdate(contentOfs: team.unsortedPlayers())
|
|
} catch {
|
|
Logger.error(error)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func _save() {
|
|
do {
|
|
try tournamentStore?.teamRegistrations.addOrUpdate(instance: team)
|
|
} catch {
|
|
Logger.error(error)
|
|
}
|
|
}
|
|
|
|
private var _networkErrorMessage: String {
|
|
ContactManagerError.getNetworkErrorMessage(sentError: sentError, networkMonitorConnected: networkMonitor.connected)
|
|
}
|
|
|
|
private func _processRefund() async {
|
|
isProcessingRefund = true
|
|
do {
|
|
let response = try await RefundService.processRefund(teamRegistrationId: team.id)
|
|
|
|
await MainActor.run {
|
|
isProcessingRefund = false
|
|
refundMessage = response.message
|
|
|
|
if response.success {
|
|
if let players = response.players {
|
|
tournamentStore?.playerRegistrations.addOrUpdate(contentOfs: players)
|
|
} else {
|
|
refundMessage = response.message + "\nLa mise à jour des équipes n'a pas été reçue pour le moment."
|
|
}
|
|
|
|
}
|
|
}
|
|
} catch {
|
|
await MainActor.run {
|
|
isProcessingRefund = false
|
|
refundMessage = "Erreur lors du remboursement. Veuillez réessayer." + "\n" + error.localizedDescription
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct RegistrationStatusPicker: View {
|
|
var player: PlayerRegistration
|
|
@State var selectedStatus: PlayerRegistration.RegistrationStatus
|
|
|
|
init(player: PlayerRegistration) {
|
|
self.player = player
|
|
_selectedStatus = .init(wrappedValue: player.registrationStatus)
|
|
}
|
|
|
|
var body: some View {
|
|
Picker(selection: $selectedStatus) {
|
|
ForEach(PlayerRegistration.RegistrationStatus.allCases) { status in
|
|
Text(status.localizedRegistrationStatus())
|
|
.tag(status)
|
|
}
|
|
} label: {
|
|
if player.registrationStatus != selectedStatus {
|
|
HStack {
|
|
FooterButtonView("Valider") {
|
|
player.registrationStatus = selectedStatus
|
|
}
|
|
Divider()
|
|
FooterButtonView("Annuler", role: .cancel) {
|
|
selectedStatus = player.registrationStatus
|
|
}
|
|
.foregroundStyle(.blue)
|
|
}
|
|
} else {
|
|
Text("État de la confirmation")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|