From 19ca2a6887621bd1651a3f3d59d8a6fdb9c5399c Mon Sep 17 00:00:00 2001 From: Nik Shevchenko Date: Tue, 17 Mar 2026 17:24:48 -0400 Subject: [PATCH] Fix macOS goal editing updates --- desktop/Desktop/Sources/APIClient.swift | 29 +++++++++++++++++- .../MainWindow/Components/GoalsWidget.swift | 4 ++- .../MainWindow/Pages/DashboardPage.swift | 30 +++++++++++++++++++ 3 files changed, 61 insertions(+), 2 deletions(-) diff --git a/desktop/Desktop/Sources/APIClient.swift b/desktop/Desktop/Sources/APIClient.swift index b034f758e6e..22179bb2dcc 100644 --- a/desktop/Desktop/Sources/APIClient.swift +++ b/desktop/Desktop/Sources/APIClient.swift @@ -2030,7 +2030,34 @@ extension APIClient { throw APIError.httpError(statusCode: (response as? HTTPURLResponse)?.statusCode ?? 0) } - return try decoder.decode(Goal.self, from: data) + let goal = try decoder.decode(Goal.self, from: data) + goalsCache = nil + return goal + } + + /// Updates editable goal fields. + func updateGoal(goalId: String, title: String, currentValue: Double, targetValue: Double) async throws -> Goal { + struct UpdateGoalRequest: Encodable { + let title: String + let currentValue: Double + let targetValue: Double + + enum CodingKeys: String, CodingKey { + case title + case currentValue = "current_value" + case targetValue = "target_value" + } + } + + let request = UpdateGoalRequest( + title: title, + currentValue: currentValue, + targetValue: targetValue + ) + + let goal: Goal = try await patch("v1/goals/\(goalId)", body: request) + goalsCache = nil + return goal } /// Gets completed goals for history diff --git a/desktop/Desktop/Sources/MainWindow/Components/GoalsWidget.swift b/desktop/Desktop/Sources/MainWindow/Components/GoalsWidget.swift index f1cca1a6231..d75b00c0c5c 100644 --- a/desktop/Desktop/Sources/MainWindow/Components/GoalsWidget.swift +++ b/desktop/Desktop/Sources/MainWindow/Components/GoalsWidget.swift @@ -5,6 +5,7 @@ import SwiftUI struct GoalsWidget: View { let goals: [Goal] let onCreateGoal: (String, Double, Double) -> Void // (title, currentValue, targetValue) + let onUpdateGoal: (Goal, String, Double, Double) -> Void let onUpdateProgress: (Goal, Double) -> Void let onDeleteGoal: (Goal) -> Void @@ -134,7 +135,7 @@ struct GoalsWidget: View { GoalEditSheet( goal: goal, onSave: { title, current, target in - onUpdateProgress(goal, current) + onUpdateGoal(goal, title, current, target) }, onDelete: { onDeleteGoal(goal) @@ -963,6 +964,7 @@ private struct GoalHeaderButton: View { GoalsWidget( goals: [], onCreateGoal: { _, _, _ in }, + onUpdateGoal: { _, _, _, _ in }, onUpdateProgress: { _, _ in }, onDeleteGoal: { _ in } ) diff --git a/desktop/Desktop/Sources/MainWindow/Pages/DashboardPage.swift b/desktop/Desktop/Sources/MainWindow/Pages/DashboardPage.swift index cdc2dc19983..1b04e36c1ec 100644 --- a/desktop/Desktop/Sources/MainWindow/Pages/DashboardPage.swift +++ b/desktop/Desktop/Sources/MainWindow/Pages/DashboardPage.swift @@ -158,6 +158,26 @@ class DashboardViewModel: ObservableObject { } } + func updateGoal(_ goal: Goal, title: String, currentValue: Double, targetValue: Double) async { + log("Goals: Updating goal '\(goal.title)' -> title='\(title)', current=\(currentValue), target=\(targetValue)") + + do { + let updated = try await APIClient.shared.updateGoal( + goalId: goal.id, + title: title, + currentValue: currentValue, + targetValue: targetValue + ) + + _ = try? await GoalStorage.shared.syncServerGoal(updated) + goals = try await GoalStorage.shared.getLocalGoals() + log("Goals: Updated goal '\(updated.title)' confirmed by API") + } catch { + logError("Failed to update goal", error: error) + goals = (try? await GoalStorage.shared.getLocalGoals()) ?? goals + } + } + func deleteGoal(_ goal: Goal) async { do { // Soft-delete locally first for instant UI update @@ -230,6 +250,16 @@ struct DashboardPage: View { ) } }, + onUpdateGoal: { goal, title, current, target in + Task { + await viewModel.updateGoal( + goal, + title: title, + currentValue: current, + targetValue: target + ) + } + }, onUpdateProgress: { goal, value in Task { await viewModel.updateGoalProgress(goal, currentValue: value)