Goals por categoría: ámbito de categoría en objetivos (build 88) #46
Un Goal ya podía acotarse por cuenta; ahora también por categoría de fuente, de modo que se puede fijar "100.000 € en Cash" y que el progreso cuente solo las fuentes de esa categoría. - CoreData: relación Goal.category ↔ Category.goals (opcional, Nullify) - Goal.includes(_:)/relevantSources(from:)/scopeKey centralizan el filtro de ámbito (cuenta Y categoría) que antes estaba duplicado en GoalsViewModel, AddSourceView y NotificationService - GoalEditorView: sección "Ámbito" con selector de categoría - Chip de categoría en la lista de Goals y en la tarjeta del Dashboard - ETA del Dashboard para goals con ámbito: usa la proyección del propio goal en vez de la evolución global de la cartera (que daría "objetivo alcanzado" en cuanto la cartera total superara el importe) - GoalRepository e ImportService deduplican por (nombre, cuenta, categoría) - Export/Import de goals incluye "category" - Nuevas claves localizadas en los 7 idiomas Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YcDn5ccuRFV83q7xWWokBT
This commit is contained in:
@@ -7,6 +7,9 @@ struct GoalEditorView: View {
|
||||
@State private var targetDate = Date()
|
||||
@State private var includeTargetDate = false
|
||||
@State private var didLoadGoal = false
|
||||
@State private var selectedCategoryId: UUID?
|
||||
|
||||
@StateObject private var categoryRepository = CategoryRepository()
|
||||
|
||||
let account: Account?
|
||||
let goal: Goal?
|
||||
@@ -51,6 +54,22 @@ struct GoalEditorView: View {
|
||||
} header: {
|
||||
Text("Goal Details")
|
||||
}
|
||||
|
||||
Section {
|
||||
Picker(String(localized: "goal_scope_category"), selection: $selectedCategoryId) {
|
||||
Text("goal_scope_all_categories").tag(UUID?.none)
|
||||
ForEach(categoryRepository.categories) { category in
|
||||
Label(category.name, systemImage: category.icon)
|
||||
.tag(UUID?.some(category.id))
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
Text("goal_scope_section")
|
||||
} footer: {
|
||||
Text(selectedCategoryId == nil
|
||||
? String(localized: "goal_scope_footer_all")
|
||||
: String(localized: "goal_scope_footer_category"))
|
||||
}
|
||||
}
|
||||
.navigationTitle(goal == nil ? "New Goal" : "Edit Goal")
|
||||
.toolbar {
|
||||
@@ -75,6 +94,7 @@ struct GoalEditorView: View {
|
||||
includeTargetDate = true
|
||||
targetDate = target
|
||||
}
|
||||
selectedCategoryId = goal.category?.safeId
|
||||
didLoadGoal = true
|
||||
}
|
||||
}
|
||||
@@ -83,22 +103,31 @@ struct GoalEditorView: View {
|
||||
!name.trimmingCharacters(in: .whitespaces).isEmpty && parseDecimal(targetAmount) != nil
|
||||
}
|
||||
|
||||
private var selectedCategory: Category? {
|
||||
guard let selectedCategoryId else { return nil }
|
||||
return categoryRepository.categories.first { $0.safeId == selectedCategoryId }
|
||||
}
|
||||
|
||||
private func saveGoal() {
|
||||
guard let value = parseDecimal(targetAmount) else { return }
|
||||
let category = selectedCategory
|
||||
if let goal {
|
||||
goalRepository.updateGoal(
|
||||
goal,
|
||||
name: name,
|
||||
targetAmount: value,
|
||||
targetDate: includeTargetDate ? targetDate : nil,
|
||||
clearTargetDate: !includeTargetDate
|
||||
clearTargetDate: !includeTargetDate,
|
||||
category: category,
|
||||
clearCategory: category == nil
|
||||
)
|
||||
} else {
|
||||
goalRepository.createGoal(
|
||||
name: name,
|
||||
targetAmount: value,
|
||||
targetDate: includeTargetDate ? targetDate : nil,
|
||||
account: account
|
||||
account: account,
|
||||
category: category
|
||||
)
|
||||
}
|
||||
dismiss()
|
||||
|
||||
Reference in New Issue
Block a user