bugger/Sources/Views/MenuBar/BugListPopover.swift

153 lines
4.5 KiB
Swift

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")
}
Button(action: { NSApp.terminate(nil) }) {
Label("Quit", systemImage: "power")
}
}
.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()
}
}