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