// // PollVotingView.swift // SportsTime // // View for ranking trips in a poll // import SwiftUI struct PollVotingView: View { @Environment(\.dismiss) private var dismiss @Environment(\.colorScheme) private var colorScheme @State private var viewModel = PollVotingViewModel() let poll: TripPoll let existingVote: PollVote? var onVoteSubmitted: (() -> Void)? var body: some View { NavigationStack { VStack(spacing: 0) { // Instructions instructionsHeader // Reorderable list List { ForEach(Array(viewModel.rankings.enumerated()), id: \.element) { index, tripIndex in RankingRow( rank: index + 1, trip: poll.tripSnapshots[tripIndex] ) } .onMove { source, destination in viewModel.moveTrip(from: source, to: destination) } } .listStyle(.plain) .environment(\.editMode, .constant(.active)) // Submit button submitButton } .navigationTitle("Rank Trips") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .cancellationAction) { Button("Cancel") { dismiss() } } } .onAppear { viewModel.initializeRankings( tripCount: poll.tripSnapshots.count, existingVote: existingVote ) } .alert("Error", isPresented: .constant(viewModel.error != nil)) { Button("OK") { viewModel.error = nil } } message: { if let error = viewModel.error { Text(error.localizedDescription) } } .onChange(of: viewModel.didSubmit) { _, didSubmit in if didSubmit { onVoteSubmitted?() dismiss() } } } } @ViewBuilder private var instructionsHeader: some View { VStack(spacing: 8) { Image(systemName: "arrow.up.arrow.down") .font(.title2) .foregroundStyle(Theme.warmOrange) Text("Drag to rank your preferences") .font(.headline) Text("Your top choice should be at the top") .font(.subheadline) .foregroundStyle(.secondary) } .padding() .frame(maxWidth: .infinity) .background(Theme.cardBackground(colorScheme)) } @ViewBuilder private var submitButton: some View { Button { Task { if let existingVote { await viewModel.updateVote(existingVote: existingVote) } else { await viewModel.submitVote(pollId: poll.id) } } } label: { HStack { if viewModel.isLoading { ProgressView() .tint(.white) } else { Text(existingVote != nil ? "Update Vote" : "Submit Vote") } } .frame(maxWidth: .infinity) .padding() .background(Theme.warmOrange) .foregroundStyle(.white) .font(.headline) } .disabled(viewModel.isLoading || !viewModel.canSubmit) } } // MARK: - Ranking Row private struct RankingRow: View { let rank: Int let trip: Trip var body: some View { HStack(spacing: 12) { // Rank badge Text("\(rank)") .font(.headline) .foregroundStyle(.white) .frame(width: 28, height: 28) .background(rankColor) .clipShape(Circle()) VStack(alignment: .leading, spacing: 2) { Text(trip.name) .font(.headline) Text(tripSummary) .font(.caption) .foregroundStyle(.secondary) } Spacer() } .padding(.vertical, 4) } private var rankColor: Color { switch rank { case 1: return Theme.warmOrange case 2: return .blue case 3: return .green default: return .secondary } } private var tripSummary: String { let cities = trip.stops.map { $0.city }.joined(separator: " → ") return cities } } #Preview { PollVotingView( poll: TripPoll( title: "Test Poll", ownerId: "test", tripSnapshots: [] ), existingVote: nil ) }