- §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。
109 lines
3.1 KiB
Swift
109 lines
3.1 KiB
Swift
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)
|
|
}
|
|
}
|