87 lines
7.2 KiB
Swift
87 lines
7.2 KiB
Swift
import SwiftUI
|
|
|
|
struct ProjectDetailView: View {
|
|
@EnvironmentObject var app: AppState
|
|
let projectKey: String
|
|
@State private var section: ProjectSection = .overview
|
|
private var item: ProjectSnapshot? { app.projects.first { $0.project.key == projectKey } }
|
|
|
|
var body: some View {
|
|
if let item {
|
|
VStack(spacing: 0) {
|
|
HStack {
|
|
VStack(alignment: .leading, spacing: 4) { Text(item.project.name).font(.system(size: 30, weight: .bold)); Text(item.project.key).foregroundStyle(.secondary) }
|
|
Spacer(); GateBadge(status: item.qualityGate)
|
|
}.padding(.horizontal, 26).padding(.top, 22).padding(.bottom, 14)
|
|
|
|
ScrollView(.horizontal, showsIndicators: false) {
|
|
HStack(spacing: 7) {
|
|
projectNav(.overview, "square.grid.2x2.fill", .blue)
|
|
projectNav(.bugs, "ladybug.fill", .red)
|
|
projectNav(.vulnerabilities, "lock.trianglebadge.exclamationmark.fill", .orange)
|
|
projectNav(.codeSmells, "exclamationmark.bubble.fill", .yellow)
|
|
projectNav(.securityHotspots, "shield.lefthalf.filled.badge.checkmark", .purple)
|
|
projectNav(.coverage, "chart.bar.fill", .green)
|
|
projectNav(.duplication, "square.on.square.fill", .purple)
|
|
projectNav(.lines, "chevron.left.forwardslash.chevron.right", .blue)
|
|
projectNav(.qualityGate, "checkmark.seal.fill", .green)
|
|
projectNav(.measures, "chart.bar.xaxis", .cyan)
|
|
}.padding(.horizontal, 26).padding(.bottom, 14)
|
|
}
|
|
Divider()
|
|
|
|
switch section {
|
|
case .overview: overview(item)
|
|
case .bugs: IssuesView(projectKey: projectKey, initialFilter: .bugs).id("\(projectKey)-bugs")
|
|
case .vulnerabilities: IssuesView(projectKey: projectKey, initialFilter: .vulnerabilities).id("\(projectKey)-vulnerabilities")
|
|
case .codeSmells: IssuesView(projectKey: projectKey, initialFilter: .codeSmells).id("\(projectKey)-smells")
|
|
case .securityHotspots: metricDetail("Security Hotspots", item.measures["security_hotspots"] ?? "0", "shield.lefthalf.filled.badge.checkmark", .purple, "Security-sensitive code requiring review in SonarQube.")
|
|
case .coverage: metricDetail("Coverage", (item.measures["coverage"] ?? "0") + "%", "chart.bar.fill", .green, "Test coverage reported by SonarQube for this project.")
|
|
case .duplication: metricDetail("Duplication", (item.measures["duplicated_lines_density"] ?? "0") + "%", "square.on.square.fill", .purple, "Duplicated lines density reported by SonarQube.")
|
|
case .lines: metricDetail("Lines of Code", item.measures["ncloc"] ?? "0", "chevron.left.forwardslash.chevron.right", .blue, "Non-comment lines of code measured by SonarQube.")
|
|
case .qualityGate: metricDetail("Quality Gate", item.qualityGate == "OK" ? "Passed" : item.qualityGate.capitalized, item.qualityGate == "OK" ? "checkmark.seal.fill" : "xmark.seal.fill", item.qualityGate == "OK" ? .green : .red, "Current quality-gate result for this project.")
|
|
case .measures: measures(item)
|
|
}
|
|
}
|
|
} else { ContentUnavailableView("Project unavailable", systemImage: "shippingbox") }
|
|
}
|
|
|
|
private func overview(_ item: ProjectSnapshot) -> some View {
|
|
ScrollView {
|
|
LazyVGrid(columns: [GridItem(.adaptive(minimum: 220), spacing: 14)], spacing: 14) {
|
|
MetricCard(title: "Bugs", value: item.measures["bugs"] ?? "0", systemImage: "ladybug.fill", tint: .red) { section = .bugs }
|
|
MetricCard(title: "Vulnerabilities", value: item.measures["vulnerabilities"] ?? "0", systemImage: "lock.trianglebadge.exclamationmark.fill", tint: .orange) { section = .vulnerabilities }
|
|
MetricCard(title: "Code Smells", value: item.measures["code_smells"] ?? "0", systemImage: "exclamationmark.bubble.fill", tint: .yellow) { section = .codeSmells }
|
|
MetricCard(title: "Security Hotspots", value: item.measures["security_hotspots"] ?? "0", systemImage: "shield.lefthalf.filled.badge.checkmark", tint: .purple) { section = .securityHotspots }
|
|
MetricCard(title: "Coverage", value: (item.measures["coverage"] ?? "0") + "%", systemImage: "chart.bar.fill", tint: .green) { section = .coverage }
|
|
MetricCard(title: "Duplication", value: (item.measures["duplicated_lines_density"] ?? "0") + "%", systemImage: "square.on.square.fill", tint: .purple) { section = .duplication }
|
|
MetricCard(title: "Lines of Code", value: item.measures["ncloc"] ?? "0", systemImage: "chevron.left.forwardslash.chevron.right", tint: .blue) { section = .lines }
|
|
MetricCard(title: "Quality Gate", value: item.qualityGate == "OK" ? "Passed" : item.qualityGate.capitalized, systemImage: "checkmark.seal.fill", tint: item.qualityGate == "OK" ? .green : .red) { section = .qualityGate }
|
|
MetricCard(title: "Measures", value: "\(item.measures.count)", systemImage: "chart.bar.xaxis", tint: .cyan) { section = .measures }
|
|
}.padding(26)
|
|
}
|
|
}
|
|
|
|
private func projectNav(_ target: ProjectSection, _ icon: String, _ tint: Color) -> some View {
|
|
Button { section = target } label: {
|
|
Label(target.title, systemImage: icon).font(.subheadline.weight(.semibold)).foregroundStyle(section == target ? Color.white : tint)
|
|
.padding(.horizontal, 12).padding(.vertical, 8)
|
|
.background(section == target ? tint : tint.opacity(0.10), in: RoundedRectangle(cornerRadius: 8))
|
|
}.buttonStyle(.plain)
|
|
}
|
|
|
|
private func metricDetail(_ title: String, _ value: String, _ icon: String, _ tint: Color, _ description: String) -> some View {
|
|
VStack(spacing: 20) { Image(systemName: icon).font(.system(size: 48)).foregroundStyle(tint); Text(value).font(.system(size: 62, weight: .bold, design: .rounded)); Text(title).font(.title2.bold()); Text(description).foregroundStyle(.secondary) }
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity).background(LinearGradient(colors: [tint.opacity(0.07), .clear], startPoint: .top, endPoint: .center))
|
|
}
|
|
|
|
private func measures(_ item: ProjectSnapshot) -> some View {
|
|
ScrollView { LazyVGrid(columns: [GridItem(.adaptive(minimum: 230), spacing: 12)], spacing: 12) { ForEach(item.measures.keys.sorted(), id: \.self) { key in MetricCard(title: key.replacingOccurrences(of: "_", with: " ").capitalized, value: item.measures[key] ?? "—", systemImage: "chart.bar.xaxis", tint: .cyan) } }.padding(26) }
|
|
}
|
|
}
|
|
|
|
private enum ProjectSection: Hashable {
|
|
case overview, bugs, vulnerabilities, codeSmells, securityHotspots, coverage, duplication, lines, qualityGate, measures
|
|
var title: String { switch self { case .overview:"Overview"; case .bugs:"Bugs"; case .vulnerabilities:"Vulnerabilities"; case .codeSmells:"Code Smells"; case .securityHotspots:"Hotspots"; case .coverage:"Coverage"; case .duplication:"Duplication"; case .lines:"Lines"; case .qualityGate:"Quality Gate"; case .measures:"Measures" } }
|
|
}
|