import SwiftUI struct RootView: View { @EnvironmentObject private var session: Session @EnvironmentObject private var router: Router @EnvironmentObject private var display: DisplaySettings var body: some View { Group { if session.isLoggedIn { HomeView() } else { LoginView() } } // Anzeigeeinstellungen (Datumsformat) gehoeren dem Server - bei einem // Serverwechsel muessen sie deshalb neu geladen werden, sonst zeigt // Server B die Formate von Server A. .task(id: session.activeProfileID) { if session.isLoggedIn { await display.load() await session.refreshMe() } } // Shortcut/URL öffnet den passenden Scan-Bildschirm direkt; eine // getippte Benachrichtigung öffnet „Bald ablaufend". .fullScreenCover(item: $router.route) { route in NavigationStack { switch route { case .checkin: CheckInView() case .checkout: CheckOutView() case .expiring: ExpiringView() .toolbar { ToolbarItem(placement: .topBarLeading) { Button("Fertig") { router.route = nil } } } } } } } } extension Route: Identifiable { var id: String { rawValue } } struct HomeView: View { @EnvironmentObject private var session: Session var body: some View { TabView { DashboardView() .tabItem { Label("Start", systemImage: "house") } ScanTabView() .tabItem { Label("Scannen", systemImage: "barcode.viewfinder") } ListsTabView() .tabItem { Label("Listen", systemImage: "list.bullet") } // Stammdaten gehoeren Administratoren - dieselbe Regel wie in der // Web-Oberflaeche (web/src/App.jsx). if session.isAdmin { AdminTabView() .tabItem { Label("Verwaltung", systemImage: "gearshape") } } } } } /// Die beiden Kacheln, die frueher die Startseite waren. struct ScanTabView: View { @EnvironmentObject private var router: Router var body: some View { NavigationStack { VStack(spacing: 16) { Button { router.route = .checkin } label: { ActionTile(title: "Einlagern", subtitle: "Barcode scannen und Bestand erfassen", systemImage: "arrow.down.to.line") } Button { router.route = .checkout } label: { // "arrow.up.from.line" gibt es als SF Symbol nicht - das Icon // blieb dadurch leer und es war nur die Kachelflaeche zu sehen. ActionTile(title: "Auslagern", subtitle: "Barcode scannen und Menge entnehmen", systemImage: "arrow.up.to.line") } Spacer() } .padding() .navigationTitle("Scannen") } } } struct ListsTabView: View { var body: some View { NavigationStack { VStack(spacing: 16) { NavigationLink { ProductListView() } label: { ActionTile(title: "Produkte", subtitle: "Suchen, ansehen und bearbeiten", systemImage: "shippingbox") } NavigationLink { ShoppingListView() } label: { ActionTile(title: "Einkaufsliste", subtitle: "Was unter dem Mindestbestand liegt", systemImage: "cart") } NavigationLink { ExpiringView() } label: { ActionTile(title: "Bald ablaufend", subtitle: "Chargen mit knappem MHD", systemImage: "clock") } NavigationLink { HistoryView() } label: { ActionTile(title: "Verlauf", subtitle: "Wer hat wann was ein- und ausgelagert", systemImage: "clock.arrow.circlepath") } Spacer() } .padding() .navigationTitle("Listen") } } } struct ActionTile: View { let title: String let subtitle: String let systemImage: String var body: some View { HStack(spacing: 14) { Image(systemName: systemImage) .font(.title2) .frame(width: 44, height: 44) .background(Color.accentColor.opacity(0.15)) .clipShape(RoundedRectangle(cornerRadius: 10)) VStack(alignment: .leading, spacing: 2) { Text(title).font(.headline) Text(subtitle).font(.caption).foregroundStyle(.secondary) .multilineTextAlignment(.leading) } Spacer() Image(systemName: "chevron.right").foregroundStyle(.tertiary) } .padding() .background(Color(.secondarySystemBackground)) .clipShape(RoundedRectangle(cornerRadius: 12)) .foregroundStyle(.primary) } }