Files
Project_Velocity/iOS/velocity-ipad/velocity/App/ContentView.swift

225 lines
7.9 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import SwiftUI
enum AppSection: String, CaseIterable, Hashable, Identifiable {
var id: String { rawValue }
case dashboard = "Dashboard"
case clients = "Clients"
case imports = "Imports"
case communications = "Communications"
case calendar = "Calendar"
case oracle = "Oracle"
case sentinel = "Sentinel"
case inventory = "Inventory"
case settings = "Settings"
var displayTitle: String {
switch self {
case .sentinel:
return SentinelScope.navigationTitle
default:
return rawValue
}
}
var systemImage: String {
switch self {
case .dashboard: return "square.grid.2x2"
case .clients: return "person.text.rectangle"
case .imports: return "tray.and.arrow.down"
case .communications: return "phone.connection"
case .calendar: return "calendar.badge.clock"
case .oracle: return "message.and.waveform"
case .sentinel: return "person.crop.rectangle"
case .inventory: return "shippingbox"
case .settings: return "gearshape"
}
}
var accentColor: Color {
switch self {
case .dashboard: return VelocityTheme.accent
case .clients: return Color(red: 0.22, green: 0.78, blue: 0.96)
case .imports: return Color(red: 0.94, green: 0.70, blue: 0.25)
case .communications: return Color(red: 0.19, green: 0.84, blue: 0.63)
case .calendar: return Color(red: 0.96, green: 0.67, blue: 0.16)
case .oracle: return Color(red: 0.13, green: 0.83, blue: 0.93) // cyan
case .sentinel: return Color(red: 0.60, green: 0.57, blue: 0.99) // indigo
case .inventory: return VelocityTheme.warning
case .settings: return VelocityTheme.mutedFg
}
}
}
struct ContentView: View {
@State private var selectedSection: AppSection? = .dashboard
@State private var session = SessionStore.shared
var body: some View {
Group {
if session.isConfigured {
NavigationSplitView(columnVisibility: .constant(.all)) {
sidebarContent
} detail: {
detailContent
}
.navigationSplitViewStyle(.balanced)
} else {
ConfigurationGateView()
}
}
}
// MARK: Sidebar
private var sidebarContent: some View {
ZStack {
VelocityTheme.sidebarBg.ignoresSafeArea()
VStack(spacing: 0) {
// App title
HStack(spacing: 10) {
ZStack {
RoundedRectangle(cornerRadius: 9)
.fill(VelocityTheme.accent.opacity(0.18))
.frame(width: 34, height: 34)
Image(systemName: "bolt.fill")
.font(.system(size: 15, weight: .semibold))
.foregroundStyle(VelocityTheme.accent)
}
VStack(alignment: .leading, spacing: 1) {
Text("Velocity")
.font(.system(size: 16, weight: .semibold))
.foregroundStyle(VelocityTheme.foreground)
Text("Project Velocity · v1.1")
.font(.system(size: 10))
.foregroundStyle(VelocityTheme.mutedFg)
}
Spacer()
}
.padding(.horizontal, 16)
.padding(.top, 20)
.padding(.bottom, 16)
Divider()
.background(VelocityTheme.borderSubtle)
.padding(.bottom, 8)
// Nav items
VStack(spacing: 2) {
ForEach(AppSection.allCases) { section in
Button {
selectedSection = section
} label: {
SidebarRow(section: section, isSelected: selectedSection == section)
}
.buttonStyle(.plain)
.accessibilityLabel(section.displayTitle)
.accessibilityAddTraits(selectedSection == section ? [.isSelected] : [])
}
}
.padding(.horizontal, 8)
Spacer()
// User footer
Divider()
.background(VelocityTheme.borderSubtle)
HStack(spacing: 10) {
ZStack {
RoundedRectangle(cornerRadius: 8)
.fill(VelocityTheme.accent)
.frame(width: 32, height: 32)
Text(operatorInitials)
.font(.system(size: 11, weight: .bold))
.foregroundStyle(.white)
}
VStack(alignment: .leading, spacing: 2) {
Text(operatorName)
.font(.system(size: 12, weight: .medium))
.foregroundStyle(VelocityTheme.foreground)
Text(session.authModeDescription)
.font(.system(size: 10))
.foregroundStyle(VelocityTheme.mutedFg)
}
Spacer()
}
.padding(16)
}
}
.navigationTitle("")
.toolbar(.hidden, for: .navigationBar)
}
// MARK: Detail
private var detailContent: some View {
ZStack {
VelocityTheme.background.ignoresSafeArea()
Group {
switch selectedSection {
case .dashboard: DashboardView()
case .clients: ClientsView()
case .imports: ImportsView()
case .communications: CommunicationsView()
case .calendar: CalendarView()
case .oracle: OracleView()
case .sentinel: SentinelView()
case .inventory: InventoryView()
case .settings: SettingsView()
case .none: DashboardView()
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
private var operatorName: String {
session.operatorIdentity
}
private var operatorInitials: String {
let source = session.operatorIdentity
let parts = source
.replacingOccurrences(of: "@", with: " ")
.split(separator: ".")
.flatMap { $0.split(separator: " ") }
let initials = parts.prefix(2).compactMap(\.first)
return initials.isEmpty ? "VO" : String(initials)
}
}
// MARK: Sidebar Row
private struct SidebarRow: View {
let section: AppSection
let isSelected: Bool
var body: some View {
HStack(spacing: 11) {
Image(systemName: section.systemImage)
.font(.system(size: 14, weight: .medium))
.foregroundStyle(isSelected ? section.accentColor : VelocityTheme.mutedFg)
.frame(width: 20)
Text(section.displayTitle)
.font(.system(size: 14, weight: isSelected ? .semibold : .regular))
.foregroundStyle(isSelected ? VelocityTheme.foreground : VelocityTheme.mutedFg)
Spacer()
}
.padding(.horizontal, 12)
.padding(.vertical, 9)
.background(
RoundedRectangle(cornerRadius: 10)
.fill(isSelected ? section.accentColor.opacity(0.12) : .clear)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(isSelected ? section.accentColor.opacity(0.25) : .clear, lineWidth: 1)
)
)
.contentShape(Rectangle())
}
}
#Preview {
ContentView()
}