Files
Vorrania/ios/Sources/CategoryPicker.swift
Scarriffle c532492286 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>
2026-07-25 23:40:32 +02:00

114 lines
3.8 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(flattened(), id: \.item.id) { eintrag in
Text(String(repeating: " ", count: eintrag.depth) + eintrag.item.name)
.tag(Int?.some(eintrag.item.id))
}
}
}
private var gefiltert: [CategoryItem] {
guard let tracking else { return categories }
return categories.filter { $0.tracking == tracking }
}
private func flattened() -> [(item: CategoryItem, depth: Int)] {
let quelle = gefiltert
let ids = Set(quelle.map(\.id))
var result: [(CategoryItem, Int)] = []
func walk(_ node: CategoryItem, _ depth: Int) {
result.append((node, depth))
for child in quelle.filter({ $0.parentId == node.id }) {
walk(child, depth + 1)
}
}
for root in quelle.filter({ $0.parentId == nil || !ids.contains($0.parentId!) }) {
walk(root, 0)
}
return result
}
}
/// 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
}
}
}