iOS: 'Anlegen & fertig' fuers Einzelstueck + Speichern-Nachfrage beim Zurueck
1) Im Einzelstueck-Anlegen gibt es jetzt 'Anlegen & fertig' - das schliesst in einem Tap bis zur Produktliste zurueck (statt Sheet schliessen + nochmal zurueck). 'Anlegen' bleibt fuer mehrere Stuecke nacheinander. 2) Beim Zurueck aus der Produkt-Detailansicht wird bei ungespeicherten Stammdaten-Aenderungen nachgefragt (Speichern/Verwerfen); ohne Aenderung direkt zurueck. Fussnote stellt klar: Foto und Lagerort-Bedarf speichern sofort. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -76,6 +76,8 @@ struct DateRow: View {
|
|||||||
struct ProductItemsSection: View {
|
struct ProductItemsSection: View {
|
||||||
let product: Product
|
let product: Product
|
||||||
var onChanged: () async -> Void
|
var onChanged: () async -> Void
|
||||||
|
/// Bis zur Liste schließen (für „Anlegen & fertig").
|
||||||
|
var onFinish: (() -> Void)? = nil
|
||||||
|
|
||||||
@State private var items: [Item] = []
|
@State private var items: [Item] = []
|
||||||
@State private var showAdd = false
|
@State private var showAdd = false
|
||||||
@@ -102,7 +104,8 @@ struct ProductItemsSection: View {
|
|||||||
.task(id: product.id) { await reload() }
|
.task(id: product.id) { await reload() }
|
||||||
.sheet(isPresented: $showAdd) {
|
.sheet(isPresented: $showAdd) {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
ItemAddSheet(productId: product.id) { await refresh() }
|
ItemAddSheet(productId: product.id, onDone: { await refresh() },
|
||||||
|
onFinish: onFinish.map { finish in { showAdd = false; finish() } })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -436,6 +439,8 @@ struct ItemEditView: View {
|
|||||||
struct ItemAddSheet: View {
|
struct ItemAddSheet: View {
|
||||||
let productId: Int
|
let productId: Int
|
||||||
var onDone: () async -> Void
|
var onDone: () async -> Void
|
||||||
|
/// Wenn gesetzt: „Anlegen & fertig" schließt bis zur Liste (nicht nur das Sheet).
|
||||||
|
var onFinish: (() -> Void)? = nil
|
||||||
|
|
||||||
@Environment(\.dismiss) private var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
@@ -514,7 +519,11 @@ struct ItemAddSheet: View {
|
|||||||
|
|
||||||
if let error { Section { Text(error).foregroundStyle(.red).font(.callout) } }
|
if let error { Section { Text(error).foregroundStyle(.red).font(.callout) } }
|
||||||
Section {
|
Section {
|
||||||
Button(busy ? "Anlegen…" : "Anlegen") { Task { await create() } }.disabled(busy)
|
Button(busy ? "Anlegen…" : "Anlegen") { Task { await create(finish: false) } }.disabled(busy)
|
||||||
|
if onFinish != nil {
|
||||||
|
Button(busy ? "Anlegen…" : "Anlegen & fertig") { Task { await create(finish: true) } }
|
||||||
|
.disabled(busy)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationTitle("Einzelstücke anlegen")
|
.navigationTitle("Einzelstücke anlegen")
|
||||||
@@ -575,7 +584,7 @@ struct ItemAddSheet: View {
|
|||||||
: "Übernommen: \(teile.joined(separator: ", "))."
|
: "Übernommen: \(teile.joined(separator: ", "))."
|
||||||
}
|
}
|
||||||
|
|
||||||
private func create() async {
|
private func create(finish: Bool) async {
|
||||||
busy = true; defer { busy = false }; error = nil
|
busy = true; defer { busy = false }; error = nil
|
||||||
do {
|
do {
|
||||||
var sid = shopId
|
var sid = shopId
|
||||||
@@ -598,7 +607,7 @@ struct ItemAddSheet: View {
|
|||||||
filename: docName.isEmpty ? "beleg" : docName, contentType: docType)
|
filename: docName.isEmpty ? "beleg" : docName, contentType: docType)
|
||||||
}
|
}
|
||||||
await onDone()
|
await onDone()
|
||||||
dismiss()
|
if finish, let onFinish { onFinish() } else { dismiss() }
|
||||||
} catch { self.error = error.localizedDescription }
|
} catch { self.error = error.localizedDescription }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,10 @@ struct ProductDetailView: View {
|
|||||||
let product: Product
|
let product: Product
|
||||||
|
|
||||||
@EnvironmentObject private var display: DisplaySettings
|
@EnvironmentObject private var display: DisplaySettings
|
||||||
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
@State private var current: Product
|
@State private var current: Product
|
||||||
|
@State private var showLeaveConfirm = false
|
||||||
@State private var lots: [Lot] = []
|
@State private var lots: [Lot] = []
|
||||||
@State private var busy = false
|
@State private var busy = false
|
||||||
@State private var error: String?
|
@State private var error: String?
|
||||||
@@ -40,6 +42,29 @@ struct ProductDetailView: View {
|
|||||||
_current = State(initialValue: product)
|
_current = State(initialValue: product)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ungespeicherte Änderungen an den Artikel-Stammdaten? (Foto und Lagerort-
|
||||||
|
/// Bedarf speichern sofort und zählen hier nicht.)
|
||||||
|
private var isDirty: Bool {
|
||||||
|
if name != current.name { return true }
|
||||||
|
if brand != (current.brand ?? "") { return true }
|
||||||
|
if categoryId != current.categoryId { return true }
|
||||||
|
if current.isObject {
|
||||||
|
if !current.isIndividual, shopId != current.shopId { return true }
|
||||||
|
if productUrl != (current.productUrl ?? "") { return true }
|
||||||
|
if fieldValues != Self.stringValues(current.fieldValues) { return true }
|
||||||
|
} else {
|
||||||
|
if packageSize != (current.packageSize.map { formatAmount($0) } ?? "") { return true }
|
||||||
|
if packageLabel != (current.packageLabel ?? "") { return true }
|
||||||
|
if datePrecision != (current.datePrecision == "month" ? "month" : "day") { return true }
|
||||||
|
if groupId != current.groupId { return true }
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private func attemptLeave() {
|
||||||
|
if isDirty { showLeaveConfirm = true } else { dismiss() }
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Form {
|
Form {
|
||||||
if let status {
|
if let status {
|
||||||
@@ -52,7 +77,8 @@ struct ProductDetailView: View {
|
|||||||
ProductPhotoSection(productId: current.id)
|
ProductPhotoSection(productId: current.id)
|
||||||
|
|
||||||
if current.isObject && current.isIndividual {
|
if current.isObject && current.isIndividual {
|
||||||
ProductItemsSection(product: current, onChanged: { await reload() })
|
ProductItemsSection(product: current, onChanged: { await reload() },
|
||||||
|
onFinish: { dismiss() })
|
||||||
} else if current.isObject {
|
} else if current.isObject {
|
||||||
ObjectStockSection(product: current, locations: locations, lots: lots,
|
ObjectStockSection(product: current, locations: locations, lots: lots,
|
||||||
onChanged: { await reload() })
|
onChanged: { await reload() })
|
||||||
@@ -193,6 +219,21 @@ struct ProductDetailView: View {
|
|||||||
}
|
}
|
||||||
.navigationTitle(current.name)
|
.navigationTitle(current.name)
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.navigationBarBackButtonHidden(true)
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItem(placement: .topBarLeading) {
|
||||||
|
Button { attemptLeave() } label: { Label("Zurück", systemImage: "chevron.left") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.confirmationDialog("Ungespeicherte Änderungen", isPresented: $showLeaveConfirm,
|
||||||
|
titleVisibility: .visible) {
|
||||||
|
Button("Speichern") { Task { await save(); dismiss() } }
|
||||||
|
Button("Verwerfen", role: .destructive) { dismiss() }
|
||||||
|
Button("Abbrechen", role: .cancel) {}
|
||||||
|
} message: {
|
||||||
|
Text("Änderungen an Name/Marke/Kategorie sind noch nicht gespeichert. "
|
||||||
|
+ "Foto und Lagerort-Bedarf werden sofort gespeichert.")
|
||||||
|
}
|
||||||
.sheet(item: $editLot) { lot in
|
.sheet(item: $editLot) { lot in
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
LotEditView(lot: lot, product: current) {
|
LotEditView(lot: lot, product: current) {
|
||||||
|
|||||||
Reference in New Issue
Block a user