From 2c1efba83d17e3a585bcaa5b7d5bc8ac1a9ce404 Mon Sep 17 00:00:00 2001 From: Scarriffle Date: Wed, 29 Jul 2026 11:28:50 +0200 Subject: [PATCH] iOS: Mindestbestaende-Uebersicht im Listen-Tab Neue "Mindestbestaende"-Ansicht: alle Artikel mit gesetztem Mindestbestand mit Soll vs. Bestand (Anzeigeeinheit) und "niedrig"-Hinweis; Tippen oeffnet den Artikel zum Bearbeiten. Kachel im Listen-Tab ergaenzt. Co-Authored-By: Claude Opus 4.8 --- ios/Sources/ListViews.swift | 69 +++++++++++++++++++++++++++++++++++++ ios/Sources/RootView.swift | 6 ++++ 2 files changed, 75 insertions(+) diff --git a/ios/Sources/ListViews.swift b/ios/Sources/ListViews.swift index ff70aeb..31e4cf9 100644 --- a/ios/Sources/ListViews.swift +++ b/ios/Sources/ListViews.swift @@ -188,6 +188,75 @@ struct ExpiringView: View { } } +// MARK: - Mindestbestände + +/// Übersicht aller Artikel mit gesetztem Mindestbestand (Soll vs. Bestand, in der +/// Anzeigeeinheit). Tippen öffnet den Artikel zum Bearbeiten des Mindestbestands. +struct MinStockView: View { + @State private var products: [Product] = [] + @State private var busy = true + @State private var error: String? + + private var mitBestand: [Product] { + products.filter { ($0.minStock ?? 0) > 0 } + .sorted { $0.name.localizedCaseInsensitiveCompare($1.name) == .orderedAscending } + } + + var body: some View { + List { + if !busy && mitBestand.isEmpty { + Text("Noch keine Mindestbestände gesetzt. Im Artikel beim Feld Mindestbestand festlegen.") + .foregroundStyle(.secondary) + } + ForEach(mitBestand) { p in + NavigationLink { ProductDetailView(product: p) } label: { row(p) } + } + } + .navigationTitle("Mindestbestände") + .navigationBarTitleDisplayMode(.inline) + .overlay { if busy && products.isEmpty { ProgressView() } } + .refreshable { await load() } + .onAppear { Task { await load() } } + .alert("Fehler", isPresented: Binding(get: { error != nil }, set: { if !$0 { error = nil } })) { + Button("OK", role: .cancel) {} + } message: { Text(error ?? "") } + } + + @ViewBuilder + private func row(_ p: Product) -> some View { + let faktor = p.unitFactor == 0 ? 1 : p.unitFactor + let soll = (p.minStock ?? 0) / faktor + let bestand = p.stock / faktor + let unter = p.stock < (p.minStock ?? 0) + HStack(spacing: 12) { + ProductThumb(productId: p.id) + VStack(alignment: .leading, spacing: 2) { + Text(p.name) + Text("Soll \(formatAmount(soll)) · Bestand \(formatAmount(bestand)) \(p.unitName)") + .font(.caption).foregroundStyle(.secondary) + } + Spacer() + if unter { + Text("niedrig").font(.caption2) + .padding(.horizontal, 8).padding(.vertical, 3) + .background(Color.orange.opacity(0.2), in: Capsule()) + } + } + } + + private func load() async { + busy = true + defer { busy = false } + do { + let geladen = try await APIClient.shared.searchProducts("") + products = geladen + Task { await ProductImageCache.shared.preload(geladen) } + } catch { + self.error = error.localizedDescription + } + } +} + // MARK: - Produktliste struct ProductListView: View { diff --git a/ios/Sources/RootView.swift b/ios/Sources/RootView.swift index a5ad680..9c93fa1 100644 --- a/ios/Sources/RootView.swift +++ b/ios/Sources/RootView.swift @@ -195,6 +195,12 @@ struct ListsTabView: View { ActionTile(title: "Einkaufsliste", subtitle: "Was unter dem Mindestbestand liegt", systemImage: "cart") } + NavigationLink { + MinStockView() + } label: { + ActionTile(title: "Mindestbestände", subtitle: "Soll je Artikel ansehen und pflegen", + systemImage: "checklist") + } NavigationLink { ExpiringView() } label: {