Skip to content

Widget Svelte (Código Avançado)

O Widget Svelte é a ferramenta definitiva para flexibilidade no Horus DataViz. Ele permite que desenvolvedores criem qualquer tipo de visualização ou interatividade utilizando código padrão Svelte, JavaScript e CSS (Tailwind).

WARNING

Versão do Svelte: O ambiente suporta Svelte 4. Funcionalidades do Svelte 5 (como Runes) não são suportadas. Utilize a sintaxe clássica de reatividade (let, $:, etc.).


🖥️ 1. Configuração Visual (UI)

Antes de mergulhar no código, o Widget possui configurações visuais que controlam seu contêiner e comportamento no Dashboard.

Aba Geral

  • Gráfico com fundo transparente: Remove o fundo branco e bordas padrão do card. Ideal para criar KPIs flutuantes, formas personalizadas ou integrar visualmente com o fundo do Dashboard.
  • Filtros Pré-aplicados: Define filtros que afetam apenas este Widget, independentemente dos filtros globais do Dashboard.

Aba Comportamento

  • Colocar link no Indicador: Adiciona funcionalidade de clique/link baseada no contexto do Widget.
  • Campos para Relatório: Seleciona colunas que serão passadas para o contexto de "Exportação" ou "Drill-through" se o Widget for usado como ponto de partida para um Relatório detalhado.
  • Filtros do Relatório: Filtros adicionais aplicados apenas quando o usuário navega para o Relatório detalhado a partir deste Widget.

🛠️ 2. Ambiente de Desenvolvimento

Ao clicar em "Editar Código", há acesso a um editor completo.

Bibliotecas Disponíveis

O ambiente já vem com bibliotecas essenciais pré-instaladas. Não é necessário (nem possível) fazer npm install.

  • Svelte: Sintaxe padrão ({#if}, {#each}, on:click).
  • Highcharts (Recomendado): Via wrapper oficial @highcharts/svelte.
  • Lodash (_): Utilitários para manipulação de arrays/objetos.
  • Moment (moment): Manipulação de datas e horas.
  • Chroma-js (chroma): Manipulação de cores.
  • XLSX (xlsx): Geração de planilhas Excel.
  • TailwindCSS: Todas as classes utilitárias estão disponíveis globalmente.

🔌 3. API do Objeto app

A variável app (export let app;) é sua ponte com os dados e o motor do Horus BI.

Buscando Dados (fetchMatrix)

Use app.fetchMatrix para executar queries no motor de dados (Hyper-c).

javascript
/* Exemplo de uso dentro de uma função async ou onMount */
const data = await app.fetchMatrix({
  columns: [
    '[Vendas]."VENDEDOR":AGP',   // Dimensão (Agrupador)
    '[Vendas]."VALOR":SUM'       // Métrica (Soma)
  ],
  filters: [], // Opcional: filtros adicionais
  orderBy: ['[Vendas]."VALOR":SUM:DESC'], // Ordenação
  limit: 100
});

// A resposta "data" contém:
// data.Columns: Array com metadados (rótulos, tipos)
// data.Result: Matriz de dados [[Vendedor A, 1000], [Vendedor B, 500]]

IMPORTANT

Expressões são Obrigatórias: Ao definir colunas, é necessário sempre incluir a expressão de agregação (:SUM, :AGP, :DATE, etc.). Sem isso, o motor não sabe como processar a query.

Gerenciamento de Filtros

É possível fazer o Widget interagir com o resto do Dashboard aplicando filtros globais.

javascript
// Criar referência à coluna (sempre com expressão!)
const colunaVendedor = app.columnFromString('[Vendas]."VENDEDOR":AGP');

// Aplicar filtro (Ex: Filtrar onde Vendedor é 'João')
app.addFilter(colunaVendedor, "=", "João");

// Remover filtro
app.addFilter(colunaVendedor, "=", false);

Formatação

Use app.formatValue para garantir que números e datas sigam o padrão do sistema (moeda, casas decimais, locale).

javascript
// row[1] é o valor numérico cru (ex: 1540.5)
// colunaValor contém os metadados de formatação
const textoFormatado = app.formatValue(row[1], colunaValor); 
// Resultado: "R$ 1.540,50" (dependendo do locale)

📋 4. Exemplo Completo

Abaixo, um exemplo de Widget que lista vendedores e filtra o Dashboard ao clicar.

html
<script>
  import { onMount } from "svelte";
  
  export let app; // Injeção automática do Horus
  
  let loading = true;
  let vendedores = [];
  let colunas = [];

  async function carregarDados() {
    loading = true;
    const res = await app.fetchMatrix({
      columns: [
        '[Vendas]."VENDEDOR":AGP', 
        '[Vendas]."TOTAL":SUM'
      ],
      limit: 10
    });
    
    // Mapear resultado para objeto mais fácil de usar
    colunas = res.Columns;
    vendedores = res.Result.map(row => ({
      nome: row[0],
      valor: row[1],
      valorFormatado: app.formatValue(row[1], res.Columns[1])
    }));
    
    loading = false;
  }
  
  function filtrar(nome) {
    const colVendedor = app.columnFromString('[Vendas]."VENDEDOR":AGP');
    app.addFilter(colVendedor, "=", nome);
  }

  // Reagir a mudanças globais (filtros externos) recarregando os dados
  $: {
      if (app) carregarDados();
  }
</script>

<div class="w-full h-full p-4 overflow-auto">
  {#if loading}
    <div class="flex items-center justify-center h-full">Carregando...</div>
  {:else}
    <h3 class="text-lg font-bold mb-4">Top Vendedores</h3>
    <ul class="space-y-2">
      {#each vendedores as v}
        <!-- svelte-ignore a11y-click-events-have-key-events -->
        <li 
          class="flex justify-between p-2 bg-slate-100 hover:bg-blue-100 cursor-pointer rounded transition"
          on:click={() => filtrar(v.nome)}
        >
          <span>{v.nome}</span>
          <span class="font-mono">{v.valorFormatado}</span>
        </li>
      {/each}
    </ul>
  {/if}
</div>

🔐 5. Autenticação JWT para Integrações Externas

O método app.generateJWTToken() permite criar tokens de autenticação para chamar APIs externas (ERP, CRM, etc.) de forma segura, sem expor credenciais no frontend.

Por que usar JWT?

Armazenar tokens de API ou senhas no código JavaScript é uma falha de segurança — qualquer usuário pode inspecionar o código do browser. O JWT resolve isso criando um mecanismo de confiança entre o Horus e seu sistema:

  1. O backend do Horus gera um token assinado com uma chave secreta
  2. O Widget envia esse token para sua API
  3. A API valida o token com a mesma chave (configurada na aba Avançado do Tenant/Cliente)
  4. Se válido → a API confia que a requisição veio de um usuário autenticado

Configuração Prévia

IMPORTANT

Para usar generateJWTToken(), é necessário primeiro habilitar e configurar a Chave JWT na aba Avançado do Tenant ou Cliente. Veja a documentação.

API do Método

javascript
const result = await app.generateJWTToken();

// result = {
//   token: "eyJhbGciOiJIUzI1NiIs...",  // Token JWT assinado
//   payload: {
//     userId: 123,
//     tenantId: 456,
//     data: "2024-01-15T10:30:00.000Z"  // ISO timestamp UTC
//   }
// }

Exemplo Completo: Botão "Aprovar Pedido"

Um Widget que lista pedidos pendentes e permite aprová-los diretamente no Dashboard:

html
<script>
  import { onMount } from "svelte";
  
  export let app;
  
  let pedidos = [];
  let loading = true;
  let aprovando = null; // ID do pedido sendo aprovado

  async function carregarPedidos() {
    loading = true;
    const res = await app.fetchMatrix({
      columns: [
        '[Pedidos]."ID":AGP',
        '[Pedidos]."CLIENTE":AGP',
        '[Pedidos]."VALOR":SUM',
        '[Pedidos]."STATUS":AGP'
      ],
      filters: [
        { column: app.columnFromString('[Pedidos]."STATUS":AGP'), operator: '=', value: 'PENDENTE' }
      ],
      limit: 50
    });
    
    pedidos = res.Result.map(row => ({
      id: row[0],
      cliente: row[1],
      valor: app.formatValue(row[2], res.Columns[2]),
      status: row[3]
    }));
    loading = false;
  }

  async function aprovarPedido(pedidoId) {
    aprovando = pedidoId;
    
    try {
      // 1. Gerar token JWT assinado pelo backend Horus
      const jwt = await app.generateJWTToken();
      
      if (!jwt?.token) {
        alert('Erro: JWT não configurado neste tenant');
        return;
      }
      
      // 2. Chamar sua API externa com o token
      const response = await fetch('https://api.seuerpcloud.com/pedidos/aprovar', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${jwt.token}`  // Token JWT no header
        },
        body: JSON.stringify({ pedidoId })
      });
      
      if (response.ok) {
        alert('Pedido aprovado com sucesso!');
        await carregarPedidos(); // Recarrega a lista
      } else {
        alert('Erro ao aprovar pedido');
      }
    } catch (error) {
      alert('Erro de conexão: ' + error.message);
    } finally {
      aprovando = null;
    }
  }

  $: if (app) carregarPedidos();
</script>

<div class="w-full h-full p-4 overflow-auto">
  <h3 class="text-lg font-bold mb-4">Pedidos Pendentes de Aprovação</h3>
  
  {#if loading}
    <div class="text-center py-8">Carregando...</div>
  {:else if pedidos.length === 0}
    <div class="text-center py-8 text-gray-500">Nenhum pedido pendente</div>
  {:else}
    <table class="w-full">
      <thead>
        <tr class="border-b">
          <th class="text-left py-2">ID</th>
          <th class="text-left py-2">Cliente</th>
          <th class="text-right py-2">Valor</th>
          <th class="text-center py-2">Ação</th>
        </tr>
      </thead>
      <tbody>
        {#each pedidos as pedido}
          <tr class="border-b hover:bg-slate-50">
            <td class="py-2">{pedido.id}</td>
            <td class="py-2">{pedido.cliente}</td>
            <td class="py-2 text-right font-mono">{pedido.valor}</td>
            <td class="py-2 text-center">
              <button
                class="px-3 py-1 bg-green-500 text-white rounded hover:bg-green-600 disabled:opacity-50"
                on:click={() => aprovarPedido(pedido.id)}
                disabled={aprovando === pedido.id}
              >
                {aprovando === pedido.id ? 'Aprovando...' : 'Aprovar'}
              </button>
            </td>
          </tr>
        {/each}
      </tbody>
    </table>
  {/if}
</div>

Validando o Token no seu Backend

No lado do servidor (ERP, API, etc.), é necessário validar o token JWT usando a mesma chave configurada no Horus.

Exemplo em Node.js:

javascript
const jwt = require('jsonwebtoken');

const CHAVE_JWT = 'sua-chave-configurada-no-horus'; // Mesma chave do Tenant

app.post('/pedidos/aprovar', (req, res) => {
  const token = req.headers.authorization?.replace('Bearer ', '');
  
  try {
    const payload = jwt.verify(token, CHAVE_JWT);
    
    // payload = { userId: 123, tenantId: 456, data: "2024-01-15T10:30:00.000Z" }
    console.log(`Usuário ${payload.userId} do tenant ${payload.tenantId} está aprovando pedido`);
    
    // Seu código de aprovação aqui...
    
    res.json({ success: true });
  } catch (error) {
    res.status(401).json({ error: 'Token inválido' });
  }
});

Exemplo em Python:

python
import jwt

CHAVE_JWT = 'sua-chave-configurada-no-horus'

@app.route('/pedidos/aprovar', methods=['POST'])
def aprovar_pedido():
    token = request.headers.get('Authorization', '').replace('Bearer ', '')
    
    try:
        payload = jwt.decode(token, CHAVE_JWT, algorithms=['HS256'])
        
        # payload = { 'userId': 123, 'tenantId': 456, 'data': '2024-01-15T10:30:00.000Z' }
        print(f"Usuário {payload['userId']} aprovando pedido")
        
        # Seu código aqui...
        
        return jsonify({'success': True})
    except jwt.InvalidTokenError:
        return jsonify({'error': 'Token inválido'}), 401

CAUTION

Nunca exponha a Chave JWT no código frontend. Ela deve existir apenas:

  • No backend do Horus (configurada na aba Avançado)
  • No backend do seu sistema externo