1→import SwiftUI 2→import Charts 3→ 4→struct DashboardView: View { 5→ @EnvironmentObject var viewModel: DashboardViewModel 6→ 7→ var body: some View { 8→ ScrollView { 9→ VStack(spacing: 20) { 10→ // Header 11→ HStack { 12→ Text("Overview") 13→ .font(.title2) 14→ .fontWeight(.semibold) 15→ 16→ Spacer() 17→ 18→ if let lastUpdate = viewModel.lastUpdate { 19→ Label(lastUpdate.toFullBRString(), systemImage: "clock") 20→ .font(.caption) 21→ .foregroundStyle(.secondary) 22→ } 23→ } 24→ .padding(.horizontal) 25→ 26→ // Main Cards 27→ LazyVGrid(columns: [ 28→ GridItem(.flexible(), spacing: 16), 29→ GridItem(.flexible(), spacing: 16), 30→ GridItem(.flexible(), spacing: 16), 31→ GridItem(.flexible(), spacing: 16) 32→ ], spacing: 16) { 33→ // USD Card 34→ CurrencyCard( 35→ title: "Dólar (USD)", 36→ symbol: "🇺🇸", 37→ value: viewModel.dollarQuote?.sellRate ?? 0, 38→ change: viewModel.dollarChange, 39→ subtitle: viewModel.dollarQuote?.formattedTime ?? "--" 40→ ) 41→ 42→ // EUR Card 43→ CurrencyCard( 44→ title: "Euro (EUR)", 45→ symbol: "🇪🇺", 46→ value: viewModel.euroQuote?.sellRate ?? 0, 47→ change: 0, 48→ subtitle: viewModel.euroQuote?.formattedTime ?? "--" 49→ ) 50→ 51→ // SELIC Card 52→ RateCard( 53→ title: "SELIC Meta", 54→ icon: "percent", 55→ iconColor: .bcbPurple, 56→ value: viewModel.selicTarget?.numericValue ?? 0, 57→ unit: "% a.a.", 58→ subtitle: viewModel.selicTarget?.date ?? "--" 59→ ) 60→ 61→ // IPCA Card 62→ RateCard( 63→ title: "IPCA 12m", 64→ icon: "chart.line.uptrend.xyaxis", 65→ iconColor: .bcbYellow, 66→ value: viewModel.ipca12m?.numericValue ?? 0, 67→ unit: "%", 68→ subtitle: viewModel.ipca12m?.date ?? "--" 69→ ) 70→ } 71→ .padding(.horizontal) 72→ 73→ // Secondary Row 74→ LazyVGrid(columns: [ 75→ GridItem(.flexible(), spacing: 16), 76→ GridItem(.flexible(), spacing: 16), 77→ GridItem(.flexible(), spacing: 16) 78→ ], spacing: 16) { 79→ // CDI Card 80→ RateCard( 81→ title: "CDI", 82→ icon: "banknote", 83→ iconColor: .bcbPink, 84→ value: viewModel.cdi?.numericValue ?? 0, 85→ unit: "% a.a.", 86→ subtitle: viewModel.cdi?.date ?? "--" 87→ ) 88→ 89→ // IPCA Mensal 90→ RateCard( 91→ title: "IPCA Mensal", 92→ icon: "calendar", 93→ iconColor: .bcbYellow, 94→ value: viewModel.ipcaMonthly?.numericValue ?? 0, 95→ unit: "%", 96→ subtitle: viewModel.ipcaMonthly?.date ?? "--" 97→ ) 98→ 99→ // Juro Real 100→ RateCard( 101→ title: "Juro Real", 102→ icon: "arrow.up.arrow.down", 103→ iconColor: .bcbGreen, 104→ value: viewModel.realInterestRate, 105→ unit: "% a.a.", 106→ subtitle: "SELIC - IPCA" 107→ ) 108→ } 109→ .padding(.horizontal) 110→ 111→ // Focus Market Expectations Section 112→ VStack(alignment: .leading, spacing: 12) { 113→ HStack { 114→ Image(systemName: "chart.line.uptrend.xyaxis") 115→ .foregroundStyle(Color.bcbBlue) 116→ Text("Expectativas de Mercado (Focus)") 117→ .font(.headline) 118→ 119→ Spacer() 120→ 121→ if viewModel.focusLoading { 122→ ProgressView() 123→ .controlSize(.small) 124→ } 125→ } 126→ 127→ LazyVGrid(columns: [ 128→ GridItem(.flexible(), spacing: 16), 129→ GridItem(.flexible(), spacing: 16), 130→ GridItem(.flexible(), spacing: 16), 131→ GridItem(.flexible(), spacing: 16), 132→ GridItem(.flexible(), spacing: 16) 133→ ], spacing: 16) { 134→ // SELIC Expected 135→ FocusExpectationCard( 136→ title: "SELIC Esperada", 137→ value: viewModel.focusStats.latestSelic.map { String(format: "%.2f%%", $0.median) } ?? "--", 138→ subtitle: viewModel.focusStats.latestSelic?.formattedMeeting ?? "Próxima reunião", 139→ icon: "percent", 140→ color: .bcbPurple 141→ ) 142→ 143→ // IPCA Expected 144→ FocusExpectationCard( 145→ title: "IPCA 12m", 146→ value: viewModel.focusStats.latestIPCA12m.map { String(format: "%.2f%%", $0.median) } ?? "--", 147→ subtitle: "Expectativa", 148→ icon: "chart.bar.fill", 149→ color: .bcbYellow 150→ ) 151→ 152→ // IGP-M Expected 153→ FocusExpectationCard( 154→ title: "IGP-M 12m", 155→ value: viewModel.focusStats.latestIGPM12m.map { String(format: "%.2f%%", $0.median) } ?? "--", 156→ subtitle: "Expectativa", 157→ icon: "building.2.fill", 158→ color: .bcbPink 159→ ) 160→ 161→ // PIB Expected 162→ FocusExpectationCard( 163→ title: "PIB \(Calendar.current.component(.year, from: Date()))", 164→ value: viewModel.focusStats.latestPIBYear.map { String(format: "%.2f%%", $0.median) } ?? "--", 165→ subtitle: "Crescimento", 166→ icon: "arrow.up.right", 167→ color: .bcbGreen 168→ ) 169→ 170→ // Câmbio Expected 171→ FocusExpectationCard( 172→ title: "USD/BRL", 173→ value: { 174→ let currentYear = Calendar.current.component(.year, from: Date()) 175→ if let cambio = viewModel.focusStats.annualCambio.first(where: { $0.referenceYear == "\(currentYear)" && $0.baseCalculo == 0 }) { 176→ return String(format: "R$ %.2f", cambio.median) 177→ } 178→ return "--" 179→ }(), 180→ subtitle: "Fim do ano", 181→ icon: "dollarsign.circle.fill", 182→ color: .bcbBlue 183→ ) 184→ } 185→ } 186→ .padding() 187→ .background(Color(NSColor.controlBackgroundColor)) 188→ .clipShape(RoundedRectangle(cornerRadius: 12)) 189→ .padding(.horizontal) 190→ 191→ // Charts Section 192→ HStack(spacing: 16) { 193→ // Dollar Chart 194→ VStack(alignment: .leading, spacing: 12) { 195→ Text("Dólar - Últimos 30 dias") 196→ .font(.headline) 197→ 198→ if !viewModel.dollarHistory.isEmpty { 199→ DollarChartView(data: viewModel.dollarHistory) 200→ .frame(height: 200) 201→ } else { 202→ ProgressView() 203→ .frame(height: 200) 204→ } 205→ } 206→ .cardStyle() 207→ 208→ // IPCA Chart 209→ VStack(alignment: .leading, spacing: 12) { 210→ Text("IPCA - Últimos 12 meses") 211→ .font(.headline) 212→ 213→ if !viewModel.ipcaHistory.isEmpty { 214→ IPCAChartView(data: viewModel.ipcaHistory) 215→ .frame(height: 200) 216→ } else { 217→ ProgressView() 218→ .frame(height: 200) 219→ } 220→ } 221→ .cardStyle() 222→ } 223→ .padding(.horizontal) 224→ 225→ // History Table 226→ VStack(alignment: .leading, spacing: 12) { 227→ Text("Histórico do Dólar") 228→ .font(.headline) 229→ 230→ DollarHistoryTable(data: Array(viewModel.dollarHistory.suffix(10).reversed())) 231→ } 232→ .cardStyle() 233→ .padding(.horizontal) 234→ 235→ Spacer(minLength: 20) 236→ } 237→ .padding(.vertical) 238→ } 239→ .background(Color(NSColor.windowBackgroundColor)) 240→ .task { 241→ // Load Focus data if not already loaded 242→ if viewModel.focusStats.selicExpectations.isEmpty { 243→ await viewModel.loadFocusStats() 244→ } 245→ } 246→ } 247→} 248→ 249→// MARK: - Focus Expectation Card 250→ 251→struct FocusExpectationCard: View { 252→ let title: String 253→ let value: String 254→ let subtitle: String 255→ let icon: String 256→ let color: Color 257→ 258→ var body: some View { 259→ VStack(alignment: .leading, spacing: 8) { 260→ HStack { 261→ Image(systemName: icon) 262→ .font(.system(size: 14)) 263→ .foregroundStyle(color) 264→ 265→ Text(title) 266→ .font(.caption) 267→ .foregroundStyle(.secondary) 268→ .lineLimit(1) 269→ } 270→ 271→ Text(value) 272→ .font(.system(size: 18, weight: .bold, design: .rounded)) 273→ .foregroundStyle(color) 274→ 275→ Text(subtitle) 276→ .font(.caption2) 277→ .foregroundStyle(.tertiary) 278→ .lineLimit(1) 279→ } 280→ .frame(maxWidth: .infinity, alignment: .leading) 281→ .padding(12) 282→ .background(color.opacity(0.1)) 283→ .clipShape(RoundedRectangle(cornerRadius: 10)) 284→ } 285→} 286→ 287→// MARK: - Currency Card 288→ 289→struct CurrencyCard: View { 290→ let title: String 291→ let symbol: String 292→ let value: Double 293→ let change: Double 294→ let subtitle: String 295→ 296→ var body: some View { 297→ VStack(alignment: .leading, spacing: 12) { 298→ HStack { 299→ Text(symbol) 300→ .font(.title2) 301→ 302→ VStack(alignment: .leading, spacing: 2) { 303→ Text(title) 304→ .font(.caption) 305→ .foregroundStyle(.secondary) 306→ } 307→ 308→ Spacer() 309→ } 310→ 311→ Text(value.toBRL()) 312→ .font(.system(size: 24, weight: .bold, design: .rounded)) 313→ .foregroundStyle(.primary) 314→ 315→ HStack { 316→ if change != 0 { 317→ Label( 318→ String(format: "%@%.2f%%", change >= 0 ? "+" : "", change), 319→ systemImage: change >= 0 ? "arrow.up.right" : "arrow.down.right" 320→ ) 321→ .font(.caption) 322→ .foregroundStyle(change >= 0 ? Color.bcbGreen : Color.bcbRed) 323→ } 324→ 325→ Spacer() 326→ 327→ Text(subtitle) 328→ .font(.caption2) 329→ .foregroundStyle(.tertiary) 330→ } 331→ } 332→ .cardStyle() 333→ } 334→} 335→ 336→// MARK: - Rate Card 337→ 338→struct RateCard: View { 339→ let title: String 340→ let icon: String 341→ let iconColor: Color 342→ let value: Double 343→ let unit: String 344→ let subtitle: String 345→ 346→ var body: some View { 347→ VStack(alignment: .leading, spacing: 12) { 348→ HStack { 349→ Image(systemName: icon) 350→ .font(.title2) 351→ .foregroundStyle(iconColor) 352→ 353→ Text(title) 354→ .font(.caption) 355→ .foregroundStyle(.secondary) 356→ 357→ Spacer() 358→ } 359→ 360→ HStack(alignment: .firstTextBaseline, spacing: 4) { 361→ Text(String(format: "%.2f", value)) 362→ .font(.system(size: 24, weight: .bold, design: .rounded)) 363→ 364→ Text(unit) 365→ .font(.caption) 366→ .foregroundStyle(.secondary) 367→ } 368→ 369→ Text(subtitle) 370→ .font(.caption2) 371→ .foregroundStyle(.tertiary) 372→ } 373→ .cardStyle() 374→ } 375→} 376→ 377→// MARK: - Charts 378→ 379→struct DollarChartView: View { 380→ let data: [DollarQuote] 381→ 382→ var body: some View { 383→ Chart(data) { quote in 384→ if let date = quote.dateTime { 385→ LineMark( 386→ x: .value("Data", date), 387→ y: .value("Cotação", quote.sellRate) 388→ ) 389→ .foregroundStyle(Color.bcbGreen) 390→ 391→ AreaMark( 392→ x: .value("Data", date), 393→ y: .value("Cotação", quote.sellRate) 394→ ) 395→ .foregroundStyle( 396→ LinearGradient( 397→ colors: [Color.bcbGreen.opacity(0.3), Color.bcbGreen.opacity(0.0)], 398→ startPoint: .top, 399→ endPoint: .bottom 400→ ) 401→ ) 402→ } 403→ } 404→ .chartYScale(domain: .automatic(includesZero: false)) 405→ .chartXAxis { 406→ AxisMarks(values: .automatic(desiredCount: 6)) { value in 407→ AxisGridLine() 408→ AxisValueLabel(format: .dateTime.day().month()) 409→ } 410→ } 411→ } 412→} 413→ 414→struct IPCAChartView: View { 415→ let data: [TimeSeriesValue] 416→ 417→ var body: some View { 418→ Chart(data) { item in 419→ BarMark( 420→ x: .value("Mês", item.date), 421→ y: .value("IPCA", item.numericValue) 422→ ) 423→ .foregroundStyle(item.numericValue >= 0 ? Color.bcbYellow : Color.bcbRed) 424→ } 425→ .chartXAxis { 426→ AxisMarks(values: .automatic(desiredCount: 6)) { _ in 427→ AxisGridLine() 428→ AxisValueLabel() 429→ } 430→ } 431→ } 432→} 433→ 434→// MARK: - History Table 435→ 436→struct DollarHistoryTable: View { 437→ let data: [DollarQuote] 438→ 439→ var body: some View { 440→ VStack(spacing: 0) { 441→ // Header 442→ HStack { 443→ Text("Data") 444→ .frame(maxWidth: .infinity, alignment: .leading) 445→ Text("Compra") 446→ .frame(maxWidth: .infinity, alignment: .trailing) 447→ Text("Venda") 448→ .frame(maxWidth: .infinity, alignment: .trailing) 449→ Text("Horário") 450→ .frame(maxWidth: .infinity, alignment: .trailing) 451→ } 452→ .font(.caption) 453→ .foregroundStyle(.secondary) 454→ .padding(.vertical, 8) 455→ 456→ Divider() 457→ 458→ // Rows 459→ ForEach(data) { quote in 460→ HStack { 461→ Text(quote.formattedDate) 462→ .frame(maxWidth: .infinity, alignment: .leading) 463→ Text(quote.buyRate.toBRL()) 464→ .frame(maxWidth: .infinity, alignment: .trailing) 465→ Text(quote.sellRate.toBRL()) 466→ .frame(maxWidth: .infinity, alignment: .trailing) 467→ Text(quote.formattedTime) 468→ .frame(maxWidth: .infinity, alignment: .trailing) 469→ } 470→ .font(.system(.body, design: .monospaced)) 471→ .padding(.vertical, 6) 472→ 473→ if quote.id != data.last?.id { 474→ Divider() 475→ } 476→ } 477→ } 478→ } 479→} 480→ 481→#Preview { 482→ DashboardView() 483→ .environmentObject(DashboardViewModel()) 484→ .frame(width: 1200, height: 800) 485→} 486→ Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.