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:
@@ -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
|
// MARK: - Produktliste
|
||||||
|
|
||||||
struct ProductListView: View {
|
struct ProductListView: View {
|
||||||
|
|||||||
@@ -195,6 +195,12 @@ struct ListsTabView: View {
|
|||||||
ActionTile(title: "Einkaufsliste", subtitle: "Was unter dem Mindestbestand liegt",
|
ActionTile(title: "Einkaufsliste", subtitle: "Was unter dem Mindestbestand liegt",
|
||||||
systemImage: "cart")
|
systemImage: "cart")
|
||||||
}
|
}
|
||||||
|
NavigationLink {
|
||||||
|
MinStockView()
|
||||||
|
} label: {
|
||||||
|
ActionTile(title: "Mindestbestände", subtitle: "Soll je Artikel ansehen und pflegen",
|
||||||
|
systemImage: "checklist")
|
||||||
|
}
|
||||||
NavigationLink {
|
NavigationLink {
|
||||||
ExpiringView()
|
ExpiringView()
|
||||||
} label: {
|
} label: {
|
||||||
|
|||||||
Reference in New Issue
Block a user