import SwiftUI /// Zuordnen per Scan: erst ein Einzelstück (…/i/) scannen, dann einen /// Lagerort (…/l/) – das Stück wird dem Ort zugewiesen. Danach gleich das /// nächste Stück, ohne Tippen. struct AssignScanView: View { @Environment(\.dismiss) private var dismiss @State private var paused = false @State private var torchOn = false @State private var torchLocked = false @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 { VStack(spacing: 14) { ScannerView(onCode: { code in Task { await handle(code) } }, isPaused: $paused, torchOn: $torchOn, torchLocked: $torchLocked) .aspectRatio(4.0 / 3.0, contentMode: .fit) .clipShape(RoundedRectangle(cornerRadius: 16)) .padding(.horizontal) if let pending { VStack(spacing: 4) { Text("Einzelstück \(pending.uid)").font(.headline) Text("Jetzt den Lagerort-QR scannen") .font(.caption).foregroundStyle(.secondary) Button("Abbrechen") { self.pending = nil } .font(.caption).padding(.top, 2) } .padding().frame(maxWidth: .infinity) .background(Color.marke.opacity(0.15)) .clipShape(RoundedRectangle(cornerRadius: 12)).padding(.horizontal) } else { 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 { Text(status).font(.callout).foregroundStyle(.green) .frame(maxWidth: .infinity, alignment: .leading).padding(.horizontal) } if let error { Text(error).font(.callout).foregroundStyle(.red) .frame(maxWidth: .infinity, alignment: .leading).padding(.horizontal) } } .padding(.vertical) } .navigationTitle("Ort zuordnen") .navigationBarTitleDisplayMode(.inline) .toolbar { 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 { // Lagerort-QR (…/l/)? Der Code ist eine 10-stellige Zeichenkette. if let r = code.range(of: "/l/") { let locCode = String(code[r.upperBound...]) .trimmingCharacters(in: CharacterSet(charactersIn: "/ ")) .uppercased() if !locCode.isEmpty { guard let it = pending else { error = "Erst ein Einzelstück scannen."; status = nil; return } do { _ = try await APIClient.shared.updateItem(id: it.id, ItemUpdateRequest( locationId: locCode, shopId: it.shopId, acquiredOn: it.acquiredOn, warrantyUntil: it.warrantyUntil, note: it.note)) status = "\(it.uid) zugeordnet. Nächstes Stück scannen." error = nil pending = nil } catch { self.error = error.localizedDescription } return } } // Einzelstück-QR (…/i/)? if let r = code.range(of: "/i/") { let uid = String(code[r.upperBound...]) .trimmingCharacters(in: CharacterSet(charactersIn: "/ ")).uppercased() if !uid.isEmpty, let it = try? await APIClient.shared.itemByUid(uid: uid) { pending = it; status = nil; error = nil return } } 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 } } } }