import Foundation /// Anzeigeeinstellungen, die am Server hinterlegt sind. /// /// Das Datumsformat ist in der Web-Oberflaeche unter Einstellungen -> /// Darstellung waehlbar. Die App liest denselben Wert, damit beide Oberflaechen /// gleich aussehen. @MainActor final class DisplaySettings: ObservableObject { static let shared = DisplaySettings() static let dateFormatKey = "date_format" static let fallbackFormat = "de" @Published private(set) var dateFormat = DisplaySettings.fallbackFormat private init() {} /// Nach der Anmeldung aufrufen. Schlaegt der Abruf fehl, bleibt es beim /// deutschen Standard - an einer fehlenden Einstellung darf die Anzeige /// nicht scheitern. func load() async { guard let entries = try? await APIClient.shared.settings() else { return } if let entry = entries.first(where: { $0.key == DisplaySettings.dateFormatKey }), !entry.value.isEmpty { dateFormat = entry.value } } // MARK: - Datum /// MHD lesbar machen. Monatsangaben immer ohne Tag ("09/2026"), sonst nach /// dem eingestellten Format. func formatBestBefore(_ raw: String?, precision: String?) -> String { guard let raw, !raw.isEmpty else { return "ohne MHD" } guard let date = DisplaySettings.isoParser.date(from: raw) else { return raw } let muster = precision == "month" ? "MM/yyyy" : DisplaySettings.pattern(for: dateFormat) let formatter = DateFormatter() formatter.locale = Locale(identifier: "de_DE") formatter.dateFormat = muster return formatter.string(from: date) } /// Entspricht den Formaten aus web/src/units.js (DATE_FORMATS). static func pattern(for format: String) -> String { switch format { case "de-short": return "dd.MM.yy" case "long": return "d. MMMM yyyy" case "iso": return "yyyy-MM-dd" case "us": return "MM/dd/yyyy" default: return "dd.MM.yyyy" // "de" } } private static let isoParser: DateFormatter = { let formatter = DateFormatter() formatter.locale = Locale(identifier: "en_US_POSIX") formatter.dateFormat = "yyyy-MM-dd" return formatter }() // MARK: - Einheiten /// Kanonische Basiseinheit der API auf Deutsch. func baseUnitLabel(_ raw: String?) -> String { DisplaySettings.baseUnitLabel(raw) } static func baseUnitLabel(_ raw: String?) -> String { switch raw { case "piece": return "Stück" case "gram": return "Gramm" case "milliliter": return "Milliliter" default: return raw ?? "" } } /// Menge in der Leitangabe: Gibt es ein Gebinde, zaehlt das, die Basismenge /// steht dahinter in Klammern (wie amountText in der Web-Oberflaeche). func amountText(_ quantity: Double, packageSize: Double?, baseUnit: String?) -> String { let einheit = DisplaySettings.baseUnitLabel(baseUnit) if let size = packageSize, size > 0 { return "\(formatAmount(quantity / size)) Pkg. (\(formatAmount(quantity)) \(einheit))" } return "\(formatAmount(quantity)) \(einheit)" } }