import SwiftUI /// Startseite: Kennzahlen des Servers und die letzten Bewegungen. /// /// Bewusst ohne Diagramme - die Kennzahl selbst ist die Antwort, und jede /// fuehrt per Tipp dorthin, wo man etwas tun kann. struct OverviewView: View { @EnvironmentObject private var session: Session @EnvironmentObject private var display: DisplaySettings @State private var stats: DashboardStats? @State private var letzte: [Movement] = [] @State private var busy = true @State private var error: String? @State private var manageShown = false private let spalten = [GridItem(.flexible()), GridItem(.flexible())] var body: some View { NavigationStack { List { if let error { Section { Text(error).foregroundStyle(.red).font(.callout) } } Section { LazyVGrid(columns: spalten, spacing: 12) { NavigationLink { ProductListView() } label: { StatTile(title: "Artikel mit Bestand", value: stats.map { String($0.productsInStock) } ?? "–", detail: stats.map { "von \($0.productsTotal)" } ?? "", systemImage: "shippingbox", tint: .accentColor) } NavigationLink { ShoppingListView() } label: { StatTile(title: "Einzukaufen", value: stats.map { String($0.shoppingItems) } ?? "–", detail: "unter Mindestbestand", systemImage: "cart", tint: (stats?.shoppingItems ?? 0) > 0 ? .orange : .secondary) } NavigationLink { ExpiringView() } label: { StatTile(title: "Läuft bald ab", value: stats.map { String($0.expiringSoon) } ?? "–", detail: "Chargen", systemImage: "clock", tint: (stats?.expiringSoon ?? 0) > 0 ? .orange : .secondary) } NavigationLink { ExpiringView() } label: { StatTile(title: "Abgelaufen", value: stats.map { String($0.expired) } ?? "–", detail: "Chargen", systemImage: "exclamationmark.triangle", tint: (stats?.expired ?? 0) > 0 ? .red : .secondary) } } .buttonStyle(.plain) .listRowInsets(EdgeInsets(top: 8, leading: 8, bottom: 8, trailing: 8)) } Section { ForEach(letzte) { movement in MovementRow(movement: movement) } if letzte.isEmpty && !busy { Text("Noch keine Bewegungen.").foregroundStyle(.secondary) } NavigationLink("Ganzen Verlauf ansehen") { HistoryView() } } header: { Text("Letzte Bewegungen") } } .navigationTitle(session.activeProfile?.name ?? "Vorrania") .toolbar { ToolbarItem(placement: .topBarTrailing) { ServerMenu(manageShown: $manageShown) } } .sheet(isPresented: $manageShown) { ServerListView() } .refreshable { await load() } .task(id: session.activeProfileID) { await load() } } } private func load() async { busy = true defer { busy = false } do { stats = try await APIClient.shared.dashboardStats() letzte = try await APIClient.shared.movements(limit: 10) error = nil } catch { self.error = error.localizedDescription } } } /// Eine Kennzahl als Kachel. struct StatTile: View { let title: String let value: String let detail: String let systemImage: String let tint: Color var body: some View { VStack(alignment: .leading, spacing: 6) { HStack(spacing: 6) { Image(systemName: systemImage).font(.caption).foregroundStyle(tint) Text(title).font(.caption).foregroundStyle(.secondary) .lineLimit(1).minimumScaleFactor(0.8) } Text(value).font(.title).bold().foregroundStyle(tint) if !detail.isEmpty { Text(detail).font(.caption2).foregroundStyle(.tertiary) .lineLimit(1).minimumScaleFactor(0.8) } } .frame(maxWidth: .infinity, alignment: .leading) .padding(12) .background(Color(.secondarySystemBackground)) .clipShape(RoundedRectangle(cornerRadius: 12)) } }