import Charts import SwiftUI struct WatchHistoryView: View { @EnvironmentObject private var model: WatchAppModel private var recentSevenDays: [WatchMeasurementSummaryV1] { let cutoff = Date().addingTimeInterval(-7 * 86_400) return model.store.measurements.filter { $0.startedAt >= cutoff && $0.isValid } } var body: some View { List { if !recentSevenDays.isEmpty { Section("Tendance 7 jours") { Chart(recentSevenDays) { measurement in LineMark( x: .value("Date", measurement.startedAt), y: .value("BPM", measurement.averageBPM ?? 0) ) .foregroundStyle(WatchTheme.accent) PointMark( x: .value("Date", measurement.startedAt), y: .value("BPM", measurement.averageBPM ?? 0) ) .foregroundStyle(WatchTheme.mint) } .chartYAxis(.hidden) .frame(height: 90) } } Section("Mesures") { if model.store.measurements.isEmpty { Text("Aucune mesure synchronisée").font(.caption).foregroundStyle(WatchTheme.secondary) } ForEach(model.store.measurements) { measurement in NavigationLink { WatchMeasurementDetailView(measurement: measurement) } label: { HStack { VStack(alignment: .leading, spacing: 2) { Text(measurement.averageBPM.map { "\(Int($0.rounded())) BPM" } ?? "Incomplète").font(.subheadline.bold()) Text(measurement.startedAt, style: .date).font(.caption2).foregroundStyle(WatchTheme.secondary) } Spacer() Text("\(Int((measurement.quality * 100).rounded())) %").font(.caption2).foregroundStyle(measurement.isValid ? WatchTheme.mint : .orange) } } } } Section("Séances") { if model.store.sessions.isEmpty { Text("Aucune séance").font(.caption).foregroundStyle(WatchTheme.secondary) } ForEach(model.store.sessions) { session in VStack(alignment: .leading, spacing: 2) { Text(WatchProtocolCatalog.protocolWithID(session.protocolID)?.title ?? session.protocolID).font(.subheadline.bold()) Text(session.startedAt, style: .relative).font(.caption2).foregroundStyle(WatchTheme.secondary) } } } } .navigationTitle("Historique") .haptikaScreen() } } struct WatchMeasurementDetailView: View { @EnvironmentObject private var model: WatchAppModel @Environment(\.dismiss) private var dismiss let measurement: WatchMeasurementSummaryV1 @State private var confirmDelete = false var body: some View { List { Section("Mesure Watch") { metric("Moyenne", measurement.averageBPM.map { "\(Int($0.rounded())) BPM" } ?? "—") metric("Min / max", "\(Int(measurement.minimumBPM ?? 0)) / \(Int(measurement.maximumBPM ?? 0))") metric("Qualité", "\(Int((measurement.quality * 100).rounded())) %") metric("Couverture", "\(Int(measurement.coverageSeconds)) s") metric("Appareil", measurement.device.name) if let feeling = measurement.feeling { metric("Ressenti", "\(feeling) / 5") } } Section { Text("Pouls optique Apple Watch. Pas de PRV ni de diagnostic médical.") .font(.caption2).foregroundStyle(WatchTheme.secondary) } Button("Supprimer", role: .destructive) { confirmDelete = true } } .navigationTitle(measurement.startedAt.formatted(date: .abbreviated, time: .shortened)) .haptikaScreen() .confirmationDialog("Supprimer cette mesure ?", isPresented: $confirmDelete) { Button("Supprimer", role: .destructive) { model.deleteMeasurement(measurement) dismiss() } Button("Annuler", role: .cancel) {} } } private func metric(_ label: String, _ value: String) -> some View { HStack { Text(label).foregroundStyle(WatchTheme.secondary) Spacer() Text(value).multilineTextAlignment(.trailing) } .font(.caption) } }