Ausgaben-Kategorien dynamisch vom Backend
ExpenseCategory DTO + GET /api/expenses/categories beim Öffnen der Liste sowie bei Refresh. Falls der Endpunkt noch nicht ausgerollt ist (oder fehlschlägt), Fallback auf eine lokale Default-Liste mit den aktuellen sechs Kategorien. AddExpenseSheet bekommt die Kategorien als Parameter, statt eigene Liste zu führen — Source of Truth ist jetzt das Backend.
This commit is contained in:
parent
c03f018c0c
commit
cf625f3391
2 changed files with 47 additions and 15 deletions
|
|
@ -109,6 +109,12 @@ struct ExpenseCreateBody: Encodable {
|
||||||
let notiz: String?
|
let notiz: String?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ExpenseCategory: Decodable, Identifiable {
|
||||||
|
let id: String
|
||||||
|
let label: String
|
||||||
|
let color: String?
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Gassi-Zeiten
|
// MARK: - Gassi-Zeiten
|
||||||
|
|
||||||
struct GassiZeit: Decodable, Identifiable {
|
struct GassiZeit: Decodable, Identifiable {
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,22 @@ import SwiftUI
|
||||||
|
|
||||||
struct AusgabenView: View {
|
struct AusgabenView: View {
|
||||||
@State private var expenses: [Expense] = []
|
@State private var expenses: [Expense] = []
|
||||||
|
@State private var categories: [ExpenseCategory] = []
|
||||||
@State private var isLoading = false
|
@State private var isLoading = false
|
||||||
@State private var errorMessage: String?
|
@State private var errorMessage: String?
|
||||||
@State private var showAdd = false
|
@State private var showAdd = false
|
||||||
|
|
||||||
|
/// Lokale Fallback-Liste, falls der Backend-Endpunkt /api/expenses/categories
|
||||||
|
/// noch nicht ausgerollt ist. Sollte im Normalfall überschrieben werden.
|
||||||
|
private static let defaultCategories: [ExpenseCategory] = [
|
||||||
|
ExpenseCategory(id: "futter", label: "Futter", color: nil),
|
||||||
|
ExpenseCategory(id: "tierarzt", label: "Tierarzt", color: nil),
|
||||||
|
ExpenseCategory(id: "zubehoer", label: "Zubehör", color: nil),
|
||||||
|
ExpenseCategory(id: "versicherung", label: "Versicherung", color: nil),
|
||||||
|
ExpenseCategory(id: "sitter", label: "Sitter", color: nil),
|
||||||
|
ExpenseCategory(id: "sonstiges", label: "Sonstiges", color: nil)
|
||||||
|
]
|
||||||
|
|
||||||
private static let dateFormatter: DateFormatter = {
|
private static let dateFormatter: DateFormatter = {
|
||||||
let f = DateFormatter()
|
let f = DateFormatter()
|
||||||
f.locale = Locale(identifier: "de_DE")
|
f.locale = Locale(identifier: "de_DE")
|
||||||
|
|
@ -30,10 +42,27 @@ struct AusgabenView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $showAdd) {
|
.sheet(isPresented: $showAdd) {
|
||||||
AddExpenseSheet { Task { await load() } }
|
AddExpenseSheet(categories: categories.isEmpty ? Self.defaultCategories : categories) {
|
||||||
|
Task { await load() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.task {
|
||||||
|
await loadCategories()
|
||||||
|
await load()
|
||||||
|
}
|
||||||
|
.refreshable {
|
||||||
|
await loadCategories()
|
||||||
|
await load()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func loadCategories() async {
|
||||||
|
if let fetched: [ExpenseCategory] = try? await APIClient.shared.get("/api/expenses/categories"),
|
||||||
|
!fetched.isEmpty {
|
||||||
|
categories = fetched
|
||||||
|
} else if categories.isEmpty {
|
||||||
|
categories = Self.defaultCategories
|
||||||
}
|
}
|
||||||
.task { await load() }
|
|
||||||
.refreshable { await load() }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
|
|
@ -139,9 +168,10 @@ private struct ExpenseRow: View {
|
||||||
|
|
||||||
private struct AddExpenseSheet: View {
|
private struct AddExpenseSheet: View {
|
||||||
@Environment(\.dismiss) private var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
let categories: [ExpenseCategory]
|
||||||
let onSaved: () -> Void
|
let onSaved: () -> Void
|
||||||
|
|
||||||
@State private var kategorie = "futter"
|
@State private var kategorie: String
|
||||||
@State private var betrag = ""
|
@State private var betrag = ""
|
||||||
@State private var date = Date()
|
@State private var date = Date()
|
||||||
@State private var notiz = ""
|
@State private var notiz = ""
|
||||||
|
|
@ -150,23 +180,19 @@ private struct AddExpenseSheet: View {
|
||||||
@State private var isSaving = false
|
@State private var isSaving = false
|
||||||
@State private var errorMessage: String?
|
@State private var errorMessage: String?
|
||||||
|
|
||||||
/// Backend-Whitelist: tierarzt, futter, zubehoer, versicherung, sitter, sonstiges
|
init(categories: [ExpenseCategory], onSaved: @escaping () -> Void) {
|
||||||
private let kategorien: [(key: String, label: String)] = [
|
self.categories = categories
|
||||||
("futter", "Futter"),
|
self.onSaved = onSaved
|
||||||
("tierarzt", "Tierarzt"),
|
_kategorie = State(initialValue: categories.first?.id ?? "sonstiges")
|
||||||
("zubehoer", "Zubehör"),
|
}
|
||||||
("versicherung", "Versicherung"),
|
|
||||||
("sitter", "Sitter"),
|
|
||||||
("sonstiges", "Sonstiges")
|
|
||||||
]
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
Form {
|
Form {
|
||||||
Section("Kategorie") {
|
Section("Kategorie") {
|
||||||
Picker("Kategorie", selection: $kategorie) {
|
Picker("Kategorie", selection: $kategorie) {
|
||||||
ForEach(kategorien, id: \.key) { entry in
|
ForEach(categories) { entry in
|
||||||
Text(entry.label).tag(entry.key)
|
Text(entry.label).tag(entry.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue