85 lines
2.7 KiB
Swift
85 lines
2.7 KiB
Swift
import Foundation
|
|
import Observation
|
|
import SwiftData
|
|
|
|
@MainActor
|
|
@Observable
|
|
final class DashboardViewModel {
|
|
private(set) var dailyGoalProgress: [GoalProgress] = []
|
|
private(set) var remainingTasks: [TaskItem] = []
|
|
private(set) var completedTaskCount: Int = 0
|
|
private(set) var totalTaskCount: Int = 0
|
|
|
|
private var context: ModelContext?
|
|
|
|
var overallProgress: Double {
|
|
if !dailyGoalProgress.isEmpty {
|
|
let sum = dailyGoalProgress.reduce(0.0) { $0 + $1.ratio }
|
|
return sum / Double(dailyGoalProgress.count)
|
|
}
|
|
guard totalTaskCount > 0 else { return 0 }
|
|
return Double(completedTaskCount) / Double(totalTaskCount)
|
|
}
|
|
|
|
var topGoalProgress: [GoalProgress] {
|
|
Array(dailyGoalProgress.sorted { $0.ratio < $1.ratio }.prefix(3))
|
|
}
|
|
|
|
func setup(context: ModelContext) {
|
|
self.context = context
|
|
refresh()
|
|
}
|
|
|
|
func refresh() {
|
|
guard let context else { return }
|
|
|
|
let goalDescriptor = FetchDescriptor<Goal>()
|
|
let goals = (try? context.fetch(goalDescriptor)) ?? []
|
|
let dailyGoals = goals.filter { $0.frequency == .daily }
|
|
dailyGoalProgress = dailyGoals.map { goal in
|
|
let current = todayValue(for: goal)
|
|
return GoalProgress(goal: goal, current: current, target: goal.conditions)
|
|
}
|
|
|
|
let taskDescriptor = FetchDescriptor<TaskItem>(sortBy: [SortDescriptor(\.name)])
|
|
let tasks = (try? context.fetch(taskDescriptor)) ?? []
|
|
totalTaskCount = tasks.count
|
|
remainingTasks = tasks.filter { !isCompletedToday($0) }
|
|
completedTaskCount = totalTaskCount - remainingTasks.count
|
|
}
|
|
|
|
func addCount(to task: TaskItem) {
|
|
guard let context else { return }
|
|
let log = TaskLog(date: .now, duration: 0, count: 1)
|
|
log.task = task
|
|
context.insert(log)
|
|
try? context.save()
|
|
refresh()
|
|
}
|
|
|
|
private func isCompletedToday(_ task: TaskItem) -> Bool {
|
|
let todayLogs = task.logs.filter { Calendar.current.isDateInToday($0.date) }
|
|
switch task.type {
|
|
case .count:
|
|
return todayLogs.reduce(0) { $0 + $1.count } > 0
|
|
case .time:
|
|
return !todayLogs.isEmpty
|
|
}
|
|
}
|
|
|
|
private func todayValue(for goal: Goal) -> Double {
|
|
let logs: [TaskLog]
|
|
if let task = goal.task {
|
|
logs = task.logs
|
|
} else if let category = goal.category {
|
|
logs = category.tasks.flatMap { $0.logs }
|
|
} else {
|
|
logs = []
|
|
}
|
|
let todayLogs = logs.filter { Calendar.current.isDateInToday($0.date) }
|
|
return goal.targetType == "count"
|
|
? Double(todayLogs.reduce(0) { $0 + $1.count })
|
|
: todayLogs.reduce(0.0) { $0 + $1.duration }
|
|
}
|
|
}
|