import SwiftUI import Charts struct DashboardView: View { @EnvironmentObject var iapService: IAPService @StateObject private var viewModel: DashboardViewModel init() { _viewModel = StateObject(wrappedValue: DashboardViewModel()) } var body: some View { NavigationStack { ScrollView { VStack(spacing: 20) { if viewModel.hasData { // Total Value Card TotalValueCard( totalValue: viewModel.portfolioSummary.formattedTotalValue, dayChange: viewModel.portfolioSummary.formattedDayChange, isPositive: viewModel.isDayChangePositive ) // Evolution Chart if !viewModel.evolutionData.isEmpty { EvolutionChartCard(data: viewModel.evolutionData) } // Period Returns PeriodReturnsCard( monthChange: viewModel.portfolioSummary.formattedMonthChange, yearChange: viewModel.portfolioSummary.formattedYearChange, allTimeChange: viewModel.portfolioSummary.formattedAllTimeReturn, isMonthPositive: viewModel.isMonthChangePositive, isYearPositive: viewModel.isYearChangePositive, isAllTimePositive: viewModel.portfolioSummary.allTimeReturn >= 0 ) // Category Breakdown if !viewModel.categoryMetrics.isEmpty { CategoryBreakdownCard(categories: viewModel.topCategories) } // Pending Updates if !viewModel.sourcesNeedingUpdate.isEmpty { PendingUpdatesCard(sources: viewModel.sourcesNeedingUpdate) } } else { EmptyDashboardView() } } .padding() } .navigationTitle("Dashboard") .refreshable { viewModel.refreshData() } .overlay { if viewModel.isLoading { ProgressView() } } } } } // MARK: - Total Value Card struct TotalValueCard: View { let totalValue: String let dayChange: String let isPositive: Bool var body: some View { VStack(spacing: 8) { Text("Total Portfolio Value") .font(.subheadline) .foregroundColor(.secondary) Text(totalValue) .font(.system(size: 42, weight: .bold, design: .rounded)) .foregroundColor(.primary) HStack(spacing: 4) { Image(systemName: isPositive ? "arrow.up.right" : "arrow.down.right") .font(.caption) Text(dayChange) .font(.subheadline.weight(.medium)) Text("today") .font(.subheadline) .foregroundColor(.secondary) } .foregroundColor(isPositive ? .positiveGreen : .negativeRed) } .frame(maxWidth: .infinity) .padding(.vertical, 24) .background(Color(.systemBackground)) .cornerRadius(AppConstants.UI.cornerRadius) .shadow(color: .black.opacity(0.05), radius: 8, y: 2) } } // MARK: - Period Returns Card struct PeriodReturnsCard: View { let monthChange: String let yearChange: String let allTimeChange: String let isMonthPositive: Bool let isYearPositive: Bool let isAllTimePositive: Bool var body: some View { VStack(alignment: .leading, spacing: 12) { Text("Returns") .font(.headline) HStack(spacing: 16) { ReturnPeriodView( period: "1M", change: monthChange, isPositive: isMonthPositive ) Divider() ReturnPeriodView( period: "1Y", change: yearChange, isPositive: isYearPositive ) Divider() ReturnPeriodView( period: "All", change: allTimeChange, isPositive: isAllTimePositive ) } .frame(maxWidth: .infinity) } .padding() .background(Color(.systemBackground)) .cornerRadius(AppConstants.UI.cornerRadius) .shadow(color: .black.opacity(0.05), radius: 8, y: 2) } } struct ReturnPeriodView: View { let period: String let change: String let isPositive: Bool var body: some View { VStack(spacing: 4) { Text(period) .font(.caption) .foregroundColor(.secondary) Text(change) .font(.subheadline.weight(.semibold)) .foregroundColor(isPositive ? .positiveGreen : .negativeRed) .lineLimit(1) .minimumScaleFactor(0.8) } .frame(maxWidth: .infinity) } } // MARK: - Empty Dashboard View struct EmptyDashboardView: View { var body: some View { VStack(spacing: 20) { Image(systemName: "chart.pie") .font(.system(size: 60)) .foregroundColor(.secondary) Text("Welcome to Investment Tracker") .font(.title2.weight(.semibold)) Text("Start by adding your first investment source to track your portfolio.") .font(.subheadline) .foregroundColor(.secondary) .multilineTextAlignment(.center) NavigationLink { AddSourceView() } label: { Label("Add Investment Source", systemImage: "plus") .font(.headline) .foregroundColor(.white) .padding() .frame(maxWidth: .infinity) .background(Color.appPrimary) .cornerRadius(AppConstants.UI.cornerRadius) } } .padding() } } // MARK: - Pending Updates Card struct PendingUpdatesCard: View { let sources: [InvestmentSource] var body: some View { VStack(alignment: .leading, spacing: 12) { HStack { Image(systemName: "bell.badge.fill") .foregroundColor(.appWarning) Text("Pending Updates") .font(.headline) Spacer() Text("\(sources.count)") .font(.subheadline) .foregroundColor(.secondary) } ForEach(sources.prefix(3)) { source in NavigationLink(destination: SourceDetailView(source: source)) { HStack { Circle() .fill(source.category?.color ?? .gray) .frame(width: 8, height: 8) Text(source.name) .font(.subheadline) Spacer() Text(source.latestSnapshot?.date.relativeDescription ?? "Never") .font(.caption) .foregroundColor(.secondary) Image(systemName: "chevron.right") .font(.caption) .foregroundColor(.secondary) } } .buttonStyle(.plain) } if sources.count > 3 { Text("+ \(sources.count - 3) more") .font(.caption) .foregroundColor(.secondary) } } .padding() .background(Color(.systemBackground)) .cornerRadius(AppConstants.UI.cornerRadius) .shadow(color: .black.opacity(0.05), radius: 8, y: 2) } } #Preview { DashboardView() .environmentObject(IAPService()) }