feat(progress): add progress tracking enhancements
- Enable zoom/pan on progress map with reset button - Add visit count badges to stadium chips - Create GamesHistoryView with year grouping and sport filters - Create StadiumVisitHistoryView for viewing all visits to a stadium - Add VisitListCard and GamesHistoryRow components - Add "See All" navigation from Recent Visits to Games History - Add tests for map interactions, visit lists, and games history Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
109
SportsTime/Features/Progress/Views/StadiumVisitHistoryView.swift
Normal file
109
SportsTime/Features/Progress/Views/StadiumVisitHistoryView.swift
Normal file
@@ -0,0 +1,109 @@
|
||||
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 {
|
||||
ProgressView()
|
||||
} 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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user