import SwiftUI struct CategoryBreakdownCard: View { let categories: [CategoryMetrics] var body: some View { VStack(alignment: .leading, spacing: 12) { HStack { Text("By Category") .font(.headline) Spacer() NavigationLink { ChartsContainerView() } label: { Text("See All") .font(.subheadline) .foregroundColor(.appPrimary) } } ForEach(categories) { category in CategoryRowView(category: category) } } .padding() .background(Color(.systemBackground)) .cornerRadius(AppConstants.UI.cornerRadius) .shadow(color: .black.opacity(0.05), radius: 8, y: 2) } } struct CategoryRowView: View { let category: CategoryMetrics 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)) .foregroundColor(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) .foregroundColor(.secondary) } Spacer() // Value and return VStack(alignment: .trailing, spacing: 2) { Text(category.formattedTotalValue) .font(.subheadline.weight(.semibold)) HStack(spacing: 2) { Image(systemName: category.metrics.cagr >= 0 ? "arrow.up.right" : "arrow.down.right") .font(.caption2) Text(category.metrics.formattedCAGR) .font(.caption) } .foregroundColor(category.metrics.cagr >= 0 ? .positiveGreen : .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) .foregroundColor(.secondary) } GeometryReader { geometry in ZStack(alignment: .leading) { Rectangle() .fill(Color.gray.opacity(0.1)) .frame(height: 6) .cornerRadius(3) Rectangle() .fill(Color(hex: category.colorHex) ?? .gray) .frame(width: geometry.size.width * progress, height: 6) .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) .foregroundColor(.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() }