iOS: Produktliste - Kategoriefilter nach Typ + Sortierung

Kategoriefilter zeigt nur noch Kategorien des Listentyps (Lebensmittel-Liste nur
Food-Kategorien, Gegenstaende-Liste nur Objekt-Kategorien) - vorher konnte man
sinnlos ueberkreuz filtern. Neue Sortierung (Name A-Z/Z-A, Zuletzt geaendert)
per Menue-Picker; Product traegt dafuer updated_at.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-07-28 13:43:44 +02:00
parent d029c882f1
commit 4f8e6e3185
2 changed files with 41 additions and 4 deletions

View File

@@ -201,15 +201,42 @@ struct ProductListView: View {
@State private var categories: [CategoryItem] = []
/// nil = alle, 0 = ohne Kategorie, sonst die ID (Unterkategorien zaehlen mit).
@State private var categoryId: Int?
@State private var sortMode: SortMode = .nameAsc
@State private var busy = false
@State private var showNew = false
enum SortMode: String, CaseIterable, Identifiable {
case nameAsc = "Name AZ"
case nameDesc = "Name ZA"
case updated = "Zuletzt geändert"
var id: String { rawValue }
}
/// Auf den festen Typ gefiltert (clientseitig, wie im Web).
private var sichtbar: [Product] {
guard let fixedType else { return products }
return products.filter { ($0.isObject ? "object" : "food") == fixedType }
}
/// Sichtbare Produkte in der gewählten Reihenfolge.
private var sortiert: [Product] {
switch sortMode {
case .nameAsc:
return sichtbar.sorted { $0.name.localizedCaseInsensitiveCompare($1.name) == .orderedAscending }
case .nameDesc:
return sichtbar.sorted { $0.name.localizedCaseInsensitiveCompare($1.name) == .orderedDescending }
case .updated:
// ISO-Strings gleichen Formats sind chronologisch vergleichbar; neueste zuerst.
return sichtbar.sorted { ($0.updatedAt ?? "") > ($1.updatedAt ?? "") }
}
}
/// Nur Kategorien des Listentyps (Lebensmittel-Liste zeigt nur Food-Kategorien usw.).
private var typKategorien: [CategoryItem] {
guard let fixedType else { return categories }
return categories.filter { ($0.tracking ?? "food") == fixedType }
}
private var titel: String {
switch fixedType {
case "food": return "Lebensmittel"
@@ -243,10 +270,16 @@ struct ProductListView: View {
Text(filterLabel).foregroundStyle(.secondary)
}
}
Picker(selection: $sortMode) {
ForEach(SortMode.allCases) { Text($0.rawValue).tag($0) }
} label: {
Label("Sortierung", systemImage: "arrow.up.arrow.down")
}
.pickerStyle(.menu)
}
Section {
ForEach(sichtbar) { product in
ForEach(sortiert) { product in
NavigationLink {
ProductDetailView(product: product)
} label: {
@@ -308,17 +341,18 @@ struct ProductListView: View {
}
private func categoryTree() -> [TreeEntry] {
let ids = Set(categories.map(\.id))
let liste = typKategorien
let ids = Set(liste.map(\.id))
var result: [TreeEntry] = []
func walk(_ node: CategoryItem, _ depth: Int) {
result.append(TreeEntry(item: node, depth: depth))
for child in categories.filter({ $0.parentId == node.id }) {
for child in liste.filter({ $0.parentId == node.id }) {
walk(child, depth + 1)
}
}
// Waisen (Oberkategorie geloescht) gelten als oberste Ebene.
for root in categories.filter({ $0.parentId == nil || !ids.contains($0.parentId!) }) {
for root in liste.filter({ $0.parentId == nil || !ids.contains($0.parentId!) }) {
walk(root, 0)
}
return result