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 HorusBI.

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