import Foundation // Die Karten des Dashboards. Anders als im Web ist die Auswahl hier fest – // ein verschiebbares Raster auf Telefonbreite wäre Arbeit ohne Gewinn. Die // vier Endpunkte sind dieselben, die das Web benutzt. /// `GET /api/dashboard/balances` struct Kontostaende: Codable, Sendable, Hashable { let accounts: [Kontozeile] /// Verfügbar, ohne gebundene und archivierte Konten. let total: Int /// Was auf gebundenen Konten liegt – Geld, an das man nicht herankommt. let gebunden: Int let currency: String var summe: Geldbetrag { Geldbetrag(cents: total, waehrung: currency) } var gebundenBetrag: Geldbetrag { Geldbetrag(cents: gebunden, waehrung: currency) } struct Kontozeile: Codable, Sendable, Identifiable, Hashable { let id: Int let name: String let icon: String let color: String let currency: String let balanceCents: Int let balanceBaseCents: Int let archived: Bool let locked: Bool private enum CodingKeys: String, CodingKey { case id, name, icon, color, currency, archived, locked case balanceCents = "balance_cents" case balanceBaseCents = "balance_base_cents" } var saldo: Geldbetrag { Geldbetrag(cents: balanceCents, waehrung: currency) } } } /// `GET /api/dashboard/budget-progress` struct Budgetfortschritt: Codable, Sendable, Hashable { let rows: [Zeile] let currency: String let label: String let groupBy: String private enum CodingKeys: String, CodingKey { case rows, currency, label case groupBy = "group_by" } struct Zeile: Codable, Sendable, Identifiable, Hashable { let name: String let color: String let planned: Int let actual: Int let over: Bool /// Sparen: mehr als geplant ist hier keine Warnung. let good: Bool var id: String { name } var anteil: Double { guard planned > 0 else { return 0 } return min(1, max(0, Double(actual) / Double(planned))) } } } /// `GET /api/dashboard/income-expense` struct EinnahmenAusgaben: Codable, Sendable, Hashable { let income: Int let expense: Int let net: Int let currency: String let label: String var einnahmen: Geldbetrag { Geldbetrag(cents: income, waehrung: currency) } var ausgaben: Geldbetrag { Geldbetrag(cents: expense, waehrung: currency) } var uebrig: Geldbetrag { Geldbetrag(cents: net, waehrung: currency) } } /// `GET /api/dashboard/recent` struct LetzteBuchungen: Codable, Sendable, Hashable { let transactions: [Zeile] let currency: String struct Zeile: Codable, Sendable, Identifiable, Hashable { let id: Int let date: Tagesdatum let type: Buchungsart /// Schon fertig formuliert: die Kategorie, sonst der Empfänger, bei /// einem Übertrag „Konto A → Konto B". let title: String let icon: String let color: String /// `"pos"`, `"neg"` oder nichts – bei einem Übertrag gibt es keine /// Richtung, weil kein Geld den Haushalt verlässt. let sign: String? let amountCents: Int let currency: String let amountBaseCents: Int private enum CodingKeys: String, CodingKey { case id, date, type, title, icon, color, sign, currency case amountCents = "amount_cents" case amountBaseCents = "amount_base_cents" } var betrag: Geldbetrag { Geldbetrag(cents: amountCents, waehrung: currency) } /// Grün bei Einnahme, rot bei Ausgabe, neutral beim Übertrag. var vorzeichen: Vorzeichen { switch sign { case "pos": return .plus case "neg": return .minus default: return .keines } } enum Vorzeichen: Sendable { case plus, minus, keines } } }