import SwiftUI struct ChallengesView: View { @StateObject private var viewModel = ChallengeViewModel() @State private var showingCreateSheet = false @State private var selectedTab: ChallengeTab = .active enum ChallengeTab: String, CaseIterable { case active, upcoming, completed } var body: some View { NavigationView { Group { if viewModel.isLoading && viewModel.challenges.isEmpty { loadingView } else if currentChallenges.isEmpty { emptyStateView } else { challengeListView } } .navigationTitle("Challenges") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button { showingCreateSheet = true } label: { Image(systemName: "plus") } } } .sheet(isPresented: $showingCreateSheet) { CreateChallengeSheet() } } .onAppear { Task { await viewModel.fetchChallenges() } } } private var currentChallenges: [Challenge] { switch selectedTab { case .active: return viewModel.activeChallenges case .upcoming: return viewModel.upcomingChallenges case .completed: return viewModel.completedChallenges } } private var challengeListView: some View { List { Picker("Challenges", selection: $selectedTab) { ForEach(ChallengeTab.allCases, id: \.self) { tab in Text(tab.rawValue.capitalized).tag(tab) } } .pickerStyle(.segmented) .padding(.top, 8) Section(currentSectionTitle) { ForEach(currentChallenges) { challenge in NavigationLink(destination: ChallengeDetailView(challenge: challenge)) { ChallengeRowView(challenge: challenge) } } } } .listStyle(.insetGrouped) .refreshable { await viewModel.fetchChallenges() } } private var currentSectionTitle: String { switch selectedTab { case .active: return "Active Challenges" case .upcoming: return "Upcoming" case .completed: return "Completed" } } private var loadingView: some View { VStack(spacing: 16) { ProgressView() Text("Loading Challenges...") .font(.subheadline) .foregroundColor(.secondary) } .padding(.vertical, 60) } private var emptyStateView: some View { VStack(spacing: 16) { Image(systemName: "flag.fill") .font(.system(size: 64)) .foregroundColor(.secondary) Text("No \(selectedTab.rawValue) Challenges") .font(.title2) .fontWeight(.semibold) Text(selectedTab == .active ? "Join or create a challenge to compete with others." : selectedTab == .upcoming ? "New challenges will appear here." : "Completed challenges are tracked here.") .font(.subheadline) .foregroundColor(.secondary) .multilineTextAlignment(.center) .padding(.horizontal, 32) } .padding(.vertical, 60) } } struct CreateChallengeSheet: View { @Environment(\.dismiss) var dismiss @State private var title = "" @State private var description = "" @State private var challengeType: ChallengeType = .distance @State private var targetMetric: ChallengeMetric = .distance @State private var targetValue = "" @State private var rules = "" var body: some View { NavigationView { Form { Section("Challenge Details") { TextField("Challenge Title", text: $title) TextField("Description", text: $description) } Section("Type") { Picker("Challenge Type", selection: $challengeType) { ForEach(ChallengeType.allCases, id: \.self) { type in Text(type.displayName).tag(type) } } } Section("Target") { Picker("Metric", selection: $targetMetric) { ForEach(ChallengeMetric.allCases, id: \.self) { metric in Text(metric.displayName).tag(metric) } } HStack { TextField("Target Value", text: $targetValue) .keyboardType(.decimalPad) Text(targetMetric.unit) .foregroundColor(.secondary) } } Section("Optional") { TextField("Rules", text: $rules) } } .navigationTitle("Create Challenge") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .navigationBarLeading) { Button("Cancel") { dismiss() } } ToolbarItem(placement: .navigationBarTrailing) { Button("Create") { let endDate = Date().addingTimeInterval(30 * 24 * 3600) let request = CreateChallengeRequest( title: title, description: description, challengeType: challengeType, startDate: Date(), endDate: endDate, targetMetric: targetMetric, targetValue: Double(targetValue) ?? 0, rules: rules.isEmpty ? nil : rules, clubId: nil ) dismiss() } .disabled(title.isEmpty || targetValue.isEmpty) } } } } } struct ChallengeRowView: View { let challenge: Challenge var body: some View { VStack(spacing: 8) { HStack(spacing: 12) { Image(systemName: challenge.challengeType.icon) .font(.system(size: 24)) .foregroundColor(challenge.challengeType.color) .frame(width: 44, height: 44) .background(challenge.challengeType.color.opacity(0.15)) .cornerRadius(10) VStack(alignment: .leading, spacing: 4) { Text(challenge.title) .font(.headline) Text("\(challenge.challengeType.displayName) \u2022 \(challenge.targetValue) \(challenge.targetUnit)") .font(.subheadline) .foregroundColor(.secondary) HStack(spacing: 8) { Text("\(challenge.participantCount) participants") .font(.caption) .foregroundColor(.secondary) if challenge.daysRemaining > 0 { Text("\(challenge.daysRemaining) days left") .font(.caption2) .foregroundColor(.orange) } } } Spacer() switch challenge.participationStatus { case .participating: Image(systemName: "checkmark.circle.fill") .foregroundColor(.green) case .invited: Image(systemName: "mail.fill") .foregroundColor(.blue) case .notParticipating: Image(systemName: "circle") .foregroundColor(.secondary) } } if challenge.participationStatus == .participating { progressView } } .padding(.vertical, 4) } private var progressView: some View { VStack(alignment: .leading, spacing: 4) { HStack { Text("\(challenge.progressPercentage, specifier: "%.0f")%") .font(.caption2) .fontWeight(.medium) Spacer() Text("\(challenge.userProgress ?? 0)/\(challenge.targetValue) \(challenge.targetUnit)") .font(.caption2) .foregroundColor(.secondary) } ProgressView(value: challenge.progressPercentage / 100) .tint(challenge.challengeType.color) } .padding(.horizontal, 4) } } #Preview { ChallengesView() }