Files
Vorrania/ios/Sources/LocalOverview.swift
Scarriffle 1648d1b917 iOS: Kassenzettel in den Scannen-Tab, Markenblau statt System-Blau, weisse Kacheln, luftigere Pruefen-Liste
- Kassenzettel-Kachel vom Listen- in den Scannen-Tab verschoben (passt thematisch).
- App-Akzent auf Markenblau (#3F51B5/#7C8AE8, wie Web/App-Icon) via .tint; alle
  Color.accentColor -> Color.marke. System-Blau verschwindet.
- Kachel-Buttons/Links auf .buttonStyle(.plain): weisse Schrift/Symbole statt
  getoentem Blau.
- Kassenzettel-Pruefen-Liste: je Artikel eine eigene Section (insetGrouped) mit
  Artikel/Menge/Lagerort in eigenen Zeilen - klar gruppiert, nicht mehr gequetscht.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-30 19:05:03 +02:00

249 lines
8.6 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
/// Die eigene, lokale App-Übersicht eines Servers: mehrere Dashboards, Karten
/// untereinander, frei zusammenstellbar. Nur auf diesem Gerät gespeichert.
///
/// Anzeige und Bearbeitung sind getrennt: Beim Bearbeiten zeigt eine kompakte,
/// sortierbare Liste die Karten (so funktionieren Ziehen und Löschen sauber),
/// beim Ansehen die vollen Karten.
struct LocalOverview: View {
let profileID: UUID
@State private var config = AppOverviewConfig()
@State private var auswahl: UUID?
@State private var bearbeiten = false
@State private var addShown = false
@State private var produkte: [Product] = []
@State private var stand = 0
@State private var renameText = ""
@State private var renameShown = false
private var aktives: AppDashboard? {
config.dashboards.first { $0.id == auswahl } ?? config.dashboards.first
}
var body: some View {
Group {
if bearbeiten {
editierListe
} else {
anzeige
}
}
.toolbar { ToolbarItemGroup(placement: .bottomBar) { werkzeugleiste } }
.sheet(isPresented: $addShown) {
CardPickerView { neu in fuegeHinzu(neu) }
}
.alert("Dashboard umbenennen", isPresented: $renameShown) {
TextField("Name", text: $renameText)
Button("Sichern") { speichereName() }
Button("Abbrechen", role: .cancel) {}
}
.task(id: profileID) {
config = AppDashboardStore.config(for: profileID)
if auswahl == nil || !config.dashboards.contains(where: { $0.id == auswahl }) {
auswahl = config.dashboards.first?.id
}
produkte = (try? await APIClient.shared.searchProducts("")) ?? []
}
}
// MARK: - Anzeige
private var anzeige: some View {
List {
if config.dashboards.count > 1 {
Section { dashboardPicker }
}
if let aktives {
ForEach(aktives.cards) { karte in
Section {
DashboardCardView(type: karte.type, productId: karte.productId,
stand: stand) { stand += 1 }
} header: {
Text(titel(karte))
}
}
if aktives.cards.isEmpty {
Text("Noch keine Karten. Über „Bearbeiten“ lassen sich welche hinzufügen.")
.foregroundStyle(.secondary)
}
}
}
.refreshable { stand += 1 }
}
// MARK: - Bearbeiten
private var editierListe: some View {
List {
if config.dashboards.count > 1 {
Section { dashboardPicker }
}
Section {
ForEach(aktives?.cards ?? []) { karte in
HStack {
VStack(alignment: .leading, spacing: 2) {
Text(CardCatalog.titel(karte.type))
if CardCatalogList.kind(for: karte.type)?.needsProduct == true {
artikelWahl(fuer: karte)
}
}
Spacer()
}
}
.onDelete(perform: loesche)
.onMove(perform: verschiebe)
} header: {
Text("Karten ziehen zum Sortieren")
}
}
.environment(\.editMode, .constant(.active))
}
private var dashboardPicker: some View {
Picker("Dashboard", selection: Binding(
get: { aktives?.id ?? UUID() }, set: { auswahl = $0 }
)) {
ForEach(config.dashboards) { Text($0.name).tag($0.id) }
}
.pickerStyle(.menu)
}
@ViewBuilder private var werkzeugleiste: some View {
if bearbeiten {
Button { addShown = true } label: { Label("Karte", systemImage: "plus") }
Spacer()
Menu {
Button("Dashboard hinzufügen", systemImage: "plus") { neuesDashboard() }
if aktives != nil {
Button("Umbenennen", systemImage: "pencil") {
renameText = aktives?.name ?? ""; renameShown = true
}
if config.dashboards.count > 1 {
Button("Dashboard löschen", systemImage: "trash", role: .destructive) {
loescheDashboard()
}
}
}
} label: {
Label("Dashboards", systemImage: "rectangle.stack")
}
Spacer()
Button("Fertig") { bearbeiten = false }
} else {
Spacer()
Button { bearbeiten = true } label: {
Label("Bearbeiten", systemImage: "slider.horizontal.3")
}
}
}
private func titel(_ karte: AppCard) -> String {
if let pid = karte.productId, let p = produkte.first(where: { $0.id == pid }) {
return "\(CardCatalog.titel(karte.type)): \(p.name)"
}
return CardCatalog.titel(karte.type)
}
private func artikelWahl(fuer karte: AppCard) -> some View {
Menu {
Button(" kein Artikel ") { setzeProdukt(karte, nil) }
ForEach(produkte) { p in
Button(p.name) { setzeProdukt(karte, p.id) }
}
} label: {
Text(produkte.first { $0.id == karte.productId }?.name ?? "Artikel wählen")
.font(.caption).foregroundStyle(Color.marke)
}
}
// MARK: - Datenänderungen (alle sofort gespeichert)
private func mutiere(_ block: (inout AppDashboard) -> Void) {
guard let id = aktives?.id,
let index = config.dashboards.firstIndex(where: { $0.id == id }) else { return }
block(&config.dashboards[index])
AppDashboardStore.save(config, for: profileID)
}
private func fuegeHinzu(_ karte: AppCard) { mutiere { $0.cards.append(karte) } }
private func setzeProdukt(_ karte: AppCard, _ pid: Int?) {
mutiere { dash in
if let i = dash.cards.firstIndex(where: { $0.id == karte.id }) {
dash.cards[i].productId = pid
}
}
stand += 1
}
private func loesche(at offsets: IndexSet) { mutiere { $0.cards.remove(atOffsets: offsets) } }
private func verschiebe(from: IndexSet, to: Int) { mutiere { $0.cards.move(fromOffsets: from, toOffset: to) } }
private func neuesDashboard() {
let neu = AppDashboard(name: "Neues Dashboard")
config.dashboards.append(neu)
AppDashboardStore.save(config, for: profileID)
auswahl = neu.id
}
private func loescheDashboard() {
guard let id = aktives?.id else { return }
config.dashboards.removeAll { $0.id == id }
AppDashboardStore.save(config, for: profileID)
auswahl = config.dashboards.first?.id
}
private func speichereName() {
let name = renameText.trimmingCharacters(in: .whitespaces)
guard !name.isEmpty else { return }
mutiere { $0.name = name }
}
}
/// Auswahl einer neuen Karte. Diagramme sind eigens gekennzeichnet.
struct CardPickerView: View {
let onPick: (AppCard) -> Void
@Environment(\.dismiss) private var dismiss
var body: some View {
NavigationStack {
List {
Section("Karten") {
ForEach(CardCatalogList.all.filter { !$0.isChart }) { kind in
zeile(kind)
}
}
Section("Diagramme") {
ForEach(CardCatalogList.all.filter { $0.isChart }) { kind in
zeile(kind)
}
}
}
.navigationTitle("Karte hinzufügen")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .topBarLeading) { Button("Abbrechen") { dismiss() } }
}
}
}
private func zeile(_ kind: CardKind) -> some View {
Button {
onPick(AppCard(type: kind.type))
dismiss()
} label: {
HStack {
Text(kind.title)
if kind.needsProduct {
Text("· Artikel").font(.caption).foregroundStyle(.secondary)
}
Spacer()
Image(systemName: "plus.circle").foregroundStyle(Color.marke)
}
}
.foregroundStyle(.primary)
}
}