b477a3e04b
Statt Fragen einzeln zu ersetzen, werden sie jetzt nacheinander von unten eingeblendet und bleiben sichtbar: - Tippen auf einen Dot zeigt die nächste Frage darunter an - "Überspringen" blendet ebenfalls die nächste Frage ein - Beantwortete Fragen bleiben sichtbar und können angepasst werden - Nach der letzten Frage erscheint ein "Speichern"-Button - Gilt für Sofort-Bewertung (MeetingRatingFlowView) und Nachwirkung (AftermathRatingFlowView) - Neuer QuestionCard-Component in RatingQuestionView.swift Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
147 lines
4.9 KiB
Swift
147 lines
4.9 KiB
Swift
import SwiftUI
|
||
import SwiftData
|
||
|
||
// MARK: - AftermathRatingFlowView
|
||
// Scrollbarer Bewertungs-Flow für die Nachwirkungs-Bewertung (4 Fragen).
|
||
// Gleiche Interaktion wie MeetingRatingFlowView – Fragen blenden nacheinander ein.
|
||
|
||
struct AftermathRatingFlowView: View {
|
||
@Environment(\.modelContext) private var modelContext
|
||
@Environment(\.nahbarTheme) var theme
|
||
@Environment(\.dismiss) private var dismiss
|
||
|
||
let moment: Moment
|
||
|
||
private let questions = RatingQuestion.aftermath // 4 Fragen
|
||
@State private var values: [Int?]
|
||
@State private var revealedCount: Int = 1
|
||
@State private var showSummary: Bool = false
|
||
|
||
init(moment: Moment) {
|
||
self.moment = moment
|
||
_values = State(initialValue: Array(repeating: nil, count: RatingQuestion.aftermath.count))
|
||
}
|
||
|
||
var body: some View {
|
||
NavigationStack {
|
||
Group {
|
||
if showSummary {
|
||
MeetingSummaryView(moment: moment, onDismiss: { dismiss() })
|
||
} else {
|
||
questionFlow
|
||
}
|
||
}
|
||
.navigationTitle("Nachwirkung")
|
||
.navigationBarTitleDisplayMode(.inline)
|
||
.toolbar {
|
||
ToolbarItem(placement: .cancellationAction) {
|
||
Button("Abbrechen") { dismiss() }
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 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 { saveAftermath() } 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 saveAftermath() {
|
||
for (i, q) in questions.enumerated() {
|
||
let rating = Rating(
|
||
category: q.category,
|
||
questionIndex: i,
|
||
value: values[i],
|
||
isAftermath: true,
|
||
moment: moment
|
||
)
|
||
modelContext.insert(rating)
|
||
}
|
||
|
||
moment.meetingStatus = .completed
|
||
moment.aftermathCompletedAt = Date()
|
||
|
||
AftermathNotificationManager.shared.cancelAftermath(momentID: moment.id)
|
||
|
||
do {
|
||
try modelContext.save()
|
||
AppEventLog.shared.record(
|
||
"Nachwirkung abgeschlossen für Moment \(moment.id.uuidString)",
|
||
level: .success, category: "Meeting"
|
||
)
|
||
} catch {
|
||
AppEventLog.shared.record(
|
||
"Fehler beim Speichern der Nachwirkung: \(error.localizedDescription)",
|
||
level: .error, category: "Meeting"
|
||
)
|
||
}
|
||
|
||
withAnimation { showSummary = true }
|
||
}
|
||
}
|