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:
Scarriffle
2026-07-25 23:40:32 +02:00
parent 8d75ad63ce
commit c532492286
3 changed files with 71 additions and 37 deletions

View File

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

View File

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

View File

@@ -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,10 +91,27 @@ struct ProductFormView: View {
LabeledField(label: "Marke", text: $brand) LabeledField(label: "Marke", text: $brand)
} }
Section { Section {
CategoryPicker(categories: categories, selection: $selectedCategoryId) Picker("Typ", selection: $modus) {
} footer: { Text("Lebensmittel").tag("food")
Text("Kategorie: nur für den Überblick in der Artikelliste.") Text("Gegenstand").tag("object")
} }
.pickerStyle(.segmented)
CategoryPicker(categories: categories, selection: $selectedCategoryId, tracking: modus)
} footer: {
Text(modus == "object"
? "Gegenstand: Menge je Lagerort und eigene Felder."
: "Lebensmittel: Chargen mit Mindesthaltbarkeit.")
}
.onChange(of: modus) { neu in
// Passt die gewählte Kategorie nicht mehr zum Typ, Auswahl leeren.
if let cid = selectedCategoryId,
categories.first(where: { $0.id == cid })?.tracking != neu {
selectedCategoryId = nil
}
}
// Lebensmittel-Felder (Gruppe, Einheit, MHD) nur im Lebensmittel-Modus.
// Fuer Gegenstaende irrelevant - sie zaehlen in Stueck je Lagerort.
if modus == "food" {
Section { Section {
Picker("Gruppe", selection: $selectedGroupId) { Picker("Gruppe", selection: $selectedGroupId) {
Text(" keine ").tag(Int?.none) Text(" keine ").tag(Int?.none)
@@ -127,6 +146,7 @@ struct ProductFormView: View {
Text("nur Monat/Jahr").tag("month") 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 {