Files
haptika/ios/HaptikaWatch/Views/WatchHistoryView.swift
2026-07-31 16:20:10 +02:00

112 lines
4.0 KiB
Swift

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)
}
}