import SwiftUI struct IOSDashboardView: View { @EnvironmentObject private var appState: AppState private var failedProjects: [ProjectSnapshot] { appState.projects.filter { $0.qualityGate.uppercased() != "OK" } } private var bugs: Int { total("bugs") } private var vulnerabilities: Int { total("vulnerabilities") } private var codeSmells: Int { total("code_smells") } var body: some View { NavigationStack { ScrollView { LazyVGrid(columns: [GridItem(.adaptive(minimum: 155), spacing: 12)], spacing: 12) { metricCard("Projects", value: appState.projects.count, icon: "square.stack.3d.up") metricCard("Failed", value: failedProjects.count, icon: "xmark.octagon.fill", isFailure: !failedProjects.isEmpty) metricCard("Bugs", value: bugs, icon: "ladybug") metricCard("Vulnerabilities", value: vulnerabilities, icon: "lock.trianglebadge.exclamationmark") metricCard("Code Smells", value: codeSmells, icon: "exclamationmark.bubble") } .padding() if !failedProjects.isEmpty { VStack(alignment: .leading, spacing: 10) { Text("Needs attention") .font(.headline) ForEach(failedProjects) { snapshot in NavigationLink { IOSProjectDetailView(snapshot: snapshot) } label: { HStack { Image(systemName: "xmark.octagon.fill") .foregroundStyle(.red) VStack(alignment: .leading) { Text(snapshot.project.name).fontWeight(.semibold) Text(snapshot.qualityGate).font(.caption) } Spacer() Image(systemName: "chevron.right") .foregroundStyle(.secondary) } .padding() .background(.red.opacity(0.10), in: RoundedRectangle(cornerRadius: 14)) } .buttonStyle(.plain) } } .padding(.horizontal) } } .navigationTitle("Dashboard") .toolbar { ToolbarItem(placement: .topBarTrailing) { Button { Task { await appState.refresh() } } label: { if appState.isLoading { ProgressView() } else { Image(systemName: "arrow.clockwise") } } .disabled(appState.isLoading || appState.selectedServer == nil) } } .overlay { if appState.selectedServer == nil { ContentUnavailableView("No SonarQube Server", systemImage: "server.rack", description: Text("Add or sync a server to start using SonarDesk.")) } } } } private func total(_ key: String) -> Int { appState.projects.reduce(0) { $0 + (Int($1.measures[key] ?? "0") ?? 0) } } @ViewBuilder private func metricCard(_ title: String, value: Int, icon: String, isFailure: Bool = false) -> some View { VStack(alignment: .leading, spacing: 12) { Image(systemName: icon).font(.title2).foregroundStyle(isFailure ? .red : .primary) Text("\(value)").font(.system(.title, design: .rounded, weight: .bold)) Text(title).font(.subheadline).foregroundStyle(.secondary) } .frame(maxWidth: .infinity, alignment: .leading) .padding() .background(isFailure ? .red.opacity(0.10) : Color.secondary.opacity(0.08), in: RoundedRectangle(cornerRadius: 16)) } }