refine field mapping ux and fix iso8601 fractional-second parsing
This commit is contained in:
@@ -12,6 +12,10 @@ struct TemplateField: Identifiable, Hashable {
|
||||
let id: String
|
||||
let title: String
|
||||
let description: String
|
||||
let targetExplanation: String
|
||||
let targetExample: String
|
||||
let targetFormat: String
|
||||
let targetAllowedValues: String?
|
||||
let type: FieldType
|
||||
let isRequired: Bool
|
||||
}
|
||||
@@ -31,6 +35,38 @@ struct ImportMapping: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
enum FieldFormatterType: String, CaseIterable, Identifiable {
|
||||
case passthrough
|
||||
case dateISO8601ToISO8601
|
||||
case dateGermanDayMonthYearToISO8601
|
||||
case dateGermanDayMonthYearHourMinuteToISO8601
|
||||
case dateUnixSecondsToISO8601
|
||||
|
||||
var id: String { rawValue }
|
||||
}
|
||||
|
||||
struct FieldFormatterConfig: Equatable {
|
||||
var formatterType: FieldFormatterType
|
||||
|
||||
static func `default`(for fieldType: TemplateField.FieldType) -> FieldFormatterConfig {
|
||||
if fieldType == .date {
|
||||
return FieldFormatterConfig(formatterType: .dateISO8601ToISO8601)
|
||||
}
|
||||
|
||||
return FieldFormatterConfig(formatterType: .passthrough)
|
||||
}
|
||||
}
|
||||
|
||||
struct FieldFormattingPreview {
|
||||
let matched: Int
|
||||
let total: Int
|
||||
let examples: [String]
|
||||
|
||||
var isPartial: Bool {
|
||||
total > 0 && matched < total
|
||||
}
|
||||
}
|
||||
|
||||
struct MoodwellPayload: Decodable {
|
||||
let mymoods: [MoodwellEntry]
|
||||
}
|
||||
@@ -57,7 +93,9 @@ struct MoodwellEntry: Decodable, Identifiable {
|
||||
"arrayOfWeathers": .stringArray(arrayOfWeathers),
|
||||
"createdAt": .string(createdAt),
|
||||
"arrayOfActivities": .stringArray(arrayOfActivities),
|
||||
"arrayOfGoodEmotions": .stringArray(arrayOfGoodEmotions)
|
||||
"arrayOfGoodEmotions": .stringArray(arrayOfGoodEmotions),
|
||||
"combinedEmotions": .stringArray(arrayOfGoodEmotions + arrayOfBadEmotions),
|
||||
"combinedAssociations": .stringArray(arrayOfActivities + arrayOfWeathers.map { "weather:\($0)" })
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -66,6 +104,7 @@ enum ImportValue: Hashable {
|
||||
case string(String)
|
||||
case number(Double)
|
||||
case stringArray([String])
|
||||
case date(Date)
|
||||
case null
|
||||
|
||||
func asString() -> String? {
|
||||
@@ -88,6 +127,87 @@ enum ImportValue: Hashable {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func asDate() -> Date? {
|
||||
if case let .date(value) = self {
|
||||
return value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func sourceString() -> String? {
|
||||
switch self {
|
||||
case let .string(value):
|
||||
return value
|
||||
case let .number(value):
|
||||
if value.rounded() == value {
|
||||
return String(Int(value))
|
||||
}
|
||||
return String(value)
|
||||
case let .stringArray(value):
|
||||
return value.joined(separator: ", ")
|
||||
case let .date(value):
|
||||
return value.formatted(date: .abbreviated, time: .shortened)
|
||||
case .null:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func displayString() -> String {
|
||||
sourceString() ?? "<null>"
|
||||
}
|
||||
}
|
||||
|
||||
enum ImportFieldFormatterEngine {
|
||||
static func apply(sourceText: String, config: FieldFormatterConfig) -> String? {
|
||||
switch config.formatterType {
|
||||
case .passthrough:
|
||||
return sourceText
|
||||
case .dateISO8601ToISO8601:
|
||||
guard let date = parseISO8601(sourceText.trimmingCharacters(in: .whitespacesAndNewlines)) else {
|
||||
return nil
|
||||
}
|
||||
return iso8601String(from: date)
|
||||
case .dateGermanDayMonthYearToISO8601:
|
||||
return convertDate(sourceText, sourcePattern: "dd.MM.yyyy")
|
||||
case .dateGermanDayMonthYearHourMinuteToISO8601:
|
||||
return convertDate(sourceText, sourcePattern: "dd.MM.yyyy HH:mm")
|
||||
case .dateUnixSecondsToISO8601:
|
||||
guard let seconds = Double(sourceText.trimmingCharacters(in: .whitespacesAndNewlines)) else {
|
||||
return nil
|
||||
}
|
||||
return iso8601String(from: Date(timeIntervalSince1970: seconds))
|
||||
}
|
||||
}
|
||||
|
||||
private static func convertDate(_ input: String, sourcePattern: String) -> String? {
|
||||
let formatter = DateFormatter()
|
||||
formatter.locale = Locale(identifier: "en_US_POSIX")
|
||||
formatter.calendar = Calendar(identifier: .gregorian)
|
||||
formatter.dateFormat = sourcePattern
|
||||
guard let date = formatter.date(from: input.trimmingCharacters(in: .whitespacesAndNewlines)) else {
|
||||
return nil
|
||||
}
|
||||
return iso8601String(from: date)
|
||||
}
|
||||
|
||||
private static func iso8601String(from date: Date) -> String {
|
||||
let output = ISO8601DateFormatter()
|
||||
output.formatOptions = [.withInternetDateTime]
|
||||
return output.string(from: date)
|
||||
}
|
||||
|
||||
private static func parseISO8601(_ input: String) -> Date? {
|
||||
let withFractional = ISO8601DateFormatter()
|
||||
withFractional.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
||||
if let date = withFractional.date(from: input) {
|
||||
return date
|
||||
}
|
||||
|
||||
let plain = ISO8601DateFormatter()
|
||||
plain.formatOptions = [.withInternetDateTime]
|
||||
return plain.date(from: input)
|
||||
}
|
||||
}
|
||||
|
||||
struct StateOfMindDraft: Identifiable, Hashable {
|
||||
|
||||
Reference in New Issue
Block a user