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.
 
 
PadelClub/PadelClub/Views/Navigation/Toolbox/APICallsListView.swift

244 lines
7.5 KiB

//
// APICallsView.swift
// PadelClub
//
// Created by Laurent Morvillier on 27/05/2024.
//
import SwiftUI
import LeStorage
import PadelClubData
struct APICallsListView: View {
@State var descriptors: [CollectionDescriptor] = []
@State var total = 0
var body: some View {
List {
Section {
LabeledContent("Total count", value: "\(total)")
}
Section {
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.load()
}
}
func load() {
Task {
self.descriptors = await StoreCenter.main.apiCollectionDescriptors().map {
CollectionDescriptor(name: $0.0, type: $0.1)
}.sorted(by: \.name, order: .ascending)
for descriptor in self.descriptors {
if let type = descriptor.type as? any SyncedStorable.Type {
await self.loadCount(type, descriptor)
}
}
}
}
func loadCount<T: SyncedStorable>(_ type: T.Type, _ descriptor: CollectionDescriptor) async {
let calls = await StoreCenter.main.apiCalls(type: type)
descriptor.count = calls.count
self.total = total + descriptor.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
var type: any SyncedStorable.Type
// @State var text: String = ""
@State var apiCalls: [any SomeCall] = []
var body: some View {
List {
Section(header:
Text("\(apiCalls.count) API calls")
.font(.headline)
.foregroundColor(.primary)
) {
ForEach(self.apiCalls, id: \.id) { apiCall in
NavigationLink {
APICallView(apiCall: apiCall, type: self.type)
} label: {
LabeledContent(apiCall.method.rawValue, value: apiCall.attemptsCount.string)
}
}
}
}.navigationTitle(self.name)
.onAppear {
self._load()
}
}
@MainActor
fileprivate func _load() {
Task {
await self.load(self.type)
// self.text = await StoreCenter.main.apiCallsFileContent(resourceName: self.name)
}
}
func load<T: SyncedStorable>(_ type: T.Type) async {
let calls: [ApiCall<T>] = 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<T: SyncedStorable>(type: T.Type) {
if let call = self.apiCall as? ApiCall<T> {
Task {
await MainActor.run {
isLoading = true
}
do {
let results = try await StoreCenter.main.execute(apiCalls: [call])
await MainActor.run {
isLoading = false
if let result = results.first {
errorMessage = result.message
showSuccessPopover = (200..<300).contains(result.status)
} else {
errorMessage = "no results"
}
}
// 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 {
// APICallsListView()
//}