import SwiftUI /// Auswahl einer Kategorie mit eingerueckten Unterkategorien. /// /// Kategorie und Gruppe sind zwei verschiedene Dinge: Die Kategorie ordnet nur /// die Artikelliste, die Gruppe zaehlt Bestaende mehrerer Marken zusammen. struct CategoryPicker: View { let categories: [CategoryItem] @Binding var selection: Int? /// Nur Kategorien dieses Typs zeigen ("food"/"object"); nil = alle. var tracking: String? = nil var body: some View { Picker("Kategorie", selection: $selection) { Text("– keine –").tag(Int?.none) ForEach(flattened(), id: \.item.id) { eintrag in Text(String(repeating: " ", count: eintrag.depth) + eintrag.item.name) .tag(Int?.some(eintrag.item.id)) } } } private var gefiltert: [CategoryItem] { guard let tracking else { return categories } return categories.filter { $0.tracking == tracking } } private func flattened() -> [(item: CategoryItem, depth: Int)] { let quelle = gefiltert let ids = Set(quelle.map(\.id)) var result: [(CategoryItem, Int)] = [] func walk(_ node: CategoryItem, _ depth: Int) { result.append((node, depth)) for child in quelle.filter({ $0.parentId == node.id }) { walk(child, depth + 1) } } for root in quelle.filter({ $0.parentId == nil || !ids.contains($0.parentId!) }) { walk(root, 0) } return result } } /// Legt eine Gruppe an Ort und Stelle an, damit man das Formular nicht /// verlassen muss. Nur fuer Administratoren - POST /groups verlangt das. struct NewGroupSheet: View { let units: [Unit] var onCreated: (GroupItem) -> Void @Environment(\.dismiss) private var dismiss @State private var name = "" @State private var minStock = "" @State private var unitId: Int? @State private var busy = false @State private var error: String? var body: some View { Form { Section { LabeledField(label: "Name", text: $name) } footer: { Text("Eine Gruppe zählt Bestände mehrerer Marken zusammen – " + "z. B. Mehl von verschiedenen Herstellern als „5 kg Mehl“.") } Section("Mindestbestand (optional)") { QuantityField(label: "Menge", text: $minStock) Picker("Einheit", selection: $unitId) { Text("– Basiseinheit –").tag(Int?.none) ForEach(units) { unit in Text(unit.name).tag(Int?.some(unit.id)) } } } if let error { Section { Text(error).foregroundStyle(.red).font(.callout) } } Section { Button(busy ? "Anlegen…" : "Anlegen") { Task { await create() } } .disabled(busy || name.trimmingCharacters(in: .whitespaces).isEmpty) } } .navigationTitle("Neue Gruppe") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .topBarLeading) { Button("Abbrechen") { dismiss() } } } } private func create() async { error = nil busy = true defer { busy = false } do { let created = try await APIClient.shared.createGroup( NewGroupRequest( name: name.trimmingCharacters(in: .whitespaces), minStock: Double(minStock.replacingOccurrences(of: ",", with: ".")), minStockUnitId: unitId ) ) onCreated(created) dismiss() } catch { self.error = error.localizedDescription } } }