feat: add iOS support and harden receipt scanning

This commit is contained in:
Timo
2026-08-20 23:48:26 +02:00
parent f5e06c32b0
commit cf1b799e1b
107 changed files with 11755 additions and 122 deletions

View File

@@ -0,0 +1,72 @@
import SwiftUI
/// One row in `ReceiptsListView`: merchant, formatted business date and
/// amount, category, and the shared `ZenithStatusBadge` (matching the web
/// dashboard's `StatusBadge.tsx`).
struct ReceiptRow: View {
let receipt: Receipt
var body: some View {
HStack(alignment: .top, spacing: ZenithSpacing.sm) {
VStack(alignment: .leading, spacing: ZenithSpacing.unit) {
Text(receipt.merchant.name.isEmpty ? "Unbekannter Händler" : receipt.merchant.name)
.zenithBodyStyle()
.fontWeight(.medium)
.lineLimit(1)
HStack(spacing: 6) {
Text(formattedDate)
Text("·")
Text(receipt.suggestedCategory.rawValue)
}
.zenithBodySmStyle()
.lineLimit(1)
}
Spacer(minLength: ZenithSpacing.xs)
VStack(alignment: .trailing, spacing: 6) {
Text(formattedAmount)
.zenithLabelMdStyle()
.fontWeight(.medium)
.multilineTextAlignment(.trailing)
ZenithStatusBadge(receipt)
.accessibilityElement(children: .combine)
}
}
.padding(.vertical, ZenithSpacing.xs)
}
// `receipt.date.isoDate` is a plain "YYYY-MM-DD" string (no time/zone
// component), so it deliberately does NOT go through ISO8601.parse
// (which expects a full timestamp and would just return nil here).
private var formattedDate: String {
guard let date = Self.isoDateOnlyFormatter.date(from: receipt.date.isoDate) else {
return receipt.date.isoDate
}
return Self.displayDateFormatter.string(from: date)
}
private var formattedAmount: String {
let code = receipt.currency.isEmpty ? "EUR" : receipt.currency
return receipt.totalAmount.value.formatted(.currency(code: code))
}
private static let isoDateOnlyFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(identifier: "UTC")
return formatter
}()
private static let displayDateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .none
formatter.locale = Locale(identifier: "de_DE")
formatter.timeZone = TimeZone(identifier: "UTC")
return formatter
}()
}