2 Commits

Author SHA1 Message Date
ed8a160a49 update 2026-02-20 15:24:10 +01:00
f42de3353c Fix iOS time display and add timer unit labels
Times were always showing 0 because ISO8601DateFormatter with default
options does not parse fractional seconds, but Prisma/Node.js serialises
dates as "2026-02-20T09:00:00.000Z" (with .000). Every date(from:) call
silently returned nil, so elapsedTime and duration always fell back to 0.

- Date+Extensions: fromISO8601 now tries .withFractionalSeconds first,
  then falls back to whole seconds — single place to maintain
- OngoingTimer.elapsedTime: use Date.fromISO8601() instead of bare formatter
- TimeEntry.duration: use Date.fromISO8601() instead of bare formatters
- TimerView: add TimerUnitLabels view showing h/min/sec column headers
  under the monospaced clock digits
2026-02-20 14:53:30 +01:00
8 changed files with 68 additions and 42 deletions

View File

@@ -44,6 +44,8 @@ struct TimerView: View {
.font(.system(size: 64, weight: .light, design: .monospaced)) .font(.system(size: 64, weight: .light, design: .monospaced))
.foregroundStyle(viewModel.activeTimer != nil ? .primary : .secondary) .foregroundStyle(viewModel.activeTimer != nil ? .primary : .secondary)
TimerUnitLabels(elapsed: viewModel.elapsedTime)
if let project = viewModel.selectedProject { if let project = viewModel.selectedProject {
ProjectColorBadge( ProjectColorBadge(
color: project.color, color: project.color,
@@ -123,6 +125,32 @@ struct TimerView: View {
} }
} }
/// Displays "h min sec" (or "min sec") column labels aligned under the
/// monospaced timer digits.
private struct TimerUnitLabels: View {
let elapsed: TimeInterval
private var showHours: Bool { Int(elapsed) >= 3600 }
var body: some View {
HStack(spacing: 0) {
if showHours {
Text("h")
.frame(width: 64)
Text("min")
.frame(width: 64)
} else {
Text("min")
.frame(width: 64)
}
Text("sec")
.frame(width: 64)
}
.font(.caption)
.foregroundStyle(.secondary)
}
}
struct ProjectPickerSheet: View { struct ProjectPickerSheet: View {
let projects: [Project] let projects: [Project]
let selectedProject: Project? let selectedProject: Project?

View File

@@ -3,7 +3,7 @@
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>API_BASE_URL</key> <key>API_BASE_URL</key>
<string>http://localhost:3001</string> <string>https://timetracker.simon-franken.de/api</string>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string> <string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>

View File

@@ -9,9 +9,7 @@ struct OngoingTimer: Codable, Identifiable, Equatable {
let updatedAt: String let updatedAt: String
var elapsedTime: TimeInterval { var elapsedTime: TimeInterval {
guard let start = ISO8601DateFormatter().date(from: startTime) else { guard let start = Date.fromISO8601(startTime) else { return 0 }
return 0
}
return Date().timeIntervalSince(start) return Date().timeIntervalSince(start)
} }
} }

View File

@@ -11,10 +11,8 @@ struct TimeEntry: Codable, Identifiable, Equatable {
let updatedAt: String let updatedAt: String
var duration: TimeInterval { var duration: TimeInterval {
guard let start = ISO8601DateFormatter().date(from: startTime), guard let start = Date.fromISO8601(startTime),
let end = ISO8601DateFormatter().date(from: endTime) else { let end = Date.fromISO8601(endTime) else { return 0 }
return 0
}
return end.timeIntervalSince(start) return end.timeIntervalSince(start)
} }
} }

View File

@@ -59,8 +59,14 @@ extension Date {
} }
static func fromISO8601(_ string: String) -> Date? { static func fromISO8601(_ string: String) -> Date? {
let formatter = ISO8601DateFormatter() // Try with fractional seconds first (e.g. "2026-02-20T09:00:00.000Z" from
formatter.formatOptions = [.withInternetDateTime] // Prisma/Node.js JSON serialisation), then fall back to whole seconds.
return formatter.date(from: string) let withFractional = ISO8601DateFormatter()
withFractional.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
if let date = withFractional.date(from: string) { return date }
let wholeSec = ISO8601DateFormatter()
wholeSec.formatOptions = [.withInternetDateTime]
return wholeSec.date(from: string)
} }
} }

View File

@@ -2,9 +2,7 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>com.apple.security.application-groups</key> <key>com.apple.security.application-groups</key>
<array> <array/>
<string>group.com.timetracker.app</string>
</array>
</dict> </dict>
</plist> </plist>

View File

@@ -2,28 +2,28 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string> <string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key> <key>CFBundleDisplayName</key>
<string>Timer</string> <string>Timer</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string> <string>6.0</string>
<key>CFBundleName</key> <key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string> <string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string> <string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string> <string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string> <string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSExtension</key> <key>NSExtension</key>
<dict> <dict>
<key>NSExtensionPointIdentifier</key> <key>NSExtensionPointIdentifier</key>
<string>com.apple.widgetkit-extension</string> <string>com.apple.widgetkit-extension</string>
</dict> </dict>
</dict> </dict>
</plist> </plist>

View File

@@ -2,9 +2,7 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>com.apple.security.application-groups</key> <key>com.apple.security.application-groups</key>
<array> <array/>
<string>group.com.timetracker.app</string>
</array>
</dict> </dict>
</plist> </plist>