Some messy stuff cleaned up in Inventory UI
This commit is contained in:
@@ -1,6 +1,52 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import LabWiseKit
|
import LabWiseKit
|
||||||
|
|
||||||
|
private let isoDateFormatter: ISO8601DateFormatter = {
|
||||||
|
let f = ISO8601DateFormatter()
|
||||||
|
f.formatOptions = [.withFullDate]
|
||||||
|
return f
|
||||||
|
}()
|
||||||
|
|
||||||
|
private let isoTimestampFormatter: ISO8601DateFormatter = {
|
||||||
|
let f = ISO8601DateFormatter()
|
||||||
|
f.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
||||||
|
return f
|
||||||
|
}()
|
||||||
|
|
||||||
|
private let displayDateFormatter: DateFormatter = {
|
||||||
|
let f = DateFormatter()
|
||||||
|
f.dateStyle = .medium
|
||||||
|
f.timeStyle = .none
|
||||||
|
return f
|
||||||
|
}()
|
||||||
|
|
||||||
|
private let displayTimestampFormatter: DateFormatter = {
|
||||||
|
let f = DateFormatter()
|
||||||
|
f.dateStyle = .medium
|
||||||
|
f.timeStyle = .short
|
||||||
|
return f
|
||||||
|
}()
|
||||||
|
|
||||||
|
private func formatDate(_ iso: String?) -> String {
|
||||||
|
guard let iso else { return "" }
|
||||||
|
if let date = isoDateFormatter.date(from: iso) {
|
||||||
|
return displayDateFormatter.string(from: date)
|
||||||
|
}
|
||||||
|
// Fallback: try full timestamp (e.g. "2025-03-01T00:00:00.000Z")
|
||||||
|
if let date = isoTimestampFormatter.date(from: iso) {
|
||||||
|
return displayDateFormatter.string(from: date)
|
||||||
|
}
|
||||||
|
return iso
|
||||||
|
}
|
||||||
|
|
||||||
|
private func formatTimestamp(_ iso: String?) -> String {
|
||||||
|
guard let iso else { return "" }
|
||||||
|
if let date = isoTimestampFormatter.date(from: iso) {
|
||||||
|
return displayTimestampFormatter.string(from: date)
|
||||||
|
}
|
||||||
|
return iso
|
||||||
|
}
|
||||||
|
|
||||||
struct ChemicalDetailView: View {
|
struct ChemicalDetailView: View {
|
||||||
@State private var chemical: Chemical
|
@State private var chemical: Chemical
|
||||||
@State private var showEdit = false
|
@State private var showEdit = false
|
||||||
@@ -56,8 +102,8 @@ struct ChemicalDetailView: View {
|
|||||||
if let lot = chemical.lotNumber {
|
if let lot = chemical.lotNumber {
|
||||||
LabeledContent("Lot #", value: lot)
|
LabeledContent("Lot #", value: lot)
|
||||||
}
|
}
|
||||||
if let exp = chemical.expirationDate {
|
if let exp = chemical.expirationDate, !exp.isEmpty {
|
||||||
LabeledContent("Expiration", value: exp)
|
LabeledContent("Expiration", value: formatDate(exp))
|
||||||
}
|
}
|
||||||
if let barcode = chemical.barcode {
|
if let barcode = chemical.barcode {
|
||||||
LabeledContent("Barcode", value: barcode)
|
LabeledContent("Barcode", value: barcode)
|
||||||
@@ -76,10 +122,10 @@ struct ChemicalDetailView: View {
|
|||||||
|
|
||||||
Section("Record") {
|
Section("Record") {
|
||||||
if let created = chemical.createdAt {
|
if let created = chemical.createdAt {
|
||||||
LabeledContent("Created", value: created)
|
LabeledContent("Created", value: formatTimestamp(created))
|
||||||
}
|
}
|
||||||
if let updated = chemical.updatedAt {
|
if let updated = chemical.updatedAt {
|
||||||
LabeledContent("Updated", value: updated)
|
LabeledContent("Updated", value: formatTimestamp(updated))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,8 +65,7 @@ final class DashboardViewModel {
|
|||||||
do {
|
do {
|
||||||
chemicals = try await client.list()
|
chemicals = try await client.list()
|
||||||
} catch {
|
} catch {
|
||||||
print("[DashboardViewModel] load error: \(error)")
|
errorMessage = "Failed to load dashboard"
|
||||||
errorMessage = "Failed to load: \(error)"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,7 @@ final class InventoryViewModel {
|
|||||||
do {
|
do {
|
||||||
chemicals = try await client.list()
|
chemicals = try await client.list()
|
||||||
} catch {
|
} catch {
|
||||||
print("[InventoryViewModel] loadChemicals error: \(error)")
|
errorMessage = "Failed to load chemicals"
|
||||||
errorMessage = "Failed to load chemicals: \(error)"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,8 +25,7 @@ final class InventoryViewModel {
|
|||||||
try await client.delete(id: chemical.id)
|
try await client.delete(id: chemical.id)
|
||||||
chemicals.removeAll { $0.id == chemical.id }
|
chemicals.removeAll { $0.id == chemical.id }
|
||||||
} catch {
|
} catch {
|
||||||
print("[InventoryViewModel] delete error: \(error)")
|
errorMessage = "Failed to delete"
|
||||||
errorMessage = "Failed to delete: \(error)"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,9 +45,6 @@ public final class APIClient: Sendable {
|
|||||||
req.httpMethod = method
|
req.httpMethod = method
|
||||||
req.setValue(contentType, forHTTPHeaderField: "Content-Type")
|
req.setValue(contentType, forHTTPHeaderField: "Content-Type")
|
||||||
|
|
||||||
let cookieNames = HTTPCookieStorage.shared.cookies(for: url)?.map(\.name) ?? []
|
|
||||||
print("[APIClient] \(method) \(path) — cookies: \(cookieNames)")
|
|
||||||
|
|
||||||
if let body {
|
if let body {
|
||||||
do {
|
do {
|
||||||
req.httpBody = try JSONEncoder.api.encode(body)
|
req.httpBody = try JSONEncoder.api.encode(body)
|
||||||
@@ -60,27 +57,20 @@ public final class APIClient: Sendable {
|
|||||||
do {
|
do {
|
||||||
(data, response) = try await session.data(for: req)
|
(data, response) = try await session.data(for: req)
|
||||||
} catch {
|
} catch {
|
||||||
print("[APIClient] Network error on \(method) \(path): \(error)")
|
|
||||||
throw APIError.networkError(error)
|
throw APIError.networkError(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let http = response as? HTTPURLResponse else {
|
guard let http = response as? HTTPURLResponse else {
|
||||||
print("[APIClient] Non-HTTP response on \(method) \(path)")
|
|
||||||
throw APIError.networkError(URLError(.badServerResponse))
|
throw APIError.networkError(URLError(.badServerResponse))
|
||||||
}
|
}
|
||||||
|
|
||||||
print("[APIClient] \(method) \(path) → \(http.statusCode)")
|
|
||||||
|
|
||||||
if http.statusCode == 401 {
|
if http.statusCode == 401 {
|
||||||
print("[APIClient] 401 — clearing session and signalling unauthorized")
|
|
||||||
clearSessionCookies()
|
clearSessionCookies()
|
||||||
onUnauthorized?()
|
onUnauthorized?()
|
||||||
throw APIError.unauthorized
|
throw APIError.unauthorized
|
||||||
}
|
}
|
||||||
|
|
||||||
guard (200..<300).contains(http.statusCode) else {
|
guard (200..<300).contains(http.statusCode) else {
|
||||||
let body = String(data: data, encoding: .utf8) ?? "<binary \(data.count)b>"
|
|
||||||
print("[APIClient] HTTP \(http.statusCode) on \(method) \(path): \(body)")
|
|
||||||
throw APIError.httpError(statusCode: http.statusCode, data: data)
|
throw APIError.httpError(statusCode: http.statusCode, data: data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user