Lagerort- und Kategorieauswahl zeigen den Baum statt einer Mischung

Die Auswahl war nicht flach, weil die Beschriftung gefehlt haette - der volle
Pfad stand laengst drin. Sie kam vom Server nach dem BLATTnamen sortiert, und
so standen "Garage -> Fach A" und "Keller -> Fach A" irgendwo zwischen allem
anderen. Geschwister und Kinder fanden nie zusammen.

Neu ist baum() in Baum.swift: bringt eine flache Eltern-Kind-Liste in
Baumreihenfolge und rueckt je Ebene ein. Waisen gelten als oberste Ebene,
damit nichts lautlos aus der Auswahl faellt.

Derselbe Lauf lag bisher dreimal im Projekt (CategoryPicker, TreeMasterView,
LocationEditor). Alle drei zeigen jetzt auf den einen - unterm Strich
verschwinden mehr Zeilen als dazukommen. Zwei der Kopien waren auch nicht
gleichwertig: LocationEditor.pfad hatte den Zyklusschutz von path(in:) nicht
und waere bei einer ringfoermigen Elternkette endlos gelaufen.

Umgestellt sind neun Auswahlfelder. Die Lagerortzeile beim Kassenzettel zeigte
nur den nackten Namen und bekommt den vollen Pfad - dort ist kein Platz zum
Einruecken.

MARKETING_VERSION stand auf 1.2 in der project.pbxproj, aber auf 1.1 in der
project.yml, aus der sie erzeugt wird. Die Quelle nachgezogen, sonst haette
das naechste "xcodegen generate" die Anhebung zurueckgedreht.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-10-15 20:11:25 +02:00
co-authored by Claude Opus 5
parent 264d3886f3
commit 501d80a9b8
11 changed files with 69 additions and 87 deletions
+8 -54
View File
@@ -197,33 +197,11 @@ struct TreeMasterView<Item: TreeItem, Header: View, Editor: View>: View {
@State private var addShown = false
@State private var pendingDeletion: Item?
private struct Node: Identifiable {
let item: Item
let depth: Int
var id: Item.ID { item.id }
}
private var shown: [Item] { items.filter(include) }
/// Baumreihenfolge mit Tiefe. Waisen (Elternteil gefiltert/gelöscht) gelten
/// als oberste Ebene, damit nichts verschwindet.
private var nodes: [Node] {
let list = shown
let ids = Set(list.map(\.id))
var result: [Node] = []
func walk(_ n: Item, _ depth: Int) {
result.append(Node(item: n, depth: depth))
for child in list.filter({ $0.parentId == n.id }) { walk(child, depth + 1) }
}
for root in list.filter({ $0.parentId == nil || !ids.contains($0.parentId!) }) {
walk(root, 0)
}
return result
}
private func childCount(_ id: Item.ID) -> Int { shown.filter { $0.parentId == id }.count }
private var visible: [Node] {
private var visible: [Baumzeile<Item>] {
let parent = Dictionary(uniqueKeysWithValues: shown.map { ($0.id, $0.parentId) })
func hidden(_ id: Item.ID) -> Bool {
var p = parent[id] ?? nil
@@ -233,7 +211,7 @@ struct TreeMasterView<Item: TreeItem, Header: View, Editor: View>: View {
}
return false
}
return nodes.filter { !hidden($0.item.id) }
return baum(shown).filter { !hidden($0.item.id) }
}
var body: some View {
@@ -286,7 +264,7 @@ struct TreeMasterView<Item: TreeItem, Header: View, Editor: View>: View {
}
@ViewBuilder
private func row(_ node: Node) -> some View {
private func row(_ node: Baumzeile<Item>) -> some View {
let kinder = childCount(node.item.id)
let zu = collapsed.contains(node.item.id)
HStack(spacing: 8) {
@@ -321,7 +299,7 @@ struct TreeMasterView<Item: TreeItem, Header: View, Editor: View>: View {
}
.buttonStyle(.plain)
}
.padding(.leading, CGFloat(node.depth) * 16)
.padding(.leading, CGFloat(node.tiefe) * 16)
}
private func toggle(_ id: Item.ID) {
@@ -468,8 +446,8 @@ struct LocationEditor: View {
Section {
Picker("Übergeordnet", selection: $parentId) {
Text("– oberste Ebene –").tag(String?.none)
ForEach(parentOptions) { loc in
Text(LocationEditor.pfad(loc, in: all)).tag(String?.some(loc.id))
ForEach(baum(parentOptions)) { eintrag in
Text(eintrag.label).tag(String?.some(eintrag.item.id))
}
}
} header: {
@@ -523,19 +501,6 @@ struct LocationEditor: View {
}
return result
}
/// "Keller → Regal" – der Pfad macht gleiche Namen im flachen Picker
/// unterscheidbar.
private static func pfad(_ loc: StorageLocation, in all: [StorageLocation]) -> String {
let byId = Dictionary(uniqueKeysWithValues: all.map { ($0.id, $0) })
var teile = [loc.name]
var pid = loc.parentId
while let cur = pid, let parent = byId[cur] {
teile.insert(parent.name, at: 0)
pid = parent.parentId
}
return teile.joined(separator: " → ")
}
}
struct UnitsView: View {
@@ -820,8 +785,8 @@ struct CategoryEditor: View {
Section {
Picker("Übergeordnet", selection: $parentId) {
Text("– oberste Ebene –").tag(Int?.none)
ForEach(all) { c in
Text(CategoryEditor.pfad(c, in: all)).tag(Int?.some(c.id))
ForEach(baum(all)) { eintrag in
Text(eintrag.label).tag(Int?.some(eintrag.item.id))
}
}
} header: {
@@ -896,17 +861,6 @@ struct CategoryEditor: View {
self.error = error.localizedDescription
}
}
private static func pfad(_ c: CategoryItem, in all: [CategoryItem]) -> String {
let byId = Dictionary(uniqueKeysWithValues: all.map { ($0.id, $0) })
var teile = [c.name]
var pid = c.parentId
while let cur = pid, let parent = byId[cur] {
teile.insert(parent.name, at: 0)
pid = parent.parentId
}
return teile.joined(separator: " → ")
}
}
// MARK: - Eigene Felder je Kategorie