InvestmentTrackerApp/PortfolioJournal/Views/Goals/GoalsView.swift

156 lines
5.7 KiB
Swift

import SwiftUI
struct GoalsView: View {
@EnvironmentObject private var accountStore: AccountStore
@StateObject private var viewModel = GoalsViewModel()
@State private var showingAddGoal = false
@State private var editingGoal: Goal?
var body: some View {
NavigationStack {
ZStack {
AppBackground()
List {
if viewModel.goals.isEmpty {
emptyState
} else {
Section {
ForEach(viewModel.goals) { goal in
GoalRowView(
goal: goal,
progress: viewModel.progress(for: goal),
totalValue: viewModel.totalValue(for: goal),
paceStatus: viewModel.paceStatus(for: goal)
)
.contentShape(Rectangle())
.onTapGesture {
editingGoal = goal
}
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
Button(role: .destructive) {
viewModel.deleteGoal(goal)
} label: {
Label("Delete", systemImage: "trash")
}
}
.swipeActions(edge: .leading, allowsFullSwipe: false) {
Button {
editingGoal = goal
} label: {
Label("Edit", systemImage: "pencil")
}
.tint(.appSecondary)
}
}
}
}
}
.scrollContentBackground(.hidden)
}
.navigationTitle("Goals")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
showingAddGoal = true
} label: {
Image(systemName: "plus")
}
}
}
.sheet(isPresented: $showingAddGoal) {
GoalEditorView(account: accountStore.showAllAccounts ? nil : accountStore.selectedAccount)
}
.sheet(item: $editingGoal) { goal in
GoalEditorView(account: goal.account, goal: goal)
}
.onAppear {
viewModel.selectedAccount = accountStore.selectedAccount
viewModel.showAllAccounts = accountStore.showAllAccounts
viewModel.refresh()
}
.onReceive(accountStore.$selectedAccount) { account in
viewModel.selectedAccount = account
viewModel.refresh()
}
.onReceive(accountStore.$showAllAccounts) { showAll in
viewModel.showAllAccounts = showAll
viewModel.refresh()
}
}
}
private var emptyState: some View {
VStack(spacing: 16) {
Image(systemName: "target")
.font(.system(size: 48))
.foregroundColor(.secondary)
Text("Set your first goal")
.font(.headline)
Text("Track progress toward milestones like \(AppSettings.getOrCreate(in: CoreDataStack.shared.viewContext).currencySymbol)1M and share your wins.")
.font(.subheadline)
.foregroundColor(.secondary)
.multilineTextAlignment(.center)
}
.frame(maxWidth: .infinity)
.padding(.vertical, 32)
}
}
struct GoalRowView: View {
let goal: Goal
let progress: Double
let totalValue: Decimal
let paceStatus: GoalPaceStatus?
var body: some View {
VStack(alignment: .leading, spacing: 12) {
HStack {
Text(goal.name)
.font(.headline)
Spacer()
Button {
GoalShareService.shared.shareGoal(
name: goal.name,
progress: progress,
currentValue: totalValue,
targetValue: goal.targetDecimal
)
} label: {
Image(systemName: "square.and.arrow.up")
.foregroundColor(.appPrimary)
}
}
GoalProgressBar(progress: progress, tint: .appSecondary, iconColor: .appSecondary)
HStack {
Text(totalValue.currencyString)
.font(.subheadline.weight(.semibold))
Spacer()
Text("of \(goal.targetDecimal.currencyString)")
.font(.subheadline)
.foregroundColor(.secondary)
}
if let targetDate = goal.targetDate {
Text("Target date: \(targetDate.mediumDateString)")
.font(.caption)
.foregroundColor(.secondary)
}
if let paceStatus {
Text(paceStatus.statusText)
.font(.caption.weight(.semibold))
.foregroundColor(paceStatus.isBehind ? .negativeRed : .positiveGreen)
}
}
.padding(.vertical, 8)
}
}
#Preview {
GoalsView()
.environmentObject(AccountStore(iapService: IAPService()))
}