Remove Add Transaction feature and clean all related code

Deleted files:
- AddTransactionView.swift
- TransactionRepository.swift
- Transaction+CoreDataClass.swift

Cleaned files:
- SourceDetailViewModel: removed transactions published property,
  showingAddTransaction flag, transactionRepository dependency,
  addTransaction() and deleteTransaction() methods
- SourceDetailView: removed transactionsSection, TransactionRow struct,
  Add Transaction button from quickActions, sheet presentation
- InvestmentSource+CoreDataClass: removed transactions NSManaged property,
  transactionsArray, totalInvested, totalDividends, totalFees computed
  properties, and all transaction Core Data accessors
- SettingsViewModel: removed "Transaction" from resetAllData entity list
- SampleDataService: removed transactionRepository and sample transaction

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
alexandrev-tibco
2026-02-20 23:04:03 +01:00
parent b17d8866a4
commit 7cb5f92cf4
20 changed files with 215 additions and 503 deletions
@@ -22,28 +22,8 @@ struct ChartsContainerView: View {
// Chart Type Selector
chartTypeSelector
// Time Range Selector
if viewModel.selectedChartType != .allocation &&
viewModel.selectedChartType != .riskReturn {
timeRangeSelector
}
// Category Filter
if viewModel.selectedChartType == .evolution ||
viewModel.selectedChartType == .prediction {
categoryFilter
}
// Source Filter
if viewModel.selectedChartType == .evolution {
sourceFilter
}
// Breakdown Selector
if viewModel.selectedChartType == .allocation ||
viewModel.selectedChartType == .performance {
breakdownSelector
}
// Unified Filters
filtersSection
// Chart Content
chartContent
@@ -108,6 +88,74 @@ struct ChartsContainerView: View {
}
}
// MARK: - Unified Filters Section
private var hasAnyFilter: Bool {
let chartType = viewModel.selectedChartType
let hasTimeRange = chartType != .allocation && chartType != .riskReturn
let hasCategories = !viewModel.availableCategories(for: chartType).isEmpty
let hasSources = !viewModel.availableSources(for: chartType).isEmpty
let hasBreakdown = chartType == .allocation || chartType == .performance
return hasTimeRange || hasCategories || hasSources || hasBreakdown
}
@ViewBuilder
private var filtersSection: some View {
if hasAnyFilter {
VStack(alignment: .leading, spacing: 10) {
let chartType = viewModel.selectedChartType
// Time Range
if chartType != .allocation && chartType != .riskReturn {
filterRow(icon: "calendar", label: "Period") {
timeRangeSelector
}
}
// Breakdown (category vs source)
if chartType == .allocation || chartType == .performance {
filterRow(icon: "square.grid.2x2", label: "Group") {
breakdownSelector
}
}
// Category
let availableCategories = viewModel.availableCategories(for: chartType)
if !availableCategories.isEmpty {
filterRow(icon: "tag", label: "Category") {
categoryFilter
}
}
// Source
let availableSources = viewModel.availableSources(for: chartType)
if !availableSources.isEmpty {
filterRow(icon: "building.2", label: "Source") {
sourceFilter
}
}
}
.padding(12)
.background(Color(.systemBackground))
.cornerRadius(AppConstants.UI.cornerRadius)
.shadow(color: .black.opacity(0.05), radius: 4, y: 1)
}
}
private func filterRow<Content: View>(icon: String, label: String, @ViewBuilder content: () -> Content) -> some View {
VStack(alignment: .leading, spacing: 6) {
HStack(spacing: 4) {
Image(systemName: icon)
.font(.caption2)
.foregroundColor(.secondary)
Text(label)
.font(.caption.weight(.medium))
.foregroundColor(.secondary)
}
content()
}
}
// MARK: - Time Range Selector
private var timeRangeSelector: some View {
@@ -229,26 +277,31 @@ struct ChartsContainerView: View {
}
}
ForEach(availableSources, id: \.id) { source in
let sourceId = source.id ?? UUID()
ForEach(Array(availableSources.enumerated()), id: \.element.id) { index, source in
let sourceId = source.id
let isSelected = viewModel.selectedSourceIds.contains(sourceId)
let sourceColor = Color.sourceColor(at: index)
Button {
viewModel.selectedSource = nil
viewModel.selectedCategory = nil
if isSelected {
viewModel.selectedSourceIds.remove(sourceId)
} else {
viewModel.selectedSourceIds.insert(sourceId)
}
} label: {
Text(source.name)
.font(.caption.weight(.medium))
.padding(.horizontal, 12)
.padding(.vertical, 6)
.background(
isSelected
? Color.appPrimary
: Color.gray.opacity(0.1)
HStack(spacing: 4) {
Circle()
.fill(sourceColor)
.frame(width: 8, height: 8)
Text(source.name)
}
.font(.caption.weight(.medium))
.padding(.horizontal, 12)
.padding(.vertical, 6)
.background(
isSelected
? sourceColor
: Color.gray.opacity(0.1)
)
.foregroundColor(
isSelected
@@ -975,15 +1028,36 @@ struct AllocationEvolutionChart: View {
.frame(height: 260)
}
/// Categories in stable order (preserving the ViewModel's sort: largest overall first)
private var stableCategoryNames: [String] {
var seen = Set<String>()
var ordered: [String] = []
for item in data {
if seen.insert(item.category).inserted {
ordered.append(item.category)
}
}
return ordered
}
private var stableCategoryColors: [Color] {
stableCategoryNames.map { name in
if let hex = data.first(where: { $0.category == name })?.color {
return Color(hex: hex) ?? .gray
}
return .gray
}
}
private var chartView: some View {
Chart(identifiableData) { item in
AreaMark(
x: .value("Date", item.date),
BarMark(
x: .value("Date", item.date, unit: .month),
y: .value("Percentage", item.percentage)
)
.foregroundStyle(by: .value("Category", item.category))
}
.chartForegroundStyleScale(domain: categoryNames, range: categoryColors)
.chartForegroundStyleScale(domain: stableCategoryNames, range: stableCategoryColors)
.chartXAxis {
AxisMarks(values: .stride(by: .month, count: 2)) { _ in
AxisValueLabel(format: .dateTime.month(.abbreviated).year(.twoDigits))
@@ -1002,19 +1076,6 @@ struct AllocationEvolutionChart: View {
.frame(height: 260)
.drawingGroup()
}
private var categoryNames: [String] {
Array(Set(data.map { $0.category })).sorted()
}
private var categoryColors: [Color] {
categoryNames.map { name in
if let hex = data.first(where: { $0.category == name })?.color {
return Color(hex: hex) ?? .gray
}
return .gray
}
}
}
#Preview {