diff --git a/ios/Sources/AssignScanView.swift b/ios/Sources/AssignScanView.swift index e2fb98a..0654edb 100644 --- a/ios/Sources/AssignScanView.swift +++ b/ios/Sources/AssignScanView.swift @@ -9,9 +9,10 @@ struct AssignScanView: View { @State private var paused = false @State private var torchOn = false @State private var torchLocked = false - @State private var pending: Item? // gescanntes Stück, wartet auf Ort + @State private var pending: Item? // gescanntes/gewähltes Stück, wartet auf Ort @State private var status: String? @State private var error: String? + @State private var showPicker = false var body: some View { ScrollView { @@ -34,8 +35,14 @@ struct AssignScanView: View { .background(Color.accentColor.opacity(0.15)) .clipShape(RoundedRectangle(cornerRadius: 12)).padding(.horizontal) } else { - Text("Schritt 1: Einzelstück-QR scannen") - .font(.caption).foregroundStyle(.secondary) + VStack(spacing: 8) { + Text("Schritt 1: Einzelstück-QR scannen – oder ohne Code:") + .font(.caption).foregroundStyle(.secondary) + // Für Stücke ohne eigenen QR (z.B. Kleidung): aus der Liste wählen. + Button { showPicker = true } label: { + Label("Einzelstück aus Liste wählen", systemImage: "list.bullet") + } + } } if let status { @@ -55,6 +62,9 @@ struct AssignScanView: View { ToolbarItem(placement: .topBarLeading) { Button("Fertig") { dismiss() } } ToolbarItem(placement: .topBarTrailing) { TorchButton(isOn: $torchOn, locked: $torchLocked) } } + .sheet(isPresented: $showPicker) { + ItemPickerSheet { it in pending = it; status = nil; error = nil } + } } private func handle(_ code: String) async { @@ -90,3 +100,49 @@ struct AssignScanView: View { error = "Unbekannter Code – bitte einen Einzelstück- oder Lagerort-QR scannen." } } + +/// Einzelstück aus einer durchsuchbaren Liste wählen – für Stücke ohne eigenen QR. +struct ItemPickerSheet: View { + var onPick: (Item) -> Void + + @Environment(\.dismiss) private var dismiss + @State private var items: [Item] = [] + @State private var locations: [StorageLocation] = [] + @State private var query = "" + @State private var busy = true + + private var gefiltert: [Item] { + let q = query.trimmingCharacters(in: .whitespaces).lowercased() + guard !q.isEmpty else { return items } + return items.filter { + [$0.productName, $0.uid, $0.productBrand, $0.note] + .compactMap { $0 }.joined(separator: " ").lowercased().contains(q) + } + } + + var body: some View { + NavigationStack { + List { + if busy && items.isEmpty { ProgressView() } + ForEach(gefiltert) { it in + Button { onPick(it); dismiss() } label: { + ItemRow(item: it, locations: locations) + } + .buttonStyle(.plain) + } + if !busy && gefiltert.isEmpty { + Text("Nichts gefunden.").foregroundStyle(.secondary) + } + } + .searchable(text: $query, prompt: "Produkt, UID oder Notiz") + .navigationTitle("Einzelstück wählen") + .navigationBarTitleDisplayMode(.inline) + .toolbar { ToolbarItem(placement: .topBarLeading) { Button("Abbrechen") { dismiss() } } } + .task { + items = (try? await APIClient.shared.allItems()) ?? [] + locations = (try? await APIClient.shared.locations()) ?? [] + busy = false + } + } + } +}