// // ScheduleListView.swift // SportsTime // import SwiftUI struct ScheduleListView: View { @Environment(\.colorScheme) private var colorScheme @State private var viewModel = ScheduleViewModel() @State private var showDatePicker = false var body: some View { Group { if viewModel.isLoading && viewModel.games.isEmpty { loadingView } else if let errorMessage = viewModel.errorMessage { errorView(message: errorMessage) } else if viewModel.games.isEmpty { emptyView } else { gamesList } } .searchable(text: $viewModel.searchText, prompt: "Search teams or venues") .scrollContentBackground(.hidden) .themedBackground() .toolbar { ToolbarItem(placement: .primaryAction) { Menu { Button { showDatePicker = true } label: { Label("Date Range", systemImage: "calendar") } if viewModel.hasFilters { Button(role: .destructive) { viewModel.resetFilters() } label: { Label("Clear Filters", systemImage: "xmark.circle") } } } label: { Image(systemName: viewModel.hasFilters ? "line.3.horizontal.decrease.circle.fill" : "line.3.horizontal.decrease.circle") } } } .sheet(isPresented: $showDatePicker) { DateRangePickerSheet( startDate: viewModel.startDate, endDate: viewModel.endDate ) { start, end in viewModel.updateDateRange(start: start, end: end) } .presentationDetents([.medium]) } .task { await viewModel.loadGames() } } // MARK: - Sport Filter private var sportFilter: some View { ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 8) { ForEach(Sport.supported) { sport in SportFilterChip( sport: sport, isSelected: viewModel.selectedSports.contains(sport) ) { viewModel.toggleSport(sport) } } } .padding(.horizontal) .padding(.vertical, 8) } } // MARK: - Games List private var gamesList: some View { List { Section { sportFilter .listRowInsets(EdgeInsets()) .listRowBackground(Color.clear) } ForEach(viewModel.gamesBySport, id: \.sport) { sportGroup in Section { ForEach(sportGroup.games) { richGame in GameRowView(game: richGame, showDate: true) } } header: { HStack(spacing: 8) { Image(systemName: sportGroup.sport.iconName) Text(sportGroup.sport.rawValue) } .font(.headline) } .listRowBackground(Theme.cardBackground(colorScheme)) } } .listStyle(.plain) .scrollContentBackground(.hidden) .refreshable { await viewModel.loadGames() } } // MARK: - Empty State private var emptyView: some View { VStack(spacing: 16) { sportFilter ContentUnavailableView { Label("No Games Found", systemImage: "sportscourt") } description: { Text("Try adjusting your filters or date range") } actions: { Button("Reset Filters") { viewModel.resetFilters() } .buttonStyle(.bordered) } } } // MARK: - Loading State private var loadingView: some View { VStack(spacing: 16) { ThemedSpinner(size: 44) Text("Loading schedule...") .foregroundStyle(.secondary) } } // MARK: - Error State private func errorView(message: String) -> some View { ContentUnavailableView { Label("Unable to Load", systemImage: "exclamationmark.icloud") } description: { Text(message) } actions: { Button { viewModel.clearError() Task { await viewModel.loadGames() } } label: { Text("Try Again") } .buttonStyle(.bordered) } } // MARK: - Helpers private func formatSectionDate(_ date: Date) -> String { let calendar = Calendar.current let formatter = DateFormatter() if calendar.isDateInToday(date) { return "Today" } else if calendar.isDateInTomorrow(date) { return "Tomorrow" } else { formatter.dateFormat = "EEEE, MMM d" return formatter.string(from: date) } } } // MARK: - Sport Filter Chip struct SportFilterChip: View { let sport: Sport let isSelected: Bool let action: () -> Void var body: some View { Button(action: action) { HStack(spacing: 6) { Image(systemName: sport.iconName) .font(.caption) Text(sport.rawValue) .font(.caption) .fontWeight(.medium) } .padding(.horizontal, 12) .padding(.vertical, 8) .background(isSelected ? Color.blue : Color(.secondarySystemBackground)) .foregroundStyle(isSelected ? .white : .primary) .clipShape(Capsule()) } .buttonStyle(.plain) } } // MARK: - Game Row View struct GameRowView: View { let game: RichGame var showDate: Bool = false private var isToday: Bool { Calendar.current.isDateInToday(game.game.dateTime) } var body: some View { VStack(alignment: .leading, spacing: 8) { // Date (when grouped by sport) if showDate { HStack(spacing: 6) { Text(formattedDate) .font(.caption) .fontWeight(.medium) .foregroundStyle(.secondary) if isToday { Text("TODAY") .font(.caption2) .fontWeight(.bold) .foregroundStyle(.white) .padding(.horizontal, 6) .padding(.vertical, 2) .background(Color.orange) .clipShape(Capsule()) } } } // Teams HStack { VStack(alignment: .leading, spacing: 4) { HStack(spacing: 8) { TeamBadge(team: game.awayTeam, isHome: false) Text("@") .font(.caption) .foregroundStyle(.secondary) TeamBadge(team: game.homeTeam, isHome: true) } } Spacer() } // Game info HStack(spacing: 12) { Label(game.game.gameTime, systemImage: "clock") Label(game.stadium.name, systemImage: "building.2") if let broadcast = game.game.broadcastInfo { Label(broadcast, systemImage: "tv") } } .font(.caption) .foregroundStyle(.secondary) } .padding(.vertical, 4) .listRowBackground(isToday ? Color.orange.opacity(0.1) : nil) } private var formattedDate: String { let formatter = DateFormatter() formatter.dateFormat = "EEE, MMM d" return formatter.string(from: game.game.dateTime) } } // MARK: - Team Badge struct TeamBadge: View { let team: Team let isHome: Bool var body: some View { HStack(spacing: 4) { if let colorHex = team.primaryColor { Circle() .fill(Color(hex: colorHex)) .frame(width: 8, height: 8) } Text(team.abbreviation) .font(.subheadline) .fontWeight(isHome ? .bold : .regular) Text(team.city) .font(.caption) .foregroundStyle(.secondary) } } } // MARK: - Date Range Picker Sheet struct DateRangePickerSheet: View { @Environment(\.dismiss) private var dismiss @State var startDate: Date @State var endDate: Date let onApply: (Date, Date) -> Void var body: some View { NavigationStack { Form { Section("Date Range") { DatePicker("Start", selection: $startDate, displayedComponents: .date) DatePicker("End", selection: $endDate, in: startDate..., displayedComponents: .date) } Section { Button("Next 7 Days") { startDate = Date() endDate = Calendar.current.date(byAdding: .day, value: 7, to: Date()) ?? Date() } Button("Next 14 Days") { startDate = Date() endDate = Calendar.current.date(byAdding: .day, value: 14, to: Date()) ?? Date() } Button("Next 30 Days") { startDate = Date() endDate = Calendar.current.date(byAdding: .day, value: 30, to: Date()) ?? Date() } } } .navigationTitle("Select Dates") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .cancellationAction) { Button("Cancel") { dismiss() } } ToolbarItem(placement: .confirmationAction) { Button("Apply") { onApply(startDate, endDate) dismiss() } } } } } } #Preview { NavigationStack { ScheduleListView() } }