1.1.0 feature work: Monthly Check-in, Charts, Goals, Share, Reviews
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@ enum MonthlyCheckInStore {
|
||||
private static let completionsKey = "monthlyCheckInCompletions"
|
||||
private static let legacyLastCheckInKey = "lastCheckInDate"
|
||||
private static let entriesKey = "monthlyCheckInEntries"
|
||||
private static let graceDays = 20
|
||||
|
||||
// MARK: - Public Accessors
|
||||
|
||||
@@ -44,7 +45,7 @@ enum MonthlyCheckInStore {
|
||||
}
|
||||
|
||||
static func monthKey(for date: Date) -> String {
|
||||
Self.monthFormatter.string(from: date)
|
||||
Self.monthFormatter.string(from: effectiveMonth(for: date))
|
||||
}
|
||||
|
||||
static func allNotes() -> [(date: Date, note: String)] {
|
||||
@@ -74,11 +75,42 @@ enum MonthlyCheckInStore {
|
||||
}
|
||||
|
||||
static func setCompletionDate(_ completionDate: Date, for month: Date) {
|
||||
let targetMonth = month.startOfMonth
|
||||
let targetMonth = effectiveMonth(for: month, relativeTo: completionDate, graceDays: graceDays)
|
||||
let targetKey = monthKey(for: targetMonth)
|
||||
var entries = loadEntries()
|
||||
var didChange = false
|
||||
|
||||
let calendar = Calendar.current
|
||||
if calendar.isDate(month, inSameDayAs: completionDate),
|
||||
calendar.component(.day, from: completionDate) > graceDays {
|
||||
let completedEntries = entries.compactMap { key, entry -> (month: Date, entry: MonthlyCheckInEntry)? in
|
||||
guard let monthDate = monthFormatter.date(from: key)?.startOfMonth else { return nil }
|
||||
guard entry.completionTime != nil else { return nil }
|
||||
guard monthDate < targetMonth else { return nil }
|
||||
return (month: monthDate, entry: entry)
|
||||
}
|
||||
|
||||
if let lastCompleted = completedEntries.max(by: { $0.month < $1.month }) {
|
||||
var cursor = lastCompleted.month.adding(months: 1).startOfMonth
|
||||
while cursor < targetMonth {
|
||||
let key = monthFormatter.string(from: cursor)
|
||||
if entries[key] == nil {
|
||||
let fallbackCompletion = min(cursor.endOfMonth, completionDate)
|
||||
let entry = MonthlyCheckInEntry(
|
||||
note: lastCompleted.entry.note,
|
||||
rating: lastCompleted.entry.rating,
|
||||
mood: lastCompleted.entry.mood,
|
||||
completionTime: fallbackCompletion.timeIntervalSince1970,
|
||||
createdAt: Date().timeIntervalSince1970
|
||||
)
|
||||
entries[key] = entry
|
||||
didChange = true
|
||||
}
|
||||
cursor = cursor.adding(months: 1).startOfMonth
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the target month entry exists.
|
||||
var targetEntry = entries[targetKey] ?? MonthlyCheckInEntry(
|
||||
note: nil,
|
||||
@@ -123,6 +155,21 @@ enum MonthlyCheckInStore {
|
||||
return Date(timeIntervalSince1970: legacy)
|
||||
}
|
||||
|
||||
static func effectiveMonth(
|
||||
for date: Date,
|
||||
relativeTo referenceDate: Date = Date(),
|
||||
graceDays: Int = 20
|
||||
) -> Date {
|
||||
let calendar = Calendar.current
|
||||
if calendar.isDate(date, inSameDayAs: referenceDate) {
|
||||
let day = calendar.component(.day, from: referenceDate)
|
||||
if day <= graceDays {
|
||||
return referenceDate.adding(months: -1).startOfMonth
|
||||
}
|
||||
}
|
||||
return date.startOfMonth
|
||||
}
|
||||
|
||||
static func stats(referenceDate: Date = Date()) -> MonthlyCheckInStats {
|
||||
let cutoff = referenceDate.endOfMonth
|
||||
let entries = allEntries().filter { $0.date <= cutoff }
|
||||
|
||||
Reference in New Issue
Block a user