From 953e0aa483427bd4a66a54ef24fc79b017ac9cc7 Mon Sep 17 00:00:00 2001 From: Scarriffle Date: Sun, 26 Jul 2026 08:04:56 +0200 Subject: [PATCH] iOS: "Nachschlagen"-Scan (nur ansehen) Neue Kachel im Scannen-Tab: Barcode -> Artikel, Einzelstueck-QR -> genau das Stueck (Kaufdatum, Garantie, Ort ...), rein lesend ohne Ein-/Auslagern. Praktisch mit Einzelstuecken, um schnell nachzuschauen, welches Exemplar man in der Hand haelt. Co-Authored-By: Claude Opus 4.8 --- ios/Sources/LookupScanView.swift | 76 ++++++++++++++++++++++++++ ios/Sources/RootView.swift | 8 +++ ios/Sources/VorraniaApp.swift | 1 + ios/Vorrania.xcodeproj/project.pbxproj | 4 ++ 4 files changed, 89 insertions(+) create mode 100644 ios/Sources/LookupScanView.swift diff --git a/ios/Sources/LookupScanView.swift b/ios/Sources/LookupScanView.swift new file mode 100644 index 0000000..3c02513 --- /dev/null +++ b/ios/Sources/LookupScanView.swift @@ -0,0 +1,76 @@ +import SwiftUI + +/// Nur ansehen: Barcode → Artikel, Einzelstück-QR → genau dieses Stück. +/// Kein Ein-/Auslagern – zum schnellen Nachschauen (z.B. wann gekauft, Garantie). +struct LookupScanView: View { + @Environment(\.dismiss) private var dismiss + @EnvironmentObject private var display: DisplaySettings + + @State private var paused = false + @State private var torchOn = false + @State private var error: String? + @State private var item: Item? + @State private var product: Product? + + var body: some View { + ScrollView { + VStack(spacing: 14) { + ScannerView(onCode: { code in Task { await resolve(code) } }, + isPaused: $paused, torchOn: $torchOn) + .aspectRatio(4.0 / 3.0, contentMode: .fit) + .clipShape(RoundedRectangle(cornerRadius: 16)) + .padding(.horizontal) + + Text("Barcode oder Einzelstück-QR scannen – nur ansehen") + .font(.caption).foregroundStyle(.secondary) + + if let error { + Text(error).font(.callout).foregroundStyle(.red) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal) + } + } + .padding(.vertical) + } + .navigationTitle("Nachschlagen") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .topBarLeading) { Button("Fertig") { dismiss() } } + ToolbarItem(placement: .topBarTrailing) { TorchButton(isOn: $torchOn) } + } + .sheet(item: $item, onDismiss: { paused = false }) { it in + NavigationStack { ItemEditView(item: it) } + } + .sheet(item: $product, onDismiss: { paused = false }) { p in + NavigationStack { ProductDetailView(product: p) } + .environmentObject(display) + } + } + + private func resolve(_ code: String) async { + paused = true + error = nil + // 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 found = try? await APIClient.shared.itemByUid(uid: uid) { + item = found + return + } + } + // Sonst als Barcode auflösen. + do { + let result = try await APIClient.shared.lookup(barcode: code) + if let existing = result.existingProduct { + product = existing + } else { + error = "Nicht im Katalog: \(code)" + paused = false + } + } catch { + self.error = error.localizedDescription + paused = false + } + } +} diff --git a/ios/Sources/RootView.swift b/ios/Sources/RootView.swift index 1cf0a65..d09a755 100644 --- a/ios/Sources/RootView.swift +++ b/ios/Sources/RootView.swift @@ -29,6 +29,7 @@ struct RootView: View { switch route { case .checkin: CheckInView() case .checkout: CheckOutView() + case .lookup: LookupScanView() case .expiring: ExpiringView() .toolbar { @@ -93,6 +94,13 @@ struct ScanTabView: View { ActionTile(title: "Auslagern", subtitle: "Barcode scannen und Menge entnehmen", systemImage: "arrow.up.to.line") } + Button { + router.route = .lookup + } label: { + ActionTile(title: "Nachschlagen", + subtitle: "Barcode/QR scannen und nur ansehen", + systemImage: "doc.text.magnifyingglass") + } // Ohne Barcode: gerade bei Gegenstaenden (Kleidung, Werkzeug) gibt // es keinen Code zum Scannen. Deshalb hier ein offensichtlicher Weg. if session.isAdmin { diff --git a/ios/Sources/VorraniaApp.swift b/ios/Sources/VorraniaApp.swift index 1f85099..eb71880 100644 --- a/ios/Sources/VorraniaApp.swift +++ b/ios/Sources/VorraniaApp.swift @@ -9,6 +9,7 @@ enum Route: String { case checkin case checkout case expiring + case lookup } final class Router: ObservableObject { diff --git a/ios/Vorrania.xcodeproj/project.pbxproj b/ios/Vorrania.xcodeproj/project.pbxproj index 45f3324..a7f8a4e 100644 --- a/ios/Vorrania.xcodeproj/project.pbxproj +++ b/ios/Vorrania.xcodeproj/project.pbxproj @@ -33,6 +33,7 @@ 9B0A1A7C19764857BDC6ED26 /* RootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4741D0E95875919C921945CF /* RootView.swift */; }; A7017E6C0532A650439AED7B /* BestBeforeText.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA022A27118BEEEC5912ECDC /* BestBeforeText.swift */; }; AAF27320D2A67C994C29C26F /* ProductDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F574168AA0F849D46C384EE /* ProductDetailView.swift */; }; + B531EEDAFAF442B1E86A9931 /* LookupScanView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F22598772AD8B5EB6394F8 /* LookupScanView.swift */; }; D70CB183C868FF0143A6A5E7 /* DisplaySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAC97A555856C6C5DD353E25 /* DisplaySettings.swift */; }; D7717F44D2CDB75DC2693B59 /* ObjectStockView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B5BB49FBEFF88768B0BC902 /* ObjectStockView.swift */; }; DFA55EF4ACA34537F445E998 /* Session.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD2F9406BD0D4F0D30FED345 /* Session.swift */; }; @@ -50,6 +51,7 @@ 121D44B9990DA931A9CD5847 /* ServerProfiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerProfiles.swift; sourceTree = ""; }; 163F5DCB00387215F0EA1F21 /* NotificationScheduler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationScheduler.swift; sourceTree = ""; }; 1847E65D41ACEDD01CD108BC /* ServerFetch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerFetch.swift; sourceTree = ""; }; + 18F22598772AD8B5EB6394F8 /* LookupScanView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LookupScanView.swift; sourceTree = ""; }; 1E86178BB91DBB86ADFB284B /* MasterDataViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterDataViews.swift; sourceTree = ""; }; 314B1BB7220691A7423E8929 /* Models.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Models.swift; sourceTree = ""; }; 369B9841E43E727ACA2E2A2A /* ProductViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductViews.swift; sourceTree = ""; }; @@ -102,6 +104,7 @@ A6785CD9174067EFBB2B9043 /* ListViews.swift */, C35D34107C8CBB8B7C6C6650 /* LocalOverview.swift */, A75926511854BB8EE316ED3A /* LoginView.swift */, + 18F22598772AD8B5EB6394F8 /* LookupScanView.swift */, 1E86178BB91DBB86ADFB284B /* MasterDataViews.swift */, 314B1BB7220691A7423E8929 /* Models.swift */, 163F5DCB00387215F0EA1F21 /* NotificationScheduler.swift */, @@ -227,6 +230,7 @@ 7738E209D4D32BFD514B76F5 /* ListViews.swift in Sources */, 82A0C22D4AD228C36868853A /* LocalOverview.swift in Sources */, E9EA455CACB6ED5951B114C3 /* LoginView.swift in Sources */, + B531EEDAFAF442B1E86A9931 /* LookupScanView.swift in Sources */, 96E5A2455B2EC26050E69232 /* MasterDataViews.swift in Sources */, 6816C33381DF52A96E5BB303 /* Models.swift in Sources */, 38133EE3EC920B462BDAA29F /* NotificationScheduler.swift in Sources */,