import SwiftUI struct CalendarYearGrid: View { let year: Int let data: CalendarData let onTapMonth: (Date) -> Void private let calendar: Calendar = { var c = Calendar(identifier: .gregorian) c.firstWeekday = 2 c.locale = Locale.current return c }() private var monthAnchors: [Date] { (1...12).compactMap { m in var comps = DateComponents() comps.year = year; comps.month = m; comps.day = 1 return calendar.date(from: comps) } } private let columns = Array(repeating: GridItem(.flexible(), spacing: 14), count: 3) var body: some View { LazyVGrid(columns: columns, spacing: 18) { ForEach(monthAnchors, id: \.self) { anchor in Button { onTapMonth(anchor) } label: { MiniMonth(anchor: anchor, data: data, calendar: calendar) } .buttonStyle(.plain) } } } } private struct MiniMonth: View { let anchor: Date let data: CalendarData let calendar: Calendar private var monthLabel: String { anchor.formatted(.dateTime.month()) } private var days: [Date] { guard let interval = calendar.dateInterval(of: .month, for: anchor) else { return [] } let count = calendar.dateComponents([.day], from: interval.start, to: interval.end).day ?? 30 return (0.. some View { let marks = data.marks(for: date, calendar: calendar) let ranges = data.ranges(touching: date, calendar: calendar) let color: Color = { if marks.abnormalCount > 0 { return Tj.Palette.brick } if let topSeverity = ranges.map(\.severity).max() { switch topSeverity { case 1, 2: return Tj.Palette.leaf case 3: return Tj.Palette.amber default: return Tj.Palette.brick } } if marks.hasAnyEvent { return Tj.Palette.text3.opacity(0.6) } return Tj.Palette.lineSoft }() let isToday = calendar.isDateInToday(date) return RoundedRectangle(cornerRadius: 2, style: .continuous) .fill(color) .frame(height: 8) .overlay( RoundedRectangle(cornerRadius: 2, style: .continuous) .strokeBorder(Tj.Palette.ink, lineWidth: isToday ? 1 : 0) ) } }