diff --git a/PadelClub/Views/Navigation/Toolbox/APICallsListView.swift b/PadelClub/Views/Navigation/Toolbox/APICallsListView.swift index 467b105..c99abd0 100644 --- a/PadelClub/Views/Navigation/Toolbox/APICallsListView.swift +++ b/PadelClub/Views/Navigation/Toolbox/APICallsListView.swift @@ -10,32 +10,91 @@ import LeStorage struct APICallsListView: View { - @State var collections: [String] = [] + @State var descriptors: [CollectionDescriptor] = [] var body: some View { List { - ForEach(self.collections) { name in - NavigationLink(name) { - APICallsView(name: name) + ForEach(self.descriptors) { descriptor in + + NavigationLink { + if let syncedType = descriptor.type as? any SyncedStorable.Type { + APICallsView(name: descriptor.name, type: syncedType) + } + } label: { + LabeledContent(descriptor.name, value: descriptor.count.string) } + } }.onAppear { - self.collections = Store.main.collectionNames() + self.load() } - } + + func load() { + self.descriptors = Store.main.collectionNames().map { + CollectionDescriptor(name: $0.0, type: $0.1) + }.sorted(by: \.name, order: .ascending) + Task { + + for descriptor in self.descriptors { + if let type = descriptor.type as? any SyncedStorable.Type { + await self.loadCount(type, descriptor) + } + } + } + } + + func loadCount(_ type: T.Type, _ descriptor: CollectionDescriptor) async { + let calls = await StoreCenter.main.apiCalls(type: type) + descriptor.count = calls.count +// Logger.log("\(descriptor.name), count = \(calls.count)") + } + +} + +@Observable +class CollectionDescriptor: Identifiable, Hashable { + + var id: UUID = UUID() + var name: String + var type: any Storable.Type + var count: Int = 0 + + init(name: String, type: any Storable.Type) { + self.name = name + self.type = type + } + + static func == (lhs: CollectionDescriptor, rhs: CollectionDescriptor) -> Bool { + return lhs.id == rhs.id + } + func hash(into hasher: inout Hasher) { + hasher.combine(self.id) + } + } struct APICallsView: View { var name: String - @State var text: String = "" + var type: any SyncedStorable.Type + +// @State var text: String = "" + @State var apiCalls: [any SomeCall] = [] var body: some View { List { - Text(self.text).lineLimit(nil) - }.onAppear { + ForEach(self.apiCalls, id: \.id) { apiCall in + NavigationLink { + APICallView(apiCall: apiCall, type: self.type) + } label: { + LabeledContent(apiCall.method.rawValue, value: apiCall.attemptsCount.string) + } + } +// Text(self.text).lineLimit(nil) + }.navigationTitle(self.name) + .onAppear { self._load() } } @@ -43,10 +102,116 @@ struct APICallsView: View { @MainActor fileprivate func _load() { Task { - self.text = await StoreCenter.main.apiCallsFileContent(resourceName: self.name) + await self.load(self.type) +// self.text = await StoreCenter.main.apiCallsFileContent(resourceName: self.name) } } + func load(_ type: T.Type) async { + let calls: [ApiCall] = await StoreCenter.main.apiCalls(type: type) + self.apiCalls = calls + } + +} + +struct APICallView: View { + + var apiCall: any SomeCall + var type: any SyncedStorable.Type + @State private var errorMessage: String? = nil + @State private var isLoading: Bool = false + @State private var showSuccessPopover: Bool = false + + var body: some View { + Form { + Section { + LabeledContent("Method", value: apiCall.method.rawValue) + LabeledContent("Data id", value: apiCall.stringId) + LabeledContent("Attempts", value: apiCall.attemptsCount.string) + } + + Section { + if isLoading { + ProgressView() + .padding() + } else { + + Button("Execute") { + self.execute() + // Clear previous error when attempting again + errorMessage = nil + } + .disabled(isLoading) + } + if let errorMessage = errorMessage { + LabeledContent("Error", value: errorMessage) + // VStack(alignment: .leading) { + // Text("Error:") + // .fontWeight(.bold) + // .foregroundColor(.red) + // Text(errorMessage) + // .foregroundColor(.red) + // } + } + if showSuccessPopover { + VStack { + Image(systemName: "checkmark.circle.fill") + .foregroundColor(.green) + .font(.title) + Text("Success") + .foregroundColor(.green) + } + .padding() + .background( + RoundedRectangle(cornerRadius: 8) + .fill(Color.white) + .shadow(radius: 2) + ) + .transition(.scale.combined(with: .opacity)) + } + + } + Section { + Text(apiCall.dataContent ?? "none").monospaced().font(.caption) + } + + } + .animation(.easeInOut, value: showSuccessPopover) + .navigationTitle(String(describing: self.type)) + } + + func execute() { + self.execute(type: self.type) + } + + func execute(type: T.Type) { + if let call = self.apiCall as? ApiCall { + Task { + await MainActor.run { + isLoading = true + } + + do { + let result = try await StoreCenter.main.execute(apiCalls: [call]) + print(result) + // Update UI on the main thread + await MainActor.run { + errorMessage = nil + isLoading = false + + // Show success popover + showSuccessPopover = true + } + } catch { + // Update UI on the main thread + await MainActor.run { + errorMessage = error.localizedDescription + isLoading = false + } + } + } + } + } } //#Preview { diff --git a/PadelClub/Views/Navigation/Toolbox/ToolboxView.swift b/PadelClub/Views/Navigation/Toolbox/ToolboxView.swift index c660ffe..439d714 100644 --- a/PadelClub/Views/Navigation/Toolbox/ToolboxView.swift +++ b/PadelClub/Views/Navigation/Toolbox/ToolboxView.swift @@ -52,82 +52,11 @@ struct ToolboxView: View { } #if DEBUG + DebugView() + #endif - Section { - NavigationLink("Settings") { - DebugSettingsView() - } - NavigationLink("API calls") { - APICallsListView() - } - } - - Section { - RowButtonView("Reset ALL API Calls") { - StoreCenter.main.resetApiCalls() - Logger.log("Api calls reset") - } - } - Section { - RowButtonView("Fix Names") { - - for tournament in dataStore.tournaments { - - if let store = tournament.tournamentStore { - let playerRegistrations = store.playerRegistrations - playerRegistrations.forEach { player in - player.firstName = player.firstName.trimmed.capitalized - player.lastName = player.lastName.trimmed.uppercased() - } - - do { - try store.playerRegistrations.addOrUpdate(contentOfs: playerRegistrations) - } catch { - Logger.error(error) - } - } - } - - } - } - - Section { - RowButtonView("Delete teams") { - - for tournament in DataStore.shared.tournaments { - - if let store: TournamentStore = tournament.tournamentStore { - - let teamRegistrations = store.teamRegistrations.filter({ $0.tournamentObject() == nil }) - do { - try store.teamRegistrations.delete(contentOfs: teamRegistrations) - } catch { - Logger.error(error) - } - } - } - } - } - - Section { - // TODO - RowButtonView("Delete players") { - - for tournament in DataStore.shared.tournaments { - if let store: TournamentStore = tournament.tournamentStore { - - let playersRegistrations = store.playerRegistrations.filter({ $0.team() == nil }) - do { - try store.playerRegistrations.delete(contentOfs: playersRegistrations) - } catch { - Logger.error(error) - } - } - } - - } - - } + #if TESTFLIGHT + DebugView() #endif Section { @@ -248,6 +177,89 @@ struct ToolboxView: View { // ToolboxView() //} +struct DebugView: View { + + @EnvironmentObject var dataStore: DataStore + + var body: some View { + + Section { + NavigationLink("Settings") { + DebugSettingsView() + } + NavigationLink("API calls") { + APICallsListView() + } + } + + Section { + RowButtonView("Reset ALL API Calls") { + StoreCenter.main.resetApiCalls() + Logger.log("Api calls reset") + } + } + Section { + RowButtonView("Fix Names") { + + for tournament in dataStore.tournaments { + + if let store = tournament.tournamentStore { + let playerRegistrations = store.playerRegistrations + playerRegistrations.forEach { player in + player.firstName = player.firstName.trimmed.capitalized + player.lastName = player.lastName.trimmed.uppercased() + } + + do { + try store.playerRegistrations.addOrUpdate(contentOfs: playerRegistrations) + } catch { + Logger.error(error) + } + } + } + + } + } + + Section { + RowButtonView("Delete teams") { + + for tournament in DataStore.shared.tournaments { + + if let store: TournamentStore = tournament.tournamentStore { + + let teamRegistrations = store.teamRegistrations.filter({ $0.tournamentObject() == nil }) + do { + try store.teamRegistrations.delete(contentOfs: teamRegistrations) + } catch { + Logger.error(error) + } + } + } + } + } + + Section { + // TODO + RowButtonView("Delete players") { + + for tournament in DataStore.shared.tournaments { + if let store: TournamentStore = tournament.tournamentStore { + + let playersRegistrations = store.playerRegistrations.filter({ $0.team() == nil }) + do { + try store.playerRegistrations.delete(contentOfs: playersRegistrations) + } catch { + Logger.error(error) + } + } + } + + } + + } + } +} struct ZipLog: Transferable { private func _getZip() -> URL? {