InvestmentTrackerApp/InvestmentTracker/Views/Dashboard/CategoryBreakdown.swift

192 lines
5.5 KiB
Swift

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()
}