docs(claude): sync §5/§7/§10 with Monitor+Profile; fix SeriesBucket SwiftData import
- §5 schema 重写为 7 @Model 完整列表(含 UserProfile + Indicator.seriesKey) - §7 IA 改成 5 槽 TabBar(2 内容 + 中间 + + 2 设置),记录入口 5 个 kind - §10.6 红线例外清单加 Monitor + Profile(Symptom 也补上) - SeriesBucket.swift 缺 import SwiftData(persistentModelID 报错) 全套测试 50 case pass / 0 fail / 0 warning。
This commit is contained in:
108
康康/Features/Trends/CalendarMarkers.swift
Normal file
108
康康/Features/Trends/CalendarMarkers.swift
Normal file
@@ -0,0 +1,108 @@
|
||||
import SwiftUI
|
||||
import SwiftData
|
||||
import Foundation
|
||||
|
||||
struct SymptomRange: Identifiable, Hashable {
|
||||
let id: String
|
||||
let name: String
|
||||
let startDay: Date
|
||||
let endDay: Date
|
||||
let severity: Int
|
||||
let isOngoing: Bool
|
||||
|
||||
var color: Color {
|
||||
switch severity {
|
||||
case 1, 2: return Tj.Palette.leaf
|
||||
case 3: return Tj.Palette.amber
|
||||
default: return Tj.Palette.brick
|
||||
}
|
||||
}
|
||||
|
||||
func contains(_ day: Date, calendar: Calendar = .current) -> Bool {
|
||||
let d = calendar.startOfDay(for: day)
|
||||
return d >= startDay && d <= endDay
|
||||
}
|
||||
|
||||
func position(_ day: Date, calendar: Calendar = .current) -> Position {
|
||||
let d = calendar.startOfDay(for: day)
|
||||
let isStart = d == startDay
|
||||
let isEnd = d == endDay
|
||||
if isStart && isEnd { return .single }
|
||||
if isStart { return .start }
|
||||
if isEnd { return .end }
|
||||
return .middle
|
||||
}
|
||||
|
||||
enum Position { case single, start, middle, end }
|
||||
}
|
||||
|
||||
struct DayMarks: Hashable {
|
||||
var abnormalCount: Int = 0
|
||||
var normalCount: Int = 0
|
||||
var reportCount: Int = 0
|
||||
var diaryCount: Int = 0
|
||||
|
||||
var hasAnyEvent: Bool {
|
||||
abnormalCount + normalCount + reportCount + diaryCount > 0
|
||||
}
|
||||
}
|
||||
|
||||
struct CalendarData {
|
||||
let dayMarks: [Date: DayMarks]
|
||||
let symptomRanges: [SymptomRange]
|
||||
|
||||
func marks(for day: Date, calendar: Calendar = .current) -> DayMarks {
|
||||
dayMarks[calendar.startOfDay(for: day)] ?? DayMarks()
|
||||
}
|
||||
|
||||
func ranges(touching day: Date, calendar: Calendar = .current) -> [SymptomRange] {
|
||||
symptomRanges.filter { $0.contains(day, calendar: calendar) }
|
||||
}
|
||||
|
||||
static func build(indicators: [Indicator],
|
||||
reports: [Report],
|
||||
diaries: [DiaryEntry],
|
||||
symptoms: [Symptom],
|
||||
now: Date = .now,
|
||||
calendar: Calendar = .current) -> CalendarData {
|
||||
var buckets: [Date: DayMarks] = [:]
|
||||
|
||||
for i in indicators {
|
||||
let day = calendar.startOfDay(for: i.capturedAt)
|
||||
var m = buckets[day] ?? DayMarks()
|
||||
if i.status == .normal {
|
||||
m.normalCount += 1
|
||||
} else {
|
||||
m.abnormalCount += 1
|
||||
}
|
||||
buckets[day] = m
|
||||
}
|
||||
|
||||
for r in reports {
|
||||
let day = calendar.startOfDay(for: r.reportDate)
|
||||
var m = buckets[day] ?? DayMarks()
|
||||
m.reportCount += 1
|
||||
buckets[day] = m
|
||||
}
|
||||
|
||||
for d in diaries {
|
||||
let day = calendar.startOfDay(for: d.createdAt)
|
||||
var m = buckets[day] ?? DayMarks()
|
||||
m.diaryCount += 1
|
||||
buckets[day] = m
|
||||
}
|
||||
|
||||
let ranges: [SymptomRange] = symptoms.map { s in
|
||||
SymptomRange(
|
||||
id: "\(s.persistentModelID)",
|
||||
name: s.name,
|
||||
startDay: calendar.startOfDay(for: s.startedAt),
|
||||
endDay: calendar.startOfDay(for: s.endedAt ?? now),
|
||||
severity: s.severity,
|
||||
isOngoing: s.isOngoing
|
||||
)
|
||||
}
|
||||
|
||||
return CalendarData(dayMarks: buckets, symptomRanges: ranges)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user