import SwiftUI struct DashboardView: View { @EnvironmentObject var authManager: AuthManager @StateObject private var healthKit = HealthKitService() @State private var readiness: ReadinessResponse? @State private var latest: LatestHealthResponse? @State private var heatmapData: [HeatmapEntry] = [] @State private var isLoading = true // Toast state @State private var showToast = false @State private var toastMessage = "" @State private var toastSuccess = true var greeting: String { let hour = Calendar.current.component(.hour, from: Date()) switch hour { case 5..<12: return "Доброе утро" case 12..<17: return "Добрый день" case 17..<22: return "Добрый вечер" default: return "Доброй ночи" } } var dateString: String { let formatter = DateFormatter() formatter.locale = Locale(identifier: "ru_RU") formatter.dateFormat = "d MMMM, EEEE" return formatter.string(from: Date()) } var body: some View { ZStack { // Background Color(hex: "0a0a1a") .ignoresSafeArea() ScrollView(showsIndicators: false) { VStack(spacing: 20) { // MARK: - Header headerView .padding(.top, 8) if isLoading { loadingView } else { // MARK: - Readiness if let r = readiness { ReadinessCardView(readiness: r) } // MARK: - Metrics Grid metricsGrid // MARK: - Weekly Chart if !heatmapData.isEmpty { WeeklyChartCard(heatmapData: heatmapData) } // MARK: - Insights InsightsCard(readiness: readiness, latest: latest) Spacer(minLength: 30) } } } .refreshable { await loadData() } } .toast(isShowing: $showToast, message: toastMessage, isSuccess: toastSuccess) .task { await loadData() } } // MARK: - Header private var headerView: some View { VStack(alignment: .leading, spacing: 4) { HStack { VStack(alignment: .leading, spacing: 4) { Text("\(greeting), \(authManager.userName) 👋") .font(.title2.bold()) .foregroundColor(.white) Text(dateString) .font(.subheadline) .foregroundColor(Color(hex: "8888aa")) } Spacer() // Sync button Button { Task { await syncHealthKit() } } label: { ZStack { Circle() .fill(Color(hex: "1a1a3e")) .frame(width: 42, height: 42) if healthKit.isSyncing { ProgressView() .tint(Color(hex: "00d4aa")) .scaleEffect(0.8) } else { Image(systemName: "arrow.triangle.2.circlepath") .font(.system(size: 16, weight: .medium)) .foregroundColor(Color(hex: "00d4aa")) } } } .disabled(healthKit.isSyncing) // 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) } // MARK: - Loading private var loadingView: some View { VStack(spacing: 16) { ProgressView() .tint(Color(hex: "00d4aa")) .scaleEffect(1.2) Text("Загрузка данных...") .font(.subheadline) .foregroundColor(Color(hex: "8888aa")) } .padding(.top, 80) } // MARK: - Metrics Grid private var metricsGrid: some View { LazyVGrid(columns: [GridItem(.flexible(), spacing: 12), GridItem(.flexible(), spacing: 12)], spacing: 12) { // Sleep if let sleep = latest?.sleep { SleepCard(sleep: sleep) } // Heart Rate if let rhr = latest?.restingHeartRate { MetricCardView( icon: "heart.fill", title: "Пульс покоя", value: "\(Int(rhr.value ?? 0)) уд/мин", subtitle: latest?.heartRate != nil ? "Avg: \(latest?.heartRate?.avg ?? 0) уд/мин" : "", color: Color(hex: "ff4757"), gradientColors: [Color(hex: "ff4757"), Color(hex: "ff6b81")] ) } // HRV if let hrv = latest?.hrv { MetricCardView( icon: "waveform.path.ecg", title: "HRV", value: "\(Int(hrv.avg ?? 0)) мс", subtitle: hrv.latest != nil ? "Последнее: \(Int(hrv.latest!)) мс" : "Вариабельность", color: Color(hex: "00d4aa"), gradientColors: [Color(hex: "00d4aa"), Color(hex: "00b894")] ) } // Steps if let steps = latest?.steps { StepsCard(steps: steps.total ?? 0) } } .padding(.horizontal) } // MARK: - Load Data func loadData() async { isLoading = true async let r = APIService.shared.getReadiness(token: authManager.token) async let l = APIService.shared.getLatest(token: authManager.token) async let h = APIService.shared.getHeatmap(token: authManager.token, days: 7) readiness = try? await r latest = try? await l heatmapData = (try? await h) ?? [] isLoading = false } // MARK: - Sync HealthKit func syncHealthKit() async { guard healthKit.isAvailable else { showToastMessage("HealthKit недоступен на этом устройстве", success: false) return } guard !authManager.apiKey.isEmpty else { showToastMessage("API ключ не найден. Войдите заново.", success: false) return } UIImpactFeedbackGenerator(style: .medium).impactOccurred() do { try await healthKit.syncToServer(apiKey: authManager.apiKey) UINotificationFeedbackGenerator().notificationOccurred(.success) showToastMessage("Данные синхронизированы ✓", success: true) // Reload dashboard after sync await loadData() } catch { UINotificationFeedbackGenerator().notificationOccurred(.error) showToastMessage(error.localizedDescription, success: false) } } private func showToastMessage(_ message: String, success: Bool) { toastMessage = message toastSuccess = success withAnimation { showToast = true } } }