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.
325 lines
13 KiB
325 lines
13 KiB
//
|
|
// FileImportView.swift
|
|
// PadelClub
|
|
//
|
|
// Created by Razmig Sarkissian on 24/03/2024.
|
|
//
|
|
|
|
import SwiftUI
|
|
import TipKit
|
|
import LeStorage
|
|
|
|
struct FileImportView: View {
|
|
@EnvironmentObject var dataStore: DataStore
|
|
@Environment(Tournament.self) var tournament: Tournament
|
|
@Environment(\.dismiss) private var dismiss
|
|
|
|
let notFoundAreWalkOutTip = NotFoundAreWalkOutTip()
|
|
|
|
@State private var fileContent: String?
|
|
@State private var teams: [FileImportManager.TeamHolder] = []
|
|
@State private var isShowing = false
|
|
@State private var didImport = false
|
|
@State private var convertingFile = false
|
|
@State private var errorMessage: String? = nil
|
|
@State private var forceRankUpdate: Bool = false
|
|
|
|
@State private var selectedOptions: Set<TeamImportStrategy> = Set()
|
|
|
|
@State private var fileProvider: FileImportManager.FileProvider = .frenchFederation
|
|
|
|
private var filteredTeams: [FileImportManager.TeamHolder] {
|
|
return teams.filter { $0.tournamentCategory == tournament.tournamentCategory }.sorted(by: \.weight)
|
|
}
|
|
|
|
var body: some View {
|
|
List {
|
|
if teams.isEmpty {
|
|
Section {
|
|
RowButtonView("Choisir le fichier", systemImage: "square.and.arrow.down") {
|
|
convertingFile = false
|
|
isShowing.toggle()
|
|
}
|
|
}
|
|
|
|
Section {
|
|
Picker(selection: $fileProvider) {
|
|
ForEach(FileImportManager.FileProvider.allCases) {
|
|
Text($0.localizedLabel).tag($0)
|
|
}
|
|
} label: {
|
|
Text("Source du fichier")
|
|
}
|
|
|
|
RowButtonView("Démarrer l'importation") {
|
|
if let fileContent {
|
|
await _startImport(fileContent: fileContent)
|
|
}
|
|
}
|
|
.disabled(fileContent == nil)
|
|
} footer: {
|
|
if fileProvider == .frenchFederation {
|
|
let footerString = "Fichier provenant de [beach-padel.app.fft.fr](\(URLs.beachPadel.rawValue))"
|
|
Text(.init(footerString))
|
|
}
|
|
}
|
|
}
|
|
|
|
// if filteredTeams.isEmpty == false && tournament.unsortedTeams().isEmpty == false {
|
|
// Section {
|
|
// ForEach(TeamImportStrategy.allCases, id: \.self) { strategy in
|
|
// LabeledContent {
|
|
// Toggle(isOn: .init(get: {
|
|
// selectedOptions.contains(strategy)
|
|
// }, set: { selected in
|
|
// if selected {
|
|
// selectedOptions.insert(strategy)
|
|
// } else {
|
|
// selectedOptions.remove(strategy)
|
|
//
|
|
// }
|
|
// })) {}
|
|
// } label: {
|
|
// Text(strategy.titleLabel())
|
|
// Text(strategy.descriptionLabel())
|
|
// }
|
|
//
|
|
// }
|
|
// } header: {
|
|
// Text("Stratégie d'importation")
|
|
// }
|
|
// }
|
|
|
|
if convertingFile {
|
|
Section {
|
|
LabeledContent {
|
|
ProgressView()
|
|
} label: {
|
|
Text("Importation en cours")
|
|
}
|
|
}
|
|
}
|
|
|
|
if let errorMessage {
|
|
Section {
|
|
Text(errorMessage)
|
|
} header: {
|
|
Text("Erreur")
|
|
}
|
|
}
|
|
|
|
// if tournament.entriesCount > 0 {
|
|
// Section {
|
|
// if tournament.inscriptionClosed {
|
|
// Label("Les inscriptions clôturées", systemImage: "lock")
|
|
// Text("Si le poids des équipes a changé, aucun déplacement entre les poules et le tableau n'est possible. Par contre, le classement sera mis à jour au sein des poules et du tableau, respectivement, en fonction de leur nouveau poids.")
|
|
// Toggle(isOn: $forceRankUpdate) {
|
|
// Text("Ne pas en tenir compte")
|
|
// }
|
|
// } else {
|
|
// Label("Les inscriptions sont ouvertes", systemImage: "lock.open")
|
|
// Text("Si le poids des équipes a changé, le classement de toutes les équipes sera mis à jour en fonction de leur nouveau poids.")
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
if filteredTeams.isEmpty && teams.isEmpty == false {
|
|
@Bindable var tournament = tournament
|
|
Section {
|
|
Text("Aucune équipe \(tournament.tournamentCategory.importingRawValue) détectée mais \(teams.count) équipes sont dans le fichier")
|
|
Picker(selection: $tournament.tournamentCategory) {
|
|
ForEach(TournamentCategory.allCases) { category in
|
|
Text(category.importingRawValue).tag(category)
|
|
}
|
|
} label: {
|
|
Text("Modifier la catégorie du tournoi ?")
|
|
}
|
|
.onChange(of: tournament.tournamentCategory) {
|
|
_save()
|
|
}
|
|
}
|
|
} else if teams.isEmpty && didImport == true {
|
|
Section {
|
|
ContentUnavailableView("Aucune équipe détectée", systemImage: "person.2.slash")
|
|
}
|
|
} else if didImport {
|
|
let _filteredTeams = filteredTeams
|
|
let previousTeams = tournament.sortedTeams()
|
|
|
|
if previousTeams.isEmpty == false {
|
|
Section {
|
|
TipView(notFoundAreWalkOutTip)
|
|
.tipStyle(tint: nil)
|
|
}
|
|
}
|
|
Section {
|
|
LabeledContent {
|
|
Text(_filteredTeams.count.formatted())
|
|
} label: {
|
|
Text("Équipe\(_filteredTeams.count.pluralSuffix) \(tournament.tournamentCategory.importingRawValue) détectée\(_filteredTeams.count.pluralSuffix)")
|
|
}
|
|
}
|
|
|
|
ForEach(_filteredTeams) { team in
|
|
_teamView(team: team, inTeams: _filteredTeams, previousTeams: previousTeams)
|
|
}
|
|
}
|
|
}
|
|
.fileImporter(isPresented: $isShowing, allowedContentTypes: [.spreadsheet, .commaSeparatedText, .text], allowsMultipleSelection: false, onCompletion: { results in
|
|
|
|
switch results {
|
|
case .success(let fileurls):
|
|
if let selectedFile = fileurls.first {
|
|
if selectedFile.startAccessingSecurityScopedResource() {
|
|
convertingFile = true
|
|
errorMessage = nil
|
|
teams.removeAll()
|
|
Task {
|
|
do {
|
|
if selectedFile.lastPathComponent.hasSuffix("xls") {
|
|
fileContent = try await CloudConvert.manager.uploadFile(selectedFile)
|
|
} else {
|
|
fileContent = try String(contentsOf: selectedFile)
|
|
}
|
|
selectedFile.stopAccessingSecurityScopedResource()
|
|
} catch {
|
|
errorMessage = error.localizedDescription
|
|
}
|
|
}
|
|
} else {
|
|
// Handle denied access
|
|
}
|
|
}
|
|
case .failure(let error):
|
|
errorMessage = error.localizedDescription
|
|
}
|
|
})
|
|
|
|
.onOpenURL { url in
|
|
do {
|
|
fileContent = try String(contentsOf: url)
|
|
} catch {
|
|
errorMessage = error.localizedDescription
|
|
}
|
|
}
|
|
.navigationTitle("Importation")
|
|
.navigationBarTitleDisplayMode(.large)
|
|
.toolbar {
|
|
ToolbarItem(placement: .cancellationAction) {
|
|
Button("Annuler", role: .cancel) {
|
|
dismiss()
|
|
}
|
|
}
|
|
|
|
ToolbarItem(placement: .topBarTrailing) {
|
|
ButtonValidateView {
|
|
// if false { //selectedOptions.contains(.deleteBeforeImport)
|
|
// try? dataStore.teamRegistrations.delete(contentOfs: tournament.unsortedTeams())
|
|
// }
|
|
|
|
if true { //selectedOptions.contains(.notFoundAreWalkOut)
|
|
let previousTeams = filteredTeams.compactMap({ $0.previousTeam })
|
|
|
|
let unfound = Set(tournament.unsortedTeams()).subtracting(Set(previousTeams))
|
|
unfound.forEach { team in
|
|
team.resetPositions()
|
|
team.wildCardBracket = false
|
|
team.wildCardGroupStage = false
|
|
team.walkOut = true
|
|
}
|
|
|
|
try? dataStore.teamRegistrations.addOrUpdate(contentOfs: unfound)
|
|
|
|
}
|
|
|
|
tournament.importTeams(filteredTeams)
|
|
dismiss()
|
|
}
|
|
.disabled(teams.isEmpty)
|
|
}
|
|
}
|
|
}
|
|
|
|
func _startImport(fileContent: String) async {
|
|
await MainActor.run {
|
|
convertingFile = true
|
|
errorMessage = nil
|
|
teams.removeAll()
|
|
}
|
|
if let rankSourceDate = tournament.rankSourceDate, tournament.unrankValue(for: false) == nil || tournament.unrankValue(for: true) == nil {
|
|
await MonthData.calculateCurrentUnrankedValues(mostRecentDateAvailable: rankSourceDate)
|
|
}
|
|
|
|
self.teams = await FileImportManager.shared.createTeams(from: fileContent, tournament: tournament, fileProvider: fileProvider)
|
|
await MainActor.run {
|
|
convertingFile = false
|
|
didImport = true
|
|
}
|
|
}
|
|
|
|
@ViewBuilder
|
|
private func _teamView(team: FileImportManager.TeamHolder, inTeams teams: [FileImportManager.TeamHolder], previousTeams: [TeamRegistration]) -> some View {
|
|
|
|
let newIndex = team.index(in: teams)
|
|
Section {
|
|
HStack {
|
|
VStack(alignment: .leading) {
|
|
ForEach(team.players.sorted(by: \.computedRank)) {
|
|
Text($0.playerLabel())
|
|
}
|
|
}
|
|
Spacer()
|
|
HStack {
|
|
if let previousTeam = team.previousTeam {
|
|
Text(previousTeam.formattedSeed(in: previousTeams))
|
|
Image(systemName: "arrowshape.forward.fill")
|
|
}
|
|
Text(team.formattedSeedIndex(index: newIndex))
|
|
}
|
|
}
|
|
if let callDate = team.previousTeam?.callDate, let newDate = tournament.getStartDate(ofSeedIndex: newIndex), callDate != newDate {
|
|
Text("Attention, cette paire a déjà été convoquée à \(callDate.localizedDate())")
|
|
.foregroundStyle(.red)
|
|
.italic()
|
|
.font(.caption)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func _save() {
|
|
do {
|
|
try dataStore.tournaments.addOrUpdate(instance: tournament)
|
|
} catch {
|
|
Logger.error(error)
|
|
}
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
FileImportView()
|
|
.environment(Tournament.mock())
|
|
}
|
|
|
|
enum TeamImportStrategy: CaseIterable {
|
|
case notFoundAreWalkOut
|
|
case deleteBeforeImport
|
|
|
|
func titleLabel() -> String {
|
|
switch self {
|
|
case .notFoundAreWalkOut:
|
|
"Mettre les équipes manquantes WO"
|
|
case .deleteBeforeImport:
|
|
"Effacer avant d'importer"
|
|
}
|
|
}
|
|
func descriptionLabel() -> String {
|
|
switch self {
|
|
case .notFoundAreWalkOut:
|
|
"Si une équipe déjà présente n'est pas dans la nouvelle liste, elle sera mise à WO"
|
|
case .deleteBeforeImport:
|
|
"Supprime toutes les équipes avant d'importer"
|
|
// case .lockWeight:
|
|
// "Permets de déplacer les équipes avec leur nouveaux classements sans les déplacer entre les blocs (p500+)"
|
|
}
|
|
}
|
|
}
|
|
|