Files
SonarDesk/SonarDesk-iOS/Views/IOSDashboardView.swift
2026-10-06 08:58:01 +01:00

87 lines
4.0 KiB
Swift

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))
}
}