Files
nahbar/nahbar/VisitRatingFlowView.swift
T
sven 18112cb52c Resolves #11 Resolves #13 Fragebogen: Abbrechen-Button entfernt
Der Speichern-Button am Ende des Flows ersetzt den Abbrechen-Button
in der Toolbar für beide Fragebögen (Meeting + Nachwirkung).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 06:04:34 +02:00

150 lines
5.0 KiB
Swift

import SwiftUI
import SwiftData
// MARK: - MeetingRatingFlowView
// Scrollbarer Bewertungs-Flow für die Sofort-Bewertung eines Treffens.
// Fragen blenden nacheinander von unten ein, sobald die vorherige beantwortet wurde.
// Nach der letzten Frage erscheint ein "Speichern"-Button.
struct MeetingRatingFlowView: View {
@Environment(\.modelContext) private var modelContext
@Environment(\.nahbarTheme) var theme
@Environment(\.dismiss) private var dismiss
let moment: Moment
var aftermathDelay: TimeInterval = 36 * 3600
private let questions = RatingQuestion.immediate // 5 Fragen
@State private var values: [Int?]
@State private var revealedCount: Int = 1
@State private var showSummary: Bool = false
init(moment: Moment, aftermathDelay: TimeInterval = 36 * 3600) {
self.moment = moment
self.aftermathDelay = aftermathDelay
_values = State(initialValue: Array(repeating: nil, count: RatingQuestion.immediate.count))
}
var body: some View {
NavigationStack {
Group {
if showSummary {
MeetingSummaryView(moment: moment, onDismiss: { dismiss() })
} else {
questionFlow
}
}
.navigationTitle("Treffen bewerten")
.navigationBarTitleDisplayMode(.inline)
}
}
// MARK: - Scrollbarer Fragen-Flow
private var questionFlow: some View {
ScrollViewReader { proxy in
ScrollView {
VStack(spacing: 14) {
ForEach(0..<revealedCount, id: \.self) { i in
QuestionCard(
question: questions[i],
index: i,
total: questions.count,
isActive: i == revealedCount - 1,
value: $values[i],
onAnswer: { revealNext(after: i) },
onSkip: { revealNext(after: i) }
)
.id(i)
.transition(.asymmetric(
insertion: .opacity.combined(with: .move(edge: .bottom)),
removal: .identity
))
}
if revealedCount == questions.count {
saveButton
.id("save")
.transition(.asymmetric(
insertion: .opacity.combined(with: .move(edge: .bottom)),
removal: .identity
))
}
}
.padding(16)
.padding(.bottom, 32)
}
.background(theme.backgroundPrimary.ignoresSafeArea())
.onChange(of: revealedCount) { _, newCount in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) {
withAnimation(.easeOut(duration: 0.4)) {
proxy.scrollTo(newCount - 1, anchor: .top)
}
}
}
}
}
private var saveButton: some View {
Button { saveRatings() } label: {
Text("Speichern")
.font(.system(size: 16, weight: .semibold))
.foregroundStyle(.white)
.frame(maxWidth: .infinity)
.padding(.vertical, 15)
.background(theme.accent)
.clipShape(RoundedRectangle(cornerRadius: theme.radiusCard))
}
}
// MARK: - Navigation
private func revealNext(after index: Int) {
guard index == revealedCount - 1 else { return }
guard revealedCount < questions.count else { return }
withAnimation(.easeOut(duration: 0.35)) {
revealedCount += 1
}
}
// MARK: - Speichern
private func saveRatings() {
for (i, q) in questions.enumerated() {
let rating = Rating(
category: q.category,
questionIndex: i,
value: values[i],
isAftermath: false,
moment: moment
)
modelContext.insert(rating)
}
moment.meetingStatus = .awaitingAftermath
do {
try modelContext.save()
AppEventLog.shared.record(
"Treffen bewertet: \(moment.person?.firstName ?? "?") (\(questions.count) Fragen)",
level: .info, category: "Meeting"
)
} catch {
AppEventLog.shared.record(
"Fehler beim Speichern der Treffen-Bewertung: \(error.localizedDescription)",
level: .error, category: "Meeting"
)
}
AftermathNotificationManager.shared.scheduleAftermath(
momentID: moment.id,
personName: moment.person?.firstName ?? "",
delay: aftermathDelay
)
moment.aftermathNotificationScheduled = true
withAnimation { showSummary = true }
}
}