Files
Project_Velocity/iOS/velocity-ipad/velocity/Features/Settings/SettingsView.swift
2026-04-28 11:32:56 +05:30

225 lines
9.9 KiB
Swift

import SwiftUI
struct SettingsView: View {
@State private var store = AppStore.shared
@State private var session = SessionStore.shared
var body: some View {
VStack(alignment: .leading, spacing: 24) {
VStack(alignment: .leading, spacing: 4) {
Text("Settings")
.font(.system(size: 28, weight: .bold))
.foregroundStyle(VelocityTheme.foreground)
Text("Live runtime configuration")
.font(.system(size: 12))
.foregroundStyle(VelocityTheme.mutedFg)
}
SettingsSection(title: "Connectivity") {
SettingsRow(
label: "Backend endpoint",
value: session.endpointDisplay,
icon: "server.rack",
accentColor: VelocityTheme.accent
)
Divider().background(VelocityTheme.borderSubtle)
SettingsRow(
label: "Dream Weaver endpoint",
value: session.dreamWeaverEndpointDisplay,
icon: "wand.and.stars",
accentColor: session.dreamWeaverEndpointModeDescription == "Dedicated gateway" ? VelocityTheme.warning : VelocityTheme.mutedFg
)
Divider().background(VelocityTheme.borderSubtle)
SettingsRow(
label: "Dream Weaver route mode",
value: session.dreamWeaverEndpointModeDescription,
icon: "point.3.connected.trianglepath.dotted",
accentColor: session.dreamWeaverEndpointModeDescription == "Dedicated gateway" ? VelocityTheme.warning : VelocityTheme.success
)
Divider().background(VelocityTheme.borderSubtle)
SettingsRow(
label: "Dream Weaver auth",
value: session.dreamWeaverAuthenticationDescription,
icon: "key.horizontal",
accentColor: session.dreamWeaverAuthenticationDescription == "API key configured" ? VelocityTheme.success : VelocityTheme.mutedFg
)
Divider().background(VelocityTheme.borderSubtle)
SettingsRow(
label: "Auth mode",
value: session.authModeDescription,
icon: "lock.shield",
accentColor: session.isConfigured ? VelocityTheme.success : VelocityTheme.warning
)
Divider().background(VelocityTheme.borderSubtle)
SettingsRow(
label: "Config source",
value: session.configurationSourceDescription,
icon: "externaldrive.badge.icloud",
accentColor: session.isUsingStoredRuntimeConfiguration ? VelocityTheme.success : VelocityTheme.mutedFg
)
Divider().background(VelocityTheme.borderSubtle)
SettingsRow(
label: "Last refresh",
value: store.lastRefreshAt?.relativeShort ?? "No live fetch yet",
icon: "arrow.clockwise",
accentColor: VelocityTheme.mutedFg
)
}
SettingsSection(title: "Operator") {
SettingsRow(
label: "Identity",
value: session.operatorIdentity,
icon: "person.crop.circle",
accentColor: VelocityTheme.accent
)
Divider().background(VelocityTheme.borderSubtle)
SettingsRow(
label: "CRM contacts loaded",
value: "\(store.contacts.count)",
icon: "person.3",
accentColor: VelocityTheme.success
)
Divider().background(VelocityTheme.borderSubtle)
SettingsRow(
label: "Pending CRM tasks loaded",
value: "\(store.tasks.count)",
icon: "checklist",
accentColor: VelocityTheme.warning
)
Divider().background(VelocityTheme.borderSubtle)
SettingsRow(
label: "Property records loaded",
value: "\(store.properties.count)",
icon: "building.2",
accentColor: VelocityTheme.warning
)
}
SettingsSection(title: "Production Readiness") {
SettingsRow(
label: "Canonical contacts",
value: "\(store.contacts.count) loaded",
icon: "person.text.rectangle",
accentColor: store.contacts.isEmpty ? VelocityTheme.warning : VelocityTheme.success
)
Divider().background(VelocityTheme.borderSubtle)
SettingsRow(
label: "Pipeline lanes",
value: "\(store.kanbanColumns.reduce(0) { $0 + $1.count }) leads",
icon: "square.grid.3x1.below.line.grid.1x2",
accentColor: store.kanbanColumns.isEmpty ? VelocityTheme.warning : VelocityTheme.success
)
Divider().background(VelocityTheme.borderSubtle)
SettingsRow(
label: "Deals",
value: "\(store.opportunities.count) opportunities",
icon: "target",
accentColor: store.opportunities.isEmpty ? VelocityTheme.warning : VelocityTheme.success
)
Divider().background(VelocityTheme.borderSubtle)
SettingsRow(
label: "Timeline events",
value: "\(store.timelineEvents.count) hydrated",
icon: "clock.arrow.circlepath",
accentColor: store.timelineEvents.isEmpty ? VelocityTheme.warning : VelocityTheme.success
)
Divider().background(VelocityTheme.borderSubtle)
SettingsRow(
label: "Last app error",
value: store.errorMessage ?? "None",
icon: "exclamationmark.triangle",
accentColor: store.errorMessage == nil ? VelocityTheme.success : VelocityTheme.danger
)
}
SessionConfigurationPanel(
title: "Session Configuration",
subtitle: "Update the production endpoint, point Dream Weaver at a dedicated gateway when needed, or rotate operator credentials without rebuilding the app. Saving clears the cached token, re-runs a live refresh, and probes the Dream Weaver routes.",
primaryActionTitle: "Save and refresh",
allowsClearingStoredConfiguration: true
)
SettingsSection(title: "Production Notes") {
VStack(alignment: .leading, spacing: 8) {
Text("This build avoids local demo data. Runtime session overrides are stored on-device so investor or operator installs no longer depend on committed build-time credentials.")
.font(.system(size: 13))
.foregroundStyle(VelocityTheme.foreground)
Text("\(SentinelScope.navigationTitle) remains the truthful iPad label for the current \(SentinelScope.productFamilyName) surface because visitor analytics stay disabled until a dedicated production feed exists; Communications, Calendar, Dashboard, Oracle pipeline, and inventory summaries are live-backed. Dream Weaver can now use a dedicated gateway with an optional per-gateway API key, and backend plus generation route health are still enforced and reported truthfully.")
.font(.system(size: 12))
.foregroundStyle(VelocityTheme.mutedFg)
}
.padding(.horizontal, 16)
.padding(.vertical, 14)
}
Spacer()
}
.padding(24)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
.background(VelocityTheme.background)
}
}
private struct SettingsSection<Content: View>: View {
let title: String
@ViewBuilder let content: Content
var body: some View {
VStack(alignment: .leading, spacing: 0) {
Text(title.uppercased())
.font(.system(size: 10, weight: .semibold))
.tracking(1.2)
.foregroundStyle(VelocityTheme.mutedFg)
.padding(.bottom, 8)
.padding(.horizontal, 4)
VStack(spacing: 0) {
content
}
.padding(.vertical, 4)
.background(
RoundedRectangle(cornerRadius: 14)
.fill(Color(red: 0.031, green: 0.039, blue: 0.071))
.overlay(
RoundedRectangle(cornerRadius: 14)
.stroke(VelocityTheme.borderSubtle, lineWidth: 1)
)
)
}
}
}
private struct SettingsRow: View {
let label: String
let value: String
let icon: String
let accentColor: Color
var body: some View {
HStack(spacing: 14) {
ZStack {
RoundedRectangle(cornerRadius: 7)
.fill(accentColor.opacity(0.12))
.frame(width: 30, height: 30)
Image(systemName: icon)
.font(.system(size: 13, weight: .medium))
.foregroundStyle(accentColor)
}
Text(label)
.font(.system(size: 14))
.foregroundStyle(VelocityTheme.foreground)
Spacer()
Text(value)
.font(.system(size: 13))
.foregroundStyle(VelocityTheme.mutedFg)
.multilineTextAlignment(.trailing)
}
.padding(.horizontal, 16)
.padding(.vertical, 12)
}
}