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 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-07-29 11:28:50 +02:00
parent 120dfea66a
commit 2c1efba83d
2 changed files with 75 additions and 0 deletions

View File

@@ -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 {