import AppKit import SwiftUI struct BugListPopover: View { @State private var bugStore = BugStore.shared @State private var tokenManager = TokenManager.shared var body: some View { VStack(spacing: 0) { header Divider() content Divider() footer } .frame(minWidth: 340, idealWidth: 360, maxWidth: 400, minHeight: 280) } private var header: some View { HStack { Text("My Bugs") .font(.headline) Spacer() if bugStore.isLoading { ProgressView() .scaleEffect(0.7) .frame(width: 16, height: 16) } if let lastUpdated = bugStore.lastUpdated { Text(formatted(lastUpdated)) .font(.caption) .foregroundStyle(.secondary) } Button("Mark all seen") { bugStore.markAllSeen() AppDelegate.shared?.updateBadge(count: bugStore.activeBugs.count) } .font(.caption) } .padding(.horizontal) .padding(.vertical, 8) } @ViewBuilder private var content: some View { if !tokenManager.isAuthenticated { OAuthSetupView() } else if let error = bugStore.error { ErrorStateView(error: error) } else if bugStore.bugsSortedByPriority.isEmpty && !bugStore.isLoading { EmptyStateView() } else { ScrollView { LazyVStack(spacing: 0) { ForEach(bugStore.bugsSortedByPriority) { bug in BugRow( bug: bug, isUnseen: bugStore.unseenBugs.contains(bug.id) ) .onTapGesture { openInFeishu(bug) bugStore.markSeen(bug.id) AppDelegate.shared?.updateBadge(count: bugStore.activeBugs.count) } Divider().padding(.leading, 44) } } } } } private var footer: some View { HStack { Button(action: openFeishuTable) { Label("Open in Feishu", systemImage: "arrow.up.forward.app") } Spacer() Button("Refresh") { Task { await PollerService.shared.fetchNow() } } SettingsLink { Label("Settings", systemImage: "gear") } } .padding(.horizontal) .padding(.vertical, 6) .buttonStyle(.plain) .font(.caption) } private func openInFeishu(_ bug: Bug) { NSWorkspace.shared.open(bug.feishuURL) AppDelegate.shared?.closePopover() } private func openFeishuTable() { guard let config = AppStateService.shared.config else { return } let url = FeishuURLBuilder.tableURL( baseDomain: config.feishuBaseDomain, appToken: config.appToken ) NSWorkspace.shared.open(url) } private func formatted(_ date: Date) -> String { let formatter = RelativeDateTimeFormatter() formatter.unitsStyle = .abbreviated return formatter.localizedString(for: date, relativeTo: Date()) } } struct EmptyStateView: View { var body: some View { VStack(spacing: 8) { Image(systemName: "checkmark.circle") .font(.largeTitle) .foregroundStyle(.green) Text("No active bugs") .font(.headline) Text("You're all clear!") .font(.subheadline) .foregroundStyle(.secondary) } .frame(maxWidth: .infinity, maxHeight: .infinity) .padding() } } struct ErrorStateView: View { let error: Error var body: some View { VStack(spacing: 8) { Image(systemName: "exclamationmark.triangle") .font(.largeTitle) .foregroundStyle(.orange) Text("Failed to load bugs") .font(.headline) Text(error.localizedDescription) .font(.caption) .foregroundStyle(.secondary) .multilineTextAlignment(.center) } .frame(maxWidth: .infinity, maxHeight: .infinity) .padding() } }