- Add LoadingSpinner component with small/medium/large sizes using system gray color - Add LoadingPlaceholder for skeleton loading states - Add LoadingSheet for full-screen blocking overlays - Replace ThemedSpinner/ThemedSpinnerCompact across all views - Remove deprecated loading components from AnimatedComponents.swift - Delete LoadingTextGenerator.swift - Fix PhotoImportView layout to fill full width Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
110 lines
3.1 KiB
Swift
110 lines
3.1 KiB
Swift
import SwiftUI
|
|
import SwiftData
|
|
|
|
struct StadiumVisitHistoryView: View {
|
|
let stadium: Stadium
|
|
@Environment(\.modelContext) private var modelContext
|
|
@Environment(\.dismiss) private var dismiss
|
|
|
|
@State private var visits: [StadiumVisit] = []
|
|
@State private var isLoading = true
|
|
@State private var showingAddVisit = false
|
|
|
|
var body: some View {
|
|
NavigationStack {
|
|
Group {
|
|
if isLoading {
|
|
LoadingSpinner(size: .medium)
|
|
} else if visits.isEmpty {
|
|
EmptyVisitHistoryView()
|
|
} else {
|
|
VisitHistoryList(visits: visits)
|
|
}
|
|
}
|
|
.navigationTitle(stadium.name)
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.toolbar {
|
|
ToolbarItem(placement: .cancellationAction) {
|
|
Button("Done") { dismiss() }
|
|
}
|
|
|
|
ToolbarItem(placement: .primaryAction) {
|
|
Button {
|
|
showingAddVisit = true
|
|
} label: {
|
|
Image(systemName: "plus")
|
|
}
|
|
}
|
|
}
|
|
.sheet(isPresented: $showingAddVisit) {
|
|
StadiumVisitSheet(initialStadium: stadium)
|
|
}
|
|
}
|
|
.task {
|
|
await loadVisits()
|
|
}
|
|
}
|
|
|
|
private func loadVisits() async {
|
|
isLoading = true
|
|
defer { isLoading = false }
|
|
|
|
let stadiumId = stadium.id
|
|
let descriptor = FetchDescriptor<StadiumVisit>(
|
|
predicate: #Predicate { $0.stadiumId == stadiumId },
|
|
sortBy: [SortDescriptor(\.visitDate, order: .reverse)]
|
|
)
|
|
|
|
do {
|
|
visits = try modelContext.fetch(descriptor)
|
|
} catch {
|
|
visits = []
|
|
}
|
|
}
|
|
}
|
|
|
|
private struct VisitHistoryList: View {
|
|
let visits: [StadiumVisit]
|
|
|
|
var body: some View {
|
|
ScrollView {
|
|
VStack(spacing: 12) {
|
|
// Visit count header
|
|
HStack {
|
|
Text("\(visits.count) Visit\(visits.count == 1 ? "" : "s")")
|
|
.font(.headline)
|
|
Spacer()
|
|
}
|
|
.padding(.horizontal)
|
|
|
|
// Visit cards
|
|
ForEach(visits, id: \.id) { visit in
|
|
VisitListCard(visit: visit)
|
|
.padding(.horizontal)
|
|
}
|
|
}
|
|
.padding(.vertical)
|
|
}
|
|
.background(Color(.systemGroupedBackground))
|
|
}
|
|
}
|
|
|
|
private struct EmptyVisitHistoryView: View {
|
|
var body: some View {
|
|
VStack(spacing: 16) {
|
|
Image(systemName: "calendar.badge.plus")
|
|
.font(.system(size: 48))
|
|
.foregroundStyle(.secondary)
|
|
|
|
Text("No visits recorded")
|
|
.font(.headline)
|
|
|
|
Text("Tap + to add your first visit to this stadium")
|
|
.font(.subheadline)
|
|
.foregroundStyle(.secondary)
|
|
.multilineTextAlignment(.center)
|
|
}
|
|
.padding()
|
|
}
|
|
}
|