import SwiftUI struct AIHistoryView: View { @Bindable var viewModel: AIHistoryViewModel var onOpen: (AIHistoryEntry) -> Void private enum Layout { static let horizontalPadding: CGFloat = 24 } var body: some View { VStack(spacing: 0) { header filterBar if viewModel.isEmpty { emptyState } else if !viewModel.hasFilteredEntries { filteredEmptyState } else { historyList } } .background(AppTheme.background) .alert("Delete this item?", isPresented: deleteBinding) { Button("Cancel", role: .cancel) { viewModel.cancelDelete() } Button("Delete", role: .destructive) { viewModel.confirmDelete() } } message: { if let entry = viewModel.entryPendingDeletion { Text("Remove \"\(entry.displayTitle)\" from history?") } } .alert("Delete all history?", isPresented: $viewModel.showDeleteAllConfirmation) { Button("Cancel", role: .cancel) { viewModel.cancelDeleteAll() } Button("Delete All", role: .destructive) { viewModel.confirmDeleteAll() } } message: { Text("This permanently removes all saved posts, titles, and comments.") } } private var deleteBinding: Binding { Binding( get: { viewModel.entryPendingDeletion != nil }, set: { isPresented in if !isPresented { viewModel.cancelDelete() } } ) } private var header: some View { VStack(spacing: 0) { HStack(spacing: 14) { ModernSidebarIconView(kind: .history, size: 40) VStack(alignment: .leading, spacing: 2) { Text("History") .font(.system(size: 18, weight: .semibold)) .foregroundStyle(AppTheme.textPrimary) Text("Saved AI responses from your tools") .font(.system(size: 11)) .foregroundStyle(AppTheme.textSecondary) } Spacer() if !viewModel.isEmpty { Button { viewModel.requestDeleteAll() } label: { Label("Delete All", systemImage: "trash") .font(.system(size: 11, weight: .medium)) } .buttonStyle(AppDestructiveButtonStyle()) } } .padding(.horizontal, Layout.horizontalPadding) .padding(.vertical, 16) FullWidthDivider() } } private var filterBar: some View { ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 6) { ForEach(AIHistoryFilter.allCases) { filter in filterChip(filter) } } .padding(.horizontal, Layout.horizontalPadding) .padding(.vertical, 12) } } private func filterChip(_ filter: AIHistoryFilter) -> some View { let isSelected = viewModel.selectedFilter == filter return Button { withAnimation(.easeInOut(duration: 0.15)) { viewModel.selectedFilter = filter } } label: { Text(filter.title) .font(.system(size: 11, weight: isSelected ? .bold : .medium)) .foregroundStyle(isSelected ? Color(hex: 0xD97706) : AppTheme.textSecondary) .padding(.horizontal, 12) .padding(.vertical, 7) .background( RoundedRectangle(cornerRadius: 8, style: .continuous) .fill(isSelected ? Color(hex: 0xD97706).opacity(0.12) : AppTheme.cardBackground) ) .overlay( RoundedRectangle(cornerRadius: 8, style: .continuous) .stroke( isSelected ? Color(hex: 0xD97706).opacity(0.5) : AppTheme.cardBorder, lineWidth: isSelected ? 1.5 : 1 ) ) } .buttonStyle(.plain) .hoverCursor() } private var historyList: some View { ScrollView { LazyVStack(spacing: 8) { ForEach(viewModel.filteredEntries) { entry in AIHistoryRowView( entry: entry, onOpen: { onOpen(entry) }, onDelete: { viewModel.requestDelete(entry) } ) } } .padding(.horizontal, Layout.horizontalPadding) .padding(.bottom, 24) } } private var emptyState: some View { VStack(spacing: 12) { Image(systemName: "clock.arrow.circlepath") .font(.system(size: 32)) .foregroundStyle(Color(hex: 0xD97706).opacity(0.7)) Text("No history yet") .font(.system(size: 15, weight: .semibold)) .foregroundStyle(AppTheme.textPrimary) Text("Generated posts, optimized titles, and comments\nwill appear here automatically.") .font(.system(size: 12)) .foregroundStyle(AppTheme.textSecondary) .multilineTextAlignment(.center) } .frame(maxWidth: .infinity, maxHeight: .infinity) } private var filteredEmptyState: some View { VStack(spacing: 12) { Image(systemName: "line.3.horizontal.decrease.circle") .font(.system(size: 28)) .foregroundStyle(AppTheme.textTertiary) Text("No \(viewModel.selectedFilter.title.lowercased()) in history") .font(.system(size: 14, weight: .semibold)) .foregroundStyle(AppTheme.textPrimary) Text("Try another filter or generate content with the AI tools.") .font(.system(size: 12)) .foregroundStyle(AppTheme.textSecondary) } .frame(maxWidth: .infinity, maxHeight: .infinity) } } private struct AIHistoryRowView: View { let entry: AIHistoryEntry var onOpen: () -> Void var onDelete: () -> Void var body: some View { HStack(alignment: .top, spacing: 12) { ModernSidebarIconView(kind: entry.toolKind.iconKind, size: 36) VStack(alignment: .leading, spacing: 4) { HStack(spacing: 6) { Text(entry.toolKind.title) .font(.system(size: 9, weight: .semibold)) .foregroundStyle(AppTheme.textTertiary) .textCase(.uppercase) .tracking(0.3) Text("ยท") .foregroundStyle(AppTheme.textTertiary) Text(entry.createdAt, style: .relative) .font(.system(size: 9)) .foregroundStyle(AppTheme.textTertiary) } Text(entry.displayTitle) .font(.system(size: 13, weight: .semibold)) .foregroundStyle(AppTheme.textPrimary) .lineLimit(2) .multilineTextAlignment(.leading) Text(entry.displaySubtitle) .font(.system(size: 11)) .foregroundStyle(AppTheme.textSecondary) } Spacer(minLength: 8) HStack(spacing: 6) { Button(action: onOpen) { Label("Open", systemImage: "arrow.up.right.square") .font(.system(size: 10, weight: .medium)) .labelStyle(.titleAndIcon) } .buttonStyle(AppSecondaryButtonStyle()) Button(action: onDelete) { Image(systemName: "trash") .font(.system(size: 10, weight: .medium)) } .buttonStyle(AppDestructiveButtonStyle()) } } .padding(14) .background( RoundedRectangle(cornerRadius: AppTheme.cornerRadiusSmall) .fill(AppTheme.cardBackground) .overlay( RoundedRectangle(cornerRadius: AppTheme.cornerRadiusSmall) .stroke(AppTheme.cardBorder, lineWidth: 1) ) ) .hoverCard(cornerRadius: AppTheme.cornerRadiusSmall) } } #Preview { AIHistoryView(viewModel: AIHistoryViewModel()) { _ in } .frame(width: 880, height: 720) }