iOS: Mengen-Sheet auf Form-Ebene praesentieren (erstes Oeffnen bleibt offen)

Das Menge-hinzufuegen/umlagern/entfernen-Sheet ging beim ersten Antippen sofort
wieder zu: das spaet fertige Laden der Entnahme-Statistik (.task im Abschnitt)
riss das gerade praesentierte Sheet weg. ObjectStockSection ist jetzt zustands-
los (bekommt removals + Sheet-Binding); Sheet und Statistik-Laden liegen in der
Detailansicht auf der stabilen Form-Ebene bzw. im reload().

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-07-29 12:03:19 +02:00
parent 4ab8d11f92
commit 620e77dff1
2 changed files with 49 additions and 46 deletions

View File

@@ -4,16 +4,11 @@ import SwiftUI
/// Lebensmittel. Erlaubt Hinzufügen, Umlagern (ohne Grund) und Entfernen (mit /// Lebensmittel. Erlaubt Hinzufügen, Umlagern (ohne Grund) und Entfernen (mit
/// Pflicht-Grund) und zeigt eine kleine Entnahme-Statistik. Wird als Abschnitt /// Pflicht-Grund) und zeigt eine kleine Entnahme-Statistik. Wird als Abschnitt
/// in die Produkt-Detailansicht eingebettet. /// in die Produkt-Detailansicht eingebettet.
struct ObjectStockSection: View { /// Welches Mengen-Sheet gerade offen ist. Wird auf der stabilen Form-Ebene der
let product: Product /// Detailansicht präsentiert (nicht im Abschnitt selbst), sonst schloss sich das
let locations: [StorageLocation] /// Fenster beim ersten Öffnen sofort wieder (das spät fertige Laden der
let lots: [Lot] /// Entnahme-Statistik hat das frische Sheet weggerissen).
var onChanged: () async -> Void enum ObjectStockSheet: Identifiable {
@State private var removals: RemovalSummary?
@State private var sheet: StockSheet?
enum StockSheet: Identifiable {
case add case add
case relocate(from: String?) case relocate(from: String?)
case remove(loc: String?) case remove(loc: String?)
@@ -26,6 +21,13 @@ struct ObjectStockSection: View {
} }
} }
struct ObjectStockSection: View {
let product: Product
let locations: [StorageLocation]
let lots: [Lot]
let removals: RemovalSummary?
@Binding var sheet: ObjectStockSheet?
private var einheit: String { product.unitName.isEmpty ? "Stück" : product.unitName } private var einheit: String { product.unitName.isEmpty ? "Stück" : product.unitName }
private func ortName(_ id: String?) -> String { private func ortName(_ id: String?) -> String {
guard let id else { return "Ohne Lagerort" } guard let id else { return "Ohne Lagerort" }
@@ -63,23 +65,6 @@ struct ObjectStockSection: View {
.buttonStyle(.borderless) .buttonStyle(.borderless)
.font(.callout) .font(.callout)
} }
// Sheet an den Abschnitt hängen (nicht an die Group) sonst ging das
// Fenster bei manchen iOS-Versionen kurz auf und sofort wieder zu.
.sheet(item: $sheet) { welche in
NavigationStack {
switch welche {
case .add:
ObjectAddSheet(product: product, locations: locations, einheit: einheit,
perform: { await afterAction() })
case .relocate(let from):
ObjectRelocateSheet(product: product, locations: locations, initialFrom: from,
perform: { await afterAction() })
case .remove(let loc):
ObjectRemoveSheet(product: product, locations: locations, einheit: einheit,
initialLoc: loc, perform: { await afterAction() })
}
}
}
if let removals, !removals.stats.isEmpty { if let removals, !removals.stats.isEmpty {
Section("Bereits entnommen") { Section("Bereits entnommen") {
@@ -90,17 +75,6 @@ struct ObjectStockSection: View {
} }
} }
} }
.task(id: product.id) { await loadRemovals() }
.onChange(of: lots) { _ in Task { await loadRemovals() } }
}
private func loadRemovals() async {
removals = try? await APIClient.shared.productRemovals(id: product.id)
}
private func afterAction() async {
await onChanged()
await loadRemovals()
} }
} }

View File

@@ -38,6 +38,10 @@ struct ProductDetailView: View {
@State private var fieldValues: [String: String] = [:] @State private var fieldValues: [String: String] = [:]
@State private var editLot: Lot? @State private var editLot: Lot?
// Mengen-Bestand (Menge je Lagerort): Sheet + Entnahme-Statistik werden hier
// gehalten und auf Form-Ebene präsentiert, damit das Sheet stabil aufgeht.
@State private var objectSheet: ObjectStockSheet?
@State private var objectRemovals: RemovalSummary?
private let labels = ["", "Glas", "Tüte", "Flasche", "Dose", "Tube", "Becher", "Beutel", "Karton"] private let labels = ["", "Glas", "Tüte", "Flasche", "Dose", "Tube", "Becher", "Beutel", "Karton"]
@@ -92,7 +96,7 @@ struct ProductDetailView: View {
} else if current.isObject && !current.isBulk { } else if current.isObject && !current.isBulk {
// Menge je Lagerort (Verbrauchsgegenstände laufen als Charge, s.u.). // Menge je Lagerort (Verbrauchsgegenstände laufen als Charge, s.u.).
ObjectStockSection(product: current, locations: locations, lots: lots, ObjectStockSection(product: current, locations: locations, lots: lots,
onChanged: { await reload() }) removals: objectRemovals, sheet: $objectSheet)
} else { } else {
// Lebensmittel und Verbrauchsgegenstände: Vorrat aus den Chargen. // Lebensmittel und Verbrauchsgegenstände: Vorrat aus den Chargen.
Section("Bestand") { Section("Bestand") {
@@ -287,6 +291,25 @@ struct ProductDetailView: View {
} }
} }
} }
// Mengen-Sheets auf der stabilen Form-Ebene (nicht im Abschnitt), sonst
// schloss sich das Fenster beim ersten Öffnen sofort wieder.
.sheet(item: $objectSheet) { welche in
NavigationStack {
switch welche {
case .add:
ObjectAddSheet(product: current, locations: locations,
einheit: current.unitName.isEmpty ? "Stück" : current.unitName,
perform: { await reload() })
case .relocate(let from):
ObjectRelocateSheet(product: current, locations: locations, initialFrom: from,
perform: { await reload() })
case .remove(let loc):
ObjectRemoveSheet(product: current, locations: locations,
einheit: current.unitName.isEmpty ? "Stück" : current.unitName,
initialLoc: loc, perform: { await reload() })
}
}
}
.task { .task {
fillForm() fillForm()
await reload() await reload()
@@ -355,6 +378,12 @@ struct ProductDetailView: View {
} else { } else {
fieldDefs = [] fieldDefs = []
} }
// Entnahme-Statistik nur für Menge je Lagerort"-Gegenstände.
if current.isObject && !current.isBulk && !current.isIndividual {
objectRemovals = try? await APIClient.shared.productRemovals(id: current.id)
} else {
objectRemovals = nil
}
} }
/// Verwaltungsart (individual/bulk) mitschicken, wenn sie umgestellt wurde. /// Verwaltungsart (individual/bulk) mitschicken, wenn sie umgestellt wurde.