112 lines
4.0 KiB
Swift
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)
|
|
}
|
|
}
|