cocottetech/@platform/codebase/@features/ai-copilot/cockpit-kit/Sources/CocotteCockpitKit/AnalyticsView.swift
autocommit e8e0195603 feat(ai-copilot): iOS/macOS Cockpit SwiftUI app (cockpit-kit + ios-fe + macos-fe)
Shared CocotteCockpitKit (views/model/LiveCockpitAPI) + iOS TabView shell
(Drops/Assets/Fleet/Activity/Insights) + macOS shell. XcodeGen project.yml.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 00:03:19 -07:00

80 lines
3.1 KiB
Swift

import SwiftUI
// Insights per-surface performance (analytics-dashboard slice).
public struct AnalyticsView: View {
@Environment(\.tokens) private var t
var model: CockpitModel
public init(model: CockpitModel) { self.model = model }
private var totalConversions: Int { model.metrics.reduce(0) { $0 + $1.conversions } }
private var totalImpressions: Int { model.metrics.reduce(0) { $0 + $1.impressions } }
private var maxImpressions: Int { max(1, model.metrics.map(\.impressions).max() ?? 1) }
public var body: some View {
Scroll {
VStack(alignment: .leading, spacing: t.s5) {
SectionLabel(text: "Insights · last 30 days")
HStack(spacing: 12) {
bigStat("\(totalConversions)", "unlocks / subs")
bigStat(compact(totalImpressions), "impressions")
bigStat("\(model.metrics.reduce(0) { $0 + $1.posts })", "posts")
}
SectionLabel(text: "By surface")
VStack(spacing: 10) {
ForEach(model.metrics) { m in metricRow(m) }
}
}
.padding(t.s5)
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
.background(t.bg)
}
private func bigStat(_ v: String, _ k: String) -> some View {
Card {
VStack(alignment: .leading, spacing: 3) {
Text(v).font(.system(size: 22, weight: .bold)).foregroundStyle(t.ink)
Text(k).font(.system(size: 10)).foregroundStyle(t.ink3)
}
.frame(maxWidth: .infinity, alignment: .leading)
}
}
private func metricRow(_ m: SurfaceMetric) -> some View {
Card {
VStack(alignment: .leading, spacing: 8) {
HStack {
SurfaceChip(surface: m.surface)
Spacer()
Text("\(m.conversions) conv").font(.system(size: 11, weight: .semibold)).foregroundStyle(t.accent)
}
GeometryReader { geo in
ZStack(alignment: .leading) {
Capsule().fill(t.bgElev).frame(height: 6)
Capsule().fill(t.accent)
.frame(width: geo.size.width * CGFloat(m.impressions) / CGFloat(maxImpressions), height: 6)
}
}
.frame(height: 6)
HStack(spacing: 14) {
metric("\(m.posts)", "posts")
metric(compact(m.impressions), "impr")
metric(String(format: "%.1f%%", m.engagementPct * 100), "eng")
}
}
}
}
private func metric(_ v: String, _ k: String) -> some View {
HStack(spacing: 4) {
Text(v).font(.system(size: 11, weight: .semibold)).foregroundStyle(t.ink2)
Text(k).font(.system(size: 10)).foregroundStyle(t.ink3)
}
}
private func compact(_ n: Int) -> String {
n >= 1000 ? String(format: "%.1fk", Double(n) / 1000) : "\(n)"
}
}