iOS (neu, ios/):
- SwiftUI-App: Login (Server + Keychain-Token), Kamera-Scanner (EAN/UPC/Code128),
Einlagern mit mehreren Chargen und eigenem MHD, Auslagern mit Chargenauswahl
oder FEFO, Artikelsuche, Anlegen mit Open-Food-Facts-Vorbefuellung.
- Home-Screen-Shortcuts: Schnellaktionen (langer Druck) und URL-Schema
projectgood://checkin | ://checkout fuer eigene Symbole via Kurzbefehle.
- project.yml (XcodeGen) + README mit Build-Anleitung. NICHT kompiliert - auf
diesem Rechner ist kein Xcode vorhanden.
Import-Modi (wie besprochen sinnvoll):
- add (Standard, nichts loeschen), replace_listed (Bestaende der in der Datei
genannten Produkte ersetzen - fuer Inventur), replace_all (alles ersetzen).
Geleerte Bestaende werden als Korrektur-Bewegung protokolliert; die Oberflaeche
fragt bei den zerstoerenden Modi nach.
Anzeige:
- "Bald ablaufend"/"Abgelaufen" zeigen jetzt das Gebinde ("1 Glas") mit der
Basiseinheit klein darunter (ExpiringItem liefert die Einheiten mit).
- MHD als Zeitspanne mit korrektem Numerus: "in 3 Tagen", "in 1 Woche",
"vor 2 Wochen", dazu heute/morgen/gestern.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
94 lines
2.9 KiB
Swift
94 lines
2.9 KiB
Swift
import SwiftUI
|
|
|
|
struct RootView: View {
|
|
@EnvironmentObject private var session: Session
|
|
@EnvironmentObject private var router: Router
|
|
|
|
var body: some View {
|
|
Group {
|
|
if session.isLoggedIn {
|
|
HomeView()
|
|
} else {
|
|
LoginView()
|
|
}
|
|
}
|
|
// Shortcut/URL öffnet den passenden Scan-Bildschirm direkt.
|
|
.fullScreenCover(item: $router.route) { route in
|
|
NavigationStack {
|
|
switch route {
|
|
case .checkin: CheckInView()
|
|
case .checkout: CheckOutView()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
extension Route: Identifiable {
|
|
var id: String { rawValue }
|
|
}
|
|
|
|
struct HomeView: View {
|
|
@EnvironmentObject private var session: Session
|
|
@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: {
|
|
ActionTile(title: "Auslagern", subtitle: "Barcode scannen und Menge entnehmen",
|
|
systemImage: "arrow.up.from.line")
|
|
}
|
|
Spacer()
|
|
}
|
|
.padding()
|
|
.navigationTitle("Project-Good")
|
|
.toolbar {
|
|
ToolbarItem(placement: .topBarTrailing) {
|
|
Menu {
|
|
Text("Angemeldet als \(session.username)")
|
|
Button("Abmelden", role: .destructive) { session.logout() }
|
|
} label: {
|
|
Image(systemName: "person.circle")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|