import SwiftUI struct GymDetailView: View { let gymId: UUID @EnvironmentObject var dataManager: ClimbingDataManager @Environment(\.dismiss) private var dismiss @State private var showingDeleteAlert = false private var gym: Gym? { dataManager.gym(withId: gymId) } private var problems: [Problem] { dataManager.problems(forGym: gymId) } private var sessions: [ClimbSession] { dataManager.sessions(forGym: gymId) } private var gymAttempts: [Attempt] { let problemIds = Set(problems.map { $0.id }) return dataManager.attempts.filter { problemIds.contains($0.problemId) } } private var gymStats: GymStats { calculateGymStats() } var body: some View { ScrollView { LazyVStack(spacing: 20) { if let gym = gym { GymHeaderCard(gym: gym) GymStatsCard(stats: gymStats) if !problems.isEmpty { RecentProblemsSection(problems: problems.prefix(5)) } if !sessions.isEmpty { RecentSessionsSection(sessions: sessions.prefix(3)) } if problems.isEmpty && sessions.isEmpty { EmptyGymStateView() } } else { Text("Gym not found") .foregroundColor(.secondary) } } .padding() } .navigationTitle(gym?.name ?? "Gym Details") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItemGroup(placement: .navigationBarTrailing) { if gym != nil { Menu { Button { // Navigate to edit view } label: { Label("Edit Gym", systemImage: "pencil") } Button(role: .destructive) { showingDeleteAlert = true } label: { Label("Delete Gym", systemImage: "trash") } } label: { Image(systemName: "ellipsis.circle") } } } } .alert("Delete Gym", isPresented: $showingDeleteAlert) { Button("Cancel", role: .cancel) {} Button("Delete", role: .destructive) { if let gym = gym { dataManager.deleteGym(gym) dismiss() } } } message: { Text( "Are you sure you want to delete this gym? This will also delete all problems and sessions associated with this gym." ) } } private func calculateGymStats() -> GymStats { let uniqueProblemsClimbed = Set(gymAttempts.map { $0.problemId }).count let totalSessions = sessions.count let activeSessions = sessions.count { $0.status == .active } return GymStats( totalProblems: problems.count, totalSessions: totalSessions, totalAttempts: gymAttempts.count, uniqueProblemsClimbed: uniqueProblemsClimbed, activeSessions: activeSessions ) } } struct GymHeaderCard: View { let gym: Gym var body: some View { VStack(alignment: .leading, spacing: 16) { VStack(alignment: .leading, spacing: 8) { Text(gym.name) .font(.title) .fontWeight(.bold) if let location = gym.location, !location.isEmpty { Text(location) .font(.subheadline) .foregroundColor(.secondary) } if let notes = gym.notes, !notes.isEmpty { Text(notes) .font(.body) .padding(.top, 4) } } // Supported Climb Types if !gym.supportedClimbTypes.isEmpty { VStack(alignment: .leading, spacing: 8) { Text("Climb Types") .font(.headline) .fontWeight(.semibold) ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 8) { ForEach(gym.supportedClimbTypes, id: \.self) { climbType in Text(climbType.displayName) .font(.caption) .padding(.horizontal, 12) .padding(.vertical, 6) .background( RoundedRectangle(cornerRadius: 12) .fill(.blue.opacity(0.1)) ) .foregroundColor(.blue) } } .padding(.horizontal, 1) } } } // Difficulty Systems if !gym.difficultySystems.isEmpty { VStack(alignment: .leading, spacing: 8) { Text("Difficulty Systems") .font(.headline) .fontWeight(.semibold) Text(gym.difficultySystems.map { $0.displayName }.joined(separator: ", ")) .font(.subheadline) .foregroundColor(.secondary) } } } .padding() .background( RoundedRectangle(cornerRadius: 16) .fill(.regularMaterial) ) } } struct GymStatsCard: View { let stats: GymStats var body: some View { VStack(alignment: .leading, spacing: 16) { Text("Statistics") .font(.title2) .fontWeight(.bold) LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 2), spacing: 16) { StatItem(label: "Problems", value: "\(stats.totalProblems)") StatItem(label: "Sessions", value: "\(stats.totalSessions)") StatItem(label: "Total Attempts", value: "\(stats.totalAttempts)") StatItem(label: "Problems Climbed", value: "\(stats.uniqueProblemsClimbed)") } if stats.activeSessions > 0 { HStack { StatItem(label: "Active Sessions", value: "\(stats.activeSessions)") Spacer() } } } .padding() .background( RoundedRectangle(cornerRadius: 16) .fill(.regularMaterial) ) } } struct RecentProblemsSection: View { let problems: any Sequence @EnvironmentObject var dataManager: ClimbingDataManager var body: some View { VStack(alignment: .leading, spacing: 16) { Text( "Problems (\(dataManager.problems(forGym: Array(problems).first?.gymId ?? UUID()).count))" ) .font(.title2) .fontWeight(.bold) LazyVStack(spacing: 12) { ForEach(Array(problems), id: \.id) { problem in NavigationLink(destination: ProblemDetailView(problemId: problem.id)) { ProblemRowCard(problem: problem) } .buttonStyle(.plain) } } if dataManager.problems(forGym: Array(problems).first?.gymId ?? UUID()).count > 5 { Text( "... and \(dataManager.problems(forGym: Array(problems).first?.gymId ?? UUID()).count - 5) more problems" ) .font(.caption) .foregroundColor(.secondary) } } .padding() .background( RoundedRectangle(cornerRadius: 16) .fill(.regularMaterial) ) } } struct RecentSessionsSection: View { let sessions: any Sequence @EnvironmentObject var dataManager: ClimbingDataManager var body: some View { VStack(alignment: .leading, spacing: 16) { Text( "Recent Sessions (\(dataManager.sessions(forGym: Array(sessions).first?.gymId ?? UUID()).count))" ) .font(.title2) .fontWeight(.bold) LazyVStack(spacing: 12) { ForEach(Array(sessions), id: \.id) { session in NavigationLink(destination: SessionDetailView(sessionId: session.id)) { SessionRowCard(session: session) } .buttonStyle(.plain) } } if dataManager.sessions(forGym: Array(sessions).first?.gymId ?? UUID()).count > 3 { Text( "... and \(dataManager.sessions(forGym: Array(sessions).first?.gymId ?? UUID()).count - 3) more sessions" ) .font(.caption) .foregroundColor(.secondary) } } .padding() .background( RoundedRectangle(cornerRadius: 16) .fill(.regularMaterial) ) } } struct ProblemRowCard: View { let problem: Problem @EnvironmentObject var dataManager: ClimbingDataManager private var problemAttempts: [Attempt] { dataManager.attempts(forProblem: problem.id) } private var isCompleted: Bool { problemAttempts.contains { $0.result.isSuccessful } } var body: some View { HStack { VStack(alignment: .leading, spacing: 4) { Text(problem.name ?? "Unnamed Problem") .font(.headline) .fontWeight(.semibold) .foregroundColor(.primary) Text( "\(problem.difficulty.grade) • \(problem.climbType.displayName) • \(problemAttempts.count) attempts" ) .font(.subheadline) .foregroundColor(.secondary) } Spacer() if isCompleted { Image(systemName: "checkmark.circle.fill") .foregroundColor(.green) } } .padding() .background( RoundedRectangle(cornerRadius: 12) .fill(.ultraThinMaterial) .stroke(.quaternary, lineWidth: 1) ) } } struct SessionRowCard: View { let session: ClimbSession @EnvironmentObject var dataManager: ClimbingDataManager private var sessionAttempts: [Attempt] { dataManager.attempts(forSession: session.id) } var body: some View { HStack { VStack(alignment: .leading, spacing: 4) { HStack { Text(session.status == .active ? "Active Session" : "Session") .font(.headline) .fontWeight(.semibold) .foregroundColor(.primary) if session.status == .active { Text("ACTIVE") .font(.caption) .fontWeight(.medium) .padding(.horizontal, 6) .padding(.vertical, 2) .background( RoundedRectangle(cornerRadius: 4) .fill(.green.opacity(0.2)) ) .foregroundColor(.green) } } Text("\(formatDate(session.date)) • \(sessionAttempts.count) attempts") .font(.subheadline) .foregroundColor(.secondary) } Spacer() if let duration = session.duration { Text("\(duration)min") .font(.caption) .foregroundColor(.secondary) } } .padding() .background( RoundedRectangle(cornerRadius: 12) .fill(.ultraThinMaterial) .stroke(.quaternary, lineWidth: 1) ) } private func formatDate(_ date: Date) -> String { let formatter = DateFormatter() formatter.dateStyle = .medium return formatter.string(from: date) } } struct EmptyGymStateView: View { var body: some View { VStack(spacing: 20) { Image(systemName: "figure.climbing") .font(.system(size: 60)) .foregroundColor(.secondary) VStack(spacing: 8) { Text("No activity yet") .font(.title2) .fontWeight(.bold) Text("Start a session or add problems to see them here") .font(.body) .foregroundColor(.secondary) .multilineTextAlignment(.center) } } .padding(40) .background( RoundedRectangle(cornerRadius: 16) .fill(.regularMaterial) ) } } struct GymStats { let totalProblems: Int let totalSessions: Int let totalAttempts: Int let uniqueProblemsClimbed: Int let activeSessions: Int } #Preview { NavigationView { GymDetailView(gymId: UUID()) .environmentObject(ClimbingDataManager.preview) } }