1→import SwiftUI 2→ 3→struct InstitutionsView: View { 4→ @EnvironmentObject var viewModel: DashboardViewModel 5→ @State private var selectedType: InstitutionType = .banks 6→ @State private var searchText = "" 7→ 8→ var body: some View { 9→ VStack(spacing: 0) { 10→ // Header with stats 11→ InstitutionStatsHeader(stats: viewModel.institutionStats, isLoading: viewModel.institutionsLoading) 12→ 13→ Divider() 14→ 15→ // Tab selector 16→ HStack(spacing: 0) { 17→ ForEach(InstitutionType.allCases) { type in 18→ TypeTabButton( 19→ type: type, 20→ count: countFor(type), 21→ isSelected: selectedType == type 22→ ) { 23→ withAnimation(.easeInOut(duration: 0.2)) { 24→ selectedType = type 25→ } 26→ } 27→ } 28→ } 29→ .padding(.horizontal) 30→ .padding(.vertical, 8) 31→ 32→ Divider() 33→ 34→ // Search bar 35→ HStack { 36→ Image(systemName: "magnifyingglass") 37→ .foregroundStyle(.secondary) 38→ 39→ TextField("Buscar por nome, CNPJ ou cidade...", text: $searchText) 40→ .textFieldStyle(.plain) 41→ 42→ if !searchText.isEmpty { 43→ Button { 44→ searchText = "" 45→ } label: { 46→ Image(systemName: "xmark.circle.fill") 47→ .foregroundStyle(.secondary) 48→ } 49→ .buttonStyle(.plain) 50→ } 51→ } 52→ .padding(10) 53→ .background(Color(NSColor.controlBackgroundColor)) 54→ .clipShape(RoundedRectangle(cornerRadius: 8)) 55→ .padding() 56→ 57→ // Content based on selected type 58→ Group { 59→ switch selectedType { 60→ case .banks: 61→ BanksListView(banks: filteredBanks, searchText: searchText) 62→ case .cooperatives: 63→ CooperativesListView(cooperatives: filteredCooperatives, searchText: searchText) 64→ case .consortia: 65→ ConsortiaListView(consortia: filteredConsortia, searchText: searchText) 66→ case .sociedades: 67→ SociedadesListView(sociedades: filteredSociedades, searchText: searchText) 68→ } 69→ } 70→ } 71→ .task { 72→ if viewModel.banks.isEmpty { 73→ await viewModel.loadInstitutions() 74→ } 75→ } 76→ } 77→ 78→ // MARK: - Filtered Data 79→ 80→ var filteredBanks: [Bank] { 81→ guard !searchText.isEmpty else { return viewModel.banks } 82→ return viewModel.banks.filter { matchesSearch($0.name, $0.cnpj, $0.city, searchText) } 83→ } 84→ 85→ var filteredCooperatives: [Cooperative] { 86→ guard !searchText.isEmpty else { return viewModel.cooperatives } 87→ return viewModel.cooperatives.filter { matchesSearch($0.name, $0.cnpj, $0.city, searchText) } 88→ } 89→ 90→ var filteredConsortia: [Consortium] { 91→ guard !searchText.isEmpty else { return viewModel.consortia } 92→ return viewModel.consortia.filter { matchesSearch($0.name, $0.cnpj, $0.city, searchText) } 93→ } 94→ 95→ var filteredSociedades: [Sociedade] { 96→ guard !searchText.isEmpty else { return viewModel.sociedades } 97→ return viewModel.sociedades.filter { matchesSearch($0.name, $0.cnpj, $0.city, searchText) } 98→ } 99→ 100→ func matchesSearch(_ name: String, _ cnpj: String, _ city: String?, _ query: String) -> Bool { 101→ name.localizedCaseInsensitiveContains(query) || 102→ cnpj.contains(query) || 103→ (city?.localizedCaseInsensitiveContains(query) ?? false) 104→ } 105→ 106→ func countFor(_ type: InstitutionType) -> Int { 107→ switch type { 108→ case .banks: return viewModel.banks.count 109→ case .cooperatives: return viewModel.cooperatives.count 110→ case .consortia: return viewModel.consortia.count 111→ case .sociedades: return viewModel.sociedades.count 112→ } 113→ } 114→} 115→ 116→// MARK: - Stats Header 117→ 118→struct InstitutionStatsHeader: View { 119→ let stats: InstitutionStats 120→ let isLoading: Bool 121→ 122→ var body: some View { 123→ HStack(spacing: 24) { 124→ StatBadge( 125→ title: "Total", 126→ value: "\(stats.total)", 127→ icon: "building.2.fill", 128→ color: .blue 129→ ) 130→ 131→ Divider() 132→ .frame(height: 40) 133→ 134→ StatBadge( 135→ title: "Bancos", 136→ value: "\(stats.totalBanks)", 137→ icon: "building.columns.fill", 138→ color: Color(hex: "#3B82F6") ?? .blue 139→ ) 140→ 141→ StatBadge( 142→ title: "Cooperativas", 143→ value: "\(stats.totalCooperatives)", 144→ icon: "person.3.fill", 145→ color: Color(hex: "#10B981") ?? .green 146→ ) 147→ 148→ StatBadge( 149→ title: "Consórcios", 150→ value: "\(stats.totalConsortia)", 151→ icon: "person.2.fill", 152→ color: Color(hex: "#F59E0B") ?? .orange 153→ ) 154→ 155→ StatBadge( 156→ title: "Sociedades", 157→ value: "\(stats.totalSociedades)", 158→ icon: "building.2.fill", 159→ color: Color(hex: "#8B5CF6") ?? .purple 160→ ) 161→ 162→ Spacer() 163→ 164→ if isLoading { 165→ ProgressView() 166→ .controlSize(.small) 167→ } 168→ } 169→ .padding() 170→ .background(Color(NSColor.windowBackgroundColor)) 171→ } 172→} 173→ 174→struct StatBadge: View { 175→ let title: String 176→ let value: String 177→ let icon: String 178→ let color: Color 179→ 180→ var body: some View { 181→ HStack(spacing: 8) { 182→ Image(systemName: icon) 183→ .font(.title3) 184→ .foregroundStyle(color) 185→ 186→ VStack(alignment: .leading, spacing: 2) { 187→ Text(value) 188→ .font(.headline) 189→ .monospacedDigit() 190→ 191→ Text(title) 192→ .font(.caption) 193→ .foregroundStyle(.secondary) 194→ } 195→ } 196→ } 197→} 198→ 199→// MARK: - Tab Button 200→ 201→struct TypeTabButton: View { 202→ let type: InstitutionType 203→ let count: Int 204→ let isSelected: Bool 205→ let action: () -> Void 206→ 207→ var body: some View { 208→ Button(action: action) { 209→ HStack(spacing: 8) { 210→ Image(systemName: type.icon) 211→ .font(.subheadline) 212→ 213→ Text(type.rawValue) 214→ .font(.subheadline) 215→ .fontWeight(isSelected ? .semibold : .regular) 216→ 217→ Text("\(count)") 218→ .font(.caption) 219→ .padding(.horizontal, 6) 220→ .padding(.vertical, 2) 221→ .background(isSelected ? Color.white.opacity(0.2) : Color.secondary.opacity(0.2)) 222→ .clipShape(Capsule()) 223→ } 224→ .padding(.horizontal, 16) 225→ .padding(.vertical, 8) 226→ .background(isSelected ? Color(hex: type.color) ?? .blue : Color.clear) 227→ .foregroundStyle(isSelected ? .white : .primary) 228→ .clipShape(RoundedRectangle(cornerRadius: 8)) 229→ } 230→ .buttonStyle(.plain) 231→ } 232→} 233→ 234→// MARK: - Banks List 235→ 236→struct BanksListView: View { 237→ let banks: [Bank] 238→ let searchText: String 239→ @State private var selectedBank: Bank? 240→ 241→ var body: some View { 242→ HSplitView { 243→ List(banks, selection: $selectedBank) { bank in 244→ BankRow(bank: bank) 245→ .tag(bank) 246→ } 247→ .listStyle(.inset) 248→ .frame(minWidth: 350, maxWidth: 450) 249→ 250→ if let bank = selectedBank { 251→ BankDetailView(bank: bank) 252→ } else { 253→ EmptyDetailView(message: "Selecione um banco") 254→ } 255→ } 256→ } 257→} 258→ 259→struct BankRow: View { 260→ let bank: Bank 261→ 262→ var body: some View { 263→ HStack(spacing: 12) { 264→ Image(systemName: "building.columns.fill") 265→ .font(.title3) 266→ .foregroundStyle(Color(hex: "#3B82F6") ?? .blue) 267→ .frame(width: 32, height: 32) 268→ .background(Color(hex: "#3B82F6")?.opacity(0.1) ?? Color.blue.opacity(0.1)) 269→ .clipShape(RoundedRectangle(cornerRadius: 6)) 270→ 271→ VStack(alignment: .leading, spacing: 2) { 272→ Text(bank.displayName) 273→ .font(.headline) 274→ .lineLimit(1) 275→ 276→ Text(bank.segment ?? "Banco") 277→ .font(.caption) 278→ .foregroundStyle(.secondary) 279→ .lineLimit(1) 280→ } 281→ 282→ Spacer() 283→ 284→ if let state = bank.state { 285→ Text(state) 286→ .font(.caption) 287→ .foregroundStyle(.secondary) 288→ } 289→ } 290→ .padding(.vertical, 4) 291→ } 292→} 293→ 294→struct BankDetailView: View { 295→ let bank: Bank 296→ 297→ var body: some View { 298→ ScrollView { 299→ VStack(alignment: .leading, spacing: 24) { 300→ // Header 301→ DetailHeader( 302→ name: bank.name, 303→ subtitle: bank.segment, 304→ icon: "building.columns.fill", 305→ color: "#3B82F6" 306→ ) 307→ 308→ // Info Cards 309→ LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 16) { 310→ InfoCard(title: "CNPJ", value: bank.formattedCNPJ, icon: "number") 311→ InfoCard(title: "Segmento", value: bank.segment ?? "--", icon: "tag") 312→ InfoCard(title: "Cidade", value: bank.city ?? "--", icon: "mappin") 313→ InfoCard(title: "Estado", value: bank.state ?? "--", icon: "map") 314→ InfoCard(title: "Telefone", value: bank.formattedPhone, icon: "phone") 315→ InfoCard(title: "Carteira Comercial", value: bank.hasCommercialPortfolio ?? "--", icon: "creditcard") 316→ } 317→ .padding(.horizontal) 318→ 319→ // Address 320→ if !bank.fullAddress.isEmpty { 321→ VStack(alignment: .leading, spacing: 8) { 322→ Label("Endereço", systemImage: "location") 323→ .font(.headline) 324→ .foregroundStyle(.secondary) 325→ 326→ Text(bank.fullAddress) 327→ .font(.body) 328→ } 329→ .padding() 330→ .frame(maxWidth: .infinity, alignment: .leading) 331→ .background(Color(NSColor.controlBackgroundColor)) 332→ .clipShape(RoundedRectangle(cornerRadius: 10)) 333→ .padding(.horizontal) 334→ } 335→ 336→ // Contact 337→ if bank.email != nil || bank.website != nil { 338→ VStack(alignment: .leading, spacing: 12) { 339→ Label("Contato", systemImage: "envelope") 340→ .font(.headline) 341→ .foregroundStyle(.secondary) 342→ 343→ if let email = bank.email { 344→ HStack { 345→ Image(systemName: "envelope") 346→ .foregroundStyle(.secondary) 347→ Text(email.lowercased()) 348→ .font(.body) 349→ } 350→ } 351→ 352→ if let website = bank.website { 353→ HStack { 354→ Image(systemName: "globe") 355→ .foregroundStyle(.secondary) 356→ Link(website.lowercased(), destination: URL(string: "https://\(website.lowercased())") ?? URL(string: "https://example.com")!) 357→ .font(.body) 358→ } 359→ } 360→ } 361→ .padding() 362→ .frame(maxWidth: .infinity, alignment: .leading) 363→ .background(Color(NSColor.controlBackgroundColor)) 364→ .clipShape(RoundedRectangle(cornerRadius: 10)) 365→ .padding(.horizontal) 366→ } 367→ 368→ Spacer() 369→ } 370→ } 371→ .background(Color(NSColor.windowBackgroundColor)) 372→ } 373→} 374→ 375→// MARK: - Cooperatives List 376→ 377→struct CooperativesListView: View { 378→ let cooperatives: [Cooperative] 379→ let searchText: String 380→ @State private var selectedCoop: Cooperative? 381→ 382→ var body: some View { 383→ HSplitView { 384→ List(cooperatives, selection: $selectedCoop) { coop in 385→ CooperativeRow(cooperative: coop) 386→ .tag(coop) 387→ } 388→ .listStyle(.inset) 389→ .frame(minWidth: 350, maxWidth: 450) 390→ 391→ if let coop = selectedCoop { 392→ CooperativeDetailView(cooperative: coop) 393→ } else { 394→ EmptyDetailView(message: "Selecione uma cooperativa") 395→ } 396→ } 397→ } 398→} 399→ 400→struct CooperativeRow: View { 401→ let cooperative: Cooperative 402→ 403→ var body: some View { 404→ HStack(spacing: 12) { 405→ Image(systemName: "person.3.fill") 406→ .font(.title3) 407→ .foregroundStyle(Color(hex: "#10B981") ?? .green) 408→ .frame(width: 32, height: 32) 409→ .background(Color(hex: "#10B981")?.opacity(0.1) ?? Color.green.opacity(0.1)) 410→ .clipShape(RoundedRectangle(cornerRadius: 6)) 411→ 412→ VStack(alignment: .leading, spacing: 2) { 413→ Text(cooperative.displayName) 414→ .font(.headline) 415→ .lineLimit(1) 416→ 417→ HStack(spacing: 8) { 418→ if let coopClass = cooperative.coopClass { 419→ Text(coopClass) 420→ .font(.caption2) 421→ .padding(.horizontal, 6) 422→ .padding(.vertical, 2) 423→ .background(Color.green.opacity(0.1)) 424→ .clipShape(Capsule()) 425→ } 426→ 427→ if let category = cooperative.category { 428→ Text(category) 429→ .font(.caption2) 430→ .foregroundStyle(.secondary) 431→ } 432→ } 433→ } 434→ 435→ Spacer() 436→ 437→ if let state = cooperative.state { 438→ Text(state) 439→ .font(.caption) 440→ .foregroundStyle(.secondary) 441→ } 442→ } 443→ .padding(.vertical, 4) 444→ } 445→} 446→ 447→struct CooperativeDetailView: View { 448→ let cooperative: Cooperative 449→ 450→ var body: some View { 451→ ScrollView { 452→ VStack(alignment: .leading, spacing: 24) { 453→ DetailHeader( 454→ name: cooperative.name, 455→ subtitle: cooperative.coopClass, 456→ icon: "person.3.fill", 457→ color: "#10B981" 458→ ) 459→ 460→ LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 16) { 461→ InfoCard(title: "CNPJ", value: cooperative.formattedCNPJ, icon: "number") 462→ InfoCard(title: "Classe", value: cooperative.coopClass ?? "--", icon: "tag") 463→ InfoCard(title: "Associação", value: cooperative.association ?? "--", icon: "person.2") 464→ InfoCard(title: "Categoria", value: cooperative.category ?? "--", icon: "folder") 465→ InfoCard(title: "Cidade", value: cooperative.city ?? "--", icon: "mappin") 466→ InfoCard(title: "Estado", value: cooperative.state ?? "--", icon: "map") 467→ InfoCard(title: "Telefone", value: cooperative.formattedPhone, icon: "phone") 468→ } 469→ .padding(.horizontal) 470→ 471→ // Affiliation 472→ if let affiliation = cooperative.affiliation, !affiliation.isEmpty { 473→ VStack(alignment: .leading, spacing: 8) { 474→ Label("Filiação", systemImage: "link") 475→ .font(.headline) 476→ .foregroundStyle(.secondary) 477→ 478→ Text(affiliation) 479→ .font(.body) 480→ } 481→ .padding() 482→ .frame(maxWidth: .infinity, alignment: .leading) 483→ .background(Color(NSColor.controlBackgroundColor)) 484→ .clipShape(RoundedRectangle(cornerRadius: 10)) 485→ .padding(.horizontal) 486→ } 487→ 488→ // Address 489→ if !cooperative.fullAddress.isEmpty { 490→ VStack(alignment: .leading, spacing: 8) { 491→ Label("Endereço", systemImage: "location") 492→ .font(.headline) 493→ .foregroundStyle(.secondary) 494→ 495→ Text(cooperative.fullAddress) 496→ .font(.body) 497→ } 498→ .padding() 499→ .frame(maxWidth: .infinity, alignment: .leading) 500→ .background(Color(NSColor.controlBackgroundColor)) 501→ .clipShape(RoundedRectangle(cornerRadius: 10)) 502→ .padding(.horizontal) 503→ } 504→ 505→ Spacer() 506→ } 507→ } 508→ .background(Color(NSColor.windowBackgroundColor)) 509→ } 510→} 511→ 512→// MARK: - Consortia List 513→ 514→struct ConsortiaListView: View { 515→ let consortia: [Consortium] 516→ let searchText: String 517→ @State private var selectedConsortium: Consortium? 518→ 519→ var body: some View { 520→ HSplitView { 521→ List(consortia, selection: $selectedConsortium) { consortium in 522→ ConsortiumRow(consortium: consortium) 523→ .tag(consortium) 524→ } 525→ .listStyle(.inset) 526→ .frame(minWidth: 350, maxWidth: 450) 527→ 528→ if let consortium = selectedConsortium { 529→ ConsortiumDetailView(consortium: consortium) 530→ } else { 531→ EmptyDetailView(message: "Selecione um consórcio") 532→ } 533→ } 534→ } 535→} 536→ 537→struct ConsortiumRow: View { 538→ let consortium: Consortium 539→ 540→ var body: some View { 541→ HStack(spacing: 12) { 542→ Image(systemName: "person.2.fill") 543→ .font(.title3) 544→ .foregroundStyle(Color(hex: "#F59E0B") ?? .orange) 545→ .frame(width: 32, height: 32) 546→ .background(Color(hex: "#F59E0B")?.opacity(0.1) ?? Color.orange.opacity(0.1)) 547→ .clipShape(RoundedRectangle(cornerRadius: 6)) 548→ 549→ VStack(alignment: .leading, spacing: 2) { 550→ Text(consortium.displayName) 551→ .font(.headline) 552→ .lineLimit(1) 553→ 554→ Text("Administradora de Consórcio") 555→ .font(.caption) 556→ .foregroundStyle(.secondary) 557→ .lineLimit(1) 558→ } 559→ 560→ Spacer() 561→ 562→ if let state = consortium.state { 563→ Text(state) 564→ .font(.caption) 565→ .foregroundStyle(.secondary) 566→ } 567→ } 568→ .padding(.vertical, 4) 569→ } 570→} 571→ 572→struct ConsortiumDetailView: View { 573→ let consortium: Consortium 574→ 575→ var body: some View { 576→ ScrollView { 577→ VStack(alignment: .leading, spacing: 24) { 578→ DetailHeader( 579→ name: consortium.name, 580→ subtitle: "Administradora de Consórcio", 581→ icon: "person.2.fill", 582→ color: "#F59E0B" 583→ ) 584→ 585→ LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 16) { 586→ InfoCard(title: "CNPJ", value: consortium.formattedCNPJ, icon: "number") 587→ InfoCard(title: "Cidade", value: consortium.city ?? "--", icon: "mappin") 588→ InfoCard(title: "Estado", value: consortium.state ?? "--", icon: "map") 589→ InfoCard(title: "Telefone", value: consortium.formattedPhone, icon: "phone") 590→ } 591→ .padding(.horizontal) 592→ 593→ if !consortium.fullAddress.isEmpty { 594→ VStack(alignment: .leading, spacing: 8) { 595→ Label("Endereço", systemImage: "location") 596→ .font(.headline) 597→ .foregroundStyle(.secondary) 598→ 599→ Text(consortium.fullAddress) 600→ .font(.body) 601→ } 602→ .padding() 603→ .frame(maxWidth: .infinity, alignment: .leading) 604→ .background(Color(NSColor.controlBackgroundColor)) 605→ .clipShape(RoundedRectangle(cornerRadius: 10)) 606→ .padding(.horizontal) 607→ } 608→ 609→ if consortium.email != nil || consortium.website != nil { 610→ VStack(alignment: .leading, spacing: 12) { 611→ Label("Contato", systemImage: "envelope") 612→ .font(.headline) 613→ .foregroundStyle(.secondary) 614→ 615→ if let email = consortium.email { 616→ HStack { 617→ Image(systemName: "envelope") 618→ .foregroundStyle(.secondary) 619→ Text(email.lowercased()) 620→ } 621→ } 622→ 623→ if let website = consortium.website { 624→ HStack { 625→ Image(systemName: "globe") 626→ .foregroundStyle(.secondary) 627→ Link(website.lowercased(), destination: URL(string: "https://\(website.lowercased())") ?? URL(string: "https://example.com")!) 628→ } 629→ } 630→ } 631→ .padding() 632→ .frame(maxWidth: .infinity, alignment: .leading) 633→ .background(Color(NSColor.controlBackgroundColor)) 634→ .clipShape(RoundedRectangle(cornerRadius: 10)) 635→ .padding(.horizontal) 636→ } 637→ 638→ Spacer() 639→ } 640→ } 641→ .background(Color(NSColor.windowBackgroundColor)) 642→ } 643→} 644→ 645→// MARK: - Sociedades List 646→ 647→struct SociedadesListView: View { 648→ let sociedades: [Sociedade] 649→ let searchText: String 650→ @State private var selectedSociedade: Sociedade? 651→ @State private var selectedSegment: SociedadeSegment? 652→ 653→ var filteredSociedades: [Sociedade] { 654→ guard let segment = selectedSegment else { return sociedades } 655→ return sociedades.filter { $0.segmentType == segment } 656→ } 657→ 658→ var body: some View { 659→ HSplitView { 660→ VStack(spacing: 0) { 661→ // Segment filter 662→ ScrollView(.horizontal, showsIndicators: false) { 663→ HStack(spacing: 6) { 664→ SegmentFilterChip( 665→ title: "Todos", 666→ count: sociedades.count, 667→ isSelected: selectedSegment == nil 668→ ) { 669→ selectedSegment = nil 670→ } 671→ 672→ ForEach(SociedadeSegment.allCases, id: \.self) { segment in 673→ let count = sociedades.filter { $0.segmentType == segment }.count 674→ if count > 0 { 675→ SegmentFilterChip( 676→ title: segment.rawValue, 677→ count: count, 678→ isSelected: selectedSegment == segment, 679→ color: segment.color 680→ ) { 681→ selectedSegment = segment 682→ } 683→ } 684→ } 685→ } 686→ .padding(.horizontal) 687→ .padding(.vertical, 8) 688→ } 689→ 690→ Divider() 691→ 692→ List(filteredSociedades, selection: $selectedSociedade) { sociedade in 693→ SociedadeRow(sociedade: sociedade) 694→ .tag(sociedade) 695→ } 696→ .listStyle(.inset) 697→ } 698→ .frame(minWidth: 350, maxWidth: 450) 699→ 700→ if let sociedade = selectedSociedade { 701→ SociedadeDetailView(sociedade: sociedade) 702→ } else { 703→ EmptyDetailView(message: "Selecione uma sociedade") 704→ } 705→ } 706→ } 707→} 708→ 709→struct SegmentFilterChip: View { 710→ let title: String 711→ let count: Int 712→ let isSelected: Bool 713→ var color: String = "#8B5CF6" 714→ let action: () -> Void 715→ 716→ var body: some View { 717→ Button(action: action) { 718→ HStack(spacing: 4) { 719→ Text(title) 720→ .font(.caption2) 721→ .lineLimit(1) 722→ 723→ Text("\(count)") 724→ .font(.caption2) 725→ .padding(.horizontal, 4) 726→ .padding(.vertical, 1) 727→ .background(isSelected ? Color.white.opacity(0.2) : Color.secondary.opacity(0.2)) 728→ .clipShape(Capsule()) 729→ } 730→ .padding(.horizontal, 8) 731→ .padding(.vertical, 4) 732→ .background(isSelected ? Color(hex: color) ?? .purple : Color(NSColor.controlBackgroundColor)) 733→ .foregroundStyle(isSelected ? .white : .primary) 734→ .clipShape(Capsule()) 735→ } 736→ .buttonStyle(.plain) 737→ } 738→} 739→ 740→struct SociedadeRow: View { 741→ let sociedade: Sociedade 742→ 743→ var body: some View { 744→ HStack(spacing: 12) { 745→ Image(systemName: sociedade.segmentType.icon) 746→ .font(.title3) 747→ .foregroundStyle(Color(hex: sociedade.segmentType.color) ?? .purple) 748→ .frame(width: 32, height: 32) 749→ .background(Color(hex: sociedade.segmentType.color)?.opacity(0.1) ?? Color.purple.opacity(0.1)) 750→ .clipShape(RoundedRectangle(cornerRadius: 6)) 751→ 752→ VStack(alignment: .leading, spacing: 2) { 753→ Text(sociedade.displayName) 754→ .font(.headline) 755→ .lineLimit(1) 756→ 757→ Text(sociedade.segment ?? "Sociedade") 758→ .font(.caption) 759→ .foregroundStyle(.secondary) 760→ .lineLimit(1) 761→ } 762→ 763→ Spacer() 764→ 765→ if let state = sociedade.state { 766→ Text(state) 767→ .font(.caption) 768→ .foregroundStyle(.secondary) 769→ } 770→ } 771→ .padding(.vertical, 4) 772→ } 773→} 774→ 775→struct SociedadeDetailView: View { 776→ let sociedade: Sociedade 777→ 778→ var body: some View { 779→ ScrollView { 780→ VStack(alignment: .leading, spacing: 24) { 781→ DetailHeader( 782→ name: sociedade.name, 783→ subtitle: sociedade.segment, 784→ icon: sociedade.segmentType.icon, 785→ color: sociedade.segmentType.color 786→ ) 787→ 788→ LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 16) { 789→ InfoCard(title: "CNPJ", value: sociedade.formattedCNPJ, icon: "number") 790→ InfoCard(title: "Segmento", value: sociedade.segment ?? "--", icon: "tag") 791→ InfoCard(title: "Cidade", value: sociedade.city ?? "--", icon: "mappin") 792→ InfoCard(title: "Estado", value: sociedade.state ?? "--", icon: "map") 793→ InfoCard(title: "Telefone", value: sociedade.formattedPhone, icon: "phone") 794→ InfoCard(title: "Tipo", value: sociedade.segmentType.rawValue, icon: "building.2") 795→ } 796→ .padding(.horizontal) 797→ 798→ if !sociedade.fullAddress.isEmpty { 799→ VStack(alignment: .leading, spacing: 8) { 800→ Label("Endereço", systemImage: "location") 801→ .font(.headline) 802→ .foregroundStyle(.secondary) 803→ 804→ Text(sociedade.fullAddress) 805→ .font(.body) 806→ } 807→ .padding() 808→ .frame(maxWidth: .infinity, alignment: .leading) 809→ .background(Color(NSColor.controlBackgroundColor)) 810→ .clipShape(RoundedRectangle(cornerRadius: 10)) 811→ .padding(.horizontal) 812→ } 813→ 814→ if sociedade.email != nil || sociedade.website != nil { 815→ VStack(alignment: .leading, spacing: 12) { 816→ Label("Contato", systemImage: "envelope") 817→ .font(.headline) 818→ .foregroundStyle(.secondary) 819→ 820→ if let email = sociedade.email { 821→ HStack { 822→ Image(systemName: "envelope") 823→ .foregroundStyle(.secondary) 824→ Text(email.lowercased()) 825→ } 826→ } 827→ 828→ if let website = sociedade.website { 829→ HStack { 830→ Image(systemName: "globe") 831→ .foregroundStyle(.secondary) 832→ Link(website.lowercased(), destination: URL(string: "https://\(website.lowercased())") ?? URL(string: "https://example.com")!) 833→ } 834→ } 835→ } 836→ .padding() 837→ .frame(maxWidth: .infinity, alignment: .leading) 838→ .background(Color(NSColor.controlBackgroundColor)) 839→ .clipShape(RoundedRectangle(cornerRadius: 10)) 840→ .padding(.horizontal) 841→ } 842→ 843→ Spacer() 844→ } 845→ } 846→ .background(Color(NSColor.windowBackgroundColor)) 847→ } 848→} 849→ 850→// MARK: - Shared Components 851→ 852→struct DetailHeader: View { 853→ let name: String 854→ let subtitle: String? 855→ let icon: String 856→ let color: String 857→ 858→ var body: some View { 859→ HStack(spacing: 16) { 860→ Image(systemName: icon) 861→ .font(.system(size: 36)) 862→ .foregroundStyle(Color(hex: color) ?? .blue) 863→ .frame(width: 60, height: 60) 864→ .background(Color(hex: color)?.opacity(0.1) ?? Color.blue.opacity(0.1)) 865→ .clipShape(RoundedRectangle(cornerRadius: 12)) 866→ 867→ VStack(alignment: .leading, spacing: 4) { 868→ Text(name) 869→ .font(.title2) 870→ .fontWeight(.bold) 871→ 872→ if let subtitle = subtitle { 873→ Text(subtitle) 874→ .font(.subheadline) 875→ .foregroundStyle(.secondary) 876→ } 877→ } 878→ 879→ Spacer() 880→ } 881→ .padding() 882→ } 883→} 884→ 885→struct EmptyDetailView: View { 886→ let message: String 887→ 888→ var body: some View { 889→ VStack(spacing: 12) { 890→ Image(systemName: "building.columns") 891→ .font(.system(size: 48)) 892→ .foregroundStyle(.tertiary) 893→ 894→ Text(message) 895→ .font(.headline) 896→ .foregroundStyle(.secondary) 897→ } 898→ .frame(maxWidth: .infinity, maxHeight: .infinity) 899→ } 900→} 901→ 902→struct InfoCard: View { 903→ let title: String 904→ let value: String 905→ let icon: String 906→ 907→ var body: some View { 908→ HStack(spacing: 12) { 909→ Image(systemName: icon) 910→ .font(.title3) 911→ .foregroundStyle(.secondary) 912→ .frame(width: 24) 913→ 914→ VStack(alignment: .leading, spacing: 4) { 915→ Text(title) 916→ .font(.caption) 917→ .foregroundStyle(.secondary) 918→ 919→ Text(value) 920→ .font(.body) 921→ .fontWeight(.medium) 922→ .lineLimit(2) 923→ } 924→ 925→ Spacer() 926→ } 927→ .padding() 928→ .background(Color(NSColor.controlBackgroundColor)) 929→ .clipShape(RoundedRectangle(cornerRadius: 10)) 930→ } 931→} 932→ 933→#Preview { 934→ InstitutionsView() 935→ .environmentObject(DashboardViewModel()) 936→ .frame(width: 1000, height: 700) 937→} 938→ 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.