import SwiftUI /// Ein Konto anlegen oder ändern. struct KontoFormular: View { enum Zweck: Equatable, Identifiable { case neu case aendern(Konto) var id: Int { switch self { case .neu: return 0 case .aendern(let konto): return konto.id } } } let zweck: Zweck var fertig: () -> Void @Environment(AppZustand.self) private var zustand @Environment(\.dismiss) private var schliesse @State private var vorgang = Speichervorgang() @State private var name = "" @State private var art: Kontoart = .girokonto @State private var waehrung = "EUR" @State private var anfangssaldoCents = 0 @State private var hatStichtag = false @State private var stichtag = Date() @State private var hatKreditrahmen = false @State private var kreditrahmenCents = 0 @State private var istGebunden = false @State private var istArchiviert = false @State private var symbol = "wallet" @State private var farbe = "#3f51b5" @State private var fragtNachLoeschen = false @State private var vorbelegt = false /// Was das Backend annimmt (`_check_currency`). Mehr braucht ein /// Haushaltsbuch hier nicht; die Hauptwährung steht in den Einstellungen. private static let waehrungen = ["EUR", "CHF", "USD", "GBP"] var body: some View { NavigationStack { Form { Section { TextField(String(localized: "konto.name"), text: $name) Picker(String(localized: "konto.art"), selection: $art) { ForEach(Kontoart.allCases, id: \.self) { eine in Text(eine.name).tag(eine) } } if case .neu = zweck { Picker(String(localized: "konto.waehrung"), selection: $waehrung) { ForEach(Self.waehrungen, id: \.self) { code in Text(code).tag(code) } } } else { // Nachträglich zu wechseln liesse jeden gespeicherten // Betrag etwas anderes bedeuten. Deshalb nur zu sehen. LabeledContent(String(localized: "konto.waehrung"), value: waehrung) } } Section { Betragsfeld(titel: String(localized: "konto.anfangssaldo"), waehrung: waehrung, cents: $anfangssaldoCents) Toggle(String(localized: "konto.stichtagKennen"), isOn: $hatStichtag) if hatStichtag { DatePicker(String(localized: "konto.stichtag"), selection: $stichtag, displayedComponents: .date) } } footer: { Text(String(localized: "konto.anfangssaldoErklaerung")) } if art == .kreditkarte { Section { Toggle(String(localized: "konto.kreditrahmenKennen"), isOn: $hatKreditrahmen) if hatKreditrahmen { Betragsfeld(titel: String(localized: "konto.kreditrahmen"), waehrung: waehrung, cents: $kreditrahmenCents) } } footer: { Text(String(localized: "konto.kreditrahmenErklaerung")) } } Section { Toggle(String(localized: "konto.gebunden"), isOn: $istGebunden) } footer: { Text(String(localized: "konto.gebundenErklaerung")) } Section { SymbolUndFarbeFeld(symbol: $symbol, farbe: $farbe) } if case .aendern = zweck { Section { Toggle(String(localized: "konto.archiviert"), isOn: $istArchiviert) Button(String(localized: "konto.loeschen"), role: .destructive) { fragtNachLoeschen = true } } footer: { Text(String(localized: "konto.archivierenStattLoeschen")) } } if let fehler = vorgang.fehler { Section { FehlerZeile(text: fehler) } } } .navigationTitle(titel) .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .cancellationAction) { Button(String(localized: "allgemein.abbrechen")) { schliesse() } } ToolbarItem(placement: .confirmationAction) { Button(String(localized: "allgemein.sichern")) { Task { await sichere() } } .disabled(!istVollstaendig || vorgang.laeuft) } } .interactiveDismissDisabled(vorgang.laeuft) .confirmationDialog(String(localized: "konto.loeschenFrage"), isPresented: $fragtNachLoeschen, titleVisibility: .visible) { Button(String(localized: "konto.loeschen"), role: .destructive) { Task { await loesche() } } Button(String(localized: "allgemein.abbrechen"), role: .cancel) {} } message: { Text(String(localized: "konto.loeschenErklaerung")) } .onAppear(perform: belegeVor) } } private var titel: String { switch zweck { case .neu: return String(localized: "konto.neu") case .aendern: return String(localized: "konto.aendern") } } private var istVollstaendig: Bool { !name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty } private func belegeVor() { guard !vorbelegt else { return } vorbelegt = true guard case .aendern(let konto) = zweck else { waehrung = zustand.einstellungen.basiswaehrung return } name = konto.name art = konto.type waehrung = konto.currency anfangssaldoCents = konto.openingBalanceCents hatStichtag = konto.openingDate != nil stichtag = konto.openingDate?.datum ?? Date() hatKreditrahmen = konto.creditLimitCents != nil kreditrahmenCents = konto.creditLimitCents ?? 0 istGebunden = konto.isLocked istArchiviert = konto.isArchived symbol = konto.icon farbe = konto.color } private func sichere() async { guard let client = zustand.client else { return } let sauberName = name.trimmingCharacters(in: .whitespacesAndNewlines) let datum = hatStichtag ? Tagesdatum(stichtag) : nil // Nur bei Kreditkarten überhaupt ein Rahmen – bei allem anderen wäre // die Zahl bedeutungslos und würde beim Umstellen der Art hängenbleiben. let rahmen = (art == .kreditkarte && hatKreditrahmen) ? kreditrahmenCents : nil let geglueckt = await vorgang.fuehreAus { switch zweck { case .neu: _ = try await client.legeKontoAn(KontoAnlegen( name: sauberName, type: art, currency: waehrung, icon: symbol, color: farbe, openingBalanceCents: anfangssaldoCents, openingDate: datum, creditLimitCents: rahmen, isLocked: istGebunden)) case .aendern(let konto): _ = try await client.aendereKonto(konto.id, KontoAendern( name: sauberName, type: art, icon: symbol, color: farbe, openingBalanceCents: anfangssaldoCents, openingDate: datum, creditLimitCents: rahmen, isLocked: istGebunden, isArchived: istArchiviert)) } } if geglueckt { await zustand.ladeNachschlag() fertig() schliesse() } } private func loesche() async { guard let client = zustand.client, case .aendern(let konto) = zweck else { return } if await vorgang.fuehreAus({ try await client.loescheKonto(konto.id) }) { await zustand.ladeNachschlag() fertig() schliesse() } } }