import SwiftUI struct ManageBindingsView: View { @State private var devices: [MagicDevice] = [] @State private var isLoading = false @State private var errorMessage: String? let onDismiss: () -> Void var body: some View { VStack(spacing: 0) { // Header HStack { VStack(alignment: .leading, spacing: 4) { Text("Gebundene Geräte verwalten") .font(.title2.bold()) Text("Hier kannst du bestehende Magic-Bindings freigeben.") .font(.caption) .foregroundStyle(.secondary) } Spacer() Button("Schließen", action: onDismiss) .keyboardShortcut(.escape) } .padding() .background(Color(nsColor: .windowBackgroundColor)) Divider() // Content if isLoading && devices.isEmpty { VStack(spacing: 12) { ProgressView() Text("Lade Geräte...") .font(.caption) .foregroundStyle(.secondary) } .frame(maxWidth: .infinity, maxHeight: .infinity) } else if let error = errorMessage { VStack(spacing: 12) { Image(systemName: "exclamationmark.triangle") .font(.largeTitle) .foregroundStyle(.red) Text(error) .font(.caption) .multilineTextAlignment(.center) Button("Erneut versuchen", action: loadDevices) .buttonStyle(.borderedProminent) } .frame(maxWidth: .infinity, maxHeight: .infinity) .padding() } else if devices.isEmpty { VStack(spacing: 12) { Image(systemName: "laptopcomputer.slash") .font(.largeTitle) .foregroundStyle(.secondary) Text("Keine gebundenen Geräte") .font(.caption) .foregroundStyle(.secondary) } .frame(maxWidth: .infinity, maxHeight: .infinity) } else { ScrollView { VStack(spacing: 12) { ForEach(devices) { device in DeviceRow(device: device, onAction: handleDeviceAction) } } .padding() } } } .frame(minWidth: 600, minHeight: 400) .onAppear(perform: loadDevices) } private func loadDevices() { Task { isLoading = true errorMessage = nil do { devices = try await MagicAPIClient.shared.listDevices() } catch { errorMessage = error.localizedDescription } isLoading = false } } private func handleDeviceAction(_ action: DeviceAction, for device: MagicDevice) { Task { do { switch action { case .requestRelease: _ = try await MagicAPIClient.shared.requestRelease(deviceId: device.deviceId) case .cancelRelease: try await MagicAPIClient.shared.cancelRelease(deviceId: device.deviceId) } // Reload list try await Task.sleep(for: .milliseconds(500)) devices = try await MagicAPIClient.shared.listDevices() } catch { errorMessage = error.localizedDescription } } } } enum DeviceAction { case requestRelease case cancelRelease } private struct DeviceRow: View { let device: MagicDevice let onAction: (DeviceAction, MagicDevice) -> Void @State private var timeRemaining: String = "" @State private var timer: Timer? var body: some View { VStack(alignment: .leading, spacing: 8) { HStack { Image(systemName: "laptopcomputer") .foregroundStyle(.blue) VStack(alignment: .leading, spacing: 2) { Text(device.hostname) .font(.headline) if let model = device.model { Text(model) .font(.caption) .foregroundStyle(.secondary) } } Spacer() if device.isReleasing { VStack(alignment: .trailing, spacing: 2) { Text("Freigabe läuft") .font(.caption.bold()) .foregroundStyle(.orange) if !timeRemaining.isEmpty { Text(timeRemaining) .font(.caption2) .foregroundStyle(.secondary) } } } else { Text("Aktiv") .font(.caption) .foregroundStyle(.green) .padding(.horizontal, 8) .padding(.vertical, 4) .background(Color.green.opacity(0.1)) .clipShape(Capsule()) } } HStack(spacing: 16) { Label(formatDate(device.enrolledDate), systemImage: "calendar") .font(.caption) .foregroundStyle(.secondary) if let os = device.osVersion { Label(os, systemImage: "info.circle") .font(.caption) .foregroundStyle(.secondary) } Spacer() if device.isReleasing { Button("Freigabe abbrechen") { onAction(.cancelRelease, device) } .buttonStyle(.borderless) .foregroundStyle(.blue) .font(.caption) } else { Button("Freigabe anfordern") { onAction(.requestRelease, device) } .buttonStyle(.borderless) .foregroundStyle(.red) .font(.caption) } } } .padding() .background(Color(nsColor: .controlBackgroundColor)) .clipShape(RoundedRectangle(cornerRadius: 8)) .onAppear { updateTimeRemaining() timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in updateTimeRemaining() } } .onDisappear { timer?.invalidate() } } private func formatDate(_ date: Date?) -> String { guard let date = date else { return "—" } let formatter = DateFormatter() formatter.dateStyle = .short formatter.timeStyle = .short return formatter.string(from: date) } private func updateTimeRemaining() { guard let releaseDate = device.releaseDate else { timeRemaining = "" return } let remaining = releaseDate.timeIntervalSince(Date()) if remaining <= 0 { timeRemaining = "Läuft ab..." return } let hours = Int(remaining) / 3600 let minutes = (Int(remaining) % 3600) / 60 if hours > 0 { timeRemaining = "noch \(hours)h \(minutes)m" } else { timeRemaining = "noch \(minutes)m" } } } #Preview { ManageBindingsView { print("Dismissed") } }