/ BalanceKit / ReportView.swift
ReportView.swift
  1  //
  2  //  ReportView.swift
  3  //  BalanceKit
  4  //
  5  //  Created by Alexander Kunau on 12.07.25.
  6  //
  7  
  8  import SwiftUI
  9  import Charts
 10  
 11  struct ReportView: View {
 12      @ObservedObject var dataManager: FoodDataManager
 13      @State private var selectedPeriod: TimePeriod = .week
 14      
 15      enum TimePeriod: String, CaseIterable {
 16          case week = "Woche"
 17          case month = "Monat"
 18          case quarter = "90 Tage"
 19      }
 20      
 21      var body: some View {
 22          ScrollView {
 23              VStack(spacing: 20) {
 24                  // Übersicht Metriken
 25                  overviewSection
 26                  
 27                  // Kalorien-Verlauf
 28                  caloriesChartSection
 29                  
 30                  // Makronährstoffe-Verlauf
 31                  macrosChartSection
 32                  
 33                  // Durchschnittswerte
 34                  averagesSection
 35              }
 36              .padding()
 37          }
 38          .navigationTitle("Berichte")
 39          .navigationBarTitleDisplayMode(.inline)
 40      }
 41      
 42      // MARK: - Übersicht Section
 43      private var overviewSection: some View {
 44          VStack(spacing: 16) {
 45              Text("Übersicht")
 46                  .font(.headline)
 47                  .frame(maxWidth: .infinity, alignment: .leading)
 48              
 49              HStack(spacing: 16) {
 50                  // Durchschnitt Kalorien
 51                  VStack(spacing: 8) {
 52                      Text("\(Int(averageCalories))")
 53                          .font(.system(size: 32, weight: .bold))
 54                          .foregroundColor(.green)
 55                      Text("Ø Kalorien/Tag")
 56                          .font(.caption)
 57                          .foregroundColor(.secondary)
 58                          .multilineTextAlignment(.center)
 59                  }
 60                  .frame(maxWidth: .infinity)
 61                  .padding()
 62                  .background(Color(.secondarySystemGroupedBackground))
 63                  .cornerRadius(12)
 64                  
 65                  // Gesamt Kalorien
 66                  VStack(spacing: 8) {
 67                      Text("\(totalCalories)")
 68                          .font(.system(size: 32, weight: .bold))
 69                          .foregroundColor(.blue)
 70                      Text("Gesamt")
 71                          .font(.caption)
 72                          .foregroundColor(.secondary)
 73                  }
 74                  .frame(maxWidth: .infinity)
 75                  .padding()
 76                  .background(Color(.secondarySystemGroupedBackground))
 77                  .cornerRadius(12)
 78              }
 79          }
 80      }
 81      
 82      // MARK: - Kalorien Chart Section
 83      private var caloriesChartSection: some View {
 84          VStack(spacing: 12) {
 85              HStack {
 86                  Text("Kalorien-Verlauf")
 87                      .font(.headline)
 88                  Spacer()
 89                  Picker("Zeitraum", selection: $selectedPeriod) {
 90                      ForEach(TimePeriod.allCases, id: \.self) { period in
 91                          Text(period.rawValue).tag(period)
 92                      }
 93                  }
 94                  .pickerStyle(.menu)
 95              }
 96              
 97              Chart {
 98                  ForEach(caloriesData, id: \.date) { data in
 99                      BarMark(
100                          x: .value("Datum", data.date),
101                          y: .value("Kalorien", data.calories)
102                      )
103                      .foregroundStyle(.green)
104                  }
105              }
106              .frame(height: 200)
107              .chartXAxis {
108                  AxisMarks(values: .automatic(desiredCount: selectedPeriod == .week ? 7 : selectedPeriod == .month ? 6 : 9))
109              }
110              .padding()
111              .background(Color(.secondarySystemGroupedBackground))
112              .cornerRadius(16)
113          }
114      }
115      
116      // MARK: - Makros Chart Section
117      private var macrosChartSection: some View {
118          VStack(spacing: 12) {
119              Text("Makronährstoffe")
120                  .font(.headline)
121                  .frame(maxWidth: .infinity, alignment: .leading)
122              
123              Chart {
124                  ForEach(macrosData, id: \.date) { data in
125                      LineMark(
126                          x: .value("Datum", data.date),
127                          y: .value("Protein", data.protein)
128                      )
129                      .foregroundStyle(Color(red: 0xfe/255, green: 0x86/255, blue: 0x61/255))
130                      .interpolationMethod(.catmullRom)
131                      
132                      LineMark(
133                          x: .value("Datum", data.date),
134                          y: .value("Kohlenhydrate", data.carbs)
135                      )
136                      .foregroundStyle(Color(red: 0x5D/255, green: 0xB9/255, blue: 0x85/255))
137                      .interpolationMethod(.catmullRom)
138                      
139                      LineMark(
140                          x: .value("Datum", data.date),
141                          y: .value("Fette", data.fat)
142                      )
143                      .foregroundStyle(Color(red: 0xFF/255, green: 0xD2/255, blue: 0x5F/255))
144                      .interpolationMethod(.catmullRom)
145                  }
146              }
147              .frame(height: 200)
148              .chartXAxis {
149                  AxisMarks(values: .automatic(desiredCount: selectedPeriod == .week ? 7 : selectedPeriod == .month ? 6 : 9))
150              }
151              .chartForegroundStyleScale([
152                  "Protein": Color(red: 0xfe/255, green: 0x86/255, blue: 0x61/255),
153                  "Kohlenhydrate": Color(red: 0x5D/255, green: 0xB9/255, blue: 0x85/255),
154                  "Fette": Color(red: 0xFF/255, green: 0xD2/255, blue: 0x5F/255)
155              ])
156              .padding()
157              .background(Color(.secondarySystemGroupedBackground))
158              .cornerRadius(16)
159          }
160      }
161      
162      // MARK: - Durchschnittswerte Section
163      private var averagesSection: some View {
164          VStack(spacing: 12) {
165              Text("Durchschnittswerte")
166                  .font(.headline)
167                  .frame(maxWidth: .infinity, alignment: .leading)
168              
169              VStack(spacing: 8) {
170                  HStack {
171                      HStack(spacing: 4) {
172                          Circle()
173                              .fill(Color(red: 0xfe/255, green: 0x86/255, blue: 0x61/255))
174                              .frame(width: 8, height: 8)
175                          Text("Protein")
176                              .font(.subheadline)
177                      }
178                      Spacer()
179                      Text("\(String(format: "%.1f", averageProtein))g/Tag")
180                          .font(.subheadline)
181                          .fontWeight(.medium)
182                  }
183                  
184                  HStack {
185                      HStack(spacing: 4) {
186                          Circle()
187                              .fill(Color(red: 0x5D/255, green: 0xB9/255, blue: 0x85/255))
188                              .frame(width: 8, height: 8)
189                          Text("Kohlenhydrate")
190                              .font(.subheadline)
191                      }
192                      Spacer()
193                      Text("\(String(format: "%.1f", averageCarbs))g/Tag")
194                          .font(.subheadline)
195                          .fontWeight(.medium)
196                  }
197                  
198                  HStack {
199                      HStack(spacing: 4) {
200                          Circle()
201                              .fill(Color(red: 0xFF/255, green: 0xD2/255, blue: 0x5F/255))
202                              .frame(width: 8, height: 8)
203                          Text("Fette")
204                              .font(.subheadline)
205                      }
206                      Spacer()
207                      Text("\(String(format: "%.1f", averageFat))g/Tag")
208                          .font(.subheadline)
209                          .fontWeight(.medium)
210                  }
211              }
212              .padding()
213              .background(Color(.secondarySystemGroupedBackground))
214              .cornerRadius(16)
215          }
216      }
217      
218      // MARK: - Berechnungen
219      
220      private var periods: Int {
221          switch selectedPeriod {
222          case .week:
223              return 7
224          case .month:
225              return 30
226          case .quarter:
227              return 90
228          }
229      }
230      
231      private var caloriesData: [(date: Date, calories: Int)] {
232          let calendar = Calendar.current
233          let now = Date()
234          var data: [(date: Date, calories: Int)] = []
235          
236          for i in (0..<periods).reversed() {
237              guard let date = calendar.date(byAdding: .day, value: -i, to: now) else { continue }
238              let calories = dataManager.totalCaloriesForDay(date)
239              data.append((date: date, calories: calories))
240          }
241          
242          return data
243      }
244      
245      private var macrosData: [(date: Date, protein: Double, carbs: Double, fat: Double)] {
246          let calendar = Calendar.current
247          let now = Date()
248          var data: [(date: Date, protein: Double, carbs: Double, fat: Double)] = []
249          
250          for i in (0..<periods).reversed() {
251              guard let date = calendar.date(byAdding: .day, value: -i, to: now) else { continue }
252              let protein = dataManager.totalProteinForDay(date)
253              let carbs = dataManager.totalCarbsForDay(date)
254              let fat = dataManager.totalFatForDay(date)
255              data.append((date: date, protein: protein, carbs: carbs, fat: fat))
256          }
257          
258          return data
259      }
260      
261      private var totalCalories: Int {
262          caloriesData.reduce(0) { $0 + $1.calories }
263      }
264      
265      private var averageCalories: Double {
266          let total = Double(totalCalories)
267          return total / Double(periods)
268      }
269      
270      private var averageProtein: Double {
271          let total = macrosData.reduce(0.0) { $0 + $1.protein }
272          return total / Double(periods)
273      }
274      
275      private var averageCarbs: Double {
276          let total = macrosData.reduce(0.0) { $0 + $1.carbs }
277          return total / Double(periods)
278      }
279      
280      private var averageFat: Double {
281          let total = macrosData.reduce(0.0) { $0 + $1.fat }
282          return total / Double(periods)
283      }
284  }
285  
286  #Preview {
287      NavigationStack {
288          ReportView(dataManager: FoodDataManager())
289      }
290  }