Files
InvestmentTrackerApp/PortfolioJournal/Views/Dashboard/CategoryBreakdown.swift
T
alexandrev-tibco a8a9ad64f3 Xcode 27: compila sin warnings y migra API deprecadas de SwiftUI
- Imports de CoreData que faltaban (MemberImportVisibility) en AccountsView,
  ImportDataView, ChartsContainerView y AddSourceView.
- Aislamiento MainActor: IsolatedDefaultValues en el target de la app,
  LinearGradient @MainActor, WatchSyncMessage nonisolated, closures de
  ReviewPromptService/MonthlyCheckInStore/AppIntents.
- Warnings de valores sin usar (SnapshotGapDetector, DashboardLayoutStore,
  OnboardingView, GoalEditorView, CoreDataStack, SettingsViewModel).
- foregroundColor→foregroundStyle, cornerRadius→clipShape,
  navigationBarLeading/Trailing→topBarLeading/Trailing,
  Task.sleep(nanoseconds:)→sleep(for:), NavigationLink(isActive:)→
  navigationDestination(isPresented:).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F1u4K16xy7eQVtgsYNZ9Vn
2026-09-16 11:44:49 +02:00

211 lines
6.3 KiB
Swift

import SwiftUI
struct CategoryBreakdownCard: View {
@EnvironmentObject private var iapService: IAPService
let categories: [CategoryMetrics]
var body: some View {
VStack(alignment: .leading, spacing: 12) {
HStack {
Text("By Category")
.font(.headline)
Spacer()
NavigationLink {
ChartsContainerView(iapService: iapService)
} label: {
Text("See All")
.font(.subheadline)
.foregroundStyle(Color.appPrimary)
}
}
ForEach(categories) { category in
CategoryRowView(category: category)
}
}
.padding()
.background(Color(.systemBackground))
.clipShape(RoundedRectangle(cornerRadius: AppConstants.UI.cornerRadius))
.shadow(color: .black.opacity(0.05), radius: 8, y: 2)
}
}
struct CategoryRowView: View {
let category: CategoryMetrics
private var targetPercentage: Double? {
AllocationTargetStore.target(for: category.id)
}
private var driftText: String? {
guard let target = targetPercentage else { return nil }
let drift = category.percentageOfPortfolio - target
let prefix = drift >= 0 ? "+" : ""
return "\(prefix)\(String(format: "%.1f%%", drift))"
}
var body: some View {
HStack(spacing: 12) {
// Icon
ZStack {
Circle()
.fill((Color(hex: category.colorHex) ?? .gray).opacity(0.2))
.frame(width: 36, height: 36)
Image(systemName: category.icon)
.font(.system(size: 14))
.foregroundStyle(Color(hex: category.colorHex) ?? .gray)
}
// Name and percentage
VStack(alignment: .leading, spacing: 2) {
Text(category.categoryName)
.font(.subheadline.weight(.medium))
Text(category.formattedPercentage)
.font(.caption)
.foregroundStyle(.secondary)
if let target = targetPercentage {
Text("Target \(String(format: "%.0f%%", target)) | Drift \(driftText ?? "")")
.font(.caption2)
.foregroundStyle(.secondary)
}
}
Spacer()
// Value and return
VStack(alignment: .trailing, spacing: 2) {
Text(category.formattedTotalValue)
.font(.subheadline.weight(.semibold))
HStack(spacing: 4) {
Text("CAGR")
.font(.caption2)
.foregroundStyle(.secondary)
Text(category.metrics.formattedCAGR)
.font(.caption.weight(.semibold))
.foregroundStyle(category.metrics.cagr >= 0 ? Color.positiveGreen : Color.negativeRed)
}
}
}
.padding(.vertical, 4)
}
}
// MARK: - Category Progress Bar
struct CategoryProgressBar: View {
let category: CategoryMetrics
let maxValue: Decimal
var progress: Double {
guard maxValue > 0 else { return 0 }
return NSDecimalNumber(decimal: category.totalValue / maxValue).doubleValue
}
var body: some View {
VStack(alignment: .leading, spacing: 4) {
HStack {
HStack(spacing: 6) {
Circle()
.fill(Color(hex: category.colorHex) ?? .gray)
.frame(width: 8, height: 8)
Text(category.categoryName)
.font(.caption)
}
Spacer()
Text(category.formattedPercentage)
.font(.caption)
.foregroundStyle(.secondary)
}
GeometryReader { geometry in
ZStack(alignment: .leading) {
Rectangle()
.fill(Color.gray.opacity(0.1))
.frame(height: 6)
.clipShape(RoundedRectangle(cornerRadius: 3))
Rectangle()
.fill(Color(hex: category.colorHex) ?? .gray)
.frame(width: geometry.size.width * progress, height: 6)
.clipShape(RoundedRectangle(cornerRadius: 3))
}
}
.frame(height: 6)
}
}
}
// MARK: - Simple Category List
struct SimpleCategoryList: View {
let categories: [CategoryMetrics]
var body: some View {
VStack(spacing: 8) {
ForEach(categories) { category in
HStack {
Circle()
.fill(Color(hex: category.colorHex) ?? .gray)
.frame(width: 10, height: 10)
Text(category.categoryName)
.font(.subheadline)
Spacer()
Text(category.formattedTotalValue)
.font(.subheadline.weight(.medium))
Text("(\(category.formattedPercentage))")
.font(.caption)
.foregroundStyle(.secondary)
}
}
}
}
}
#Preview {
let sampleCategories = [
CategoryMetrics(
id: UUID(),
categoryName: "Stocks",
colorHex: "#10B981",
icon: "chart.line.uptrend.xyaxis",
totalValue: 50000,
percentageOfPortfolio: 50,
metrics: .empty
),
CategoryMetrics(
id: UUID(),
categoryName: "Bonds",
colorHex: "#3B82F6",
icon: "building.columns.fill",
totalValue: 30000,
percentageOfPortfolio: 30,
metrics: .empty
),
CategoryMetrics(
id: UUID(),
categoryName: "Real Estate",
colorHex: "#F59E0B",
icon: "house.fill",
totalValue: 20000,
percentageOfPortfolio: 20,
metrics: .empty
)
]
return CategoryBreakdownCard(categories: sampleCategories)
.padding()
.environmentObject(IAPService())
}