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:
@@ -23,10 +23,7 @@ struct ResidencesListView: View {
|
||||
isLoading: viewModel.isLoading,
|
||||
errorMessage: viewModel.errorMessage,
|
||||
content: { residences in
|
||||
ResidencesContent(
|
||||
residences: residences,
|
||||
tasksResponse: taskViewModel.tasksResponse
|
||||
)
|
||||
ResidencesContent(residences: residences)
|
||||
},
|
||||
emptyContent: {
|
||||
OrganicEmptyResidencesView()
|
||||
@@ -170,67 +167,28 @@ private struct OrganicToolbarButton: View {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Residence Task Stats
|
||||
struct ResidenceTaskStats {
|
||||
let totalCount: Int
|
||||
let overdueCount: Int
|
||||
let dueThisWeekCount: Int
|
||||
let dueNext30DaysCount: Int
|
||||
}
|
||||
|
||||
// MARK: - Residences Content View
|
||||
|
||||
private struct ResidencesContent: View {
|
||||
let residences: [ResidenceResponse]
|
||||
let tasksResponse: TaskColumnsResponse?
|
||||
@ObservedObject private var dataManager = DataManagerObservable.shared
|
||||
|
||||
/// Extract active tasks - skip completed_tasks and cancelled_tasks columns
|
||||
private var activeTasks: [TaskResponse] {
|
||||
guard let response = tasksResponse else { return [] }
|
||||
|
||||
var tasks: [TaskResponse] = []
|
||||
for column in response.columns {
|
||||
// Skip completed and cancelled columns (cancelled includes archived)
|
||||
let columnName = column.name.lowercased()
|
||||
if columnName == "completed_tasks" || columnName == "cancelled_tasks" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Add tasks from this column
|
||||
for task in column.tasks {
|
||||
tasks.append(task)
|
||||
}
|
||||
}
|
||||
return tasks
|
||||
}
|
||||
|
||||
/// Compute total summary from task data using shared TaskStatsCalculator
|
||||
/// Compute total summary from DataManagerObservable (single source of truth)
|
||||
private var computedSummary: TotalSummary {
|
||||
let dueDates = activeTasks.map { $0.effectiveDueDate }
|
||||
let stats = TaskStatsCalculator.calculate(from: dueDates)
|
||||
|
||||
let metrics = dataManager.totalTaskMetrics
|
||||
return TotalSummary(
|
||||
totalResidences: Int32(residences.count),
|
||||
totalTasks: Int32(activeTasks.count),
|
||||
totalTasks: Int32(dataManager.activeTasks.count),
|
||||
totalPending: 0,
|
||||
totalOverdue: Int32(stats.overdueCount),
|
||||
tasksDueNextWeek: Int32(stats.next7DaysCount),
|
||||
tasksDueNextMonth: Int32(stats.next30DaysCount)
|
||||
totalOverdue: Int32(metrics.overdueCount),
|
||||
tasksDueNextWeek: Int32(metrics.upcoming7Days),
|
||||
tasksDueNextMonth: Int32(metrics.upcoming30Days)
|
||||
)
|
||||
}
|
||||
|
||||
/// Get task stats for a specific residence using shared TaskStatsCalculator
|
||||
private func taskStats(for residenceId: Int32) -> ResidenceTaskStats {
|
||||
let residenceTasks = activeTasks.filter { $0.residenceId == residenceId }
|
||||
let dueDates = residenceTasks.map { $0.effectiveDueDate }
|
||||
let stats = TaskStatsCalculator.calculate(from: dueDates)
|
||||
|
||||
return ResidenceTaskStats(
|
||||
totalCount: residenceTasks.count,
|
||||
overdueCount: stats.overdueCount,
|
||||
dueThisWeekCount: stats.next7DaysCount,
|
||||
dueNext30DaysCount: stats.next30DaysCount
|
||||
)
|
||||
/// Get task metrics for a specific residence from DataManagerObservable
|
||||
private func taskMetrics(for residenceId: Int32) -> WidgetDataManager.TaskMetrics {
|
||||
dataManager.taskMetrics(for: residenceId)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
@@ -247,7 +205,7 @@ private struct ResidencesContent: View {
|
||||
NavigationLink(destination: ResidenceDetailView(residenceId: residence.id)) {
|
||||
ResidenceCard(
|
||||
residence: residence,
|
||||
taskStats: taskStats(for: residence.id)
|
||||
taskMetrics: taskMetrics(for: residence.id)
|
||||
)
|
||||
.padding(.horizontal, 16)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user