import SwiftUI struct DashboardView: View { @EnvironmentObject var authManager: AuthManager @State private var tasks: [PulseTask] = [] @State private var habits: [Habit] = [] @State private var readiness: ReadinessResponse? @State private var isLoading = true var greeting: String { let h = Calendar.current.component(.hour, from: Date()) switch h { case 5..<12: return "Доброе утро" case 12..<17: return "Добрый день" case 17..<22: return "Добрый вечер" default: return "Доброй ночи" } } var pendingTasks: [PulseTask] { tasks.filter { !$0.completed } } var completedHabitsToday: Int { habits.filter { $0.completedToday == true }.count } var body: some View { ZStack { Color(hex: "0a0a1a").ignoresSafeArea() ScrollView { VStack(spacing: 20) { // Header HStack { VStack(alignment: .leading, spacing: 4) { Text(greeting + ", " + authManager.userName + "!") .font(.title2.bold()).foregroundColor(.white) Text(Date(), style: .date) .font(.subheadline).foregroundColor(Color(hex: "8888aa")) } Spacer() // Logout Button { UIImpactFeedbackGenerator(style: .light).impactOccurred() authManager.logout() } label: { ZStack { Circle() .fill(Color(hex: "1a1a3e")) .frame(width: 42, height: 42) Image(systemName: "rectangle.portrait.and.arrow.right") .font(.system(size: 14, weight: .medium)) .foregroundColor(Color(hex: "8888aa")) } } } .padding(.horizontal) .padding(.top) if isLoading { ProgressView().tint(Color(hex: "00d4aa")).padding(.top, 40) } else { // Readiness Score mini card if let r = readiness { ReadinessMiniCard(readiness: r) } // Stats row HStack(spacing: 12) { StatCard(icon: "checkmark.circle.fill", value: "\(pendingTasks.count)", label: "Задач", color: "00d4aa") StatCard(icon: "flame.fill", value: "\(completedHabitsToday)/\(habits.count)", label: "Привычек", color: "ffa502") if let r = readiness { StatCard(icon: "heart.fill", value: "\(r.score)", label: "Готовность", color: r.score >= 80 ? "00d4aa" : r.score >= 60 ? "ffa502" : "ff4757") } } .padding(.horizontal) // Today's tasks if !pendingTasks.isEmpty { VStack(alignment: .leading, spacing: 12) { Text("Задачи на сегодня").font(.headline).foregroundColor(.white).padding(.horizontal) ForEach(pendingTasks.prefix(3)) { task in TaskRowView(task: task) { await completeTask(task) } } } } // Habits progress if !habits.isEmpty { VStack(alignment: .leading, spacing: 12) { Text("Привычки сегодня").font(.headline).foregroundColor(.white).padding(.horizontal) ForEach(habits.prefix(4)) { habit in HabitRowView(habit: habit) { await logHabit(habit) } } } } } Spacer(minLength: 20) } } .refreshable { await loadData(refresh: true) } } .task { await loadData() } } func loadData(refresh: Bool = false) async { if !refresh { isLoading = true } async let t = APIService.shared.getTodayTasks(token: authManager.token) async let h = APIService.shared.getHabits(token: authManager.token) async let r = HealthAPIService.shared.getReadiness(apiKey: authManager.healthApiKey) tasks = (try? await t) ?? [] habits = (try? await h) ?? [] readiness = try? await r isLoading = false } func completeTask(_ task: PulseTask) async { try? await APIService.shared.completeTask(token: authManager.token, id: task.id) await loadData() } func logHabit(_ habit: Habit) async { try? await APIService.shared.logHabit(token: authManager.token, id: habit.id) await loadData() } } // MARK: - StatCard struct StatCard: View { let icon: String let value: String let label: String let color: String var body: some View { VStack(spacing: 6) { Image(systemName: icon).foregroundColor(Color(hex: color)).font(.title3) Text(value).font(.headline.bold()).foregroundColor(.white) Text(label).font(.caption).foregroundColor(Color(hex: "8888aa")) } .frame(maxWidth: .infinity) .padding(12) .background(RoundedRectangle(cornerRadius: 16).fill(Color.white.opacity(0.05))) } } // MARK: - ReadinessMiniCard struct ReadinessMiniCard: View { let readiness: ReadinessResponse var statusColor: Color { readiness.score >= 80 ? Color(hex: "00d4aa") : readiness.score >= 60 ? Color(hex: "ffa502") : Color(hex: "ff4757") } var body: some View { HStack(spacing: 16) { ZStack { Circle().stroke(Color.white.opacity(0.1), lineWidth: 6).frame(width: 60, height: 60) Circle().trim(from: 0, to: CGFloat(readiness.score) / 100) .stroke(statusColor, style: StrokeStyle(lineWidth: 6, lineCap: .round)) .frame(width: 60, height: 60).rotationEffect(.degrees(-90)) Text("\(readiness.score)").font(.headline.bold()).foregroundColor(statusColor) } VStack(alignment: .leading, spacing: 4) { Text("Готовность").font(.subheadline).foregroundColor(Color(hex: "8888aa")) Text(readiness.recommendation).font(.callout).foregroundColor(.white).lineLimit(2) } Spacer() } .padding(16) .background(RoundedRectangle(cornerRadius: 16).fill(Color.white.opacity(0.05))) .padding(.horizontal) } }