iOS: Shops (Bezugsquellen) verwalten
Die iOS-Stammdaten hatten keinen Punkt fuer Shops – die "gekauft bei"-Liste war nur im Web pflegbar. Jetzt gibt es unter Verwaltung > Stammdaten "Shops" mit Anlegen, Bearbeiten (Name + optionale Website) und Loeschen, analog zum Web. Client um createShop/updateShop/deleteShop samt Request-Models ergaenzt. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -24,6 +24,9 @@ struct AdminTabView: View {
|
||||
NavigationLink { GroupsView() } label: {
|
||||
Label("Gruppen", systemImage: "square.stack.3d.up")
|
||||
}
|
||||
NavigationLink { ShopsView() } label: {
|
||||
Label("Shops", systemImage: "cart")
|
||||
}
|
||||
}
|
||||
|
||||
Section("App") {
|
||||
@@ -550,3 +553,100 @@ struct GroupsView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Bezugsquellen für „gekauft bei" (nur Gegenstände). Wie im Web pflegbar –
|
||||
/// Name plus optionale Website.
|
||||
struct ShopsView: View {
|
||||
var body: some View {
|
||||
MasterDataListView(
|
||||
title: "Shops",
|
||||
singular: "Der Shop",
|
||||
load: {
|
||||
try await APIClient.shared.shops().map {
|
||||
// Die Website steht im Untertitel und wird beim Bearbeiten
|
||||
// wieder daraus gelesen (wie bei den Gebinden die Mehrzahl).
|
||||
MasterDataItem(id: $0.id, title: $0.name,
|
||||
subtitle: $0.website ?? "", isBuiltin: false)
|
||||
}
|
||||
},
|
||||
delete: { try await APIClient.shared.deleteShop(id: $0) }
|
||||
) { item, done in
|
||||
ShopEditor(id: item?.id,
|
||||
name: item?.title ?? "",
|
||||
website: item?.subtitle ?? "",
|
||||
done: done)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Shops haben Name und optionale Website.
|
||||
struct ShopEditor: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
let id: Int?
|
||||
let done: () -> Void
|
||||
|
||||
@State private var name: String
|
||||
@State private var website: String
|
||||
@State private var error: String?
|
||||
@State private var busy = false
|
||||
|
||||
init(id: Int?, name: String, website: String, done: @escaping () -> Void) {
|
||||
self.id = id
|
||||
self.done = done
|
||||
_name = State(initialValue: name)
|
||||
_website = State(initialValue: website)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
Form {
|
||||
Section("Name") {
|
||||
TextField("z. B. Digitec", text: $name)
|
||||
}
|
||||
Section {
|
||||
TextField("https://…", text: $website)
|
||||
.keyboardType(.URL)
|
||||
.textInputAutocapitalization(.never)
|
||||
.autocorrectionDisabled()
|
||||
} header: {
|
||||
Text("Website (optional)")
|
||||
}
|
||||
if let error {
|
||||
Section { Text(error).foregroundStyle(.red).font(.callout) }
|
||||
}
|
||||
}
|
||||
.navigationTitle(id == nil ? "Shop anlegen" : "Shop bearbeiten")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .topBarLeading) {
|
||||
Button("Abbrechen") { dismiss() }
|
||||
}
|
||||
ToolbarItem(placement: .topBarTrailing) {
|
||||
Button(busy ? "Sichern…" : "Sichern") { Task { await submit() } }
|
||||
.disabled(busy || name.trimmingCharacters(in: .whitespaces).isEmpty)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func submit() async {
|
||||
busy = true
|
||||
defer { busy = false }
|
||||
let n = name.trimmingCharacters(in: .whitespaces)
|
||||
// Leeres Feld bewusst als "" senden (nicht nil): der Server setzt die
|
||||
// Website damit auf leer – so lässt sie sich auch wieder entfernen.
|
||||
let w = website.trimmingCharacters(in: .whitespaces)
|
||||
do {
|
||||
if let id {
|
||||
_ = try await APIClient.shared.updateShop(id: id, ShopUpdateRequest(name: n, website: w))
|
||||
} else {
|
||||
_ = try await APIClient.shared.createShop(NewShopRequest(name: n, website: w))
|
||||
}
|
||||
done()
|
||||
dismiss()
|
||||
} catch {
|
||||
self.error = error.localizedDescription
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user