// ── Reschedule Undo Banner ──────────────────────────────────── // Toast-style banner that appears after reschedule with undo button import SwiftUI struct RescheduleUndoBanner: View { @EnvironmentObject var store: TimerStore @State private var isVisible = false var body: some View { if let result = store.lastReschedule { HStack(spacing: CMSpacing.md) { Image(systemName: "arrow.uturn.backward.circle.fill") .font(.title3) .foregroundStyle(CMColors.accent) VStack(alignment: .leading, spacing: CMSpacing.xxs) { Text(result.description) .font(CMFonts.body(size: 13, weight: .medium)) .foregroundStyle(CMColors.text) Text("\(result.affectedTimerIds.count) timer\(result.affectedTimerIds.count == 1 ? "" : "s") affected") .font(CMFonts.body(size: 11)) .foregroundStyle(CMColors.textMuted) } Spacer() Button { HapticEngine.tap() withAnimation(.easeInOut(duration: 0.3)) { store.undoReschedule() } } label: { Text("Undo") .font(CMFonts.body(size: 14, weight: .bold)) .foregroundStyle(CMColors.accent) .padding(.horizontal, CMSpacing.md) .padding(.vertical, CMSpacing.sm) .background(CMColors.accent.opacity(0.15)) .clipShape(Capsule()) } Button { withAnimation(.easeOut(duration: 0.2)) { store.lastReschedule = nil } } label: { Image(systemName: "xmark") .font(.caption.weight(.bold)) .foregroundStyle(CMColors.textMuted) } } .padding(CMSpacing.md) .background(CMColors.surface) .clipShape(RoundedRectangle(cornerRadius: CMRadius.md)) .overlay( RoundedRectangle(cornerRadius: CMRadius.md) .stroke(CMColors.accent.opacity(0.3), lineWidth: 1) ) .shadow(color: CMShadow.md, radius: 8, y: 4) .padding(.horizontal, CMSpacing.lg) .transition(.move(edge: .top).combined(with: .opacity)) .onAppear { withAnimation(.spring(response: 0.4, dampingFraction: 0.8)) { isVisible = true } } } } } // MARK: - Smart Suggestion Banner struct RescheduleSuggestionBanner: View { @EnvironmentObject var store: TimerStore @State private var showSheet = false var body: some View { if !store.rescheduleSuggestions.isEmpty { VStack(spacing: CMSpacing.sm) { HStack(spacing: CMSpacing.sm) { Image(systemName: "sparkles") .foregroundStyle(CMColors.accent) Text("Running late? Reschedule your timers") .font(CMFonts.body(size: 13, weight: .medium)) .foregroundStyle(CMColors.text) Spacer() Button { store.clearRescheduleSuggestions() } label: { Image(systemName: "xmark") .font(.caption.weight(.bold)) .foregroundStyle(CMColors.textMuted) } } HStack(spacing: CMSpacing.sm) { // Show first suggestion as quick action if let first = store.rescheduleSuggestions.first { Button { HapticEngine.tap() store.applyReschedule(first.action) store.clearRescheduleSuggestions() } label: { Text(first.title) .font(CMFonts.body(size: 13, weight: .semibold)) .foregroundStyle(.white) .padding(.horizontal, CMSpacing.md) .padding(.vertical, CMSpacing.sm) .background(CMColors.accent) .clipShape(Capsule()) } } Button { showSheet = true } label: { Text("More options") .font(CMFonts.body(size: 13, weight: .medium)) .foregroundStyle(CMColors.accent) .padding(.horizontal, CMSpacing.md) .padding(.vertical, CMSpacing.sm) .background(CMColors.accent.opacity(0.1)) .clipShape(Capsule()) } } } .padding(CMSpacing.md) .background(CMColors.surface) .clipShape(RoundedRectangle(cornerRadius: CMRadius.md)) .overlay( RoundedRectangle(cornerRadius: CMRadius.md) .stroke(CMColors.accent.opacity(0.3), lineWidth: 1) ) .padding(.horizontal, CMSpacing.lg) .transition(.move(edge: .top).combined(with: .opacity)) .sheet(isPresented: $showSheet) { RescheduleSheet() } } } }