iOS: Einzelstücke (UID/QR) + Scan öffnet das Stück; Seed ohne Modell-Kaufdatum
iOS-Parität zum Web: Item-Modell + API, Einzelstueck-Liste in der Produkt- Detailansicht (anlegen, je-Stueck Lagerort/Kaufdatum/Garantie/Bezugsquelle/Notiz aendern, entfernen mit Grund, QR je Stueck via CoreImage). Anlegen-Formular hat den Schalter "Einzelstuecke". Der Scanner erkennt Einzelstueck-QR (…/i/<UID>) und oeffnet direkt das Stueck. Seed: "Kaufdatum"/"Garantie bis" nicht mehr als Modell-Felder auf Elektronik - diese Angaben gehoeren je Einzelstueck ans Item, nicht ans Modell. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -152,6 +152,40 @@ actor APIClient {
|
||||
try await sendNoContent(try makeRequest("/products/\(id)/image", method: "DELETE"))
|
||||
}
|
||||
|
||||
// MARK: - Einzelstücke (Items mit UID/QR)
|
||||
|
||||
func items(productId: Int) async throws -> [Item] {
|
||||
try await send(try makeRequest("/products/\(productId)/items"), as: [Item].self)
|
||||
}
|
||||
|
||||
func createItems(productId: Int, _ payload: ItemCreateRequest) async throws -> [Item] {
|
||||
var request = try makeRequest("/products/\(productId)/items", method: "POST")
|
||||
try jsonBody(&request, payload)
|
||||
return try await send(request, as: [Item].self)
|
||||
}
|
||||
|
||||
/// QR-Auflösung: UID → Einzelstück.
|
||||
func itemByUid(uid: String) async throws -> Item {
|
||||
let escaped = uid.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? uid
|
||||
return try await send(try makeRequest("/items/by-uid/\(escaped)"), as: Item.self)
|
||||
}
|
||||
|
||||
func updateItem(id: Int, _ payload: ItemUpdateRequest) async throws -> Item {
|
||||
var request = try makeRequest("/items/\(id)", method: "PATCH")
|
||||
try jsonBody(&request, payload)
|
||||
return try await send(request, as: Item.self)
|
||||
}
|
||||
|
||||
func removeItem(id: Int, _ payload: ItemRemoveRequest) async throws {
|
||||
var request = try makeRequest("/items/\(id)/remove", method: "POST")
|
||||
try jsonBody(&request, payload)
|
||||
try await sendNoContent(request)
|
||||
}
|
||||
|
||||
func deleteItem(id: Int) async throws {
|
||||
try await sendNoContent(try makeRequest("/items/\(id)", method: "DELETE"))
|
||||
}
|
||||
|
||||
func groups() async throws -> [GroupItem] {
|
||||
try await send(try makeRequest("/groups"), as: [GroupItem].self)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ struct CheckInView: View {
|
||||
@State private var error: String?
|
||||
|
||||
@State private var product: Product?
|
||||
@State private var scannedItem: Item?
|
||||
@State private var suggestion: LookupResult.Suggestion?
|
||||
@State private var suggestedGroupId: Int?
|
||||
@State private var suggestedGroupName: String?
|
||||
@@ -98,6 +99,9 @@ struct CheckInView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.sheet(item: $scannedItem, onDismiss: { paused = false }) { it in
|
||||
NavigationStack { ItemEditView(item: it) }
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Bausteine
|
||||
@@ -201,6 +205,16 @@ struct CheckInView: View {
|
||||
suggestion = nil
|
||||
unknownCode = nil
|
||||
do {
|
||||
// Einzelstück-QR (…/i/<UID>) → direkt das Stück öffnen statt Barcode-Suche.
|
||||
if let r = code.range(of: "/i/") {
|
||||
let uid = String(code[r.upperBound...])
|
||||
.trimmingCharacters(in: CharacterSet(charactersIn: "/ "))
|
||||
.uppercased()
|
||||
if !uid.isEmpty, let item = try? await APIClient.shared.itemByUid(uid: uid) {
|
||||
scannedItem = item
|
||||
return
|
||||
}
|
||||
}
|
||||
let result = try await APIClient.shared.lookup(barcode: code)
|
||||
suggestedGroupId = result.groupId
|
||||
suggestedGroupName = result.groupName
|
||||
|
||||
266
ios/Sources/ItemViews.swift
Normal file
266
ios/Sources/ItemViews.swift
Normal file
@@ -0,0 +1,266 @@
|
||||
import SwiftUI
|
||||
import CoreImage
|
||||
|
||||
/// QR-Code als Bild (CoreImage). Inhalt ist der Link auf das Einzelstück, damit
|
||||
/// ein Scan – auch mit der Systemkamera – die App/Weboberfläche öffnet.
|
||||
enum QRImage {
|
||||
static func make(_ text: String, scale: CGFloat = 8) -> UIImage? {
|
||||
guard let data = text.data(using: .utf8),
|
||||
let filter = CIFilter(name: "CIQRCodeGenerator") else { return nil }
|
||||
filter.setValue(data, forKey: "inputMessage")
|
||||
filter.setValue("M", forKey: "inputCorrectionLevel")
|
||||
guard let ci = filter.outputImage?
|
||||
.transformed(by: CGAffineTransform(scaleX: scale, y: scale)) else { return nil }
|
||||
let context = CIContext()
|
||||
guard let cg = context.createCGImage(ci, from: ci.extent) else { return nil }
|
||||
return UIImage(cgImage: cg)
|
||||
}
|
||||
}
|
||||
|
||||
func qrLink(forUid uid: String) -> String {
|
||||
let base = Session.shared.baseURL?.absoluteString
|
||||
.trimmingCharacters(in: CharacterSet(charactersIn: "/")) ?? ""
|
||||
return base.isEmpty ? uid : "\(base)/i/\(uid)"
|
||||
}
|
||||
|
||||
private func dateToString(_ d: Date) -> String {
|
||||
let f = DateFormatter(); f.dateFormat = "yyyy-MM-dd"; return f.string(from: d)
|
||||
}
|
||||
private func stringToDate(_ s: String) -> Date? {
|
||||
let f = DateFormatter(); f.dateFormat = "yyyy-MM-dd"; return f.date(from: s)
|
||||
}
|
||||
|
||||
// MARK: - Liste (in der Produkt-Detailansicht)
|
||||
|
||||
/// Einzelstücke eines Gegenstands: Liste mit Anlegen; jedes Stück öffnet die
|
||||
/// Detail-/Bearbeiten-Ansicht.
|
||||
struct ProductItemsSection: View {
|
||||
let product: Product
|
||||
var onChanged: () async -> Void
|
||||
|
||||
@State private var items: [Item] = []
|
||||
@State private var showAdd = false
|
||||
|
||||
var body: some View {
|
||||
Section("Einzelstücke (\(items.count))") {
|
||||
ForEach(items) { it in
|
||||
NavigationLink {
|
||||
ItemEditView(item: it) { Task { await refresh() } }
|
||||
} label: {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(it.uid).font(.body.monospaced()).bold()
|
||||
Text(untertitel(it)).font(.caption).foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
if items.isEmpty {
|
||||
Text("Noch keine Einzelstücke.").foregroundStyle(.secondary)
|
||||
}
|
||||
Button { showAdd = true } label: {
|
||||
Label("Einzelstücke anlegen", systemImage: "plus")
|
||||
}
|
||||
}
|
||||
.task(id: product.id) { await reload() }
|
||||
.sheet(isPresented: $showAdd) {
|
||||
NavigationStack {
|
||||
ItemAddSheet(productId: product.id) { await refresh() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func untertitel(_ it: Item) -> String {
|
||||
[it.locationName, it.acquiredOn.map { "gekauft \($0)" }, it.shopName]
|
||||
.compactMap { $0 }.joined(separator: " · ")
|
||||
}
|
||||
private func reload() async {
|
||||
items = (try? await APIClient.shared.items(productId: product.id)) ?? []
|
||||
}
|
||||
private func refresh() async { await reload(); await onChanged() }
|
||||
}
|
||||
|
||||
// MARK: - Detail / Bearbeiten (auch Ziel eines Scans)
|
||||
|
||||
struct ItemEditView: View {
|
||||
let item: Item
|
||||
var onChanged: (() -> Void)? = nil
|
||||
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
@State private var locations: [StorageLocation] = []
|
||||
@State private var shops: [ShopItem] = []
|
||||
@State private var locationId: Int?
|
||||
@State private var shopId: Int?
|
||||
@State private var hasAcquired = false
|
||||
@State private var acquired = Date()
|
||||
@State private var hasWarranty = false
|
||||
@State private var warranty = Date()
|
||||
@State private var note = ""
|
||||
@State private var busy = false
|
||||
@State private var error: String?
|
||||
@State private var showRemove = false
|
||||
@State private var reason = "broken"
|
||||
@State private var removeNote = ""
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section {
|
||||
if let img = QRImage.make(qrLink(forUid: item.uid)) {
|
||||
Image(uiImage: img)
|
||||
.interpolation(.none).resizable().scaledToFit()
|
||||
.frame(maxWidth: .infinity).frame(height: 180)
|
||||
}
|
||||
LabeledContent("UID", value: item.uid)
|
||||
if let name = item.productName {
|
||||
LabeledContent("Artikel", value: name)
|
||||
}
|
||||
}
|
||||
|
||||
Section {
|
||||
Picker("Lagerort", selection: $locationId) {
|
||||
Text("– ohne –").tag(Int?.none)
|
||||
ForEach(locations) { l in Text(l.name).tag(Int?.some(l.id)) }
|
||||
}
|
||||
Picker("Gekauft bei", selection: $shopId) {
|
||||
Text("– unbekannt –").tag(Int?.none)
|
||||
ForEach(shops) { s in Text(s.name).tag(Int?.some(s.id)) }
|
||||
}
|
||||
Toggle("Kaufdatum", isOn: $hasAcquired)
|
||||
if hasAcquired { DatePicker("Gekauft am", selection: $acquired, displayedComponents: .date) }
|
||||
Toggle("Garantie", isOn: $hasWarranty)
|
||||
if hasWarranty { DatePicker("Garantie bis", selection: $warranty, displayedComponents: .date) }
|
||||
LabeledField(label: "Notiz", text: $note)
|
||||
}
|
||||
|
||||
if let error { Section { Text(error).foregroundStyle(.red).font(.callout) } }
|
||||
|
||||
Section {
|
||||
Button(busy ? "Speichern…" : "Speichern") { Task { await save() } }.disabled(busy)
|
||||
Button("Entfernen (mit Grund)", role: .destructive) { showRemove = true }
|
||||
}
|
||||
}
|
||||
.navigationTitle("Einzelstück")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar { ToolbarItem(placement: .topBarLeading) { Button("Fertig") { dismiss() } } }
|
||||
.task { await load() }
|
||||
.sheet(isPresented: $showRemove) {
|
||||
NavigationStack { removeSheet }
|
||||
}
|
||||
}
|
||||
|
||||
private var removeSheet: some View {
|
||||
Form {
|
||||
Picker("Grund", selection: $reason) {
|
||||
ForEach(RemovalReasons.all, id: \.value) { Text($0.label).tag($0.value) }
|
||||
}
|
||||
LabeledField(label: "Notiz", text: $removeNote)
|
||||
Section {
|
||||
Button("Entfernen", role: .destructive) { Task { await remove() } }
|
||||
}
|
||||
}
|
||||
.navigationTitle("Entfernen")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar { ToolbarItem(placement: .topBarLeading) { Button("Abbrechen") { showRemove = false } } }
|
||||
}
|
||||
|
||||
private func load() async {
|
||||
locations = (try? await APIClient.shared.locations()) ?? []
|
||||
shops = (try? await APIClient.shared.shops()) ?? []
|
||||
locationId = item.locationId
|
||||
shopId = item.shopId
|
||||
note = item.note ?? ""
|
||||
if let s = item.acquiredOn, let d = stringToDate(s) { acquired = d; hasAcquired = true }
|
||||
if let s = item.warrantyUntil, let d = stringToDate(s) { warranty = d; hasWarranty = true }
|
||||
}
|
||||
|
||||
private func save() async {
|
||||
busy = true; defer { busy = false }; error = nil
|
||||
do {
|
||||
_ = try await APIClient.shared.updateItem(id: item.id, ItemUpdateRequest(
|
||||
locationId: locationId, shopId: shopId,
|
||||
acquiredOn: hasAcquired ? dateToString(acquired) : nil,
|
||||
warrantyUntil: hasWarranty ? dateToString(warranty) : nil,
|
||||
note: note.isEmpty ? nil : note))
|
||||
onChanged?()
|
||||
dismiss()
|
||||
} catch { self.error = error.localizedDescription }
|
||||
}
|
||||
|
||||
private func remove() async {
|
||||
do {
|
||||
try await APIClient.shared.removeItem(id: item.id, ItemRemoveRequest(
|
||||
reason: reason, note: removeNote.isEmpty ? nil : removeNote))
|
||||
showRemove = false
|
||||
onChanged?()
|
||||
dismiss()
|
||||
} catch { self.error = error.localizedDescription; showRemove = false }
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Anlegen
|
||||
|
||||
struct ItemAddSheet: View {
|
||||
let productId: Int
|
||||
var onDone: () async -> Void
|
||||
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
@State private var count = 1
|
||||
@State private var locations: [StorageLocation] = []
|
||||
@State private var shops: [ShopItem] = []
|
||||
@State private var locationId: Int?
|
||||
@State private var shopId: Int?
|
||||
@State private var hasAcquired = false
|
||||
@State private var acquired = Date()
|
||||
@State private var hasWarranty = false
|
||||
@State private var warranty = Date()
|
||||
@State private var note = ""
|
||||
@State private var busy = false
|
||||
@State private var error: String?
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section {
|
||||
Stepper("Anzahl: \(count)", value: $count, in: 1...200)
|
||||
Picker("Lagerort", selection: $locationId) {
|
||||
Text("– ohne –").tag(Int?.none)
|
||||
ForEach(locations) { l in Text(l.name).tag(Int?.some(l.id)) }
|
||||
}
|
||||
Picker("Gekauft bei", selection: $shopId) {
|
||||
Text("– unbekannt –").tag(Int?.none)
|
||||
ForEach(shops) { s in Text(s.name).tag(Int?.some(s.id)) }
|
||||
}
|
||||
Toggle("Kaufdatum", isOn: $hasAcquired)
|
||||
if hasAcquired { DatePicker("Gekauft am", selection: $acquired, displayedComponents: .date) }
|
||||
Toggle("Garantie", isOn: $hasWarranty)
|
||||
if hasWarranty { DatePicker("Garantie bis", selection: $warranty, displayedComponents: .date) }
|
||||
LabeledField(label: "Notiz", text: $note)
|
||||
} footer: {
|
||||
Text("Gemeinsame Startwerte – danach je Stück änderbar. Jedes Stück bekommt eine eigene UID + QR.")
|
||||
}
|
||||
if let error { Section { Text(error).foregroundStyle(.red).font(.callout) } }
|
||||
Section {
|
||||
Button(busy ? "Anlegen…" : "Anlegen") { Task { await create() } }.disabled(busy)
|
||||
}
|
||||
}
|
||||
.navigationTitle("Einzelstücke anlegen")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar { ToolbarItem(placement: .topBarLeading) { Button("Abbrechen") { dismiss() } } }
|
||||
.task {
|
||||
locations = (try? await APIClient.shared.locations()) ?? []
|
||||
shops = (try? await APIClient.shared.shops()) ?? []
|
||||
}
|
||||
}
|
||||
|
||||
private func create() async {
|
||||
busy = true; defer { busy = false }; error = nil
|
||||
do {
|
||||
_ = try await APIClient.shared.createItems(productId: productId, ItemCreateRequest(
|
||||
count: count, locationId: locationId, shopId: shopId,
|
||||
acquiredOn: hasAcquired ? dateToString(acquired) : nil,
|
||||
warrantyUntil: hasWarranty ? dateToString(warranty) : nil,
|
||||
note: note.isEmpty ? nil : note))
|
||||
await onDone()
|
||||
dismiss()
|
||||
} catch { self.error = error.localizedDescription }
|
||||
}
|
||||
}
|
||||
@@ -74,9 +74,11 @@ struct Product: Codable, Identifiable, Hashable {
|
||||
let productUrl: String?
|
||||
/// Selbst definierte Feldwerte: {feld_id (als Text): Wert}.
|
||||
let fieldValues: [String: String?]?
|
||||
/// Gegenstand als Einzelstücke (Items mit UID/QR) statt als Menge geführt.
|
||||
let individual: Bool?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id, barcode, name, brand, stock, kind, barcodes, tracking
|
||||
case id, barcode, name, brand, stock, kind, barcodes, tracking, individual
|
||||
case datePrecision = "date_precision"
|
||||
case categoryId = "category_id"
|
||||
case categoryName = "category_name"
|
||||
@@ -99,6 +101,8 @@ struct Product: Codable, Identifiable, Hashable {
|
||||
|
||||
/// Gegenstand (Menge je Lagerort) statt Lebensmittel (Chargen/MHD)?
|
||||
var isObject: Bool { tracking == "object" }
|
||||
/// Gegenstand, der als Einzelstücke (UID/QR) geführt wird?
|
||||
var isIndividual: Bool { individual == true }
|
||||
|
||||
/// Bezeichnung der Artikeleinheit (Gebinde, sonst Produkteinheit).
|
||||
var articleUnitLabel: String {
|
||||
@@ -264,9 +268,10 @@ struct NewProductRequest: Codable {
|
||||
let datePrecision: String
|
||||
let groupId: Int?
|
||||
let categoryId: Int?
|
||||
var individual: Bool? = nil
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case barcode, name, brand
|
||||
case barcode, name, brand, individual
|
||||
case imageUrl = "image_url"
|
||||
case baseUnit = "base_unit"
|
||||
case packageSize = "package_size"
|
||||
@@ -564,6 +569,86 @@ enum RemovalReasons {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Einzelstücke (Items mit UID/QR)
|
||||
|
||||
struct Item: Codable, Identifiable, Hashable {
|
||||
let id: Int
|
||||
let uid: String
|
||||
let productId: Int
|
||||
let locationId: Int?
|
||||
let locationName: String?
|
||||
let shopId: Int?
|
||||
let shopName: String?
|
||||
let acquiredOn: String? // "yyyy-MM-dd"
|
||||
let warrantyUntil: String?
|
||||
let note: String?
|
||||
let createdAt: String
|
||||
let productName: String?
|
||||
let productBrand: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id, uid, note
|
||||
case productId = "product_id"
|
||||
case locationId = "location_id"
|
||||
case locationName = "location_name"
|
||||
case shopId = "shop_id"
|
||||
case shopName = "shop_name"
|
||||
case acquiredOn = "acquired_on"
|
||||
case warrantyUntil = "warranty_until"
|
||||
case createdAt = "created_at"
|
||||
case productName = "product_name"
|
||||
case productBrand = "product_brand"
|
||||
}
|
||||
}
|
||||
|
||||
struct ItemCreateRequest: Codable {
|
||||
let count: Int
|
||||
let locationId: Int?
|
||||
let shopId: Int?
|
||||
let acquiredOn: String?
|
||||
let warrantyUntil: String?
|
||||
let note: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case count, note
|
||||
case locationId = "location_id"
|
||||
case shopId = "shop_id"
|
||||
case acquiredOn = "acquired_on"
|
||||
case warrantyUntil = "warranty_until"
|
||||
}
|
||||
}
|
||||
|
||||
struct ItemUpdateRequest: Encodable {
|
||||
var locationId: Int?
|
||||
var shopId: Int?
|
||||
var acquiredOn: String?
|
||||
var warrantyUntil: String?
|
||||
var note: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case note
|
||||
case locationId = "location_id"
|
||||
case shopId = "shop_id"
|
||||
case acquiredOn = "acquired_on"
|
||||
case warrantyUntil = "warranty_until"
|
||||
}
|
||||
|
||||
// Immer alle Felder senden (auch null), damit sich ein Feld leeren lässt.
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var c = encoder.container(keyedBy: CodingKeys.self)
|
||||
try c.encode(locationId, forKey: .locationId)
|
||||
try c.encode(shopId, forKey: .shopId)
|
||||
try c.encode(acquiredOn, forKey: .acquiredOn)
|
||||
try c.encode(warrantyUntil, forKey: .warrantyUntil)
|
||||
try c.encode(note, forKey: .note)
|
||||
}
|
||||
}
|
||||
|
||||
struct ItemRemoveRequest: Codable {
|
||||
let reason: String
|
||||
let note: String?
|
||||
}
|
||||
|
||||
// MARK: - Uebersicht und Verlauf
|
||||
|
||||
/// Kennzahlen der Startseite (GET /dashboard/stats).
|
||||
|
||||
@@ -51,7 +51,9 @@ struct ProductDetailView: View {
|
||||
|
||||
ProductPhotoSection(productId: current.id)
|
||||
|
||||
if current.isObject {
|
||||
if current.isObject && current.isIndividual {
|
||||
ProductItemsSection(product: current, onChanged: { await reload() })
|
||||
} else if current.isObject {
|
||||
ObjectStockSection(product: current, locations: locations, lots: lots,
|
||||
onChanged: { await reload() })
|
||||
} else {
|
||||
|
||||
@@ -72,6 +72,8 @@ struct ProductFormView: View {
|
||||
@State private var selectedCategoryId: Int?
|
||||
// Umschalter Lebensmittel/Gegenstand: filtert die Kategorieauswahl.
|
||||
@State private var modus = "object"
|
||||
// Gegenstand als Einzelstücke (UID/QR je Stück) führen?
|
||||
@State private var individual = false
|
||||
@State private var groups: [GroupItem] = []
|
||||
@State private var categories: [CategoryItem] = []
|
||||
@State private var units: [Unit] = []
|
||||
@@ -97,9 +99,14 @@ struct ProductFormView: View {
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
CategoryPicker(categories: categories, selection: $selectedCategoryId, tracking: modus)
|
||||
if modus == "object" {
|
||||
Toggle("Einzelstücke (UID/QR je Stück)", isOn: $individual)
|
||||
}
|
||||
} footer: {
|
||||
Text(modus == "object"
|
||||
? "Gegenstand: Menge je Lagerort und eigene Felder."
|
||||
? (individual
|
||||
? "Einzelstücke: jedes Stück mit eigener UID/QR, Kaufdatum, Garantie."
|
||||
: "Gegenstand: Menge je Lagerort und eigene Felder.")
|
||||
: "Lebensmittel: Chargen mit Mindesthaltbarkeit.")
|
||||
}
|
||||
.onChange(of: modus) { neu in
|
||||
@@ -209,7 +216,8 @@ struct ProductFormView: View {
|
||||
packageLabel: packageLabel.isEmpty ? nil : packageLabel,
|
||||
datePrecision: datePrecision,
|
||||
groupId: selectedGroupId,
|
||||
categoryId: selectedCategoryId
|
||||
categoryId: selectedCategoryId,
|
||||
individual: modus == "object" ? individual : nil
|
||||
)
|
||||
)
|
||||
onCreated(product)
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
4B4A74E8A2964FF8F3D2CE02 /* NotificationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3731608B8D48960DC98912BC /* NotificationSettings.swift */; };
|
||||
4C486D190D1AB338925D29B5 /* HistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DACFBB69CB536BA7CBBD484 /* HistoryView.swift */; };
|
||||
4F86620DF8303DB02314DB5A /* CheckInView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0365A16FEAE3F2BEC321E68A /* CheckInView.swift */; };
|
||||
56A5DFAC86DF972B1481A691 /* ItemViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69B59B9F69BC30451D45A3C7 /* ItemViews.swift */; };
|
||||
6816C33381DF52A96E5BB303 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314B1BB7220691A7423E8929 /* Models.swift */; };
|
||||
728CF5124D2D5BBF826BCD2C /* ServerFetch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1847E65D41ACEDD01CD108BC /* ServerFetch.swift */; };
|
||||
75F805F8AE93479933BD811D /* VorraniaApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9948219CDDC4188EA4298F22 /* VorraniaApp.swift */; };
|
||||
@@ -58,6 +59,7 @@
|
||||
4BF4E4F5524B15728CEEE116 /* Vorrania.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = Vorrania.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
5312AFE88B7284401AA7B0A3 /* ProductPhotoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductPhotoView.swift; sourceTree = "<group>"; };
|
||||
660ACEFA22BC77D52FBE736A /* AppDashboards.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDashboards.swift; sourceTree = "<group>"; };
|
||||
69B59B9F69BC30451D45A3C7 /* ItemViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemViews.swift; sourceTree = "<group>"; };
|
||||
6F574168AA0F849D46C384EE /* ProductDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductDetailView.swift; sourceTree = "<group>"; };
|
||||
717C8EB336170526F5F3E695 /* DateScanView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateScanView.swift; sourceTree = "<group>"; };
|
||||
7B5BB49FBEFF88768B0BC902 /* ObjectStockView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectStockView.swift; sourceTree = "<group>"; };
|
||||
@@ -96,6 +98,7 @@
|
||||
717C8EB336170526F5F3E695 /* DateScanView.swift */,
|
||||
AAC97A555856C6C5DD353E25 /* DisplaySettings.swift */,
|
||||
7DACFBB69CB536BA7CBBD484 /* HistoryView.swift */,
|
||||
69B59B9F69BC30451D45A3C7 /* ItemViews.swift */,
|
||||
A6785CD9174067EFBB2B9043 /* ListViews.swift */,
|
||||
C35D34107C8CBB8B7C6C6650 /* LocalOverview.swift */,
|
||||
A75926511854BB8EE316ED3A /* LoginView.swift */,
|
||||
@@ -220,6 +223,7 @@
|
||||
ED61DAE4824165D6D87F8C1D /* DateScanView.swift in Sources */,
|
||||
D70CB183C868FF0143A6A5E7 /* DisplaySettings.swift in Sources */,
|
||||
4C486D190D1AB338925D29B5 /* HistoryView.swift in Sources */,
|
||||
56A5DFAC86DF972B1481A691 /* ItemViews.swift in Sources */,
|
||||
7738E209D4D32BFD514B76F5 /* ListViews.swift in Sources */,
|
||||
82A0C22D4AD228C36868853A /* LocalOverview.swift in Sources */,
|
||||
E9EA455CACB6ED5951B114C3 /* LoginView.swift in Sources */,
|
||||
|
||||
Reference in New Issue
Block a user