Buscar K
Aparência
Aparência
Consultar dados, pôr um filtro na barra do Dashboard, formatar um valor, saber o tema e quem está vendo: tudo o que o código de um Widget Svelte faz com a Aplicação passa pelo objeto app. O componente o recebe como propriedade (export let app;); um componente paramétrico recebe também config.
O app é um store do Svelte. app.fetchMatrix(...) chama uma função; $app.theme lê o estado e reage quando ele muda.
| Membro | Devolve | O que faz |
|---|---|---|
fetchMatrix(params) | Promise<{ Result, Columns, columnKeys, Warnings, SQL }> | Consulta os dados da Aplicação; cada linha de Result é uma lista, na ordem de columns. Veja fetchMatrix |
fetchData(params) | Promise<{ Result, Columns, columnKeys, Warnings, SQL }> | A mesma consulta, com cada linha como objeto. Veja fetchData |
columnFromString(texto) | objeto de coluna ou null | Converte '[Vendas]."VALOR":SUM' no objeto que addFilter, getFilter e getReportUrl pedem |
formatValue(valor, coluna) | texto | Formata o valor com a máscara da coluna. coluna pode ser o objeto ou o texto |
isColumnNumeric(coluna) | boolean | true para sufixo numérico (SUM, AVG, MIN, MAX, COUNT, DISTINCT) ou expressão numérica |
addFilter(coluna, operador, valor, comparativos?, encode?) | índice do filtro ou nada | Põe ou troca um filtro na barra do Dashboard. Veja Filtros |
getFilter(coluna) | filtro ou undefined | O filtro da barra nessa coluna, com o mesmo sufixo |
removeFilter(filtro) | nada | Tira um filtro da barra. Recebe o objeto que getFilter devolveu |
fetchFilterValues(coluna) | Promise<{ data: { Result, Columns } }> | Os valores distintos da coluna, com os outros filtros do Dashboard aplicados |
getDateFilterReference(column_id, padrão?) | { start, end, relativeToken? } | O período do filtro de data. Veja Período do filtro de data |
getReportUrl(params) | texto | Monta o link para o Explorer. Não navega. Veja Link para o Explorer |
evalExpression(corpoJs, params?, nome?) | Promise<valor ou undefined> | Avalia uma fórmula em JavaScript, como as dos componentes paramétricos. Em erro, loga no console e devolve undefined; nunca lança |
generateJWTToken() | Promise<{ token, payload } ou null> | Gera um token assinado para chamar uma API sua. Veja JWT |
user | { id, nome } ou null | Quem está vendo. Veja Usuário |
$app.theme | 'light' ou 'dark' | O tema da página. Veja Tema |
$app.moldura | { ativa, cores? } | As cores da marca no aplicativo móvel. Veja Tema |
writeback | objeto com listTables, metadata, find, findOne, save e delete | Lê e grava registros de um Cadastro da Aplicação. Veja Gravar em Cadastro |
refreshDashboard() | Promise<void> | Recarrega os outros Widgets do Dashboard, sem remontar o que chamou. Veja Depois de gravar |
get() | o estado da Aplicação | Leitura pontual do estado (filtros, dados da Aplicação, tema). Sem reatividade |
Toda coluna, em columns, filters, orderBy e columnFromString, é um texto com o nome e um sufixo. A Aplicação resolve o nome para a coluna do Tenant onde o Widget está rodando. Maiúscula e minúscula não fazem diferença.
| Forma | Exemplo | Resolve para |
|---|---|---|
| Coluna pelo nome no banco | '[Vendas]."VALOR":SUM' | A coluna VALOR da tabela Vendas da Aplicação |
| Coluna pelo rótulo | '[Vendas].[Valor Líquido]:SUM' | A coluna cujo rótulo é "Valor Líquido" |
| Expressão da Aplicação | '["Receita YTD"]' | A expressão da aba Expressões. Sem prefixo de tabela |
Vendas é o nome da tabela na Aplicação. A expressão da Aplicação usa o próprio cálculo: o sufixo, se houver, é trocado por EXPR.
Nunca ID numérico no código
'7905:SUM' e 'X141197:EXPR' funcionam no Tenant onde o código foi escrito e não leem nada em outro. A instalação de um template troca os IDs da configuração do Widget, como Filtros Pré Aplicados e Campos para Relatório, mas não mexe no texto do código; clonar e publicar geram IDs novos para as expressões. Com o nome, o mesmo código roda em qualquer Tenant que tenha a tabela.
Coluna que não existe na Aplicação faz columnFromString devolver null (e logar "Tentativa de buscar coluna inexistente" no console) e aparece como null em Columns da resposta.
| Grupo | Sufixos |
|---|---|
| Agregação | SUM, AVG, MIN, MAX, COUNT, DISTINCT (aceita COUNTDISTINCT, DCOUNT, COUNT_DISTINCT), PCT (PERCENTAGE), PCTA |
| Agrupamento | AGP |
| Data | DATE, DATETIME, HOUR, HOURDAY, YEARMONTH, MONTHDAY, DAY, MONTH, YEAR, WEEKDAY, YEARWEEK |
columnFromString sem sufixo assume AGP. Em consulta, escreva sempre o sufixo da coluna física: '[Vendas]."VENDEDOR"' sem :AGP é um erro comum.
const res = await app.fetchMatrix({
columns: ['[Vendas]."VENDEDOR":AGP', '[Vendas]."VALOR":SUM'],
filters: [{ column: '[Vendas]."STATUS":AGP', operator: '=', value: 'FATURADO' }],
orderBy: ['[Vendas]."VALOR":SUM:DESC'],
limit: 10,
});
// res.Result → [["Ana", 1540.5], ["Bruno", 980]]
// res.Columns → [{ column_id, expression, label, sqlCode, column: { data_type, ... } }, ...]| Parâmetro | Formato | Padrão |
|---|---|---|
columns | Lista de textos de coluna ou de objetos de columnFromString | obrigatório |
filters | Lista de { column, operator, value }. column em texto, com sufixo, ou objeto | [] |
orderBy | Lista de '<coluna>:ASC' / '<coluna>:DESC', ou { column, direction } | sem ordem |
limit | Número de linhas | 500 |
options | { limit, bringNulls, isTotalizer }. bringNulls: true traz as linhas com dimensão vazia. isTotalizer: true devolve uma linha só, com as medidas recalculadas sobre o agrupamento (o rodapé de uma tabela) | { limit: 500 } |
ignoreCrossfilters | Lista de column_id: os filtros do Dashboard nessas colunas ficam fora desta consulta. Veja Ignorar um filtro numa consulta | [] |
Operadores de filtro: =, !=, >, <, >=, <=, between (valor 'início,fim'), like, not like e relative (períodos relativos do filtro de data).
| Campo da resposta | O que é |
|---|---|
Result | As linhas. Em fetchMatrix, Result[linha][coluna], na ordem de columns |
Columns | Um objeto de coluna por item de columns, na mesma ordem, para usar em formatValue e addFilter. null para coluna que não foi encontrada |
columnKeys | O código de cada coluna na resposta do servidor (sqlCode) |
Warnings | Avisos do servidor. Lista vazia quando não há |
SQL | A consulta que rodou, para depurar |
ignoreCrossfilters compara com === o column_id de cada item da lista com o do filtro do Dashboard. O column_id que columnFromString devolve vem do cadastro como número para coluna de tabela (7905) e é texto para expressão (X141197); um filtro que voltou de um link ou de um favorito traz o número como texto ('7905'), e o === não os iguala. Passe as duas formas e a consulta ignora o filtro de qualquer origem:
const id = app.columnFromString('[Vendas]."VENDEDOR":AGP').column_id;
const res = await app.fetchMatrix({
columns: ['[Vendas]."VALOR":SUM'],
ignoreCrossfilters: [id, String(id)],
});Tire o column_id de columnFromString na hora, nunca escrito à mão. Para ignorar só o filtro de período, há também o filtro com value: false, em Exemplos.
A consulta junta três fontes, nesta ordem de força:
filters que o código passa.Na mesma coluna (mesmo column_id, qualquer sufixo), a fonte mais forte substitui a mais fraca. Um filtro em filters na coluna de data troca o período do Dashboard só nesta consulta. Filtro com value: false sai da consulta.
fetchMatrix e fetchData lançam exceção quando o servidor recusa a consulta (coluna sem acesso, expressão com erro, tempo esgotado). O objeto lançado é o erro do servidor ou { message, sql }. Consulta sem Aplicação carregada devolve Result vazio, sem lançar.
try {
const res = await app.fetchMatrix({ columns: [...] });
if (!res.Result.length) { vazio = true; return; }
linhas = res.Result;
} catch (e) {
erro = e?.message ?? String(e);
}Mesmos parâmetros e mesmos filtros de fetchMatrix. Cada linha de Result é um objeto, com duas chaves por coluna: o columnKeys correspondente e o texto pedido em columns com tudo que não é letra, número ou _ trocado por _, em maiúsculas.
const res = await app.fetchData({ columns: ['[Vendas]."VENDEDOR":AGP', '[Vendas]."VALOR":SUM'] });
res.Result[0].VENDAS_VENDEDOR_AGP; // "Ana"
res.Result[0].VENDAS_VALOR_SUM; // 1540.5Prefira fetchMatrix: a posição não depende de como o nome vira chave.
const col = app.columnFromString('[Vendas]."VENDEDOR":AGP');
app.addFilter(col, '=', 'Ana'); // filtra o Dashboard
app.addFilter(col, '=', ['Ana', 'Bruno']); // mais de um valor
app.addFilter(col, '=', false); // tira o filtro desta coluna
const atual = app.getFilter(col); // { column, operator, value, comparativeValues } ou undefined| Regra | Efeito |
|---|---|
Valor false, null, 0, '' ou lista vazia | Remove o filtro da coluna. Para filtrar pelo número zero, passe '0' |
| Coluna fora dos filtros permitidos no Dashboard | addFilter não faz nada, sem aviso |
encode não informado | No app, vira true para valor único: , | ; : & + dentro do valor são escapados. Lista sempre é escapada |
| Já existe filtro na mesma coluna e sufixo | É substituído |
YEAR ou YEARMONTH com = numa coluna que já tem filtro DATE | Vira between sobre a data, do primeiro ao último dia do ano ou do mês |
| Filtro obrigatório declarado no Dashboard | removeFilter não remove; filtro de valor único fica com um valor só |
Todo addFilter recarrega o Dashboard, e isso inclui o Widget que filtrou: ele é montado de novo. Para mostrar qual item está selecionado, leia app.getFilter(col) ao montar, em vez de guardar a seleção numa variável.
getFilter(col).value vem como foi guardado, escapado. Compare com getFilter(col)?.value === valor só quando o valor não tem os caracteres acima.
import { format } from 'date-fns';
const data = app.columnFromString('[Vendas]."DATA":DATE');
const { start, end } = app.getDateFilterReference(data.column_id);
const res = await app.fetchMatrix({
columns: ['[Vendas]."VALOR":SUM'],
filters: [{ column: '[Vendas]."DATA":DATE', operator: 'between',
value: `${format(start, 'yyyy-MM-dd')},${format(end, 'yyyy-MM-dd')}` }],
});O primeiro argumento é o column_id, e o filtro precisa estar na mesma coluna com sufixo DATE. Sem filtro, start e end são o segundo argumento, ou a data de agora. Com período relativo ("últimos 30 dias"), a resposta traz também relativeToken.
const url = app.getReportUrl({
columns: [app.columnFromString('[Vendas]."CLIENTE":AGP'), app.columnFromString('[Vendas]."VALOR":SUM')],
orderBy: [{ column: app.columnFromString('[Vendas]."VALOR":SUM'), direction: 'DESC' }],
filters: [{ column: app.columnFromString('[Vendas]."VENDEDOR":AGP'), operator: '=', value: nome }],
});
window.location.href = url;| Chave | O que é |
|---|---|
columns | Objetos de coluna, na ordem do relatório |
orderBy | { column, direction } |
filters | { column, operator, value }, com column como objeto |
matrixAxis | Objeto de coluna para o eixo da matriz |
A função só devolve o texto /app/report/<id da Aplicação>?…; nada acontece até o código navegar. Os filtros do Dashboard entram por baixo, e os seus vencem na mesma coluna com o mesmo sufixo.
app.user é { id, nome } de quem está vendo, e não há outros campos (nem e-mail, nem grupo). É null em link público, que abre sem sessão. Na exportação de imagem e PDF, é quem disparou a exportação: um alerta enviado para trinta pessoas mostra o nome do dono do alerta.
const saudacao = app.user?.nome ? `Bom dia, ${app.user.nome}` : 'Bom dia';Não use app.user para esconder dado. A permissão de linha e coluna é aplicada no servidor, em toda consulta; o que o código esconde no navegador, quem abre o Dashboard consegue ler.
| Campo | Valores |
|---|---|
$app.theme | 'light' ou 'dark' |
$app.moldura.ativa | true só dentro do aplicativo móvel do Lumo |
$app.moldura.cores | { fundo, cartao, rebaixado, linha, texto, textoSecundario, acao }, em hex, com as cores da marca do aplicativo |
Os dois mudam com a página aberta (troca de tema, cor nova vinda do aplicativo). Use com $ dentro de $: para o Widget acompanhar. Como aplicar as cores está em Boas práticas.
app.generateJWTToken() devolve { token, payload }, com payload = { userId, tenantId, data } (data em ISO, UTC). O token é HS256, assinado no servidor com a chave do Tenant, e o código do Widget nunca vê a chave.
Para ligar, um administrador marca Habilitar Gerador de JWT e preenche Chave JWT (até 128 caracteres) no HEC (Horus Enterprise Control), em Tenants › Avançado › Segurança. Sem a opção ligada ou sem a chave, o servidor recusa com "JWT Key não configurada para o tenant.", a mensagem vai para o console do navegador e a função devolve null.
const jwt = await app.generateJWTToken();
if (!jwt?.token) return;
await fetch('https://api.exemplo.com.br/pedidos/aprovar', {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${jwt.token}` },
body: JSON.stringify({ pedidoId }),
});A sua API valida o token com a mesma chave:
import jwt
from datetime import datetime, timedelta, timezone
payload = jwt.decode(token, CHAVE_JWT, algorithms=['HS256'])
emitido = datetime.fromisoformat(payload['data'].replace('Z', '+00:00'))
if datetime.now(timezone.utc) - emitido > timedelta(minutes=5):
raise PermissionError('token velho')O token não tem exp. Confira o campo data na sua API, como acima, para recusar token antigo.
this dos Widgets prontos