1.1.0 feature work: Monthly Check-in, Charts, Goals, Share, Reviews

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
alexandrev-tibco
2026-02-11 23:37:11 +01:00
parent daaca95913
commit 4761e2e5c8
19 changed files with 1118 additions and 90 deletions
@@ -3,6 +3,8 @@ import Charts
struct AllocationPieChart: View {
let data: [(category: String, value: Decimal, color: String)]
var title: String = "Asset Allocation"
var showsTargetsComparison: Bool = true
@State private var selectedSlice: String?
@@ -12,7 +14,7 @@ struct AllocationPieChart: View {
var body: some View {
VStack(alignment: .leading, spacing: 16) {
Text("Asset Allocation")
Text(title)
.font(.headline)
if !data.isEmpty {
@@ -98,7 +100,9 @@ struct AllocationPieChart: View {
.frame(maxWidth: .infinity, alignment: .leading)
}
AllocationTargetsComparisonChart(data: data)
if showsTargetsComparison {
AllocationTargetsComparisonChart(data: data)
}
} else {
Text("No allocation data available")
.foregroundColor(.secondary)
@@ -23,7 +23,6 @@ struct ChartsContainerView: View {
// Time Range Selector
if viewModel.selectedChartType != .allocation &&
viewModel.selectedChartType != .performance &&
viewModel.selectedChartType != .riskReturn {
timeRangeSelector
}
@@ -34,6 +33,17 @@ struct ChartsContainerView: View {
categoryFilter
}
// Source Filter
if viewModel.selectedChartType == .evolution {
sourceFilter
}
// Breakdown Selector
if viewModel.selectedChartType == .allocation ||
viewModel.selectedChartType == .performance {
breakdownSelector
}
// Chart Content
chartContent
}
@@ -101,7 +111,7 @@ struct ChartsContainerView: View {
private var timeRangeSelector: some View {
HStack(spacing: 8) {
ForEach(ChartsViewModel.TimeRange.allCases) { range in
ForEach(viewModel.availableTimeRanges(for: viewModel.selectedChartType)) { range in
Button {
viewModel.selectedTimeRange = range
} label: {
@@ -158,6 +168,7 @@ struct ChartsContainerView: View {
ForEach(availableCategories) { category in
Button {
viewModel.selectedCategory = category
viewModel.selectedSource = nil
} label: {
HStack(spacing: 4) {
Circle()
@@ -186,6 +197,91 @@ struct ChartsContainerView: View {
}
}
// MARK: - Source Filter
@ViewBuilder
private var sourceFilter: some View {
let availableSources = viewModel.availableSources(for: viewModel.selectedChartType)
if !availableSources.isEmpty {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 8) {
if availableSources.count > 1 {
Button {
viewModel.selectedSource = nil
} label: {
Text("All Sources")
.font(.caption.weight(.medium))
.padding(.horizontal, 12)
.padding(.vertical, 6)
.background(
viewModel.selectedSource == nil
? Color.appPrimary
: Color.gray.opacity(0.1)
)
.foregroundColor(
viewModel.selectedSource == nil
? .white
: .primary
)
.cornerRadius(16)
}
}
ForEach(availableSources, id: \.id) { source in
Button {
viewModel.selectedSource = source
viewModel.selectedCategory = nil
} label: {
Text(source.name)
.font(.caption.weight(.medium))
.padding(.horizontal, 12)
.padding(.vertical, 6)
.background(
viewModel.selectedSource?.id == source.id
? Color.appPrimary
: Color.gray.opacity(0.1)
)
.foregroundColor(
viewModel.selectedSource?.id == source.id
? .white
: .primary
)
.cornerRadius(16)
}
}
}
}
}
}
// MARK: - Breakdown Selector
private var breakdownSelector: some View {
HStack(spacing: 8) {
ForEach(ChartsViewModel.BreakdownMode.allCases) { mode in
Button {
viewModel.selectedBreakdown = mode
} label: {
Text(mode.rawValue)
.font(.subheadline.weight(.medium))
.padding(.horizontal, 14)
.padding(.vertical, 8)
.background(
viewModel.selectedBreakdown == mode
? Color.appPrimary
: Color.gray.opacity(0.1)
)
.foregroundColor(
viewModel.selectedBreakdown == mode
? .white
: .primary
)
.cornerRadius(18)
}
}
}
}
// MARK: - Chart Content
@ViewBuilder
@@ -204,9 +300,16 @@ struct ChartsContainerView: View {
goals: goalsViewModel.goals
)
case .allocation:
AllocationPieChart(data: viewModel.allocationData)
AllocationPieChart(
data: viewModel.allocationData,
title: viewModel.selectedBreakdown == .source ? "Allocation by Source" : "Asset Allocation",
showsTargetsComparison: ChartsViewModel.supportsAllocationTargets(for: viewModel.selectedBreakdown)
)
case .performance:
PerformanceBarChart(data: viewModel.performanceData)
PerformanceBarChart(
data: viewModel.performanceData,
title: viewModel.selectedBreakdown == .source ? "Performance by Source" : "Performance by Category"
)
case .contributions:
ContributionsChartView(data: viewModel.contributionsData)
case .rollingReturn:
@@ -3,10 +3,11 @@ import Charts
struct PerformanceBarChart: View {
let data: [(category: String, cagr: Double, color: String)]
var title: String = "Performance by Category"
var body: some View {
VStack(alignment: .leading, spacing: 16) {
Text("Performance by Category")
Text(title)
.font(.headline)
Text("Compound Annual Growth Rate (CAGR)")