Files
Vorrania/ios/Sources/CategoryPicker.swift
T
ScarriffleandClaude Opus 5 501d80a9b8 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>
2026-10-15 20:11:25 +02:00

96 lines
3.1 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import SwiftUI
/// Auswahl einer Kategorie mit eingerueckten Unterkategorien.
///
/// Kategorie und Gruppe sind zwei verschiedene Dinge: Die Kategorie ordnet nur
/// die Artikelliste, die Gruppe zaehlt Bestaende mehrerer Marken zusammen.
struct CategoryPicker: View {
let categories: [CategoryItem]
@Binding var selection: Int?
/// Nur Kategorien dieses Typs zeigen ("food"/"object"); nil = alle.
var tracking: String? = nil
var body: some View {
Picker("Kategorie", selection: $selection) {
Text("– keine –").tag(Int?.none)
ForEach(baum(gefiltert)) { eintrag in
Text(eintrag.label).tag(Int?.some(eintrag.item.id))
}
}
}
private var gefiltert: [CategoryItem] {
guard let tracking else { return categories }
return categories.filter { $0.tracking == tracking }
}
}
/// Legt eine Gruppe an Ort und Stelle an, damit man das Formular nicht
/// verlassen muss. Nur fuer Administratoren - POST /groups verlangt das.
struct NewGroupSheet: View {
let units: [Unit]
var onCreated: (GroupItem) -> Void
@Environment(\.dismiss) private var dismiss
@State private var name = ""
@State private var minStock = ""
@State private var unitId: Int?
@State private var busy = false
@State private var error: String?
var body: some View {
Form {
Section {
LabeledField(label: "Name", text: $name)
} footer: {
Text("Eine Gruppe zählt Bestände mehrerer Marken zusammen – "
+ "z. B. Mehl von verschiedenen Herstellern als „5 kg Mehl“.")
}
Section("Mindestbestand (optional)") {
QuantityField(label: "Menge", text: $minStock)
Picker("Einheit", selection: $unitId) {
Text("– Basiseinheit –").tag(Int?.none)
ForEach(units) { unit in
Text(unit.name).tag(Int?.some(unit.id))
}
}
}
if let error {
Section { Text(error).foregroundStyle(.red).font(.callout) }
}
Section {
Button(busy ? "Anlegen…" : "Anlegen") { Task { await create() } }
.disabled(busy || name.trimmingCharacters(in: .whitespaces).isEmpty)
}
}
.navigationTitle("Neue Gruppe")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .topBarLeading) { Button("Abbrechen") { dismiss() } }
}
}
private func create() async {
error = nil
busy = true
defer { busy = false }
do {
let created = try await APIClient.shared.createGroup(
NewGroupRequest(
name: name.trimmingCharacters(in: .whitespaces),
minStock: Double(minStock.replacingOccurrences(of: ",", with: ".")),
minStockUnitId: unitId
)
)
onCreated(created)
dismiss()
} catch {
self.error = error.localizedDescription
}
}
}