iOS: mehrere Server, TabBar, Uebersicht, Verlauf und Stammdaten
Die App konnte fuenf Dinge, die Web-Oberflaeche vierzehn. Alles darueber hinaus
- nachsehen was gestern passiert ist, einen Lagerort anlegen - zwang zurueck an
den Browser.
Mehrere Server: Statt einer Adresse und einem Token kennt die App jetzt
beliebig viele Profile, jedes mit eigenem Token im Keychain. Ein Wechsel ist
damit ein Tipp und meldet an keinem der Server ab. Jedes Profil hat einen frei
waehlbaren Namen ("Zuhause"), der oben in der App steht; ohne Angabe faellt er
auf den Rechnernamen zurueck. Eine bestehende Anmeldung wird beim ersten Start
in ein Profil ueberfuehrt - ohne das waere man nach dem Update abgemeldet.
Statt einer Startseite mit Kacheln gibt es vier Tabs: Start, Scannen, Listen
und - nur fuer Administratoren, wie im Web - Verwaltung. Neu darin sind eine
Uebersicht mit den Kennzahlen des Servers, die per Tipp in die passende Liste
fuehren, der Bewegungsverlauf (auch je Artikel, weil /movements danach filtern
kann) und die Stammdaten: Lagerorte, Einheiten, Gebinde, Kategorien und
Gruppen. Die fuenf teilen sich eine Ansicht und unterscheiden sich nur darin,
wie geladen und geschrieben wird.
Zwei Dinge, die beim Pruefen gegen einen echten Server auffielen: Der Server
schickt Zeitstempel in UTC, haengt hinter SQLite aber keine Zeitzone an. Naiv
gelesen haette die App jede Uhrzeit um die eigene Zeitverschiebung daneben
angezeigt, deshalb wird die nackte Form ausdruecklich als UTC gelesen. Und die
Anzeigeeinstellungen gehoeren dem Server, werden beim Wechsel also neu geladen.
Bewusst nicht dabei: Benutzer, Einstellungen, Branding und Import/Export. Die
bleiben vorerst im Web.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -26,6 +26,13 @@ struct Unit: Codable, Identifiable, Hashable {
|
||||
let name: String
|
||||
let kind: String
|
||||
let factor: Double
|
||||
/// Eingebaute Einheiten lassen sich umbenennen, aber nicht loeschen.
|
||||
let isBuiltin: Bool
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id, name, kind, factor
|
||||
case isBuiltin = "is_builtin"
|
||||
}
|
||||
}
|
||||
|
||||
struct BarcodeEntry: Codable, Identifiable, Hashable {
|
||||
@@ -404,6 +411,119 @@ struct CategoryItem: Codable, Identifiable, Hashable {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Uebersicht und Verlauf
|
||||
|
||||
/// Kennzahlen der Startseite (GET /dashboard/stats).
|
||||
struct DashboardStats: Codable {
|
||||
let productsInStock: Int
|
||||
let articleUnits: Double
|
||||
let expiringSoon: Int
|
||||
let expired: Int
|
||||
let shoppingItems: Int
|
||||
let productsTotal: Int
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case productsInStock = "products_in_stock"
|
||||
case articleUnits = "article_units"
|
||||
case expiringSoon = "expiring_soon"
|
||||
case expired
|
||||
case shoppingItems = "shopping_items"
|
||||
case productsTotal = "products_total"
|
||||
}
|
||||
}
|
||||
|
||||
/// Ein Eintrag im Bewegungsverlauf (GET /movements).
|
||||
struct Movement: Codable, Identifiable, Hashable {
|
||||
let id: Int
|
||||
let productId: Int
|
||||
let productName: String
|
||||
/// "in" oder "out"
|
||||
let type: String
|
||||
/// in Basiseinheiten
|
||||
let quantity: Double
|
||||
let baseUnit: String
|
||||
let unitUsed: String
|
||||
let username: String?
|
||||
let note: String?
|
||||
let createdAt: String
|
||||
let packageSize: Double?
|
||||
let packageLabel: String?
|
||||
let unitName: String
|
||||
let unitFactor: Double
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id, type, quantity, username, note
|
||||
case productId = "product_id"
|
||||
case productName = "product_name"
|
||||
case baseUnit = "base_unit"
|
||||
case unitUsed = "unit_used"
|
||||
case createdAt = "created_at"
|
||||
case packageSize = "package_size"
|
||||
case packageLabel = "package_label"
|
||||
case unitName = "unit_name"
|
||||
case unitFactor = "unit_factor"
|
||||
}
|
||||
|
||||
var isIncoming: Bool { type == "in" }
|
||||
}
|
||||
|
||||
// MARK: - Stammdaten
|
||||
|
||||
/// Gebinde (Packung, Glas, ...) mit Einzahl und Mehrzahl.
|
||||
struct PackageType: Codable, Identifiable, Hashable {
|
||||
let id: Int
|
||||
let singular: String
|
||||
let plural: String
|
||||
let isBuiltin: Bool
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id, singular, plural
|
||||
case isBuiltin = "is_builtin"
|
||||
}
|
||||
}
|
||||
|
||||
struct NewLocationRequest: Codable {
|
||||
let name: String
|
||||
let parentId: Int?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case name
|
||||
case parentId = "parent_id"
|
||||
}
|
||||
}
|
||||
|
||||
struct NewUnitRequest: Codable {
|
||||
let name: String
|
||||
let kind: String
|
||||
let factor: Double
|
||||
}
|
||||
|
||||
struct NewPackageTypeRequest: Codable {
|
||||
let singular: String
|
||||
let plural: String
|
||||
}
|
||||
|
||||
struct NewCategoryRequest: Codable {
|
||||
let name: String
|
||||
let parentId: Int?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case name
|
||||
case parentId = "parent_id"
|
||||
}
|
||||
}
|
||||
|
||||
/// Umbenennen von Stammdaten. Ein einzelnes Feld reicht, weil das Backend nur
|
||||
/// mitgeschickte Schluessel auswertet.
|
||||
struct RenameRequest: Codable {
|
||||
let name: String
|
||||
}
|
||||
|
||||
struct PackageTypeUpdateRequest: Codable {
|
||||
let singular: String
|
||||
let plural: String
|
||||
}
|
||||
|
||||
/// Neue Gruppe anlegen (nur fuer Administratoren).
|
||||
struct NewGroupRequest: Codable {
|
||||
let name: String
|
||||
|
||||
Reference in New Issue
Block a user