import SwiftUI /// Artikel per Namenssuche auswählen (Alternative zum Scannen). struct ProductSearchView: View { var onPick: (Product) -> Void @Environment(\.dismiss) private var dismiss @State private var query = "" @State private var results: [Product] = [] @State private var busy = false var body: some View { List { ForEach(results) { product in Button { onPick(product) dismiss() } label: { VStack(alignment: .leading, spacing: 2) { Text(product.name) Text("\(formatted(product.stockInArticleUnits)) \(product.articleUnitLabel)") .font(.caption).foregroundStyle(.secondary) } } } if results.isEmpty && !busy { Text("Keine Treffer").foregroundStyle(.secondary) } } .searchable(text: $query, prompt: "Artikel suchen") .onSubmit(of: .search) { Task { await search() } } .task { await search() } .onChange(of: query) { _, _ in Task { await search() } } .navigationTitle("Artikel suchen") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .topBarLeading) { Button("Abbrechen") { dismiss() } } } } private func formatted(_ value: Double) -> String { value == value.rounded() ? String(Int(value)) : String(format: "%.2f", value) } private func search() async { busy = true defer { busy = false } results = (try? await APIClient.shared.searchProducts(query)) ?? [] } } /// Neues Produkt anlegen – bei Bedarf mit den Daten von Open Food Facts vorbefüllt. struct ProductFormView: View { let prefillBarcode: String? let groupId: Int? var suggestion: LookupResult.Suggestion? var onCreated: (Product) -> Void @Environment(\.dismiss) private var dismiss @State private var barcode = "" @State private var name = "" @State private var brand = "" @State private var baseUnit = "piece" @State private var packageSize = "" @State private var packageLabel = "" @State private var busy = false @State private var error: String? private let baseUnits = [("piece", "Stück"), ("gram", "Gramm"), ("milliliter", "Milliliter")] private let labels = ["", "Glas", "Tüte", "Flasche", "Dose", "Tube", "Becher", "Beutel", "Karton"] var body: some View { Form { Section("Artikel") { TextField("Barcode", text: $barcode).keyboardType(.numberPad) TextField("Name", text: $name) TextField("Marke", text: $brand) } Section("Einheit") { Picker("Basiseinheit", selection: $baseUnit) { ForEach(baseUnits, id: \.0) { Text($0.1).tag($0.0) } } TextField("Packungsgröße (in Basiseinheit)", text: $packageSize) .keyboardType(.decimalPad) Picker("Bezeichnung", selection: $packageLabel) { ForEach(labels, id: \.self) { Text($0.isEmpty ? "Packung (Standard)" : $0).tag($0) } } } if let error { Section { Text(error).foregroundStyle(.red).font(.callout) } } Section { Button(busy ? "Anlegen…" : "Anlegen & weiter") { Task { await create() } } .disabled(busy || name.isEmpty) } } .navigationTitle("Neuer Artikel") .navigationBarTitleDisplayMode(.inline) .onAppear(perform: prefill) } private func prefill() { barcode = suggestion?.barcode ?? prefillBarcode ?? "" if let suggestion { name = suggestion.name brand = suggestion.brand ?? "" baseUnit = suggestion.baseUnit ?? "piece" if let size = suggestion.packageSize { packageSize = String(size) } } } private func create() async { error = nil busy = true defer { busy = false } do { let product = try await APIClient.shared.createProduct( NewProductRequest( barcode: barcode.isEmpty ? nil : barcode, name: name, brand: brand.isEmpty ? nil : brand, imageUrl: suggestion?.imageUrl, baseUnit: baseUnit, packageSize: Double(packageSize.replacingOccurrences(of: ",", with: ".")), packageLabel: packageLabel.isEmpty ? nil : packageLabel, groupId: groupId ) ) onCreated(product) dismiss() } catch { self.error = error.localizedDescription } } }