Deniz Cengiz 1 год назад
Родитель
Сommit
5f320d4de2

+ 4 - 0
Trio.xcodeproj/project.pbxproj

@@ -510,6 +510,7 @@
 		DD88C8E22C50420800F2D558 /* DefinitionRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD88C8E12C50420800F2D558 /* DefinitionRow.swift */; };
 		DD940BAA2CA7585D000830A5 /* GlucoseColorScheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD940BA92CA7585D000830A5 /* GlucoseColorScheme.swift */; };
 		DD940BAC2CA75889000830A5 /* DynamicGlucoseColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD940BAB2CA75889000830A5 /* DynamicGlucoseColor.swift */; };
+		DD98ACC02D71013200C0778F /* StatsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD98ACBF2D71013200C0778F /* StatsHelper.swift */; };
 		DD9ECB682CA99F4500AA7C45 /* TrioRemoteControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD9ECB672CA99F4500AA7C45 /* TrioRemoteControl.swift */; };
 		DD9ECB6A2CA99F6C00AA7C45 /* PushMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD9ECB692CA99F6C00AA7C45 /* PushMessage.swift */; };
 		DD9ECB702CA9A0BA00AA7C45 /* RemoteControlConfigStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD9ECB6D2CA9A0BA00AA7C45 /* RemoteControlConfigStateModel.swift */; };
@@ -1219,6 +1220,7 @@
 		DD88C8E12C50420800F2D558 /* DefinitionRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefinitionRow.swift; sourceTree = "<group>"; };
 		DD940BA92CA7585D000830A5 /* GlucoseColorScheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseColorScheme.swift; sourceTree = "<group>"; };
 		DD940BAB2CA75889000830A5 /* DynamicGlucoseColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicGlucoseColor.swift; sourceTree = "<group>"; };
+		DD98ACBF2D71013200C0778F /* StatsHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsHelper.swift; sourceTree = "<group>"; };
 		DD9ECB672CA99F4500AA7C45 /* TrioRemoteControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrioRemoteControl.swift; sourceTree = "<group>"; };
 		DD9ECB692CA99F6C00AA7C45 /* PushMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushMessage.swift; sourceTree = "<group>"; };
 		DD9ECB6D2CA9A0BA00AA7C45 /* RemoteControlConfigStateModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteControlConfigStateModel.swift; sourceTree = "<group>"; };
@@ -2498,6 +2500,7 @@
 		BD249D842D42FBD200412DEB /* ViewElements */ = {
 			isa = PBXGroup;
 			children = (
+				DD98ACBF2D71013200C0778F /* StatsHelper.swift */,
 				BD249D932D42FC5C00412DEB /* TDDChart.swift */,
 				BD249D912D42FC5000412DEB /* SectorChart.swift */,
 				BD249D8F2D42FC4300412DEB /* MealStatsView.swift */,
@@ -3613,6 +3616,7 @@
 				CE7CA3552A064973004BE681 /* ListStateIntent.swift in Sources */,
 				BDF530D82B40F8AC002CAF43 /* LockScreenView.swift in Sources */,
 				195D80B72AF697B800D25097 /* DynamicSettingsDataFlow.swift in Sources */,
+				DD98ACC02D71013200C0778F /* StatsHelper.swift in Sources */,
 				3862CC2E2743F9F700BF832C /* CalendarManager.swift in Sources */,
 				CEA4F62329BE10F70011ADF7 /* SavitzkyGolayFilter.swift in Sources */,
 				38B4F3C325E2A20B00E76A18 /* PumpSetupView.swift in Sources */,

+ 1 - 7
Trio/Sources/Localizations/Main/Localizable.xcstrings

@@ -15571,9 +15571,6 @@
     "10-90" : {
 
     },
-    "10% - 90%" : {
-
-    },
     "10%:" : {
 
     },
@@ -16127,9 +16124,6 @@
     "25-75" : {
 
     },
-    "25% - 75%" : {
-
-    },
     "25%:" : {
 
     },
@@ -180513,4 +180507,4 @@
     }
   },
   "version" : "1.0"
-}
+}

+ 59 - 204
Trio/Sources/Modules/Stat/View/ViewElements/BolusStatsView.swift

@@ -1,173 +1,76 @@
 import Charts
 import SwiftUI
 
+/// A view that displays a bar chart for bolus insulin statistics.
+///
+/// This view presents different types of bolus insulin (manual, SMB, and external) over time,
+/// allowing users to adjust the time interval and scroll through historical data.
 struct BolusStatsView: View {
+    /// The selected time interval for displaying statistics.
     @Binding var selectedDuration: Stat.StateModel.StatsTimeInterval
+    /// The list of bolus statistics data.
     let bolusStats: [BolusStats]
+    /// The state model containing cached statistics data.
     let state: Stat.StateModel
 
-    @State private var scrollPosition = Date() // gets updated in onAppear block
+    /// The current scroll position in the chart.
+    @State private var scrollPosition = Date()
+    /// The currently selected date in the chart.
     @State private var selectedDate: Date?
+    /// The calculated bolus insulin averages for the visible range.
     @State private var currentAverages: (manual: Double, smb: Double, external: Double) = (0, 0, 0)
+    /// Timer to throttle updates when scrolling.
     @State private var updateTimer = Stat.UpdateTimer()
 
-    /// Returns the time interval length for the visible domain based on selected duration
-    private var visibleDomainLength: TimeInterval {
-        switch selectedDuration {
-        case .Day: return 24 * 3600 // One day in seconds
-        case .Week: return 7 * 24 * 3600 // One week in seconds
-        case .Month: return 30 * 24 * 3600 // One month in seconds
-        case .Total: return 90 * 24 * 3600 // Three months in seconds
-        }
-    }
-
-    /// Calculates the visible date range based on scroll position and domain length
+    /// Computes the visible date range based on the current scroll position.
     private var visibleDateRange: (start: Date, end: Date) {
-        let start = scrollPosition // Current scroll position marks the start
-        let end = start.addingTimeInterval(visibleDomainLength)
-        return (start, end)
-    }
-
-    /// Returns the appropriate date format style based on the selected time interval
-    private var dateFormat: Date.FormatStyle {
-        switch selectedDuration {
-        case .Day:
-            return .dateTime.hour()
-        case .Week:
-            return .dateTime.weekday(.abbreviated)
-        case .Month:
-            return .dateTime.day()
-        case .Total:
-            return .dateTime.month(.abbreviated)
-        }
+        StatsHelper.visibleDateRange(from: scrollPosition, for: selectedDuration)
     }
 
-    /// Returns DateComponents for aligning dates based on the selected duration
-    private var alignmentComponents: DateComponents {
-        switch selectedDuration {
-        case .Day:
-            return DateComponents(hour: 0) // Align to midnight
-        case .Week:
-            return DateComponents(weekday: 2) // Monday is weekday 2
-        case .Month,
-             .Total:
-            return DateComponents(day: 1) // First day of month
-        }
-    }
-
-    /// Returns bolus statistics for a specific date
+    /// Retrieves the bolus statistic for a given date.
+    /// - Parameter date: The date for which to retrieve bolus data.
+    /// - Returns: The `BolusStats` object if available, otherwise `nil`.
     private func getBolusForDate(_ date: Date) -> BolusStats? {
-        let calendar = Calendar.current
-
-        return bolusStats.first { stat in
-            switch selectedDuration {
-            case .Day:
-                return calendar.isDate(stat.date, equalTo: date, toGranularity: .hour)
-            default:
-                return calendar.isDate(stat.date, inSameDayAs: date)
-            }
+        bolusStats.first { stat in
+            StatsHelper.isSameTimeUnit(stat.date, date, for: selectedDuration)
         }
     }
 
-    /// Updates the current averages for bolus insulin based on the visible date range
+    /// Updates the bolus insulin averages based on the visible date range.
     private func updateAverages() {
         currentAverages = state.getCachedBolusAverages(for: visibleDateRange)
     }
 
-    /// Formats the visible date range into a human-readable string
-    private func formatVisibleDateRange() -> String {
-        let start = visibleDateRange.start
-        let end = visibleDateRange.end
-        let calendar = Calendar.current
-        let today = Date()
-
-        // Special handling for Day view with relative dates
-        if selectedDuration == .Day {
-            let startDateText: String
-            let endDateText: String
-            let timeFormat = start.formatted(.dateTime.hour().minute())
-
-            // Format start date
-            if calendar.isDate(start, inSameDayAs: today) {
-                startDateText = "Today"
-            } else if calendar.isDate(start, inSameDayAs: calendar.date(byAdding: .day, value: -1, to: today)!) {
-                startDateText = "Yesterday"
-            } else if calendar.isDate(start, inSameDayAs: calendar.date(byAdding: .day, value: 1, to: today)!) {
-                startDateText = "Tomorrow"
-            } else {
-                startDateText = start.formatted(.dateTime.day().month())
-            }
-
-            // Format end date
-            if calendar.isDate(end, inSameDayAs: today) {
-                endDateText = "Today"
-            } else if calendar.isDate(end, inSameDayAs: calendar.date(byAdding: .day, value: -1, to: today)!) {
-                endDateText = "Yesterday"
-            } else if calendar.isDate(end, inSameDayAs: calendar.date(byAdding: .day, value: 1, to: today)!) {
-                endDateText = "Tomorrow"
-            } else {
-                endDateText = end.formatted(.dateTime.day().month())
-            }
-
-            // If start and end are on the same day, show date only once
-            if calendar.isDate(start, inSameDayAs: end) {
-                return "\(startDateText), \(timeFormat) - \(end.formatted(.dateTime.hour().minute()))"
+    /// A view displaying the statistics summary including bolus insulin averages.
+    private var statsView: some View {
+        HStack {
+            Grid(alignment: .leading) {
+                GridRow {
+                    Text("Manual:")
+                    Text(currentAverages.manual.formatted(.number.precision(.fractionLength(1))))
+                    Text("U")
+                }
+                GridRow {
+                    Text("SMB:")
+                    Text(currentAverages.smb.formatted(.number.precision(.fractionLength(1))))
+                    Text("U")
+                }
+                GridRow {
+                    Text("External:")
+                    Text(currentAverages.external.formatted(.number.precision(.fractionLength(1))))
+                    Text("U")
+                }
             }
+            .font(.headline)
 
-            return "\(startDateText), \(timeFormat) - \(endDateText), \(end.formatted(.dateTime.hour().minute()))"
-        }
-
-        // Standard format for other views - only show dates without time
-        let startText: String
-        let endText: String
-
-        // Check for relative dates
-        if calendar.isDate(start, inSameDayAs: today) {
-            startText = "Today"
-        } else if calendar.isDate(start, inSameDayAs: calendar.date(byAdding: .day, value: -1, to: today)!) {
-            startText = "Yesterday"
-        } else if calendar.isDate(start, inSameDayAs: calendar.date(byAdding: .day, value: 1, to: today)!) {
-            startText = "Tomorrow"
-        } else {
-            startText = start.formatted(.dateTime.day().month())
-        }
-
-        if calendar.isDate(end, inSameDayAs: today) {
-            endText = "Today"
-        } else if calendar.isDate(end, inSameDayAs: calendar.date(byAdding: .day, value: -1, to: today)!) {
-            endText = "Yesterday"
-        } else if calendar.isDate(end, inSameDayAs: calendar.date(byAdding: .day, value: 1, to: today)!) {
-            endText = "Tomorrow"
-        } else {
-            endText = end.formatted(.dateTime.day().month())
-        }
-
-        return "\(startText) - \(endText)"
-    }
-
-    private func isSameTimeUnit(_ date1: Date, _ date2: Date) -> Bool {
-        switch selectedDuration {
-        case .Day:
-            return Calendar.current.isDate(date1, equalTo: date2, toGranularity: .hour)
-        default:
-            return Calendar.current.isDate(date1, inSameDayAs: date2)
-        }
-    }
-
-    /// Returns the initial scroll position date based on the selected duration
-    private func getInitialScrollPosition() -> Date {
-        let calendar = Calendar.current
-        let now = Date()
+            Spacer()
 
-        switch selectedDuration {
-        case .Day:
-            return calendar.date(byAdding: .day, value: -1, to: now)!
-        case .Week:
-            return calendar.date(byAdding: .day, value: -7, to: now)!
-        case .Month:
-            return calendar.date(byAdding: .month, value: -1, to: now)!
-        case .Total:
-            return calendar.date(byAdding: .month, value: -3, to: now)!
+            Text(
+                StatsHelper
+                    .formatVisibleDateRange(from: visibleDateRange.start, to: visibleDateRange.end, for: selectedDuration)
+            )
+            .font(.footnote)
+            .foregroundStyle(.secondary)
         }
     }
 
@@ -177,7 +80,7 @@ struct BolusStatsView: View {
             chartsView
         }
         .onAppear {
-            scrollPosition = getInitialScrollPosition()
+            scrollPosition = StatsHelper.getInitialScrollPosition(for: selectedDuration)
             updateAverages()
         }
         .onChange(of: scrollPosition) {
@@ -187,61 +90,13 @@ struct BolusStatsView: View {
         }
         .onChange(of: selectedDuration) {
             Task {
-                scrollPosition = getInitialScrollPosition()
+                scrollPosition = StatsHelper.getInitialScrollPosition(for: selectedDuration)
                 updateAverages()
             }
         }
     }
 
-    private var statsView: some View {
-        HStack {
-            Grid(alignment: .leading) {
-                GridRow {
-                    Text("Manual:")
-                        .font(.headline)
-                        .foregroundStyle(.secondary)
-                    Text(currentAverages.manual.formatted(.number.precision(.fractionLength(1))))
-                        .font(.headline)
-                        .foregroundStyle(.secondary)
-                        .gridColumnAlignment(.trailing)
-                    Text("U")
-                        .font(.headline)
-                        .foregroundStyle(.secondary)
-                }
-                GridRow {
-                    Text("SMB:")
-                        .font(.headline)
-                        .foregroundStyle(.secondary)
-                    Text(currentAverages.smb.formatted(.number.precision(.fractionLength(1))))
-                        .font(.headline)
-                        .foregroundStyle(.secondary)
-                        .gridColumnAlignment(.trailing)
-                    Text("U")
-                        .font(.headline)
-                        .foregroundStyle(.secondary)
-                }
-                GridRow {
-                    Text("External:")
-                        .font(.headline)
-                        .foregroundStyle(.secondary)
-                    Text(currentAverages.external.formatted(.number.precision(.fractionLength(1))))
-                        .font(.headline)
-                        .foregroundStyle(.secondary)
-                        .gridColumnAlignment(.trailing)
-                    Text("U")
-                        .font(.headline)
-                        .foregroundStyle(.secondary)
-                }
-            }
-
-            Spacer()
-
-            Text(formatVisibleDateRange())
-                .font(.footnote)
-                .foregroundStyle(.secondary)
-        }
-    }
-
+    /// A view displaying the bar chart for bolus insulin statistics.
     private var chartsView: some View {
         Chart {
             ForEach(bolusStats) { stat in
@@ -254,7 +109,7 @@ struct BolusStatsView: View {
                 .position(by: .value("Type", "Boluses"))
                 .opacity(
                     selectedDate.map { date in
-                        isSameTimeUnit(stat.date, date) ? 1 : 0.3
+                        StatsHelper.isSameTimeUnit(stat.date, date, for: selectedDuration) ? 1 : 0.3
                     } ?? 1
                 )
 
@@ -267,7 +122,7 @@ struct BolusStatsView: View {
                 .position(by: .value("Type", "Boluses"))
                 .opacity(
                     selectedDate.map { date in
-                        isSameTimeUnit(stat.date, date) ? 1 : 0.3
+                        StatsHelper.isSameTimeUnit(stat.date, date, for: selectedDuration) ? 1 : 0.3
                     } ?? 1
                 )
                 // Correction Bolus Bar
@@ -279,7 +134,7 @@ struct BolusStatsView: View {
                 .position(by: .value("Type", "Boluses"))
                 .opacity(
                     selectedDate.map { date in
-                        isSameTimeUnit(stat.date, date) ? 1 : 0.3
+                        StatsHelper.isSameTimeUnit(stat.date, date, for: selectedDuration) ? 1 : 0.3
                     } ?? 1
                 )
             }
@@ -326,25 +181,25 @@ struct BolusStatsView: View {
                     switch selectedDuration {
                     case .Day:
                         if hour % 6 == 0 { // Show only every 6 hours
-                            AxisValueLabel(format: dateFormat, centered: true)
+                            AxisValueLabel(format: StatsHelper.dateFormat(for: selectedDuration), centered: true)
                                 .font(.footnote)
                             AxisGridLine()
                         }
                     case .Month:
                         if day % 5 == 0 { // Only show every 5th day
-                            AxisValueLabel(format: dateFormat, centered: true)
+                            AxisValueLabel(format: StatsHelper.dateFormat(for: selectedDuration), centered: true)
                                 .font(.footnote)
                             AxisGridLine()
                         }
                     case .Total:
                         // Only show January, April, July, October
                         if day == 1 && Calendar.current.component(.month, from: date) % 3 == 1 {
-                            AxisValueLabel(format: dateFormat, centered: true)
+                            AxisValueLabel(format: StatsHelper.dateFormat(for: selectedDuration), centered: true)
                                 .font(.footnote)
                             AxisGridLine()
                         }
                     default:
-                        AxisValueLabel(format: dateFormat, centered: true)
+                        AxisValueLabel(format: StatsHelper.dateFormat(for: selectedDuration), centered: true)
                             .font(.footnote)
                         AxisGridLine()
                     }
@@ -360,11 +215,11 @@ struct BolusStatsView: View {
                     DateComponents(minute: 0) : // Align to next hour for Day view
                     DateComponents(hour: 0), // Align to start of day for other views
                 majorAlignment: .matching(
-                    alignmentComponents
+                    StatsHelper.alignmentComponents(for: selectedDuration)
                 )
             )
         )
-        .chartXVisibleDomain(length: visibleDomainLength)
+        .chartXVisibleDomain(length: StatsHelper.visibleDomainLength(for: selectedDuration))
         .frame(height: 250)
     }
 }

+ 48 - 98
Trio/Sources/Modules/Stat/View/ViewElements/GlucosePercentileChart.swift

@@ -1,20 +1,32 @@
 import Charts
 import SwiftUI
 
+/// A view that displays an Ambulatory Glucose Profile (AGP) chart.
+///
+/// This chart visualizes glucose percentile statistics over a 24-hour period.
+/// It includes the 10-90 percentile, 25-75 percentile, median glucose values,
+/// and high/low glucose limits.
 struct GlucosePercentileChart: View {
+    /// The list of stored glucose values.
     let glucose: [GlucoseStored]
+    /// The upper glucose limit for the chart.
     let highLimit: Decimal
+    /// The lower glucose limit for the chart.
     let lowLimit: Decimal
+    /// The units used for glucose measurement (mg/dL or mmol/L).
     let units: GlucoseUnits
+    /// The hourly glucose statistics.
     let hourlyStats: [HourlyStats]
+    /// Flag indicating whether the chart represents today's data.
     let isToday: Bool
 
+    /// The currently selected hour in the chart.
     @State private var selection: Date? = nil
 
+    /// Retrieves the hourly statistics for the selected time.
     private var selectedStats: HourlyStats? {
         guard let selection = selection else { return nil }
 
-        // Don't show stats for future times if viewing today
         if isToday && selection > Date() {
             return nil
         }
@@ -123,66 +135,36 @@ struct GlucosePercentileChart: View {
         }
     }
 
+    /// A view displaying the legend for the chart.
     private var legend: some View {
         HStack(spacing: 20) {
             VStack {
-                // 10-90 Percentile
-                HStack(spacing: 8) {
-                    Rectangle()
-                        .frame(width: 20, height: 8)
-                        .foregroundStyle(.blue.opacity(0.2))
-                    Text("10% - 90%")
-                        .font(.caption)
-                        .foregroundStyle(.secondary)
-                }
-
-                // 25-75 Percentile
-                HStack(spacing: 8) {
-                    Rectangle()
-                        .frame(width: 20, height: 8)
-                        .foregroundStyle(.blue.opacity(0.3))
-                    Text("25% - 75%")
-                        .font(.caption)
-                        .foregroundStyle(.secondary)
-                }
+                legendItem(color: .blue.opacity(0.2), text: "10% - 90%")
+                legendItem(color: .blue.opacity(0.3), text: "25% - 75%")
             }
-
-            // Median
-            HStack(spacing: 8) {
-                Rectangle()
-                    .frame(width: 20, height: 2)
-                    .foregroundStyle(.blue)
-                Text("Median")
-                    .font(.caption)
-                    .foregroundStyle(.secondary)
-            }
-
+            legendItem(color: .blue, text: "Median")
             VStack {
-                // High Limit
-                HStack(spacing: 8) {
-                    Rectangle()
-                        .frame(width: 20, height: 1)
-                        .foregroundStyle(.orange)
-                    Text("High Limit")
-                        .font(.caption)
-                        .foregroundStyle(.secondary)
-                }
-
-                // Low Limit
-                HStack(spacing: 8) {
-                    Rectangle()
-                        .frame(width: 20, height: 1)
-                        .foregroundStyle(.red)
-                    Text("Low Limit")
-                        .font(.caption)
-                        .foregroundStyle(.secondary)
-                }
+                legendItem(color: .orange, text: "High Limit")
+                legendItem(color: .red, text: "Low Limit")
             }
         }
         .padding(.horizontal)
     }
+
+    /// Creates a legend item with a given color and text.
+    private func legendItem(color: Color, text: String) -> some View {
+        HStack(spacing: 8) {
+            Rectangle()
+                .frame(width: 20, height: 8)
+                .foregroundStyle(color)
+            Text(text)
+                .font(.caption)
+                .foregroundStyle(.secondary)
+        }
+    }
 }
 
+/// A popover view displaying detailed glucose statistics for a selected time.
 struct AGPSelectionPopover: View {
     let stats: HourlyStats
     let time: Date
@@ -198,75 +180,43 @@ struct AGPSelectionPopover: View {
         }
     }
 
+    /// A helper function to format glucose values based on the selected unit.
+    private func formattedGlucoseValue(_ value: Double) -> String {
+        units == .mmolL ? value.formattedAsMmolL :
+            value.formatted()
+    }
+
     var body: some View {
         VStack(alignment: .leading, spacing: 4) {
-            HStack {
-                Image(systemName: "clock")
-                Text(timeText)
-                    .bold()
-                    .foregroundStyle(.secondary)
-            }
-            .font(.subheadline)
-            .padding(.bottom, 4)
+            Text(timeText).bold().font(.subheadline)
 
             Grid(alignment: .leading, horizontalSpacing: 8, verticalSpacing: 4) {
                 GridRow {
-                    Text("90%:").bold()
-                    Text(units == .mmolL ? stats.percentile90.asMmolL.formatted(
-                        .number.grouping(.never).rounded()
-                            .precision(.fractionLength(1))
-                    ) : stats.percentile90.formatted(
-                        .number.grouping(.never).rounded()
-                            .precision(.fractionLength(0))
-                    ))
+                    Text("Median:").bold()
+                    Text(formattedGlucoseValue(stats.median))
                     Text(units.rawValue).foregroundStyle(.secondary)
                 }
                 GridRow {
-                    Text("75%:").bold()
-                    Text(units == .mmolL ? stats.percentile75.asMmolL.formatted(
-                        .number.grouping(.never).rounded()
-                            .precision(.fractionLength(1))
-                    ) : stats.percentile75.formatted(
-                        .number.grouping(.never).rounded()
-                            .precision(.fractionLength(0))
-                    ))
+                    Text("90%:").bold()
+                    Text(formattedGlucoseValue(stats.percentile90))
                     Text(units.rawValue).foregroundStyle(.secondary)
                 }
                 GridRow {
-                    Text("Median:").bold()
-                    Text(units == .mmolL ? stats.median.asMmolL.formatted(
-                        .number.grouping(.never).rounded()
-                            .precision(.fractionLength(1))
-                    ) : stats.median.formatted(
-                        .number.grouping(.never).rounded()
-                            .precision(.fractionLength(0))
-                    ))
+                    Text("75%:").bold()
+                    Text(formattedGlucoseValue(stats.percentile75))
                     Text(units.rawValue).foregroundStyle(.secondary)
                 }
                 GridRow {
                     Text("25%:").bold()
-                    Text(units == .mmolL ? stats.percentile25.asMmolL.formatted(
-                        .number.grouping(.never).rounded()
-                            .precision(.fractionLength(1))
-                    ) : stats.percentile25.formatted(
-                        .number.grouping(.never).rounded()
-                            .precision(.fractionLength(0))
-                    ))
+                    Text(formattedGlucoseValue(stats.percentile25))
                     Text(units.rawValue).foregroundStyle(.secondary)
                 }
                 GridRow {
                     Text("10%:").bold()
-                    Text(units == .mmolL ? stats.percentile10.asMmolL.formatted(
-                        .number.grouping(.never).rounded()
-                            .precision(.fractionLength(1))
-                    ) : stats.percentile10.formatted(
-                        .number.grouping(.never).rounded()
-                            .precision(.fractionLength(0))
-                    ))
+                    Text(formattedGlucoseValue(stats.percentile10))
                     Text(units.rawValue).foregroundStyle(.secondary)
                 }
-            }
-            .font(.headline)
+            }.font(.headline)
         }
         .padding(20)
         .background {

+ 60 - 258
Trio/Sources/Modules/Stat/View/ViewElements/MealStatsView.swift

@@ -1,302 +1,104 @@
 import Charts
 import SwiftUI
 
+/// A view that displays a bar chart for meal statistics.
+///
+/// This view presents macronutrient intake (carbohydrates, fats, and proteins) over time,
+/// allowing users to adjust the time interval and scroll through historical data.
 struct MealStatsView: View {
+    /// The selected time interval for displaying statistics.
     @Binding var selectedDuration: Stat.StateModel.StatsTimeInterval
+    /// The list of meal statistics data.
     let mealStats: [MealStats]
+    /// The state model containing cached statistics data.
     let state: Stat.StateModel
 
-    @State private var scrollPosition = Date() // gets updated in onAppear block
+    /// The current scroll position in the chart.
+    @State private var scrollPosition = Date()
+    /// The currently selected date in the chart.
     @State private var selectedDate: Date?
+    /// The calculated macronutrient averages for the visible range.
     @State private var currentAverages: (carbs: Double, fat: Double, protein: Double) = (0, 0, 0)
+    /// Timer to throttle updates when scrolling.
     @State private var updateTimer = Stat.UpdateTimer()
 
-    /// Returns the time interval length for the visible domain based on selected duration
-    /// - Returns: TimeInterval representing the visible time range in seconds
-    ///
-    /// Time intervals:
-    /// - Day: 24 hours (86400 seconds)
-    /// - Week: 7 days (604800 seconds)
-    /// - Month: 30 days (2592000 seconds)
-    /// - Total: 90 days (7776000 seconds)
-    private var visibleDomainLength: TimeInterval {
-        switch selectedDuration {
-        case .Day: return 24 * 3600 // One day in seconds
-        case .Week: return 7 * 24 * 3600 // One week in seconds
-        case .Month: return 30 * 24 * 3600 // One month in seconds (approximated)
-        case .Total: return 90 * 24 * 3600 // Three months in seconds
-        }
-    }
-
-    /// Calculates the visible date range based on scroll position and domain length
-    /// - Returns: Tuple containing start and end dates of the visible range
-    ///
-    /// The start date is determined by the current scroll position, while the end date
-    /// is calculated by adding the visible domain length to the start date
+    /// Computes the visible date range based on the current scroll position.
     private var visibleDateRange: (start: Date, end: Date) {
-        let start = scrollPosition // Current scroll position marks the start
-        let end = start.addingTimeInterval(visibleDomainLength)
-        return (start, end)
+        StatsHelper.visibleDateRange(from: scrollPosition, for: selectedDuration)
     }
 
-    /// Returns the appropriate date format style based on the selected time interval
-    /// - Returns: A Date.FormatStyle configured for the current time interval
-    ///
-    /// Format styles:
-    /// - Day: Shows hour only (e.g. "13")
-    /// - Week: Shows abbreviated weekday (e.g. "Mon")
-    /// - Month: Shows day of month (e.g. "15")
-    /// - Total: Shows abbreviated month (e.g. "Jan")
-    private var dateFormat: Date.FormatStyle {
-        switch selectedDuration {
-        case .Day:
-            return .dateTime.hour()
-        case .Week:
-            return .dateTime.weekday(.abbreviated)
-        case .Month:
-            return .dateTime.day()
-        case .Total:
-            return .dateTime.month(.abbreviated)
-        }
-    }
-
-    /// Returns DateComponents for aligning dates based on the selected duration
-    /// - Returns: DateComponents configured for the appropriate alignment
-    ///
-    /// This property provides date components for aligning dates in the chart:
-    /// - For Day view: Aligns to start of day (midnight)
-    /// - For Week view: Aligns to Monday (weekday 2)
-    /// - For Month/Total view: Aligns to first day of month
-    private var alignmentComponents: DateComponents {
-        switch selectedDuration {
-        case .Day:
-            return DateComponents(hour: 0) // Align to midnight
-        case .Week:
-            return DateComponents(weekday: 2) // Monday is weekday 2 in Calendar
-        case .Month,
-             .Total:
-            return DateComponents(day: 1) // First day of month
-        }
-    }
-
-    /// Returns meal statistics for a specific date
-    /// - Parameter date: The date to find meal statistics for
-    /// - Returns: MealStats object if found for the given date, nil otherwise
-    ///
-    /// This function searches through the meal statistics array to find the first entry
-    /// that matches the provided date (comparing only the day component, not time).
+    /// Retrieves the meal statistic for a given date.
+    /// - Parameter date: The date for which to retrieve meal data.
+    /// - Returns: The `MealStats` object if available, otherwise `nil`.
     private func getMealForDate(_ date: Date) -> MealStats? {
-        let calendar = Calendar.current
-
-        return mealStats.first { stat in
-            switch selectedDuration {
-            case .Day:
-                return calendar.isDate(stat.date, equalTo: date, toGranularity: .hour)
-            default:
-                return calendar.isDate(stat.date, inSameDayAs: date)
-            }
+        mealStats.first { stat in
+            StatsHelper.isSameTimeUnit(stat.date, date, for: selectedDuration)
         }
     }
 
-    /// Updates the current averages for macronutrients based on the visible date range
-    ///
-    /// This function:
-    /// - Gets the cached meal averages for the currently visible date range from the state
-    /// - Updates the currentAverages property with the retrieved values (carbs, fat, protein)
+    /// Updates the macronutrient averages based on the visible date range.
     private func updateAverages() {
-        // Get cached averages for visible time window
         currentAverages = state.getCachedMealAverages(for: visibleDateRange)
     }
 
-    /// Formats the visible date range into a human-readable string
-    /// - Returns: A formatted string representing the visible date range
-    ///
-    /// For Day view:
-    /// - Uses relative terms like "Today", "Yesterday", "Tomorrow" when applicable
-    /// - Shows time range in hours and minutes
-    /// - Combines dates if start and end are on the same day
-    ///
-    /// For other views:
-    /// - Uses standard date formatting
-    private func formatVisibleDateRange() -> String {
-        let start = visibleDateRange.start
-        let end = visibleDateRange.end
-        let calendar = Calendar.current
-        let today = Date()
-
-        // Special handling for Day view with relative dates
-        if selectedDuration == .Day {
-            let startDateText: String
-            let endDateText: String
-            let timeFormat = start.formatted(.dateTime.hour().minute())
-
-            // Format start date
-            if calendar.isDate(start, inSameDayAs: today) {
-                startDateText = "Today"
-            } else if calendar.isDate(start, inSameDayAs: calendar.date(byAdding: .day, value: -1, to: today)!) {
-                startDateText = "Yesterday"
-            } else if calendar.isDate(start, inSameDayAs: calendar.date(byAdding: .day, value: 1, to: today)!) {
-                startDateText = "Tomorrow"
-            } else {
-                startDateText = start.formatted(.dateTime.day().month())
-            }
-
-            // Format end date
-            if calendar.isDate(end, inSameDayAs: today) {
-                endDateText = "Today"
-            } else if calendar.isDate(end, inSameDayAs: calendar.date(byAdding: .day, value: -1, to: today)!) {
-                endDateText = "Yesterday"
-            } else if calendar.isDate(end, inSameDayAs: calendar.date(byAdding: .day, value: 1, to: today)!) {
-                endDateText = "Tomorrow"
-            } else {
-                endDateText = end.formatted(.dateTime.day().month())
-            }
-
-            // If start and end are on the same day, show date only once
-            if calendar.isDate(start, inSameDayAs: end) {
-                return "\(startDateText), \(timeFormat) - \(end.formatted(.dateTime.hour().minute()))"
-            }
-
-            return "\(startDateText), \(timeFormat) - \(endDateText), \(end.formatted(.dateTime.hour().minute()))"
-        }
-
-        // Standard format for other views - only show dates without time
-        let startText: String
-        let endText: String
-
-        // Check for relative dates
-        if calendar.isDate(start, inSameDayAs: today) {
-            startText = "Today"
-        } else if calendar.isDate(start, inSameDayAs: calendar.date(byAdding: .day, value: -1, to: today)!) {
-            startText = "Yesterday"
-        } else if calendar.isDate(start, inSameDayAs: calendar.date(byAdding: .day, value: 1, to: today)!) {
-            startText = "Tomorrow"
-        } else {
-            startText = start.formatted(.dateTime.day().month())
-        }
-
-        if calendar.isDate(end, inSameDayAs: today) {
-            endText = "Today"
-        } else if calendar.isDate(end, inSameDayAs: calendar.date(byAdding: .day, value: -1, to: today)!) {
-            endText = "Yesterday"
-        } else if calendar.isDate(end, inSameDayAs: calendar.date(byAdding: .day, value: 1, to: today)!) {
-            endText = "Tomorrow"
-        } else {
-            endText = end.formatted(.dateTime.day().month())
-        }
-
-        return "\(startText) - \(endText)"
-    }
-
-    /// Returns the initial scroll position date based on the selected duration
-    /// - Returns: A Date representing where the chart should initially scroll to
-    ///
-    /// This function calculates an appropriate starting scroll position by subtracting
-    /// a time interval from the current date based on the selected duration:
-    /// - For Day view: 1 day before now
-    /// - For Week view: 7 days before now
-    /// - For Month view: 1 month before now
-    /// - For Total view: 3 months before now
-    private func getInitialScrollPosition() -> Date {
-        let calendar = Calendar.current
-        let now = Date()
-
-        // Calculate scroll position based on selected time interval
-        switch selectedDuration {
-        case .Day:
-            return calendar.date(byAdding: .day, value: -1, to: now)!
-        case .Week:
-            return calendar.date(byAdding: .day, value: -7, to: now)!
-        case .Month:
-            return calendar.date(byAdding: .month, value: -1, to: now)!
-        case .Total:
-            return calendar.date(byAdding: .month, value: -3, to: now)!
-        }
-    }
-
-    private func isSameTimeUnit(_ date1: Date, _ date2: Date) -> Bool {
-        switch selectedDuration {
-        case .Day:
-            return Calendar.current.isDate(date1, equalTo: date2, toGranularity: .hour)
-        default:
-            return Calendar.current.isDate(date1, inSameDayAs: date2)
-        }
-    }
-
-    var body: some View {
-        VStack(alignment: .leading, spacing: 8) {
-            statsView
-            chartsView
-        }
-
-        .onAppear {
-            scrollPosition = getInitialScrollPosition()
-            updateAverages()
-        }
-        .onChange(of: scrollPosition) {
-            updateTimer.scheduleUpdate {
-                updateAverages()
-            }
-        }
-        .onChange(of: selectedDuration) {
-            Task {
-                scrollPosition = getInitialScrollPosition()
-                updateAverages()
-            }
-        }
-    }
-
+    /// A view displaying the statistics summary including macronutrient averages.
     private var statsView: some View {
         HStack {
             Grid(alignment: .leading) {
                 GridRow {
                     Text("Carbs:")
-                        .font(.headline)
-                        .foregroundStyle(.secondary)
                     Text(currentAverages.carbs.formatted(.number.precision(.fractionLength(1))))
-                        .font(.headline)
-                        .foregroundStyle(.secondary)
-                        .gridColumnAlignment(.trailing)
                     Text("g")
-                        .font(.headline)
-                        .foregroundStyle(.secondary)
                 }
                 if state.useFPUconversion {
                     GridRow {
                         Text("Fat:")
-                            .font(.headline)
-                            .foregroundStyle(.secondary)
                         Text(currentAverages.fat.formatted(.number.precision(.fractionLength(1))))
-                            .font(.headline)
-                            .foregroundStyle(.secondary)
-                            .gridColumnAlignment(.trailing)
                         Text("g")
-                            .font(.headline)
-                            .foregroundStyle(.secondary)
                     }
                     GridRow {
                         Text("Protein:")
-                            .font(.headline)
-                            .foregroundStyle(.secondary)
                         Text(currentAverages.protein.formatted(.number.precision(.fractionLength(1))))
-                            .font(.headline)
-                            .foregroundStyle(.secondary)
-                            .gridColumnAlignment(.trailing)
                         Text("g")
-                            .font(.headline)
-                            .foregroundStyle(.secondary)
                     }
                 }
             }
+            .font(.headline)
 
             Spacer()
 
-            Text(formatVisibleDateRange())
-                .font(.footnote)
-                .foregroundStyle(.secondary)
+            Text(
+                StatsHelper
+                    .formatVisibleDateRange(from: visibleDateRange.start, to: visibleDateRange.end, for: selectedDuration)
+            )
+            .font(.footnote)
+            .foregroundStyle(.secondary)
+        }
+    }
+
+    var body: some View {
+        VStack(alignment: .leading, spacing: 8) {
+            statsView
+            chartsView
+        }
+        .onAppear {
+            scrollPosition = StatsHelper.getInitialScrollPosition(for: selectedDuration)
+            updateAverages()
+        }
+        .onChange(of: scrollPosition) {
+            updateTimer.scheduleUpdate {
+                updateAverages()
+            }
+        }
+        .onChange(of: selectedDuration) {
+            Task {
+                scrollPosition = StatsHelper.getInitialScrollPosition(for: selectedDuration)
+                updateAverages()
+            }
         }
     }
 
+    /// A view displaying the bar chart for meal statistics.
     private var chartsView: some View {
         Chart {
             ForEach(mealStats) { stat in
@@ -309,7 +111,7 @@ struct MealStatsView: View {
                 .position(by: .value("Type", "Macros"))
                 .opacity(
                     selectedDate.map { date in
-                        isSameTimeUnit(stat.date, date) ? 1 : 0.3
+                        StatsHelper.isSameTimeUnit(stat.date, date, for: selectedDuration) ? 1 : 0.3
                     } ?? 1
                 )
                 if state.useFPUconversion {
@@ -322,7 +124,7 @@ struct MealStatsView: View {
                     .position(by: .value("Type", "Macros"))
                     .opacity(
                         selectedDate.map { date in
-                            isSameTimeUnit(stat.date, date) ? 1 : 0.3
+                            StatsHelper.isSameTimeUnit(stat.date, date, for: selectedDuration) ? 1 : 0.3
                         } ?? 1
                     )
                     // Protein Bar (top)
@@ -334,7 +136,7 @@ struct MealStatsView: View {
                     .position(by: .value("Type", "Macros"))
                     .opacity(
                         selectedDate.map { date in
-                            isSameTimeUnit(stat.date, date) ? 1 : 0.3
+                            StatsHelper.isSameTimeUnit(stat.date, date, for: selectedDuration) ? 1 : 0.3
                         } ?? 1
                     )
                 }
@@ -388,24 +190,24 @@ struct MealStatsView: View {
                     switch selectedDuration {
                     case .Day:
                         if hour % 6 == 0 {
-                            AxisValueLabel(format: dateFormat, centered: true)
+                            AxisValueLabel(format: StatsHelper.dateFormat(for: selectedDuration), centered: true)
                                 .font(.footnote)
                             AxisGridLine()
                         }
                     case .Month:
                         if day % 5 == 0 {
-                            AxisValueLabel(format: dateFormat, centered: true)
+                            AxisValueLabel(format: StatsHelper.dateFormat(for: selectedDuration), centered: true)
                                 .font(.footnote)
                             AxisGridLine()
                         }
                     case .Total:
                         if day == 1 && Calendar.current.component(.month, from: date) % 3 == 1 {
-                            AxisValueLabel(format: dateFormat, centered: true)
+                            AxisValueLabel(format: StatsHelper.dateFormat(for: selectedDuration), centered: true)
                                 .font(.footnote)
                             AxisGridLine()
                         }
                     default:
-                        AxisValueLabel(format: dateFormat, centered: true)
+                        AxisValueLabel(format: StatsHelper.dateFormat(for: selectedDuration), centered: true)
                             .font(.footnote)
                         AxisGridLine()
                     }
@@ -420,10 +222,10 @@ struct MealStatsView: View {
                 matching: selectedDuration == .Day ?
                     DateComponents(minute: 0) :
                     DateComponents(hour: 0),
-                majorAlignment: .matching(alignmentComponents)
+                majorAlignment: .matching(StatsHelper.alignmentComponents(for: selectedDuration))
             )
         )
-        .chartXVisibleDomain(length: visibleDomainLength)
+        .chartXVisibleDomain(length: StatsHelper.visibleDomainLength(for: selectedDuration))
         .frame(height: 250)
     }
 }

+ 112 - 0
Trio/Sources/Modules/Stat/View/ViewElements/StatsHelper.swift

@@ -0,0 +1,112 @@
+import Foundation
+import SwiftUI
+
+struct StatsHelper {
+    /// Returns the time interval length for the visible domain based on the selected duration.
+    /// - Parameter selectedDuration: The selected time interval for statistics.
+    /// - Returns: The time interval in seconds.
+    static func visibleDomainLength(for selectedDuration: Stat.StateModel.StatsTimeInterval) -> TimeInterval {
+        switch selectedDuration {
+        case .Day: return 24 * 3600
+        case .Week: return 7 * 24 * 3600
+        case .Month: return 30 * 24 * 3600
+        case .Total: return 90 * 24 * 3600
+        }
+    }
+
+    /// Computes the visible date range based on the scroll position and selected duration.
+    /// - Parameters:
+    ///   - scrollPosition: The current scroll position in the chart.
+    ///   - selectedDuration: The selected time interval for statistics.
+    /// - Returns: A tuple containing the start and end dates of the visible range.
+    static func visibleDateRange(
+        from scrollPosition: Date,
+        for selectedDuration: Stat.StateModel.StatsTimeInterval
+    ) -> (start: Date, end: Date) {
+        let end = scrollPosition.addingTimeInterval(visibleDomainLength(for: selectedDuration))
+        return (scrollPosition, end)
+    }
+
+    /// Returns the appropriate date format style based on the selected time interval.
+    /// - Parameter selectedDuration: The selected time interval for statistics.
+    /// - Returns: A Date.FormatStyle configured for the current time interval.
+    static func dateFormat(for selectedDuration: Stat.StateModel.StatsTimeInterval) -> Date.FormatStyle {
+        switch selectedDuration {
+        case .Day: return .dateTime.hour()
+        case .Week: return .dateTime.weekday(.abbreviated)
+        case .Month: return .dateTime.day()
+        case .Total: return .dateTime.month(.abbreviated)
+        }
+    }
+
+    /// Returns DateComponents for aligning dates based on the selected duration.
+    /// - Parameter selectedDuration: The selected time interval for statistics.
+    /// - Returns: DateComponents configured for the appropriate alignment.
+    static func alignmentComponents(for selectedDuration: Stat.StateModel.StatsTimeInterval) -> DateComponents {
+        switch selectedDuration {
+        case .Day: return DateComponents(hour: 0)
+        case .Week: return DateComponents(weekday: 2)
+        case .Month,
+             .Total: return DateComponents(day: 1)
+        }
+    }
+
+    /// Returns the initial scroll position date based on the selected duration.
+    /// - Parameter selectedDuration: The selected time interval for statistics.
+    /// - Returns: A Date representing the initial scroll position.
+    static func getInitialScrollPosition(for selectedDuration: Stat.StateModel.StatsTimeInterval) -> Date {
+        let calendar = Calendar.current
+        let now = Date()
+
+        switch selectedDuration {
+        case .Day: return calendar.date(byAdding: .day, value: -1, to: now)!
+        case .Week: return calendar.date(byAdding: .day, value: -7, to: now)!
+        case .Month: return calendar.date(byAdding: .month, value: -1, to: now)!
+        case .Total: return calendar.date(byAdding: .month, value: -3, to: now)!
+        }
+    }
+
+    /// Checks if two dates belong to the same time unit based on the selected duration.
+    /// - Parameters:
+    ///   - date1: The first date.
+    ///   - date2: The second date.
+    ///   - selectedDuration: The selected time interval for statistics.
+    /// - Returns: A Boolean indicating whether the two dates are in the same time unit.
+    static func isSameTimeUnit(_ date1: Date, _ date2: Date, for selectedDuration: Stat.StateModel.StatsTimeInterval) -> Bool {
+        let calendar = Calendar.current
+        switch selectedDuration {
+        case .Day:
+            return calendar.isDate(date1, equalTo: date2, toGranularity: .hour)
+        default:
+            return calendar.isDate(date1, inSameDayAs: date2)
+        }
+    }
+
+    /// Formats the visible date range into a human-readable string.
+    /// - Parameters:
+    ///   - start: The start date of the range.
+    ///   - end: The end date of the range.
+    ///   - selectedDuration: The selected time interval for statistics.
+    /// - Returns: A formatted string representing the visible date range.
+    static func formatVisibleDateRange(from start: Date, to end: Date, for _: Stat.StateModel.StatsTimeInterval) -> String {
+        let calendar = Calendar.current
+        let today = Date()
+
+        let formatDate: (Date) -> String = { date in
+            if calendar.isDate(date, inSameDayAs: today) {
+                return "Today"
+            } else if calendar.isDate(date, inSameDayAs: calendar.date(byAdding: .day, value: -1, to: today)!) {
+                return "Yesterday"
+            } else if calendar.isDate(date, inSameDayAs: calendar.date(byAdding: .day, value: 1, to: today)!) {
+                return "Tomorrow"
+            } else {
+                return date.formatted(.dateTime.day().month())
+            }
+        }
+
+        let startText = formatDate(start)
+        let endText = formatDate(end)
+
+        return startText == endText ? startText : "\(startText) - \(endText)"
+    }
+}

+ 55 - 166
Trio/Sources/Modules/Stat/View/ViewElements/TDDChart.swift

@@ -1,166 +1,67 @@
 import Charts
 import SwiftUI
 
+/// A view that displays a bar chart for Total Daily Dose (TDD) statistics.
+///
+/// This view presents insulin usage over time, with the ability to adjust the time interval
+/// and scroll through historical data.
 struct TDDChartView: View {
+    /// The selected time interval for displaying statistics.
     @Binding var selectedDuration: Stat.StateModel.StatsTimeInterval
+    /// The list of TDD statistics data.
     let tddStats: [TDDStats]
+    /// The state model containing cached statistics data.
     let state: Stat.StateModel
 
+    /// The current scroll position in the chart.
     @State private var scrollPosition = Date()
+    /// The currently selected date in the chart.
     @State private var selectedDate: Date?
+    /// The calculated average TDD for the visible range.
     @State private var currentAverage: Double = 0
+    /// Timer to throttle updates when scrolling.
     @State private var updateTimer = Stat.UpdateTimer()
 
-    private var visibleDomainLength: TimeInterval {
-        switch selectedDuration {
-        case .Day: return 24 * 3600
-        case .Week: return 7 * 24 * 3600
-        case .Month: return 30 * 24 * 3600
-        case .Total: return 90 * 24 * 3600
-        }
-    }
-
+    /// Computes the visible date range based on the current scroll position.
     private var visibleDateRange: (start: Date, end: Date) {
-        let start = scrollPosition
-        let end = start.addingTimeInterval(visibleDomainLength)
-        return (start, end)
-    }
-
-    private var dateFormat: Date.FormatStyle {
-        switch selectedDuration {
-        case .Day:
-            return .dateTime.hour()
-        case .Week:
-            return .dateTime.weekday(.abbreviated)
-        case .Month:
-            return .dateTime.day()
-        case .Total:
-            return .dateTime.month(.abbreviated)
-        }
-    }
-
-    private var alignmentComponents: DateComponents {
-        switch selectedDuration {
-        case .Day:
-            return DateComponents(hour: 0)
-        case .Week:
-            return DateComponents(weekday: 2)
-        case .Month,
-             .Total:
-            return DateComponents(day: 1)
-        }
+        StatsHelper.visibleDateRange(from: scrollPosition, for: selectedDuration)
     }
 
+    /// Retrieves the TDD statistic for a given date.
+    /// - Parameter date: The date for which to retrieve TDD data.
+    /// - Returns: The `TDDStats` object if available, otherwise `nil`.
     private func getTDDForDate(_ date: Date) -> TDDStats? {
-        let calendar = Calendar.current
-
-        return tddStats.first { stat in
-            switch selectedDuration {
-            case .Day:
-                return calendar.isDate(stat.date, equalTo: date, toGranularity: .hour)
-            default:
-                return calendar.isDate(stat.date, inSameDayAs: date)
-            }
+        tddStats.first { stat in
+            StatsHelper.isSameTimeUnit(stat.date, date, for: selectedDuration)
         }
     }
 
+    /// Updates the average TDD value based on the visible date range.
     private func updateAverages() {
         currentAverage = state.getCachedTDDAverages(for: visibleDateRange)
     }
 
-    /// Formats the visible date range into a human-readable string
-    private func formatVisibleDateRange() -> String {
-        let start = visibleDateRange.start
-        let end = visibleDateRange.end
-        let calendar = Calendar.current
-        let today = Date()
-
-        // Special handling for Day view with relative dates
-        if selectedDuration == .Day {
-            let startDateText: String
-            let endDateText: String
-            let timeFormat = start.formatted(.dateTime.hour().minute())
-
-            // Format start date
-            if calendar.isDate(start, inSameDayAs: today) {
-                startDateText = "Today"
-            } else if calendar.isDate(start, inSameDayAs: calendar.date(byAdding: .day, value: -1, to: today)!) {
-                startDateText = "Yesterday"
-            } else if calendar.isDate(start, inSameDayAs: calendar.date(byAdding: .day, value: 1, to: today)!) {
-                startDateText = "Tomorrow"
-            } else {
-                startDateText = start.formatted(.dateTime.day().month())
-            }
-
-            // Format end date
-            if calendar.isDate(end, inSameDayAs: today) {
-                endDateText = "Today"
-            } else if calendar.isDate(end, inSameDayAs: calendar.date(byAdding: .day, value: -1, to: today)!) {
-                endDateText = "Yesterday"
-            } else if calendar.isDate(end, inSameDayAs: calendar.date(byAdding: .day, value: 1, to: today)!) {
-                endDateText = "Tomorrow"
-            } else {
-                endDateText = end.formatted(.dateTime.day().month())
-            }
-
-            // If start and end are on the same day, show date only once
-            if calendar.isDate(start, inSameDayAs: end) {
-                return "\(startDateText), \(timeFormat) - \(end.formatted(.dateTime.hour().minute()))"
-            }
-
-            return "\(startDateText), \(timeFormat) - \(endDateText), \(end.formatted(.dateTime.hour().minute()))"
-        }
-
-        // Standard format for other views - only show dates without time
-        let startText: String
-        let endText: String
-
-        // Check for relative dates
-        if calendar.isDate(start, inSameDayAs: today) {
-            startText = "Today"
-        } else if calendar.isDate(start, inSameDayAs: calendar.date(byAdding: .day, value: -1, to: today)!) {
-            startText = "Yesterday"
-        } else if calendar.isDate(start, inSameDayAs: calendar.date(byAdding: .day, value: 1, to: today)!) {
-            startText = "Tomorrow"
-        } else {
-            startText = start.formatted(.dateTime.day().month())
-        }
-
-        if calendar.isDate(end, inSameDayAs: today) {
-            endText = "Today"
-        } else if calendar.isDate(end, inSameDayAs: calendar.date(byAdding: .day, value: -1, to: today)!) {
-            endText = "Yesterday"
-        } else if calendar.isDate(end, inSameDayAs: calendar.date(byAdding: .day, value: 1, to: today)!) {
-            endText = "Tomorrow"
-        } else {
-            endText = end.formatted(.dateTime.day().month())
-        }
-
-        return "\(startText) - \(endText)"
-    }
-
-    private func getInitialScrollPosition() -> Date {
-        let calendar = Calendar.current
-        let now = Date()
+    /// A view displaying the statistics summary including average TDD.
+    private var statsView: some View {
+        HStack {
+            Text("Average:")
+                .font(.headline)
+                .foregroundStyle(.secondary)
+            Text(currentAverage.formatted(.number.precision(.fractionLength(1))))
+                .font(.headline)
+                .foregroundStyle(.secondary)
+            Text("U")
+                .font(.headline)
+                .foregroundStyle(.secondary)
 
-        switch selectedDuration {
-        case .Day:
-            return calendar.date(byAdding: .day, value: -1, to: now)!
-        case .Week:
-            return calendar.date(byAdding: .day, value: -7, to: now)!
-        case .Month:
-            return calendar.date(byAdding: .month, value: -1, to: now)!
-        case .Total:
-            return calendar.date(byAdding: .month, value: -3, to: now)!
-        }
-    }
+            Spacer()
 
-    private func isSameTimeUnit(_ date1: Date, _ date2: Date) -> Bool {
-        switch selectedDuration {
-        case .Day:
-            return Calendar.current.isDate(date1, equalTo: date2, toGranularity: .hour)
-        default:
-            return Calendar.current.isDate(date1, inSameDayAs: date2)
+            Text(
+                StatsHelper
+                    .formatVisibleDateRange(from: visibleDateRange.start, to: visibleDateRange.end, for: selectedDuration)
+            )
+            .font(.footnote)
+            .foregroundStyle(.secondary)
         }
     }
 
@@ -170,7 +71,7 @@ struct TDDChartView: View {
             chartsView
         }
         .onAppear {
-            scrollPosition = getInitialScrollPosition()
+            scrollPosition = StatsHelper.getInitialScrollPosition(for: selectedDuration)
             updateAverages()
         }
         .onChange(of: scrollPosition) {
@@ -180,32 +81,13 @@ struct TDDChartView: View {
         }
         .onChange(of: selectedDuration) {
             Task {
-                scrollPosition = getInitialScrollPosition()
+                scrollPosition = StatsHelper.getInitialScrollPosition(for: selectedDuration)
                 updateAverages()
             }
         }
     }
 
-    private var statsView: some View {
-        HStack {
-            Text("Average:")
-                .font(.headline)
-                .foregroundStyle(.secondary)
-            Text(currentAverage.formatted(.number.precision(.fractionLength(1))))
-                .font(.headline)
-                .foregroundStyle(.secondary)
-            Text("U")
-                .font(.headline)
-                .foregroundStyle(.secondary)
-
-            Spacer()
-
-            Text(formatVisibleDateRange())
-                .font(.footnote)
-                .foregroundStyle(.secondary)
-        }
-    }
-
+    /// A view displaying the bar chart for TDD statistics.
     private var chartsView: some View {
         Chart {
             ForEach(tddStats) { stat in
@@ -216,7 +98,7 @@ struct TDDChartView: View {
                 .foregroundStyle(Color.insulin)
                 .opacity(
                     selectedDate.map { date in
-                        isSameTimeUnit(stat.date, date) ? 1 : 0.3
+                        StatsHelper.isSameTimeUnit(stat.date, date, for: selectedDuration) ? 1 : 0.3
                     } ?? 1
                 )
             }
@@ -258,24 +140,24 @@ struct TDDChartView: View {
                     switch selectedDuration {
                     case .Day:
                         if hour % 6 == 0 {
-                            AxisValueLabel(format: dateFormat, centered: true)
+                            AxisValueLabel(format: StatsHelper.dateFormat(for: selectedDuration), centered: true)
                                 .font(.footnote)
                             AxisGridLine()
                         }
                     case .Month:
                         if day % 5 == 0 {
-                            AxisValueLabel(format: dateFormat, centered: true)
+                            AxisValueLabel(format: StatsHelper.dateFormat(for: selectedDuration), centered: true)
                                 .font(.footnote)
                             AxisGridLine()
                         }
                     case .Total:
                         if day == 1 && Calendar.current.component(.month, from: date) % 3 == 1 {
-                            AxisValueLabel(format: dateFormat, centered: true)
+                            AxisValueLabel(format: StatsHelper.dateFormat(for: selectedDuration), centered: true)
                                 .font(.footnote)
                             AxisGridLine()
                         }
                     default:
-                        AxisValueLabel(format: dateFormat, centered: true)
+                        AxisValueLabel(format: StatsHelper.dateFormat(for: selectedDuration), centered: true)
                             .font(.footnote)
                         AxisGridLine()
                     }
@@ -290,14 +172,21 @@ struct TDDChartView: View {
                 matching: selectedDuration == .Day ?
                     DateComponents(minute: 0) :
                     DateComponents(hour: 0),
-                majorAlignment: .matching(alignmentComponents)
+                majorAlignment: .matching(StatsHelper.alignmentComponents(for: selectedDuration))
             )
         )
-        .chartXVisibleDomain(length: visibleDomainLength)
+        .chartXVisibleDomain(length: StatsHelper.visibleDomainLength(for: selectedDuration))
         .frame(height: 250)
     }
 }
 
+/// A popover view displaying TDD (Total Daily Dose) for a given time period.
+/// Shows the insulin amount in units (U) for an hourly or daily interval, depending on `selectedDuration`.
+///
+/// - Parameters:
+///   - date: The reference date for determining the displayed time range.
+///   - tdd: The TDDStats containing insulin usage data.
+///   - selectedDuration: The selected time interval (hourly or daily).
 private struct TDDSelectionPopover: View {
     let date: Date
     let tdd: TDDStats