@@ -105,6 +105,79 @@ final class DailyGoalStore: ObservableObject {
105105 }
106106 }
107107
108+ @discardableResult
109+ func addGoal(
110+ title: String ,
111+ tag: String ,
112+ note: String ,
113+ in horizon: GoalHorizon
114+ ) throws -> DailyGoal {
115+ let normalizedTitle = title. trimmingCharacters ( in: . whitespacesAndNewlines)
116+ guard !normalizedTitle. isEmpty else {
117+ throw GoalStoreMutationError . emptyTitle
118+ }
119+ let goal = DailyGoal (
120+ id: UUID ( ) ,
121+ title: normalizedTitle,
122+ isDone: false ,
123+ tag: GoalTagLogic . resolve ( tag, title: normalizedTitle) . rawValue,
124+ note: note
125+ )
126+ mutate ( horizon) { $0. append ( goal) }
127+ return goal
128+ }
129+
130+ func updateGoal(
131+ id: UUID ,
132+ title: String ? ,
133+ tag: String ? ,
134+ note: String ? ,
135+ in horizon: GoalHorizon
136+ ) throws {
137+ var next = state
138+ guard let index = next [ horizon] . firstIndex ( where: { $0. id == id } ) else {
139+ throw GoalStoreMutationError . goalNotFound
140+ }
141+ if let title {
142+ let normalizedTitle = title. trimmingCharacters ( in: . whitespacesAndNewlines)
143+ guard !normalizedTitle. isEmpty else {
144+ throw GoalStoreMutationError . emptyTitle
145+ }
146+ next [ horizon] [ index] . title = normalizedTitle
147+ }
148+ if let tag {
149+ next [ horizon] [ index] . tag = GoalTagLogic . resolve (
150+ tag,
151+ title: next [ horizon] [ index] . title
152+ ) . rawValue
153+ }
154+ if let note {
155+ next [ horizon] [ index] . note = note
156+ }
157+ state = next
158+ if horizon == . today { recordToday ( ) }
159+ }
160+
161+ func setGoalCompleted( id: UUID , isDone: Bool , in horizon: GoalHorizon ) throws {
162+ var next = state
163+ guard let index = next [ horizon] . firstIndex ( where: { $0. id == id } ) else {
164+ throw GoalStoreMutationError . goalNotFound
165+ }
166+ next [ horizon] [ index] . isDone = isDone
167+ state = next
168+ if horizon == . today { recordToday ( ) }
169+ }
170+
171+ func deleteGoal( id: UUID , in horizon: GoalHorizon ) throws {
172+ var next = state
173+ guard next [ horizon] . contains ( where: { $0. id == id } ) else {
174+ throw GoalStoreMutationError . goalNotFound
175+ }
176+ next [ horizon] . removeAll { $0. id == id }
177+ state = next
178+ if horizon == . today { recordToday ( ) }
179+ }
180+
108181 func move( _ goal: DailyGoal , in horizon: GoalHorizon , offset: Int ) {
109182 mutate ( horizon) { goals in
110183 guard let source = goals. firstIndex ( where: { $0. id == goal. id } ) else { return }
@@ -209,3 +282,8 @@ final class DailyGoalStore: ObservableObject {
209282 }
210283
211284}
285+
286+ enum GoalStoreMutationError : Error {
287+ case goalNotFound
288+ case emptyTitle
289+ }
0 commit comments