Especialização em Front-End com Angular • Demonstrações Práticas Compiláveis


💻 Código de Exemplo 20 — Projeto Corporativo Integrado: Portal Financeiro com Auditoria e Exportação JSON

Este exemplo consolida toda a arquitetura desenvolvida ao longo da especialização: Standalone Components no Angular 18/19, tipagem estrita com TypeScript, reatividade nativa com Angular Signals, cálculo de KPIs em tempo real com computed, filtragem dinâmica com @if / @for e exportação em memória de relatórios de auditoria no formato JSON via Blob API.

// src/app/pages/financial-portal.component.ts
import { Component, signal, computed } from '@angular/core';
import { CommonModule, CurrencyPipe, DatePipe } from '@angular/common';
import { FormsModule } from '@angular/forms';

// 1. CONTRATOS DE DADOS CORPORATIVOS
export interface FinancialTransaction {
  id: string;
  payee: string;
  amount: number;
  category: 'CLOUD_INFRA' | 'DEV_TOOLS' | 'CONSULTING' | 'LICENSES';
  status: 'APPROVED' | 'PENDING' | 'REJECTED';
  timestamp: string;
}

export interface FinancialKPIs {
  totalApprovedAmount: number;
  approvalRate: number;
  activeCount: number;
}

@Component({
  selector: 'app-financial-portal',
  standalone: true,
  imports: [CommonModule, FormsModule, CurrencyPipe, DatePipe],
  template: `
    <div class="max-w-5xl mx-auto p-6 space-y-6 font-sans">
      <!-- Cabeçalho do Portal Executivo -->
      <div class="flex flex-col md:flex-row md:items-center justify-between gap-4 bg-white border border-slate-200 p-6 rounded-2xl shadow-sm">
        <div>
          <div class="flex items-center gap-2">
            <span class="w-3 h-3 rounded-full bg-red-600 animate-pulse"></span>
            <h1 class="text-2xl font-black text-slate-900 tracking-tight">
              Portal Financeiro & Auditoria de Custos
            </h1>
          </div>
          <p class="text-xs text-slate-500 mt-1">
            Angular 18 Enterprise • Standalone Architecture, Signals & Auditoria JSON
          </p>
        </div>

        <div class="flex items-center gap-3">
          <button
            type="button"
            (click)="simulateNewTransaction()"
            class="px-3.5 py-2 text-xs font-semibold bg-red-50 hover:bg-red-100 text-red-700 rounded-xl transition-colors"
          >
            + Simular Lançamento
          </button>
          <button
            type="button"
            [disabled]="isExporting()"
            (click)="exportAuditJson()"
            class="px-4 py-2 text-xs font-bold bg-red-600 hover:bg-red-700 text-white rounded-xl transition-colors shadow-sm disabled:opacity-50 flex items-center gap-2"
          >
            <span>📥</span>
            <span>{{ isExporting() ? 'Gerando Arquivo...' : 'Exportar Auditoria JSON' }}</span>
          </button>
        </div>
      </div>

      <!-- Grade de Indicadores Executivos (KPIs Computados) -->
      <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
        <div class="bg-white border border-slate-200 p-5 rounded-2xl shadow-sm">
          <span class="text-xs font-bold text-slate-400 uppercase tracking-wider">Volume Aprovado</span>
          <div class="text-2xl font-black text-slate-900 mt-1">
            {{ kpis().totalApprovedAmount | currency:'BRL':'symbol':'1.2-2' }}
          </div>
          <span class="text-xs text-emerald-600 font-semibold mt-1 inline-block">Liquidação em D+0</span>
        </div>

        <div class="bg-white border border-slate-200 p-5 rounded-2xl shadow-sm">
          <span class="text-xs font-bold text-slate-400 uppercase tracking-wider">Taxa de Conformidade</span>
          <div class="text-2xl font-black text-emerald-600 mt-1">{{ kpis().approvalRate }}%</div>
          <span class="text-xs text-slate-400 mt-1 inline-block">Meta mínima exigida: 80.0%</span>
        </div>

        <div class="bg-white border border-slate-200 p-5 rounded-2xl shadow-sm">
          <span class="text-xs font-bold text-slate-400 uppercase tracking-wider">Total de Lançamentos</span>
          <div class="text-2xl font-black text-indigo-600 mt-1">{{ kpis().activeCount }}</div>
          <span class="text-xs text-slate-400 mt-1 inline-block">Eventos no período fiscal</span>
        </div>
      </div>

      <!-- Tabela de Auditoria com Filtros Dinâmicos -->
      <div class="bg-white border border-slate-200 rounded-2xl shadow-sm overflow-hidden space-y-4 p-5">
        <div class="flex flex-col sm:flex-row gap-3 justify-between items-center">
          <input
            type="text"
            [(ngModel)]="searchFilter"
            placeholder="Filtrar por favorecido ou ID..."
            class="w-full sm:w-72 border px-3 py-2 text-xs rounded-xl outline-none focus:ring-2 focus:ring-red-500"
          />

          <div class="flex items-center gap-2">
            <span class="text-xs text-slate-500 font-medium">Status:</span>
            <select
              [(ngModel)]="statusFilter"
              class="border px-3 py-1.5 text-xs rounded-xl outline-none bg-white font-medium text-slate-700"
            >
              <option value="ALL">Todos os Status</option>
              <option value="APPROVED">Apenas Aprovados</option>
              <option value="PENDING">Apenas Pendentes</option>
              <option value="REJECTED">Apenas Rejeitados</option>
            </select>
          </div>
        </div>

        <!-- Grade de Registros -->
        <div class="border rounded-xl overflow-hidden">
          <table class="w-full text-left text-xs">
            <thead class="bg-slate-50 text-slate-600 font-semibold border-b">
              <tr>
                <th class="px-4 py-3">Código</th>
                <th class="px-4 py-3">Favorecido / Fornecedor</th>
                <th class="px-4 py-3">Categoria</th>
                <th class="px-4 py-3 text-right">Valor Líquido</th>
                <th class="px-4 py-3 text-center">Status</th>
                <th class="px-4 py-3 text-right">Data/Hora</th>
              </tr>
            </thead>
            <tbody class="divide-y text-slate-700">
              @for (tx of filteredTransactions(); track tx.id) {
                <tr class="hover:bg-slate-50 transition-colors">
                  <td class="px-4 py-3 font-mono font-bold text-red-600">{{ tx.id }}</td>
                  <td class="px-4 py-3 font-medium text-slate-800">{{ tx.payee }}</td>
                  <td class="px-4 py-3 font-mono text-[10px] text-slate-500 uppercase">{{ tx.category }}</td>
                  <td class="px-4 py-3 font-bold text-right font-mono">
                    {{ tx.amount | currency:'BRL':'symbol':'1.2-2' }}
                  </td>
                  <td class="px-4 py-3 text-center">
                    <span
                      class="px-2.5 py-0.5 rounded-full font-bold text-[10px]"
                      [ngClass]="{
                        'bg-emerald-100 text-emerald-800': tx.status === 'APPROVED',
                        'bg-amber-100 text-amber-800': tx.status === 'PENDING',
                        'bg-red-100 text-red-800': tx.status === 'REJECTED'
                      }"
                    >
                      {{ tx.status }}
                    </span>
                  </td>
                  <td class="px-4 py-3 text-right font-mono text-slate-400 text-[11px]">
                    {{ tx.timestamp | date:'dd/MM/yyyy HH:mm' }}
                  </td>
                </tr>
              } @empty {
                <tr>
                  <td colspan="6" class="p-8 text-center text-slate-400 text-xs">
                    Nenhuma transação atende aos critérios de pesquisa selecionados.
                  </td>
                </tr>
              }
            </tbody>
          </table>
        </div>
      </div>
    </div>
  `
})
export class FinancialPortalComponent {
  // 1. Estado Reativo Gerenciado com Signals
  transactions = signal<FinancialTransaction[]>([
    { id: 'tx-801', payee: 'Amazon Web Services Inc.', amount: 28400.0, category: 'CLOUD_INFRA', status: 'APPROVED', timestamp: '2026-09-28T09:10:00Z' },
    { id: 'tx-802', payee: 'GitHub Enterprise Suite', amount: 4800.0, category: 'DEV_TOOLS', status: 'APPROVED', timestamp: '2026-09-28T09:25:30Z' },
    { id: 'tx-803', payee: 'Consultoria Segura InfoSec', amount: 15000.0, category: 'CONSULTING', status: 'PENDING', timestamp: '2026-09-28T09:30:15Z' },
    { id: 'tx-804', payee: 'Datadog Cloud Monitoring', amount: 8200.0, category: 'CLOUD_INFRA', status: 'APPROVED', timestamp: '2026-09-28T09:42:00Z' },
    { id: 'tx-805', payee: 'Licenças JetBrains IDEs', amount: 2150.0, category: 'LICENSES', status: 'REJECTED', timestamp: '2026-09-28T10:00:00Z' }
  ]);

  searchFilter = '';
  statusFilter: 'ALL' | 'APPROVED' | 'PENDING' | 'REJECTED' = 'ALL';
  isExporting = signal<boolean>(false);

  // 2. Computed Signals para Indicadores Analíticos (KPIs)
  kpis = computed<FinancialKPIs>(() => {
    const list = this.transactions();
    const approved = list.filter((t) => t.status === 'APPROVED');
    const totalApproved = approved.reduce((acc, t) => acc + t.amount, 0);
    const rate = list.length > 0 ? (approved.length / list.length) * 100 : 0;

    return {
      totalApprovedAmount: totalApproved,
      approvalRate: Number(rate.toFixed(1)),
      activeCount: list.length
    };
  });

  // 3. Filtragem Reativa Multicritério
  filteredTransactions = computed(() => {
    const term = this.searchFilter.toLowerCase().trim();
    const status = this.statusFilter;

    return this.transactions().filter((tx) => {
      const matchStatus = status === 'ALL' || tx.status === status;
      const matchSearch = tx.payee.toLowerCase().includes(term) || tx.id.toLowerCase().includes(term);
      return matchStatus && matchSearch;
    });
  });

  simulateNewTransaction(): void {
    const payees = ['Cloudflare Zero Trust', 'Confluent Cloud Kafka', 'Docker Hub Enterprise'];
    const payee = payees[Math.floor(Math.random() * payees.length)];
    const amount = Math.floor(Math.random() * 12000) + 1500;

    const newTx: FinancialTransaction = {
      id: `tx-${Math.floor(Math.random() * 900) + 810}`,
      payee,
      amount,
      category: 'CLOUD_INFRA',
      status: Math.random() > 0.25 ? 'APPROVED' : 'PENDING',
      timestamp: new Date().toISOString()
    };

    this.transactions.update((curr) => [newTx, ...curr]);
  }

  // 4. Exportação de Snapshot de Auditoria em Formato JSON Padronizado
  exportAuditJson(): void {
    this.isExporting.set(true);

    const auditPayload = {
      complianceStandard: 'SOX-404-FINANCIAL-AUDIT',
      generatedAt: new Date().toISOString(),
      angularVersion: '18.2.0',
      kpisSnapshot: this.kpis(),
      recordsCount: this.filteredTransactions().length,
      auditTrail: this.filteredTransactions()
    };

    const blob = new Blob([JSON.stringify(auditPayload, null, 2)], { type: 'application/json' });
    const url = URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.href = url;
    link.download = `financial_audit_snapshot_${Date.now()}.json`;
    link.click();
    URL.revokeObjectURL(url);

    setTimeout(() => {
      this.isExporting.set(false);
    }, 500);
  }
}

🖥️ Saída / Comportamento Esperado

Renderização Inicial:
Volume Aprovado: R$ 41.400,00 (Soma de AWS, GitHub e Datadog)
Taxa de Conformidade: 60.0% (3 aprovadas de 5 totais)
Total de Lançamentos: 5 transações

Ao Clicar em "+ Simular Lançamento":
transactions.update() insere nova transação no topo da tabela.
Volume Aprovado e Taxa de Conformidade recalculam reativamente em tempo real.

Ao Filtrar por "Apenas Pendentes":
A tabela exibe apenas o lançamento da Consultoria Segura InfoSec.
Total de linhas exibidas: 1.

Ao Clicar em "Exportar Auditoria JSON":
O navegador realiza download do arquivo 'financial_audit_snapshot_[timestamp].json'.
O payload contém cabeçalho normativo, KPIs consolidados e trilha de auditoria para fins fiscais.

🔍 Destaques da Implementação

  1. Padrão Ouro Standalone no Angular 18: Componente isolado, autônomo, tipado e sem necessidade de módulos externos.
  2. Reatividade Instantânea com Signals: Modificações nos filtros disparam reconciliação cirúrgica sem a pesadez de ChangeDetection global.
  3. Download Nativo em Memória (Blob): Geração client-side que desonera a infraestrutura de backend de relatórios.