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? @State private var showAchievedGoals = false var body: some View { NavigationStack { ZStack { AppBackground() List { if filteredGoals.isEmpty { if viewModel.goals.isEmpty { emptyState } else { hiddenAchievedState } } else { Section { ForEach(filteredGoals) { goal in GoalRowView( goal: goal, progress: viewModel.progress(for: goal), totalValue: viewModel.totalValue(for: goal), paceStatus: viewModel.paceStatus(for: goal), estimatedCompletionDate: viewModel.estimateCompletionDate(for: goal), onEdit: { 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: .navigationBarLeading) { Button(showAchievedGoals ? "Hide Achieved" : "Show Achieved") { showAchievedGoals.toggle() } .font(.caption.weight(.semibold)) } 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 filteredGoals: [Goal] { guard !showAchievedGoals else { return viewModel.goals } return viewModel.goals.filter { !viewModel.isAchieved($0) } } 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) } private var hiddenAchievedState: some View { VStack(spacing: 12) { Image(systemName: "party.popper") .font(.system(size: 40)) .foregroundColor(.appSecondary) Text("All visible goals are achieved") .font(.headline) Text("Tap \"Show Achieved\" to review completed goals.") .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? let estimatedCompletionDate: Date? let onEdit: () -> Void @State private var showingShareOptions = false private var isAchieved: Bool { GoalsViewModel.isAchieved(progress: progress) } private var targetUrgency: GoalUrgencyLevel { GoalsViewModel.urgencyLevel( targetDate: goal.targetDate, isBehind: paceStatus?.isBehind ?? false, isAchieved: isAchieved ) } private var targetDateColor: Color { switch targetUrgency { case .normal: return .secondary case .warning: return .appWarning case .critical: return .negativeRed } } var body: some View { ZStack(alignment: .topTrailing) { Button(action: onEdit) { VStack(alignment: .leading, spacing: 12) { HStack { Text(goal.name) .font(.headline) .foregroundColor(isAchieved ? .appSecondary : .primary) if isAchieved { Text("Achieved") .font(.caption2.weight(.bold)) .foregroundColor(.white) .padding(.horizontal, 8) .padding(.vertical, 4) .background(Color.appSecondary) .clipShape(Capsule()) } } GoalProgressBar( progress: progress, tint: isAchieved ? .appSuccess : .appSecondary, iconColor: isAchieved ? .appSuccess : .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(targetDateColor) } if let paceStatus { Text(paceStatus.statusText) .font(.caption.weight(.semibold)) .foregroundColor( isAchieved ? .appSuccess : (paceStatus.isBehind ? .negativeRed : .positiveGreen) ) } } .frame(maxWidth: .infinity, alignment: .leading) .padding(12) .background(isAchieved ? Color.appSuccess.opacity(0.10) : Color.clear) .overlay( RoundedRectangle(cornerRadius: 12) .stroke(isAchieved ? Color.appSuccess.opacity(0.35) : Color.clear, lineWidth: 1) ) .cornerRadius(12) } .buttonStyle(.plain) Button { showingShareOptions = true } label: { Image(systemName: "square.and.arrow.up") .foregroundColor(.appPrimary) .padding(.top, 2) } .buttonStyle(.borderless) } .padding(.vertical, 8) .confirmationDialog("Share Goal", isPresented: $showingShareOptions, titleVisibility: .visible) { Button("Share with amounts") { shareGoal(privacyMode: false) } Button("Share (privacy mode)") { shareGoal(privacyMode: true) } Button("Cancel", role: .cancel) {} } message: { Text("Choose how to share your goal progress") } } private func shareGoal(privacyMode: Bool) { GoalShareService.shared.shareGoal( name: goal.name, progress: progress, currentValue: totalValue, targetValue: goal.targetDecimal, targetDate: goal.targetDate, estimatedCompletionDate: estimatedCompletionDate, privacyMode: privacyMode ) } } #Preview { GoalsView() .environmentObject(AccountStore(iapService: IAPService())) }