Consolidate task metrics to single source of truth
- Add API column name constants to WidgetDataManager (overdueColumn, dueWithin7DaysColumn, due8To30DaysColumn, etc.) - Update DataManagerObservable to use WidgetDataManager column constants - Remove duplicate ResidenceTaskStats struct, use TaskMetrics everywhere - Delete TaskStatsCalculator.swift (consolidated into WidgetDataManager) - Rename confusing flags: isUpcoming → isDueWithin7Days, isLater → isDue8To30Days - Add comprehensive unit tests for TaskMetrics and WidgetTask 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -69,6 +69,8 @@ class CacheManager {
|
||||
let category: String?
|
||||
let residenceName: String?
|
||||
let isOverdue: Bool
|
||||
let isDueWithin7Days: Bool
|
||||
let isDue8To30Days: Bool
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id, title, description, priority, category
|
||||
@@ -76,6 +78,8 @@ class CacheManager {
|
||||
case dueDate = "due_date"
|
||||
case residenceName = "residence_name"
|
||||
case isOverdue = "is_overdue"
|
||||
case isDueWithin7Days = "is_due_within_7_days"
|
||||
case isDue8To30Days = "is_due_8_to_30_days"
|
||||
}
|
||||
|
||||
/// Whether this task is pending completion (tapped on widget, waiting for sync)
|
||||
@@ -203,27 +207,19 @@ struct SimpleEntry: TimelineEntry {
|
||||
upcomingTasks.first
|
||||
}
|
||||
|
||||
/// Computed task stats using shared TaskStatsCalculator
|
||||
/// Uses exclusive buckets: overdue | next 7 days | next 30 days (8-30)
|
||||
private var calculatedStats: TaskStats {
|
||||
let dueDates = upcomingTasks.map { $0.dueDate }
|
||||
return TaskStatsCalculator.calculate(from: dueDates)
|
||||
}
|
||||
|
||||
/// Overdue tasks count - uses the isOverdue flag from kanban categorization
|
||||
/// which correctly excludes in-progress tasks
|
||||
/// Overdue tasks count - uses isOverdue flag from API column
|
||||
var overdueCount: Int {
|
||||
upcomingTasks.filter { $0.isOverdue }.count
|
||||
}
|
||||
|
||||
/// Tasks due within the next 7 days (exclusive of overdue)
|
||||
/// Tasks due within the next 7 days
|
||||
var dueNext7DaysCount: Int {
|
||||
calculatedStats.next7DaysCount
|
||||
upcomingTasks.filter { $0.isDueWithin7Days }.count
|
||||
}
|
||||
|
||||
/// Tasks due within next 30 days (days 8-30, exclusive of next 7 days)
|
||||
/// Tasks due within 8-30 days
|
||||
var dueNext30DaysCount: Int {
|
||||
calculatedStats.next30DaysCount
|
||||
upcomingTasks.filter { $0.isDue8To30Days }.count
|
||||
}
|
||||
}
|
||||
|
||||
@@ -785,7 +781,9 @@ struct Casera: Widget {
|
||||
dueDate: "2024-12-15",
|
||||
category: "plumbing",
|
||||
residenceName: "Home",
|
||||
isOverdue: false
|
||||
isOverdue: false,
|
||||
isDueWithin7Days: true,
|
||||
isDue8To30Days: false
|
||||
),
|
||||
CacheManager.CustomTask(
|
||||
id: 2,
|
||||
@@ -796,7 +794,9 @@ struct Casera: Widget {
|
||||
dueDate: "2024-12-20",
|
||||
category: "painting",
|
||||
residenceName: "Home",
|
||||
isOverdue: false
|
||||
isOverdue: false,
|
||||
isDueWithin7Days: true,
|
||||
isDue8To30Days: false
|
||||
)
|
||||
],
|
||||
isInteractive: true
|
||||
@@ -816,7 +816,9 @@ struct Casera: Widget {
|
||||
dueDate: "2024-12-15",
|
||||
category: "plumbing",
|
||||
residenceName: "Home",
|
||||
isOverdue: false
|
||||
isOverdue: false,
|
||||
isDueWithin7Days: true,
|
||||
isDue8To30Days: false
|
||||
),
|
||||
CacheManager.CustomTask(
|
||||
id: 2,
|
||||
@@ -827,7 +829,9 @@ struct Casera: Widget {
|
||||
dueDate: "2024-12-20",
|
||||
category: "painting",
|
||||
residenceName: "Home",
|
||||
isOverdue: false
|
||||
isOverdue: false,
|
||||
isDueWithin7Days: true,
|
||||
isDue8To30Days: false
|
||||
),
|
||||
CacheManager.CustomTask(
|
||||
id: 3,
|
||||
@@ -838,7 +842,9 @@ struct Casera: Widget {
|
||||
dueDate: "2024-12-25",
|
||||
category: "maintenance",
|
||||
residenceName: "Home",
|
||||
isOverdue: false
|
||||
isOverdue: false,
|
||||
isDueWithin7Days: false,
|
||||
isDue8To30Days: true
|
||||
)
|
||||
],
|
||||
isInteractive: false
|
||||
@@ -868,7 +874,9 @@ struct Casera: Widget {
|
||||
dueDate: "2024-12-15",
|
||||
category: "plumbing",
|
||||
residenceName: "Home",
|
||||
isOverdue: true
|
||||
isOverdue: true,
|
||||
isDueWithin7Days: false,
|
||||
isDue8To30Days: false
|
||||
),
|
||||
CacheManager.CustomTask(
|
||||
id: 2,
|
||||
@@ -879,7 +887,9 @@ struct Casera: Widget {
|
||||
dueDate: "2024-12-20",
|
||||
category: "painting",
|
||||
residenceName: "Cabin",
|
||||
isOverdue: false
|
||||
isOverdue: false,
|
||||
isDueWithin7Days: true,
|
||||
isDue8To30Days: false
|
||||
),
|
||||
CacheManager.CustomTask(
|
||||
id: 3,
|
||||
@@ -890,7 +900,9 @@ struct Casera: Widget {
|
||||
dueDate: "2024-12-25",
|
||||
category: "maintenance",
|
||||
residenceName: "Home",
|
||||
isOverdue: false
|
||||
isOverdue: false,
|
||||
isDueWithin7Days: false,
|
||||
isDue8To30Days: true
|
||||
)
|
||||
],
|
||||
isInteractive: true
|
||||
@@ -910,7 +922,9 @@ struct Casera: Widget {
|
||||
dueDate: nil,
|
||||
category: nil,
|
||||
residenceName: nil,
|
||||
isOverdue: false
|
||||
isOverdue: false,
|
||||
isDueWithin7Days: true,
|
||||
isDue8To30Days: false
|
||||
),
|
||||
CacheManager.CustomTask(
|
||||
id: 2,
|
||||
@@ -921,7 +935,9 @@ struct Casera: Widget {
|
||||
dueDate: nil,
|
||||
category: nil,
|
||||
residenceName: nil,
|
||||
isOverdue: false
|
||||
isOverdue: false,
|
||||
isDueWithin7Days: true,
|
||||
isDue8To30Days: false
|
||||
),
|
||||
CacheManager.CustomTask(
|
||||
id: 3,
|
||||
@@ -932,7 +948,9 @@ struct Casera: Widget {
|
||||
dueDate: nil,
|
||||
category: nil,
|
||||
residenceName: nil,
|
||||
isOverdue: false
|
||||
isOverdue: false,
|
||||
isDueWithin7Days: false,
|
||||
isDue8To30Days: true
|
||||
),
|
||||
CacheManager.CustomTask(
|
||||
id: 4,
|
||||
@@ -943,7 +961,9 @@ struct Casera: Widget {
|
||||
dueDate: nil,
|
||||
category: nil,
|
||||
residenceName: nil,
|
||||
isOverdue: false
|
||||
isOverdue: false,
|
||||
isDueWithin7Days: false,
|
||||
isDue8To30Days: true
|
||||
),
|
||||
CacheManager.CustomTask(
|
||||
id: 5,
|
||||
@@ -954,7 +974,9 @@ struct Casera: Widget {
|
||||
dueDate: nil,
|
||||
category: nil,
|
||||
residenceName: nil,
|
||||
isOverdue: false
|
||||
isOverdue: false,
|
||||
isDueWithin7Days: true,
|
||||
isDue8To30Days: false
|
||||
)
|
||||
],
|
||||
isInteractive: false
|
||||
@@ -984,7 +1006,9 @@ struct Casera: Widget {
|
||||
dueDate: "2024-12-15",
|
||||
category: "plumbing",
|
||||
residenceName: "Home",
|
||||
isOverdue: true
|
||||
isOverdue: true,
|
||||
isDueWithin7Days: false,
|
||||
isDue8To30Days: false
|
||||
),
|
||||
CacheManager.CustomTask(
|
||||
id: 2,
|
||||
@@ -995,7 +1019,9 @@ struct Casera: Widget {
|
||||
dueDate: "2024-12-20",
|
||||
category: "painting",
|
||||
residenceName: "Cabin",
|
||||
isOverdue: false
|
||||
isOverdue: false,
|
||||
isDueWithin7Days: true,
|
||||
isDue8To30Days: false
|
||||
),
|
||||
CacheManager.CustomTask(
|
||||
id: 3,
|
||||
@@ -1006,7 +1032,9 @@ struct Casera: Widget {
|
||||
dueDate: "2024-12-25",
|
||||
category: "maintenance",
|
||||
residenceName: "Home",
|
||||
isOverdue: false
|
||||
isOverdue: false,
|
||||
isDueWithin7Days: true,
|
||||
isDue8To30Days: false
|
||||
),
|
||||
CacheManager.CustomTask(
|
||||
id: 4,
|
||||
@@ -1017,7 +1045,9 @@ struct Casera: Widget {
|
||||
dueDate: "2024-12-28",
|
||||
category: "hvac",
|
||||
residenceName: "Beach House",
|
||||
isOverdue: false
|
||||
isOverdue: false,
|
||||
isDueWithin7Days: true,
|
||||
isDue8To30Days: false
|
||||
),
|
||||
CacheManager.CustomTask(
|
||||
id: 5,
|
||||
@@ -1028,7 +1058,9 @@ struct Casera: Widget {
|
||||
dueDate: "2024-12-30",
|
||||
category: "safety",
|
||||
residenceName: "Home",
|
||||
isOverdue: false
|
||||
isOverdue: false,
|
||||
isDueWithin7Days: false,
|
||||
isDue8To30Days: true
|
||||
),
|
||||
CacheManager.CustomTask(
|
||||
id: 6,
|
||||
@@ -1039,7 +1071,9 @@ struct Casera: Widget {
|
||||
dueDate: "2025-01-05",
|
||||
category: "plumbing",
|
||||
residenceName: "Cabin",
|
||||
isOverdue: false
|
||||
isOverdue: false,
|
||||
isDueWithin7Days: false,
|
||||
isDue8To30Days: true
|
||||
),
|
||||
CacheManager.CustomTask(
|
||||
id: 7,
|
||||
@@ -1050,7 +1084,9 @@ struct Casera: Widget {
|
||||
dueDate: "2025-01-10",
|
||||
category: "exterior",
|
||||
residenceName: "Home",
|
||||
isOverdue: false
|
||||
isOverdue: false,
|
||||
isDueWithin7Days: false,
|
||||
isDue8To30Days: true
|
||||
),
|
||||
CacheManager.CustomTask(
|
||||
id: 8,
|
||||
@@ -1061,7 +1097,9 @@ struct Casera: Widget {
|
||||
dueDate: "2025-01-12",
|
||||
category: "appliances",
|
||||
residenceName: "Beach House",
|
||||
isOverdue: false
|
||||
isOverdue: false,
|
||||
isDueWithin7Days: false,
|
||||
isDue8To30Days: true
|
||||
)
|
||||
],
|
||||
isInteractive: true
|
||||
@@ -1081,7 +1119,9 @@ struct Casera: Widget {
|
||||
dueDate: nil,
|
||||
category: nil,
|
||||
residenceName: nil,
|
||||
isOverdue: false
|
||||
isOverdue: false,
|
||||
isDueWithin7Days: false,
|
||||
isDue8To30Days: false
|
||||
),
|
||||
CacheManager.CustomTask(
|
||||
id: 2,
|
||||
@@ -1092,7 +1132,9 @@ struct Casera: Widget {
|
||||
dueDate: nil,
|
||||
category: nil,
|
||||
residenceName: nil,
|
||||
isOverdue: false
|
||||
isOverdue: false,
|
||||
isDueWithin7Days: false,
|
||||
isDue8To30Days: false
|
||||
),
|
||||
CacheManager.CustomTask(
|
||||
id: 3,
|
||||
@@ -1103,7 +1145,9 @@ struct Casera: Widget {
|
||||
dueDate: nil,
|
||||
category: nil,
|
||||
residenceName: nil,
|
||||
isOverdue: false
|
||||
isOverdue: false,
|
||||
isDueWithin7Days: false,
|
||||
isDue8To30Days: false
|
||||
),
|
||||
CacheManager.CustomTask(
|
||||
id: 4,
|
||||
@@ -1114,7 +1158,9 @@ struct Casera: Widget {
|
||||
dueDate: nil,
|
||||
category: nil,
|
||||
residenceName: nil,
|
||||
isOverdue: false
|
||||
isOverdue: false,
|
||||
isDueWithin7Days: false,
|
||||
isDue8To30Days: false
|
||||
),
|
||||
CacheManager.CustomTask(
|
||||
id: 5,
|
||||
@@ -1125,7 +1171,9 @@ struct Casera: Widget {
|
||||
dueDate: nil,
|
||||
category: nil,
|
||||
residenceName: nil,
|
||||
isOverdue: false
|
||||
isOverdue: false,
|
||||
isDueWithin7Days: false,
|
||||
isDue8To30Days: false
|
||||
),
|
||||
CacheManager.CustomTask(
|
||||
id: 6,
|
||||
@@ -1136,7 +1184,9 @@ struct Casera: Widget {
|
||||
dueDate: nil,
|
||||
category: nil,
|
||||
residenceName: nil,
|
||||
isOverdue: false
|
||||
isOverdue: false,
|
||||
isDueWithin7Days: false,
|
||||
isDue8To30Days: false
|
||||
),
|
||||
CacheManager.CustomTask(
|
||||
id: 7,
|
||||
@@ -1147,7 +1197,9 @@ struct Casera: Widget {
|
||||
dueDate: nil,
|
||||
category: nil,
|
||||
residenceName: nil,
|
||||
isOverdue: false
|
||||
isOverdue: false,
|
||||
isDueWithin7Days: false,
|
||||
isDue8To30Days: false
|
||||
)
|
||||
],
|
||||
isInteractive: false
|
||||
|
||||
Reference in New Issue
Block a user