iOS: Umschalter Lebensmittel/Gegenstand im Artikelformular
Gleiche Idee wie im Web: ein Typ-Umschalter filtert die Kategorieauswahl, statt Werkzeug & Co. zwischen den Lebensmittel-Kategorien zu suchen. Im Anlege-Formular blenden sich zudem die Lebensmittel-Felder (Gruppe, Einheit, MHD) aus, wenn "Gegenstand" gewaehlt ist. Die Detailansicht filtert die Kategorieliste auf den Typ des Artikels. CategoryPicker bekam dafuer einen optionalen Typ-Filter. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,8 @@ import SwiftUI
|
|||||||
struct CategoryPicker: View {
|
struct CategoryPicker: View {
|
||||||
let categories: [CategoryItem]
|
let categories: [CategoryItem]
|
||||||
@Binding var selection: Int?
|
@Binding var selection: Int?
|
||||||
|
/// Nur Kategorien dieses Typs zeigen ("food"/"object"); nil = alle.
|
||||||
|
var tracking: String? = nil
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Picker("Kategorie", selection: $selection) {
|
Picker("Kategorie", selection: $selection) {
|
||||||
@@ -18,17 +20,23 @@ struct CategoryPicker: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var gefiltert: [CategoryItem] {
|
||||||
|
guard let tracking else { return categories }
|
||||||
|
return categories.filter { $0.tracking == tracking }
|
||||||
|
}
|
||||||
|
|
||||||
private func flattened() -> [(item: CategoryItem, depth: Int)] {
|
private func flattened() -> [(item: CategoryItem, depth: Int)] {
|
||||||
let ids = Set(categories.map(\.id))
|
let quelle = gefiltert
|
||||||
|
let ids = Set(quelle.map(\.id))
|
||||||
var result: [(CategoryItem, Int)] = []
|
var result: [(CategoryItem, Int)] = []
|
||||||
|
|
||||||
func walk(_ node: CategoryItem, _ depth: Int) {
|
func walk(_ node: CategoryItem, _ depth: Int) {
|
||||||
result.append((node, depth))
|
result.append((node, depth))
|
||||||
for child in categories.filter({ $0.parentId == node.id }) {
|
for child in quelle.filter({ $0.parentId == node.id }) {
|
||||||
walk(child, depth + 1)
|
walk(child, depth + 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for root in categories.filter({ $0.parentId == nil || !ids.contains($0.parentId!) }) {
|
for root in quelle.filter({ $0.parentId == nil || !ids.contains($0.parentId!) }) {
|
||||||
walk(root, 0)
|
walk(root, 0)
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -84,11 +84,12 @@ struct ProductDetailView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
CategoryPicker(categories: categories, selection: $categoryId)
|
CategoryPicker(categories: categories, selection: $categoryId,
|
||||||
|
tracking: current.tracking)
|
||||||
} footer: {
|
} footer: {
|
||||||
Text(current.isObject
|
Text(current.isObject
|
||||||
? "Kategorie bestimmt die Verwaltungsart (Gegenstand) und die eigenen Felder."
|
? "Nur Gegenstands-Kategorien. Bestimmt die eigenen Felder."
|
||||||
: "Kategorie: nur für den Überblick in der Artikelliste.")
|
: "Nur Lebensmittel-Kategorien.")
|
||||||
}
|
}
|
||||||
|
|
||||||
if current.isObject {
|
if current.isObject {
|
||||||
|
|||||||
@@ -70,6 +70,8 @@ struct ProductFormView: View {
|
|||||||
@State private var datePrecision = "day"
|
@State private var datePrecision = "day"
|
||||||
@State private var selectedGroupId: Int?
|
@State private var selectedGroupId: Int?
|
||||||
@State private var selectedCategoryId: Int?
|
@State private var selectedCategoryId: Int?
|
||||||
|
// Umschalter Lebensmittel/Gegenstand: filtert die Kategorieauswahl.
|
||||||
|
@State private var modus = "object"
|
||||||
@State private var groups: [GroupItem] = []
|
@State private var groups: [GroupItem] = []
|
||||||
@State private var categories: [CategoryItem] = []
|
@State private var categories: [CategoryItem] = []
|
||||||
@State private var units: [Unit] = []
|
@State private var units: [Unit] = []
|
||||||
@@ -89,43 +91,61 @@ struct ProductFormView: View {
|
|||||||
LabeledField(label: "Marke", text: $brand)
|
LabeledField(label: "Marke", text: $brand)
|
||||||
}
|
}
|
||||||
Section {
|
Section {
|
||||||
CategoryPicker(categories: categories, selection: $selectedCategoryId)
|
Picker("Typ", selection: $modus) {
|
||||||
|
Text("Lebensmittel").tag("food")
|
||||||
|
Text("Gegenstand").tag("object")
|
||||||
|
}
|
||||||
|
.pickerStyle(.segmented)
|
||||||
|
CategoryPicker(categories: categories, selection: $selectedCategoryId, tracking: modus)
|
||||||
} footer: {
|
} footer: {
|
||||||
Text("Kategorie: nur für den Überblick in der Artikelliste.")
|
Text(modus == "object"
|
||||||
|
? "Gegenstand: Menge je Lagerort und eigene Felder."
|
||||||
|
: "Lebensmittel: Chargen mit Mindesthaltbarkeit.")
|
||||||
}
|
}
|
||||||
Section {
|
.onChange(of: modus) { neu in
|
||||||
Picker("Gruppe", selection: $selectedGroupId) {
|
// Passt die gewählte Kategorie nicht mehr zum Typ, Auswahl leeren.
|
||||||
Text("– keine –").tag(Int?.none)
|
if let cid = selectedCategoryId,
|
||||||
ForEach(groups) { gruppe in
|
categories.first(where: { $0.id == cid })?.tracking != neu {
|
||||||
Text(gruppe.name).tag(Int?.some(gruppe.id))
|
selectedCategoryId = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Lebensmittel-Felder (Gruppe, Einheit, MHD) nur im Lebensmittel-Modus.
|
||||||
|
// Fuer Gegenstaende irrelevant - sie zaehlen in Stueck je Lagerort.
|
||||||
|
if modus == "food" {
|
||||||
|
Section {
|
||||||
|
Picker("Gruppe", selection: $selectedGroupId) {
|
||||||
|
Text("– keine –").tag(Int?.none)
|
||||||
|
ForEach(groups) { gruppe in
|
||||||
|
Text(gruppe.name).tag(Int?.some(gruppe.id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if session.isAdmin {
|
||||||
|
Button {
|
||||||
|
newGroupShown = true
|
||||||
|
} label: {
|
||||||
|
Label("Neue Gruppe anlegen", systemImage: "plus")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} footer: {
|
||||||
|
Text("Gruppe: zählt Bestände mehrerer Marken zusammen. Der EAN-Code "
|
||||||
|
+ "dieses Artikels erscheint danach automatisch bei der Gruppe.")
|
||||||
|
}
|
||||||
|
Section("Einheit") {
|
||||||
|
Picker("Basiseinheit", selection: $baseUnit) {
|
||||||
|
ForEach(baseUnits, id: \.0) { Text($0.1).tag($0.0) }
|
||||||
|
}
|
||||||
|
QuantityField(label: "Packungsgröße", text: $packageSize,
|
||||||
|
suffix: DisplaySettings.baseUnitLabel(baseUnit))
|
||||||
|
Picker("Bezeichnung", selection: $packageLabel) {
|
||||||
|
ForEach(labels, id: \.self) { Text($0.isEmpty ? "Packung (Standard)" : $0).tag($0) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if session.isAdmin {
|
Section("MHD") {
|
||||||
Button {
|
Picker("Angabe", selection: $datePrecision) {
|
||||||
newGroupShown = true
|
Text("Tagesdatum").tag("day")
|
||||||
} label: {
|
Text("nur Monat/Jahr").tag("month")
|
||||||
Label("Neue Gruppe anlegen", systemImage: "plus")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} footer: {
|
|
||||||
Text("Gruppe: zählt Bestände mehrerer Marken zusammen. Der EAN-Code "
|
|
||||||
+ "dieses Artikels erscheint danach automatisch bei der Gruppe.")
|
|
||||||
}
|
|
||||||
Section("Einheit") {
|
|
||||||
Picker("Basiseinheit", selection: $baseUnit) {
|
|
||||||
ForEach(baseUnits, id: \.0) { Text($0.1).tag($0.0) }
|
|
||||||
}
|
|
||||||
QuantityField(label: "Packungsgröße", text: $packageSize,
|
|
||||||
suffix: DisplaySettings.baseUnitLabel(baseUnit))
|
|
||||||
Picker("Bezeichnung", selection: $packageLabel) {
|
|
||||||
ForEach(labels, id: \.self) { Text($0.isEmpty ? "Packung (Standard)" : $0).tag($0) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Section("MHD") {
|
|
||||||
Picker("Angabe", selection: $datePrecision) {
|
|
||||||
Text("Tagesdatum").tag("day")
|
|
||||||
Text("nur Monat/Jahr").tag("month")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if let error {
|
if let error {
|
||||||
Section { Text(error).foregroundStyle(.red).font(.callout) }
|
Section { Text(error).foregroundStyle(.red).font(.callout) }
|
||||||
@@ -142,6 +162,11 @@ struct ProductFormView: View {
|
|||||||
groups = (try? await APIClient.shared.groups()) ?? []
|
groups = (try? await APIClient.shared.groups()) ?? []
|
||||||
categories = (try? await APIClient.shared.categories()) ?? []
|
categories = (try? await APIClient.shared.categories()) ?? []
|
||||||
units = (try? await APIClient.shared.units()) ?? []
|
units = (try? await APIClient.shared.units()) ?? []
|
||||||
|
// Kam eine Kategorie aus dem Barcode-Vorschlag, den Typ dazu setzen.
|
||||||
|
if let cid = selectedCategoryId,
|
||||||
|
let t = categories.first(where: { $0.id == cid })?.tracking {
|
||||||
|
modus = t
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $newGroupShown) {
|
.sheet(isPresented: $newGroupShown) {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
|
|||||||
Reference in New Issue
Block a user