import SwiftUI // MARK: – Oracle Canvas Modes enum OracleMode: String, CaseIterable { case pipeline = "Pipeline" case teamPerformance = "Team Performance" case accountTimeline = "Account Timeline" case leadMap = "Lead Map" case calendarTasks = "Calendar & Tasks" var icon: String { switch self { case .pipeline: return "square.grid.3x1.below.line.grid.1x2" case .teamPerformance: return "person.3" case .accountTimeline: return "clock.arrow.circlepath" case .leadMap: return "map" case .calendarTasks: return "calendar" } } } // MARK: – Pipeline mock data (extended with detail fields) struct OracleLeadCard: Identifiable { let id = UUID() let initials: String let name: String let company: String let value: String let status: LeadStatus let phone: String let interest: String let qualification: String } private let pipelineData: [(stage: String, cards: [OracleLeadCard])] = [ ("New", [ OracleLeadCard(initials: "JW", name: "James Wilson", company: "Website", value: "AED 3.5M", status: .new, phone: "+971 52 456 7890", interest: "1BR Investment", qualification: "Potential"), ]), ("Qualified", [ OracleLeadCard(initials: "FH", name: "Fatima Hassan", company: "WhatsApp", value: "AED 12M", status: .qualified, phone: "+971 54 321 0987", interest: "3BR + Maid", qualification: "Whale"), OracleLeadCard(initials: "SC", name: "Sarah Chen", company: "Walk-in", value: "AED 6.5M", status: .engaged, phone: "+971 50 987 6543", interest: "2BR Sea View", qualification: "Potential"), ]), ("Proposal", [ OracleLeadCard(initials: "MA", name: "Mohammed Al-Rashid", company: "WhatsApp", value: "AED 15M+", status: .hot, phone: "+971 55 123 4567", interest: "Penthouse Suite", qualification: "Whale"), ]), ("Closed", [ OracleLeadCard(initials: "DK", name: "David Kumar", company: "Walk-in", value: "AED 20M", status: .closed, phone: "+971 56 789 0123", interest: "Full Floor", qualification: "Whale"), ]), ] struct TeamMemberData: Identifiable { let id = UUID() let initials: String; let name: String; let deals: Int; let revenue: String; let trend: String } private let teamData: [TeamMemberData] = [ .init(initials: "RA", name: "Rania Al-Farsi", deals: 42, revenue: "$2.1M", trend: "↑ 18%"), .init(initials: "KM", name: "Khaled Mensah", deals: 31, revenue: "$1.6M", trend: "↑ 12%"), .init(initials: "LT", name: "Lina Torres", deals: 28, revenue: "$1.3M", trend: "→ 2%"), .init(initials: "AH", name: "Ahmed Hassan", deals: 19, revenue: "$0.9M", trend: "↓ 5%"), ] struct OracleTimelineEvent: Identifiable { let id = UUID() let badge: String; let summary: String; let when: String; let detail: String } private let timelineEvents: [OracleTimelineEvent] = [ .init(badge: "MEETING", summary: "VR Amenity Tour – Apex Innovations", when: "2h ago", detail: "CFO and Legal Director attended. Strong reaction to the panoramic sea view suite. Follow-up proposal requested by Tuesday."), .init(badge: "EMAIL", summary: "Proposal deck sent to legal team", when: "Yesterday", detail: "58-page proposal + payment plan schedule sent via DocuSign. Legal team has 5 business days to review."), .init(badge: "CALL", summary: "Budget discussion – CFO confirmed", when: "Mon", detail: "Budget ceiling confirmed at AED 15M+. CFO expressed strong preference for a penthouse unit with private terrace."), .init(badge: "VISIT", summary: "Site walkthrough – Penthouse Suite", when: "Last week", detail: "First in-person visit. 45-minute walkthrough of Penthouse A & B. Visitor dwell time analysis showed 'excited' sentiment for 90% of the visit."), ] struct RegionPin: Identifiable { let id = UUID() let label: String; let country: String; let count: Int; let temp: String; let topLead: String } private let mapPins: [RegionPin] = [ .init(label: "UAE", country: "🇦🇪", count: 8, temp: "hot", topLead: "Mohammed Al-Rashid"), .init(label: "Saudi Arabia", country: "🇸🇦", count: 5, temp: "warm", topLead: "Al-Mansour Group"), .init(label: "UK", country: "🇬🇧", count: 3, temp: "cold", topLead: "Rexford Capital"), .init(label: "USA", country: "🇺🇸", count: 4, temp: "warm", topLead: "Apex Innovations"), .init(label: "India", country: "🇮🇳", count: 6, temp: "hot", topLead: "Starlight Systems"), .init(label: "Germany", country: "🇩🇪", count: 2, temp: "cold", topLead: "TechWave GmbH"), ] struct CalTask: Identifiable { let id = UUID() let title: String; let subtitle: String; let due: String } private let calTasks: [CalTask] = [ .init(title: "Follow up with Mohammed", subtitle: "High-value penthouse lead – 2 unread messages", due: "Today 3 PM"), .init(title: "Send contract to Fatima", subtitle: "3BR unit finalised – payment plan to confirm", due: "Tomorrow 10 AM"), .init(title: "Schedule VR tour – James", subtitle: "Website lead, potential 1BR investor", due: "Thu 2 PM"), ] // MARK: – OracleView (main) struct OracleView: View { @State private var selectedMode: OracleMode = .pipeline @State private var prompt = "Show me a pipeline view by stage for Q4." @State private var insightText = "Pipeline is healthy. Mohammed Al-Rashid is your highest-value close opportunity — follow up within 24 hours." @State private var isSubmitting = false // Sheet states @State private var selectedLead: OracleLeadCard? = nil @State private var selectedMember: TeamMemberData? = nil @State private var selectedRegion: RegionPin? = nil @State private var scheduledTask: CalTask? = nil @State private var showScheduleConfirm = false var body: some View { ZStack(alignment: .bottom) { VStack(alignment: .leading, spacing: 0) { pageHeader .padding(.horizontal, 24).padding(.top, 24).padding(.bottom, 16) insightCard .padding(.horizontal, 24).padding(.bottom, 14) ScrollView { canvasView .padding(.horizontal, 24) .padding(.bottom, 120) } } promptBar .padding(.horizontal, 20) .padding(.bottom, 12) } .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) .background(VelocityTheme.background) // Lead detail sheet .sheet(item: $selectedLead) { card in LeadDetailSheet(card: card) } // Team member sheet .sheet(item: $selectedMember) { member in MemberDetailSheet(member: member) } // Region callout sheet .sheet(item: $selectedRegion) { pin in RegionDetailSheet(pin: pin) } // Schedule confirmation alert .alert("Confirm Schedule", isPresented: $showScheduleConfirm, presenting: scheduledTask) { task in Button("Schedule") { // In a real app this would create a calendar event } Button("Cancel", role: .cancel) {} } message: { task in Text("Add \"\(task.title)\" to your calendar for \(task.due)?") } } // MARK: – Sub-views private var pageHeader: some View { HStack { VStack(alignment: .leading, spacing: 3) { Text("Oracle").font(.system(size: 28, weight: .bold)).foregroundStyle(VelocityTheme.foreground) Text("AI intelligence pipeline").font(.system(size: 12)).foregroundStyle(VelocityTheme.mutedFg) } Spacer() if isSubmitting { ProgressView() .progressViewStyle(CircularProgressViewStyle(tint: VelocityTheme.accent)) .scaleEffect(0.8) } } } private var insightCard: some View { HStack(alignment: .center, spacing: 0) { RoundedRectangle(cornerRadius: 2) .fill(LinearGradient(colors: [Color(red: 0.58, green: 0.77, blue: 0.99), VelocityTheme.accent], startPoint: .top, endPoint: .bottom)) .frame(width: 3) HStack { VStack(alignment: .leading, spacing: 3) { Text("AI INSIGHT").font(.system(size: 9, weight: .semibold)).tracking(1.5) .foregroundStyle(VelocityTheme.accent) Text(insightText).font(.system(size: 13)).foregroundStyle(VelocityTheme.foreground).lineLimit(2) } Spacer() HStack(spacing: 5) { Image(systemName: selectedMode.icon).font(.system(size: 11)).foregroundStyle(VelocityTheme.accent) Text(selectedMode.rawValue).font(.system(size: 11, weight: .medium)) .foregroundStyle(Color(red: 0.58, green: 0.77, blue: 0.99)) } } .padding(.horizontal, 14).padding(.vertical, 12) } .background( RoundedRectangle(cornerRadius: 12) .fill(Color(red: 0.09, green: 0.15, blue: 0.33).opacity(0.55)) .overlay(RoundedRectangle(cornerRadius: 12).stroke(VelocityTheme.borderAccent, lineWidth: 1)) ) .clipShape(RoundedRectangle(cornerRadius: 12)) } @ViewBuilder private var canvasView: some View { switch selectedMode { case .pipeline: PipelineCanvas(onSelectLead: { selectedLead = $0 }) case .teamPerformance: TeamPerformanceCanvas(onSelectMember: { selectedMember = $0 }) case .accountTimeline: AccountTimelineCanvas() case .leadMap: LeadMapCanvas(onSelectRegion: { selectedRegion = $0 }) case .calendarTasks: CalendarCanvas(onSchedule: { task in scheduledTask = task showScheduleConfirm = true }) } } // MARK: – Prompt Bar private var promptBar: some View { VStack(spacing: 0) { TextField("Ask Oracle anything…", text: $prompt) .font(.system(size: 14)) .foregroundStyle(VelocityTheme.foreground) .tint(VelocityTheme.accent) .onSubmit { submitPrompt() } .padding(.horizontal, 16).padding(.top, 12).padding(.bottom, 8) HStack { Menu { ForEach(OracleMode.allCases, id: \.self) { mode in Button { selectedMode = mode prompt = modeSamplePrompt(mode) insightText = oracleInsight(for: mode) } label: { Label(mode.rawValue, systemImage: mode.icon) } } } label: { HStack(spacing: 5) { Image(systemName: selectedMode.icon).font(.system(size: 10)) Text(selectedMode.rawValue).font(.system(size: 11, weight: .medium)) Image(systemName: "chevron.down").font(.system(size: 8)) } .foregroundStyle(Color(red: 0.58, green: 0.77, blue: 0.99)) .padding(.horizontal, 10).padding(.vertical, 6) .background(Capsule().fill(VelocityTheme.accent.opacity(0.14)) .overlay(Capsule().stroke(VelocityTheme.accent.opacity(0.3), lineWidth: 1))) } Spacer() Button(action: submitPrompt) { ZStack { Circle() .fill(isSubmitting ? VelocityTheme.mutedFg : VelocityTheme.accent) .shadow(color: VelocityTheme.accent.opacity(0.5), radius: 8) if isSubmitting { ProgressView().progressViewStyle(CircularProgressViewStyle(tint: .white)).scaleEffect(0.6) } else { Image(systemName: "paperplane.fill").font(.system(size: 12)).foregroundStyle(.white) } } .frame(width: 34, height: 34) } .disabled(isSubmitting || prompt.trimmingCharacters(in: .whitespaces).isEmpty) } .padding(.horizontal, 12).padding(.bottom, 12) } .background( RoundedRectangle(cornerRadius: 18) .fill(Color(red: 0.039, green: 0.043, blue: 0.063).opacity(0.95)) .overlay(RoundedRectangle(cornerRadius: 18).stroke(Color.white.opacity(0.11), lineWidth: 1)) .shadow(color: .black.opacity(0.6), radius: 20, y: -4) ) } // MARK: – Prompt logic private func submitPrompt() { let clean = prompt.trimmingCharacters(in: .whitespaces) guard !clean.isEmpty && !isSubmitting else { return } isSubmitting = true let lower = clean.lowercased() if lower.contains("team") || lower.contains("performance") || lower.contains("sales") { selectedMode = .teamPerformance } else if lower.contains("account") || lower.contains("apex") || lower.contains("timeline") { selectedMode = .accountTimeline } else if lower.contains("map") || lower.contains("geographic") || lower.contains("location") { selectedMode = .leadMap } else if lower.contains("calendar") || lower.contains("schedule") || lower.contains("task") { selectedMode = .calendarTasks } else { selectedMode = .pipeline } DispatchQueue.main.asyncAfter(deadline: .now() + 0.9) { withAnimation(.easeInOut(duration: 0.3)) { insightText = oracleInsight(for: selectedMode) isSubmitting = false } } } private func modeSamplePrompt(_ mode: OracleMode) -> String { switch mode { case .pipeline: return "Show me a pipeline view by stage for Q4." case .teamPerformance: return "What's the performance of the sales team this month?" case .accountTimeline: return "Find all contacts at Apex Innovations and their recent activity." case .leadMap: return "Give me a geographic map of all leads." case .calendarTasks: return "Schedule follow-ups with the top 3 high-value leads." } } private func oracleInsight(for mode: OracleMode) -> String { switch mode { case .pipeline: return "Pipeline is healthy. Mohammed Al-Rashid is your highest-value close opportunity — follow up within 24 hours." case .teamPerformance: return "Rania Al-Farsi leads the team at $2.1M closed. Overall quota attainment is at 87% — ahead of last month." case .accountTimeline: return "Apex Innovations has 4 active stakeholders. Confusion detected in legal stage — recommend expediting contract review." case .leadMap: return "UAE and India show the hottest lead concentration. 8 high-value prospects in UAE require immediate outreach." case .calendarTasks: return "3 high-priority follow-ups scheduled. Mohammed Al-Rashid has 2 unread messages — respond within 24h." } } } // MARK: – Pipeline Canvas private struct PipelineCanvas: View { let onSelectLead: (OracleLeadCard) -> Void private let cols = [GridItem(.adaptive(minimum: 200), spacing: 12)] var body: some View { LazyVGrid(columns: cols, alignment: .leading, spacing: 12) { ForEach(pipelineData, id: \.stage) { col in VStack(alignment: .leading, spacing: 10) { HStack { Text(col.stage.uppercased()) .font(.system(size: 10, weight: .semibold)).tracking(1) .foregroundStyle(VelocityTheme.mutedFg) Spacer() Text("\(col.cards.count)") .font(.system(size: 10, weight: .semibold)) .foregroundStyle(VelocityTheme.accent) .padding(.horizontal, 7).padding(.vertical, 3) .background(Capsule().fill(VelocityTheme.accent.opacity(0.12)) .overlay(Capsule().stroke(VelocityTheme.accent.opacity(0.2), lineWidth: 1))) } ForEach(col.cards) { card in TappableLeadCard(card: card, onTap: { onSelectLead(card) }) } } .padding(16) .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 TappableLeadCard: View { let card: OracleLeadCard let onTap: () -> Void @State private var pressed = false var body: some View { HStack(spacing: 10) { ZStack { RoundedRectangle(cornerRadius: 8).fill(card.status.color.opacity(0.18)).frame(width: 36, height: 36) Text(card.initials).font(.system(size: 12, weight: .bold)).foregroundStyle(card.status.color) } VStack(alignment: .leading, spacing: 2) { Text(card.name).font(.system(size: 12, weight: .medium)).foregroundStyle(VelocityTheme.foreground) Text(card.company).font(.system(size: 10)).foregroundStyle(VelocityTheme.mutedFg) } Spacer() VStack(alignment: .trailing, spacing: 2) { Text(card.value).font(.system(size: 11, weight: .semibold)).foregroundStyle(VelocityTheme.accent) Image(systemName: "chevron.right").font(.system(size: 9)).foregroundStyle(VelocityTheme.mutedFg) } } .padding(10) .background( RoundedRectangle(cornerRadius: 10) .fill(pressed ? VelocityTheme.accent.opacity(0.10) : Color.white.opacity(0.04)) .overlay(RoundedRectangle(cornerRadius: 10) .stroke(pressed ? VelocityTheme.accent.opacity(0.35) : Color.white.opacity(0.06), lineWidth: 1)) ) .scaleEffect(pressed ? 0.97 : 1.0) .animation(.easeInOut(duration: 0.12), value: pressed) .onTapGesture { pressed = true DispatchQueue.main.asyncAfter(deadline: .now() + 0.12) { pressed = false } onTap() } } } // MARK: – Lead Detail Sheet private struct LeadDetailSheet: View { let card: OracleLeadCard @Environment(\.dismiss) private var dismiss var body: some View { NavigationStack { VStack(alignment: .leading, spacing: 20) { // Avatar + name HStack(spacing: 16) { ZStack { RoundedRectangle(cornerRadius: 14).fill(card.status.color.opacity(0.20)).frame(width: 60, height: 60) Text(card.initials).font(.system(size: 22, weight: .bold)).foregroundStyle(card.status.color) } VStack(alignment: .leading, spacing: 4) { Text(card.name).font(.system(size: 20, weight: .bold)).foregroundStyle(VelocityTheme.foreground) HStack(spacing: 6) { Text(card.status.rawValue) .font(.system(size: 11, weight: .semibold)) .foregroundStyle(card.status.color) .padding(.horizontal, 8).padding(.vertical, 3) .background(Capsule().fill(card.status.color.opacity(0.14))) Text(card.qualification).font(.system(size: 11)).foregroundStyle(VelocityTheme.mutedFg) } } } .padding(.top, 8) Divider().background(VelocityTheme.borderSubtle) // Details grid LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 14) { DetailField(label: "Deal Value", value: card.value) DetailField(label: "Source", value: card.company) DetailField(label: "Interest", value: card.interest) DetailField(label: "Phone", value: card.phone) } Divider().background(VelocityTheme.borderSubtle) // Action buttons HStack(spacing: 12) { ActionChip(icon: "phone.fill", label: "Call", color: VelocityTheme.success) ActionChip(icon: "message.fill", label: "Message", color: VelocityTheme.accent) ActionChip(icon: "calendar.badge.plus", label: "Schedule", color: Color(red: 0.60, green: 0.57, blue: 0.99)) } Spacer() } .padding(24) .background(VelocityTheme.background.ignoresSafeArea()) .navigationTitle("Lead Details") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button("Done") { dismiss() } .foregroundStyle(VelocityTheme.accent) } } } } } private struct DetailField: View { let label: String; let value: String var body: some View { VStack(alignment: .leading, spacing: 4) { Text(label.uppercased()).font(.system(size: 9, weight: .semibold)).tracking(1) .foregroundStyle(VelocityTheme.mutedFg) Text(value).font(.system(size: 13, weight: .medium)).foregroundStyle(VelocityTheme.foreground) } .padding(12) .frame(maxWidth: .infinity, alignment: .leading) .background(RoundedRectangle(cornerRadius: 10).fill(Color.white.opacity(0.04)) .overlay(RoundedRectangle(cornerRadius: 10).stroke(Color.white.opacity(0.07), lineWidth: 1))) } } private struct ActionChip: View { let icon: String; let label: String; let color: Color @State private var pressed = false var body: some View { HStack(spacing: 6) { Image(systemName: icon).font(.system(size: 12)) Text(label).font(.system(size: 12, weight: .semibold)) } .foregroundStyle(color) .padding(.horizontal, 16).padding(.vertical, 10) .frame(maxWidth: .infinity) .background(RoundedRectangle(cornerRadius: 10).fill(color.opacity(pressed ? 0.22 : 0.13)) .overlay(RoundedRectangle(cornerRadius: 10).stroke(color.opacity(0.30), lineWidth: 1))) .scaleEffect(pressed ? 0.96 : 1.0) .animation(.easeInOut(duration: 0.12), value: pressed) .onTapGesture { pressed = true; DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) { pressed = false } } } } // MARK: – Team Performance Canvas private struct TeamPerformanceCanvas: View { let onSelectMember: (TeamMemberData) -> Void var body: some View { VStack(spacing: 14) { quotaPanel teamListPanel } } private var quotaPanel: some View { HStack(spacing: 14) { ZStack { Circle().stroke(Color.white.opacity(0.06), lineWidth: 10).frame(width: 110, height: 110) Circle() .trim(from: 0, to: 0.87) .stroke(AngularGradient(colors: [VelocityTheme.accent, Color(red: 0.13, green: 0.83, blue: 0.93)], center: .center), style: StrokeStyle(lineWidth: 10, lineCap: .round)) .rotationEffect(.degrees(-90)) .frame(width: 110, height: 110) VStack(spacing: 2) { Text("87%").font(.system(size: 26, weight: .bold)).foregroundStyle(VelocityTheme.foreground) Text("QUOTA").font(.system(size: 8, weight: .semibold)).tracking(1.2).foregroundStyle(VelocityTheme.accent) } } VStack(alignment: .leading, spacing: 4) { Text("Quota Attainment").font(.system(size: 13, weight: .semibold)).foregroundStyle(VelocityTheme.foreground) Text("Monthly target exceeded").font(.system(size: 11)).foregroundStyle(VelocityTheme.success) Text("Q4 FY2025–26").font(.system(size: 11)).foregroundStyle(VelocityTheme.mutedFg) } Spacer() } .padding(20) .background(RoundedRectangle(cornerRadius: 14).fill(Color(red: 0.031, green: 0.039, blue: 0.071)) .overlay(RoundedRectangle(cornerRadius: 14).stroke(VelocityTheme.borderAccent, lineWidth: 1))) } private var teamListPanel: some View { VStack(alignment: .leading, spacing: 2) { Text("TEAM PERFORMANCE").font(.system(size: 10, weight: .semibold)).tracking(1.2) .foregroundStyle(VelocityTheme.mutedFg).padding(.bottom, 8) ForEach(teamData) { member in TappableTeamRow(member: member, onTap: { onSelectMember(member) }) } } .padding(20) .background(RoundedRectangle(cornerRadius: 14).fill(Color(red: 0.031, green: 0.039, blue: 0.071)) .overlay(RoundedRectangle(cornerRadius: 14).stroke(VelocityTheme.borderAccent, lineWidth: 1))) } } private struct TappableTeamRow: View { let member: TeamMemberData let onTap: () -> Void @State private var pressed = false var body: some View { HStack(spacing: 12) { ZStack { RoundedRectangle(cornerRadius: 8).fill(VelocityTheme.accent.opacity(0.18)).frame(width: 36, height: 36) Text(member.initials).font(.system(size: 12, weight: .bold)).foregroundStyle(VelocityTheme.accent) } VStack(alignment: .leading, spacing: 2) { Text(member.name).font(.system(size: 13, weight: .medium)).foregroundStyle(VelocityTheme.foreground) Text("\(member.deals) deals closed").font(.system(size: 11)).foregroundStyle(VelocityTheme.mutedFg) } Spacer() VStack(alignment: .trailing, spacing: 2) { Text(member.revenue).font(.system(size: 12, weight: .semibold)).foregroundStyle(VelocityTheme.accent) Text(member.trend) .font(.system(size: 10, weight: .medium)) .foregroundStyle(member.trend.hasPrefix("↑") ? VelocityTheme.success : member.trend.hasPrefix("↓") ? VelocityTheme.danger : VelocityTheme.mutedFg) } Image(systemName: "chevron.right").font(.system(size: 10)).foregroundStyle(VelocityTheme.mutedFg) } .padding(12) .background(RoundedRectangle(cornerRadius: 10) .fill(pressed ? VelocityTheme.accent.opacity(0.08) : Color.white.opacity(0.04)) .overlay(RoundedRectangle(cornerRadius: 10) .stroke(pressed ? VelocityTheme.accent.opacity(0.25) : Color.white.opacity(0.06), lineWidth: 1))) .scaleEffect(pressed ? 0.98 : 1.0) .animation(.easeInOut(duration: 0.12), value: pressed) .onTapGesture { pressed = true DispatchQueue.main.asyncAfter(deadline: .now() + 0.12) { pressed = false } onTap() } } } // MARK: – Team Member Detail Sheet private struct MemberDetailSheet: View { let member: TeamMemberData @Environment(\.dismiss) private var dismiss var body: some View { NavigationStack { VStack(alignment: .leading, spacing: 20) { HStack(spacing: 16) { ZStack { RoundedRectangle(cornerRadius: 14).fill(VelocityTheme.accent.opacity(0.18)).frame(width: 60, height: 60) Text(member.initials).font(.system(size: 22, weight: .bold)).foregroundStyle(VelocityTheme.accent) } VStack(alignment: .leading, spacing: 4) { Text(member.name).font(.system(size: 20, weight: .bold)).foregroundStyle(VelocityTheme.foreground) Text("Sales Executive").font(.system(size: 12)).foregroundStyle(VelocityTheme.mutedFg) } } .padding(.top, 8) Divider().background(VelocityTheme.borderSubtle) LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 14) { DetailField(label: "Revenue Closed", value: member.revenue) DetailField(label: "Deals Closed", value: "\(member.deals)") DetailField(label: "Trend", value: member.trend) DetailField(label: "Period", value: "Q4 FY2025–26") } Spacer() } .padding(24) .background(VelocityTheme.background.ignoresSafeArea()) .navigationTitle("Team Member") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button("Done") { dismiss() }.foregroundStyle(VelocityTheme.accent) } } } } } // MARK: – Account Timeline Canvas private struct AccountTimelineCanvas: View { @State private var expandedId: UUID? = nil var body: some View { VStack(spacing: 14) { // Account overview VStack(alignment: .leading, spacing: 12) { Text("ACCOUNT OVERVIEW").font(.system(size: 9, weight: .semibold)).tracking(1.5).foregroundStyle(VelocityTheme.accent) Text("Apex Innovations").font(.system(size: 24, weight: .bold)).foregroundStyle(VelocityTheme.foreground) HStack(spacing: 14) { InfoMini(label: "Deal Value", value: "AED 15M+") InfoMini(label: "Primary Contact", value: "CEO – James T.") InfoMini(label: "Industry", value: "Technology") } } .padding(20) .background(RoundedRectangle(cornerRadius: 14).fill(Color(red: 0.031, green: 0.039, blue: 0.071)) .overlay(RoundedRectangle(cornerRadius: 14).stroke(VelocityTheme.borderAccent, lineWidth: 1))) // Expandable timeline VStack(alignment: .leading, spacing: 0) { Text("Activity Timeline").font(.system(size: 13, weight: .semibold)) .foregroundStyle(VelocityTheme.foreground).padding(.bottom, 16) ForEach(Array(timelineEvents.enumerated()), id: \.offset) { i, event in TimelineEventRow(event: event, isLast: i == timelineEvents.count - 1, isExpanded: expandedId == event.id) { withAnimation(.easeInOut(duration: 0.25)) { expandedId = expandedId == event.id ? nil : event.id } } } } .padding(20) .background(RoundedRectangle(cornerRadius: 14).fill(Color(red: 0.031, green: 0.039, blue: 0.071)) .overlay(RoundedRectangle(cornerRadius: 14).stroke(VelocityTheme.borderAccent, lineWidth: 1))) } } } private struct TimelineEventRow: View { let event: OracleTimelineEvent let isLast: Bool let isExpanded: Bool let onTap: () -> Void var body: some View { HStack(alignment: .top, spacing: 14) { VStack(spacing: 0) { Circle().fill(VelocityTheme.accent).frame(width: 10, height: 10) .overlay(Circle().stroke(VelocityTheme.background, lineWidth: 2)) if !isLast { Rectangle() .fill(LinearGradient(colors: [VelocityTheme.accent.opacity(0.5), .clear], startPoint: .top, endPoint: .bottom)) .frame(width: 2) .frame(height: isExpanded ? 100 : 50) .animation(.easeInOut(duration: 0.25), value: isExpanded) } } VStack(alignment: .leading, spacing: 6) { HStack { Text(event.badge).font(.system(size: 9, weight: .bold)).tracking(1.2) .foregroundStyle(VelocityTheme.accent) .padding(.horizontal, 7).padding(.vertical, 3) .background(RoundedRectangle(cornerRadius: 4).fill(VelocityTheme.accent.opacity(0.15)) .overlay(RoundedRectangle(cornerRadius: 4).stroke(VelocityTheme.accent.opacity(0.25), lineWidth: 1))) Spacer() Text(event.when).font(.system(size: 10)).foregroundStyle(VelocityTheme.mutedFg) Image(systemName: isExpanded ? "chevron.up" : "chevron.down") .font(.system(size: 9)).foregroundStyle(VelocityTheme.mutedFg) } Text(event.summary).font(.system(size: 13, weight: .medium)).foregroundStyle(VelocityTheme.foreground) if isExpanded { Text(event.detail).font(.system(size: 12)).foregroundStyle(VelocityTheme.mutedFg) .padding(.top, 4) .transition(.opacity.combined(with: .move(edge: .top))) } } .padding(12) .background(RoundedRectangle(cornerRadius: 10) .fill(isExpanded ? VelocityTheme.accent.opacity(0.06) : Color.white.opacity(0.04)) .overlay(RoundedRectangle(cornerRadius: 10) .stroke(isExpanded ? VelocityTheme.accent.opacity(0.2) : Color.white.opacity(0.06), lineWidth: 1))) .onTapGesture { onTap() } .padding(.bottom, 8) } } } private struct InfoMini: View { let label: String; let value: String var body: some View { VStack(alignment: .leading, spacing: 2) { Text(label).font(.system(size: 9)).tracking(0.8).foregroundStyle(VelocityTheme.mutedFg) Text(value).font(.system(size: 12, weight: .semibold)).foregroundStyle(VelocityTheme.accent) } .padding(10) .background(RoundedRectangle(cornerRadius: 8).fill(Color.white.opacity(0.04)) .overlay(RoundedRectangle(cornerRadius: 8).stroke(Color.white.opacity(0.06), lineWidth: 1))) } } // MARK: – Lead Map Canvas private struct LeadMapCanvas: View { let onSelectRegion: (RegionPin) -> Void private let cols = [GridItem(.adaptive(minimum: 140), spacing: 10)] var body: some View { VStack(alignment: .leading, spacing: 14) { HStack(spacing: 16) { LegendDot(color: VelocityTheme.danger, label: "Hot Lead") LegendDot(color: Color(red: 0.13, green: 0.83, blue: 0.93), label: "Warm Lead") LegendDot(color: VelocityTheme.mutedFg, label: "Cold Lead") Spacer() } LazyVGrid(columns: cols, spacing: 10) { ForEach(mapPins) { pin in TappableRegionPin(pin: pin, onTap: { onSelectRegion(pin) }) } } } .padding(20) .background(RoundedRectangle(cornerRadius: 14).fill(Color(red: 0.031, green: 0.039, blue: 0.071)) .overlay(RoundedRectangle(cornerRadius: 14).stroke(VelocityTheme.borderAccent, lineWidth: 1))) } } private struct TappableRegionPin: View { let pin: RegionPin let onTap: () -> Void @State private var pressed = false private var pinColor: Color { pin.temp == "hot" ? VelocityTheme.danger : pin.temp == "warm" ? Color(red: 0.13, green: 0.83, blue: 0.93) : VelocityTheme.mutedFg } var body: some View { HStack(spacing: 10) { Text(pin.country).font(.system(size: 24)) VStack(alignment: .leading, spacing: 2) { Text(pin.label).font(.system(size: 12, weight: .semibold)).foregroundStyle(VelocityTheme.foreground) HStack(spacing: 4) { Circle().fill(pinColor).frame(width: 6, height: 6).shadow(color: pinColor.opacity(0.8), radius: 3) Text("\(pin.count) leads").font(.system(size: 10)).foregroundStyle(VelocityTheme.mutedFg) } } Spacer() Image(systemName: "arrow.up.right.circle") .font(.system(size: 13)).foregroundStyle(VelocityTheme.mutedFg) } .padding(12) .background(RoundedRectangle(cornerRadius: 10) .fill(pressed ? pinColor.opacity(0.12) : Color(red: 0.031, green: 0.039, blue: 0.071)) .overlay(RoundedRectangle(cornerRadius: 10).stroke(pinColor.opacity(pressed ? 0.5 : 0.25), lineWidth: 1))) .scaleEffect(pressed ? 0.97 : 1.0) .animation(.easeInOut(duration: 0.12), value: pressed) .onTapGesture { pressed = true DispatchQueue.main.asyncAfter(deadline: .now() + 0.12) { pressed = false } onTap() } } } private struct LegendDot: View { let color: Color; let label: String var body: some View { HStack(spacing: 6) { Circle().fill(color).frame(width: 8, height: 8).shadow(color: color.opacity(0.8), radius: 3) Text(label).font(.system(size: 11)).foregroundStyle(VelocityTheme.mutedFg) } } } // MARK: – Region Detail Sheet private struct RegionDetailSheet: View { let pin: RegionPin @Environment(\.dismiss) private var dismiss private var pinColor: Color { pin.temp == "hot" ? VelocityTheme.danger : pin.temp == "warm" ? Color(red: 0.13, green: 0.83, blue: 0.93) : VelocityTheme.mutedFg } var body: some View { NavigationStack { VStack(alignment: .leading, spacing: 20) { HStack(spacing: 16) { Text(pin.country).font(.system(size: 52)) VStack(alignment: .leading, spacing: 4) { Text(pin.label).font(.system(size: 22, weight: .bold)).foregroundStyle(VelocityTheme.foreground) HStack(spacing: 6) { Circle().fill(pinColor).frame(width: 7, height: 7) Text(pin.temp.capitalized + " Market") .font(.system(size: 12, weight: .medium)).foregroundStyle(pinColor) } } } .padding(.top, 8) Divider().background(VelocityTheme.borderSubtle) LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 14) { DetailField(label: "Active Leads", value: "\(pin.count)") DetailField(label: "Top Lead", value: pin.topLead) DetailField(label: "Temperature", value: pin.temp.capitalized) DetailField(label: "Priority", value: pin.temp == "hot" ? "High 🔴" : pin.temp == "warm" ? "Medium 🟡" : "Low ⚪") } Spacer() } .padding(24) .background(VelocityTheme.background.ignoresSafeArea()) .navigationTitle("Region Details") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button("Done") { dismiss() }.foregroundStyle(VelocityTheme.accent) } } } } } // MARK: – Calendar Canvas private struct CalendarCanvas: View { let onSchedule: (CalTask) -> Void let days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] var body: some View { VStack(spacing: 14) { weekPanel tasksPanel } } private var weekPanel: some View { VStack(alignment: .leading, spacing: 12) { Text("Weekly Schedule").font(.system(size: 13, weight: .semibold)).foregroundStyle(VelocityTheme.foreground) HStack(spacing: 6) { ForEach(days, id: \.self) { day in Text(day).font(.system(size: 10, weight: .medium)).tracking(0.5) .foregroundStyle(VelocityTheme.mutedFg).frame(maxWidth: .infinity) } } HStack(spacing: 6) { ForEach(Array(days.enumerated()), id: \.offset) { i, _ in RoundedRectangle(cornerRadius: 8) .fill(i == 2 ? VelocityTheme.accent.opacity(0.15) : Color.white.opacity(0.03)) .overlay(RoundedRectangle(cornerRadius: 8) .stroke(i == 2 ? VelocityTheme.accent.opacity(0.3) : Color.white.opacity(0.05), lineWidth: 1)) .frame(height: 60) } } } .padding(16) .background(RoundedRectangle(cornerRadius: 14).fill(Color(red: 0.031, green: 0.039, blue: 0.071)) .overlay(RoundedRectangle(cornerRadius: 14).stroke(VelocityTheme.borderAccent, lineWidth: 1))) } private var tasksPanel: some View { VStack(alignment: .leading, spacing: 10) { HStack(spacing: 5) { Circle().fill(Color(red: 0.13, green: 0.83, blue: 0.93)).frame(width: 6, height: 6) Text("Tasks & Actions").font(.system(size: 13, weight: .semibold)).foregroundStyle(VelocityTheme.foreground) } .padding(.bottom, 4) ForEach(calTasks) { task in CalTaskRow(task: task, onSchedule: { onSchedule(task) }) } } .padding(16) .background(RoundedRectangle(cornerRadius: 14).fill(Color(red: 0.031, green: 0.039, blue: 0.071)) .overlay(RoundedRectangle(cornerRadius: 14).stroke(VelocityTheme.borderAccent, lineWidth: 1))) } } private struct CalTaskRow: View { let task: CalTask let onSchedule: () -> Void @State private var scheduled = false var body: some View { VStack(alignment: .leading, spacing: 6) { HStack { Text(task.title).font(.system(size: 13, weight: .semibold)).foregroundStyle(VelocityTheme.foreground) Spacer() Text(scheduled ? "Scheduled ✓" : "Action") .font(.system(size: 9, weight: .semibold)) .foregroundStyle(scheduled ? VelocityTheme.success : VelocityTheme.mutedFg) .padding(.horizontal, 6).padding(.vertical, 3) .background(RoundedRectangle(cornerRadius: 4) .fill(scheduled ? VelocityTheme.success.opacity(0.12) : Color.white.opacity(0.06))) } Text(task.subtitle).font(.system(size: 11)).foregroundStyle(VelocityTheme.mutedFg).lineLimit(2) HStack { Image(systemName: "clock").font(.system(size: 10)).foregroundStyle(VelocityTheme.accent) Text(task.due).font(.system(size: 11)).foregroundStyle(VelocityTheme.accent) Spacer() Button { onSchedule() withAnimation(.easeInOut(duration: 0.3)) { scheduled = true } } label: { HStack(spacing: 5) { Image(systemName: scheduled ? "checkmark" : "calendar.badge.plus") .font(.system(size: 10, weight: .semibold)) Text(scheduled ? "Scheduled" : "Schedule") .font(.system(size: 11, weight: .semibold)) } .foregroundStyle(.white) .padding(.horizontal, 12).padding(.vertical, 5) .background(RoundedRectangle(cornerRadius: 7) .fill(scheduled ? VelocityTheme.success : VelocityTheme.accent) .shadow(color: (scheduled ? VelocityTheme.success : VelocityTheme.accent).opacity(0.4), radius: 6)) } } } .padding(12) .background(RoundedRectangle(cornerRadius: 10) .fill(scheduled ? VelocityTheme.success.opacity(0.05) : Color.white.opacity(0.04)) .overlay(RoundedRectangle(cornerRadius: 10) .stroke(scheduled ? VelocityTheme.success.opacity(0.2) : Color.white.opacity(0.06), lineWidth: 1))) } }