diff --git a/PortfolioJournal/Views/Dashboard/MonthlyCheckInView.swift b/PortfolioJournal/Views/Dashboard/MonthlyCheckInView.swift index 5e2844a..c3d39a6 100644 --- a/PortfolioJournal/Views/Dashboard/MonthlyCheckInView.swift +++ b/PortfolioJournal/Views/Dashboard/MonthlyCheckInView.swift @@ -98,6 +98,7 @@ struct MonthlyCheckInView: View { VStack(spacing: 20) { headerCard summaryCard + monthlyHighlightsCard reflectionCard sourcesCard notesCard @@ -418,6 +419,73 @@ struct MonthlyCheckInView: View { .shadow(color: .black.opacity(0.05), radius: 8, y: 2) } + private var sourcePerformances: [(name: String, diff: Decimal, percentage: Double)] { + viewModel.sources.compactMap { source -> (name: String, diff: Decimal, percentage: Double)? in + let snapshots = source.sortedSnapshotsByDateAscending + guard snapshots.count >= 2 else { return nil } + let latest = snapshots[snapshots.count - 1] + let previous = snapshots[snapshots.count - 2] + let diff = latest.decimalValue - previous.decimalValue + let pct = previous.decimalValue > 0 + ? NSDecimalNumber(decimal: diff / previous.decimalValue * 100).doubleValue + : 0 + return (name: source.name, diff: diff, percentage: pct) + } + } + + @ViewBuilder + private var monthlyHighlightsCard: some View { + let perfs = sourcePerformances + if perfs.count >= 2 { + let best = perfs.max(by: { $0.percentage < $1.percentage }) + let worst = perfs.min(by: { $0.percentage < $1.percentage }) + VStack(alignment: .leading, spacing: 12) { + Text("Monthly Highlights") + .font(.headline) + + if let best { + HStack { + Image(systemName: "arrow.up.circle.fill") + .foregroundColor(.positiveGreen) + VStack(alignment: .leading, spacing: 2) { + Text("Best Performer") + .font(.caption) + .foregroundColor(.secondary) + Text("\(best.name)") + .font(.subheadline.weight(.semibold)) + } + Spacer() + Text(String(format: "%+.1f%%", best.percentage)) + .font(.subheadline.weight(.bold)) + .foregroundColor(.positiveGreen) + } + } + + if let worst, worst.name != best?.name { + HStack { + Image(systemName: "arrow.down.circle.fill") + .foregroundColor(.negativeRed) + VStack(alignment: .leading, spacing: 2) { + Text("Worst Performer") + .font(.caption) + .foregroundColor(.secondary) + Text("\(worst.name)") + .font(.subheadline.weight(.semibold)) + } + Spacer() + Text(String(format: "%+.1f%%", worst.percentage)) + .font(.subheadline.weight(.bold)) + .foregroundColor(.negativeRed) + } + } + } + .padding() + .background(Color(.systemBackground)) + .cornerRadius(AppConstants.UI.cornerRadius) + .shadow(color: .black.opacity(0.05), radius: 8, y: 2) + } + } + private var sourcesCard: some View { VStack(alignment: .leading, spacing: 12) { HStack {