Filtros de impressão
Parâmetros interativos que o usuário preenche antes de imprimir ou exportar o relatório. Definem variáveis injetadas no ScriptEngine e como placeholders em URLs de fontes externas.
Os filtros suportam lookups dinâmicos (opções carregadas via HTTP ou banco de dados), paginação, validação declarativa e dependências em cascata entre parâmetros.
Definindo parâmetros no JSON
Adicione o campo parameters ao ReportDefinition:
{
"reportId": "orders_report",
"reportName": "Pedidos por Período",
"parameters": [
{
"id": "startDate",
"label": "Data Início",
"type": "date",
"defaultValue": "2024-01-01",
"required": true
},
{
"id": "endDate",
"label": "Data Fim",
"type": "date",
"defaultValue": "2024-12-31",
"required": true,
"validation": {
"rule": "dateAfter",
"compareWith": "startDate",
"errorMessage": "Data Fim deve ser posterior à Data Início"
}
},
{
"id": "status",
"label": "Status",
"type": "select",
"options": ["todos", "aprovado", "pendente"],
"defaultValue": "todos"
}
],
"bands": [...]
}Tipos de parâmetro
type | Descrição | Entrada gerada |
|---|---|---|
text | Texto livre | Campo de texto |
number | Número (inteiro ou decimal) | Campo numérico com validação |
date | Data (ISO 8601 yyyy-MM-dd) | Date picker |
datetime | Data e hora (yyyy-MM-ddTHH:mm) | Date + time picker |
currency | Valor monetário formatado | Campo com máscara monetária |
select | Seleção entre opções (estáticas ou dinâmicas) | Dropdown ou lookup |
Campos format e rounding
Para os tipos date, datetime, currency e number é possível personalizar o formato de entrada/exibição:
{
"id": "invoiceDate",
"label": "Data da Nota",
"type": "date",
"format": "MM/dd/yyyy"
}{
"id": "totalValue",
"label": "Valor Total",
"type": "currency",
"format": ".,2",
"rounding": "half_even"
}type | format | Significado |
|---|---|---|
date | dd/MM/yyyy (padrão) | Padrão intl para exibição e entrada |
datetime | dd/MM/yyyy (padrão) | Aplica à parte de data; hora sempre HH:mm |
currency | ".,2" (padrão) | <milhar><decimal><casas> — ex.: ".,2" → 1.234,56; ",.2" → 1,234.56 |
number | null (ilimitado) | Número de casas decimais como string (ex.: "3") |
O campo rounding aceita os mesmos modos de RoundingConfig: half_even (padrão), half_up, half_down, up, down, truncate. Aplica-se apenas a currency e number quando format define precisão.
Lookups dinâmicos
Um parâmetro select pode ter suas opções carregadas dinamicamente via lookup. Existem dois backends:
http— busca opções em uma URL (padrão, sem necessidade de declararsource)db— executa uma query SQL em uma conexão doConnectionRegistry
Lookup HTTP
{
"id": "categoryId",
"label": "Categoria",
"type": "select",
"required": true,
"lookup": {
"source": "http",
"url": "https://api.example.com/categories",
"valueField": "id",
"labelField": "name",
"method": "GET",
"cacheSeconds": 300,
"pageSize": 20,
"searchOnOpen": true
}
}O campo
"source": "http"pode ser omitido — documentos semsourcesão tratados como HTTP (compatibilidade retroativa).
Lookup de banco de dados
{
"id": "productId",
"label": "Produto",
"type": "select",
"required": true,
"lookup": {
"source": "db",
"connectionRef": "main_db",
"query": "SELECT id, name FROM products WHERE active = true AND ({_search} = '' OR name ILIKE '%' || {_search} || '%') ORDER BY name LIMIT {_page_size} OFFSET {_offset}",
"valueField": "id",
"labelField": "name",
"cacheSeconds": 120,
"pageSize": 25,
"searchOnOpen": true
}
}A query usa placeholders {paramId} (mesmo padrão das URLs REST). Os seguintes são injetados automaticamente:
| Placeholder | Valor |
|---|---|
{_search} | Texto digitado na caixa de busca |
{_page} | Página atual (1-based) |
{_page_size} | Valor de pageSize (ou 0 quando sem paginação) |
{_offset} | (page - 1) * pageSize |
Para substituir o nome do schema dinamicamente, use {_schema} na query — ele é resolvido a partir do campo extra.schema da DatabaseConnectionEntry antes da parametrização.
Propriedades comuns dos lookups
| Campo | Tipo | Default | Descrição |
|---|---|---|---|
source | string | "http" | Backend do lookup: "http" ou "db" |
valueField | string | — | Campo do resultado usado como valor enviado ao relatório |
labelField | string | — | Campo do resultado exibido ao usuário |
cacheSeconds | int | 300 | Tempo de cache em memória (0 = sem cache) |
refreshOnDependencyChange | bool | false | Recarrega quando um parâmetro de dependsOn muda |
pageSize | int | 0 | Itens por página (0 = busca tudo de uma vez) |
searchOnOpen | bool | true | Carrega os primeiros itens ao abrir o diálogo |
Propriedades exclusivas do lookup HTTP
| Campo | Tipo | Default | Descrição |
|---|---|---|---|
url | string | — | URL com placeholders {paramId} |
method | string | "GET" | Método HTTP: "GET" ou "POST" |
Propriedades exclusivas do lookup DB
| Campo | Tipo | Default | Descrição |
|---|---|---|---|
connectionRef | string | — | ID de uma entrada DatabaseConnectionEntry no ConnectionRegistry |
query | string | — | Query SQL com placeholders {paramId} |
Formatos de resposta HTTP aceitos
O HttpLookupResolver aceita dois formatos:
- Lista raiz:
[{"id": "1", "name": "São Paulo"}, ...] - Chave
data:{"data": [{"id": "1", "name": "São Paulo"}, ...]}
Para paginação, o resolver lê meta da resposta para calcular hasMore:
{
"data": [...],
"meta": { "total": 100, "page": 2, "perPage": 20 }
}Quando meta está ausente, usa a heurística items.length >= pageSize.
Dependências em cascata
Use dependsOn para criar filtros encadeados. Quando o valor de um parâmetro-pai muda, os filhos são recarregados automaticamente:
{
"parameters": [
{
"id": "countryId",
"label": "País",
"type": "select",
"lookup": {
"url": "https://api.example.com/countries",
"valueField": "id",
"labelField": "name"
}
},
{
"id": "stateId",
"label": "Estado",
"type": "select",
"dependsOn": ["countryId"],
"lookup": {
"url": "https://api.example.com/countries/{countryId}/states",
"valueField": "id",
"labelField": "name",
"refreshOnDependencyChange": true
}
},
{
"id": "cityId",
"label": "Cidade",
"type": "select",
"dependsOn": ["stateId"],
"lookup": {
"url": "https://api.example.com/states/{stateId}/cities",
"valueField": "id",
"labelField": "name",
"refreshOnDependencyChange": true
}
}
]
}Segurança (SSRF)
O HttpLookupResolver só aceita as seguintes origens (rejeita qualquer outra com ArgumentError):
https://— qualquer host externo via HTTPShttp://localhost— desenvolvimento localhttp://127.0.0.1— loopback alternativo (útil em testes e servidores locais)
Validação declarativa
Cada parâmetro pode ter uma regra de validação inline via validation:
{
"id": "quantity",
"label": "Quantidade",
"type": "number",
"required": true,
"validation": {
"rule": "min",
"value": "1",
"errorMessage": "Quantidade mínima é 1"
}
}Regras disponíveis
rule | Parâmetros | Descrição |
|---|---|---|
required | — | Campo não pode ser vazio |
minLength | value ou compareWith | Comprimento mínimo do texto |
maxLength | value ou compareWith | Comprimento máximo do texto |
min | value ou compareWith | Valor numérico mínimo |
max | value ou compareWith | Valor numérico máximo |
dateBefore | value ou compareWith | Data deve ser anterior ao limite |
dateAfter | value ou compareWith | Data deve ser posterior ao limite |
lessThan | value ou compareWith | Valor estritamente menor |
greaterThan | value ou compareWith | Valor estritamente maior |
value— valor fixo para comparação (ex:"100","2024-01-01")compareWith— ID de outro parâmetro para comparação dinâmica (ex:"startDate")errorMessage— mensagem exibida quando a validação falha
Injetando parâmetros nas expressões
Os valores dos parâmetros são injetados diretamente no contexto das expressões do ScriptEngine. Use os IDs dos parâmetros nas expressões de filtro:
{
"op": "filter",
"source": "orders",
"where": "date >= startDate AND date <= endDate"
}{
"op": "filter",
"source": "orders",
"where": "status == orderStatus"
}Operadores de comparação suportados para strings e datas (comparação lexicográfica): >=, <=, >, <, ==, !=
Injetando parâmetros em URLs REST
Use {paramId} como placeholder na URL da fonte de dados:
{
"id": "orders",
"type": "list",
"source": "external",
"url": "https://api.example.com/orders?from={startDate}&to={endDate}"
}Os valores são URI-encoded automaticamente antes da requisição.
Usando no Studio
Parâmetros são editados no painel de propriedades do relatório (Configurações → Parâmetros):
- Clique em + para adicionar um parâmetro
- Preencha ID, rótulo, tipo, valor padrão e se é obrigatório
- Para tipo
select, adicione as opções estáticas separadas por vírgula ou configure um lookup - Escolha o backend do lookup (
httpoudb) e preencha os campos correspondentes - Defina
dependsOnpara criar filtros encadeados - Configure
pageSizepara ativar paginação no diálogo de seleção - Adicione uma regra de validação se necessário
Ao clicar em Imprimir ou Salvar PDF, o Studio abre automaticamente o SulfiteFilterScreen se houver parâmetros definidos. O diálogo exibe cada parâmetro com o widget apropriado, validação inline e indicador de carregamento para lookups.
Passando parâmetros programaticamente
Via SulfiteEngine.generate():
import 'package:sulfite_core/sulfite_core.dart';
final params = <String, String>{
'startDate': '2024-06-01',
'endDate': '2024-12-31',
'orderStatus': 'aprovado',
};
final pdfBytes = await engine.generate(
report: report,
data: payload,
params: params,
);Usando o SulfiteFilterScreen standalone
O SulfiteFilterScreen pode ser usado fora do Studio como um diálogo independente:
import 'package:sulfite_studio/sulfite_studio.dart';
import 'package:sulfite_core/sulfite_core.dart';
final params = await SulfiteFilterScreen.show(
context,
parameters: report.parameters,
initialValues: {'status': 'aprovado'},
lookupResolver: HttpLookupResolver(),
);
if (params != null) {
// Usar params para gerar o relatório
}Resolver customizado
Implemente a interface LookupResolver para controlar como as opções são buscadas:
class MyResolver implements LookupResolver {
@override
Future<List<LookupOption>> fetchOptions(
LookupConfig config,
ReportParams currentParams,
) async {
// Sua lógica de busca — banco local, GraphQL, etc.
return [
LookupOption(value: '1', label: 'Opção A'),
LookupOption(value: '2', label: 'Opção B'),
];
}
}Para lookups de banco, use o DbLookupResolver do pacote sulfite_datasources:
import 'package:sulfite_datasources/sulfite_datasources.dart';
final registry = ConnectionRegistry();
registry.register(DatabaseConnectionEntry(
id: 'main_db',
host: 'localhost',
port: 5432,
database: 'myapp',
username: 'user',
password: 'secret',
));
final dbResolver = DbLookupResolver(registry: registry);
final params = await SulfiteFilterScreen.show(
context,
parameters: report.parameters,
lookupResolver: dbResolver,
);Referência do modelo
// ReportParameter
ReportParameter(
id: 'startDate', // identificador único (sem espaços)
label: 'Data Início', // rótulo exibido no diálogo
type: 'date', // 'text' | 'number' | 'date' | 'datetime' | 'currency' | 'select'
defaultValue: '2024-01-01',
format: 'dd/MM/yyyy', // opcional — formato de entrada/exibição
rounding: 'half_even', // opcional — para 'currency' e 'number'
options: [], // para type == 'select' (opções estáticas)
required: true, // bloqueia confirmação se vazio
lookup: LookupConfig.http( // HTTP lookup
url: 'https://api.example.com/items',
valueField: 'id',
labelField: 'name',
method: 'GET',
cacheSeconds: 300,
pageSize: 20,
searchOnOpen: true,
refreshOnDependencyChange: true,
),
// --- OU ---
lookup: LookupConfig.db( // DB lookup
connectionRef: 'main_db',
query: 'SELECT id, name FROM categories WHERE {_search} = \'\' OR name ILIKE \'%\' || {_search} || \'%\' LIMIT {_page_size} OFFSET {_offset}',
valueField: 'id',
labelField: 'name',
cacheSeconds: 120,
pageSize: 25,
),
dependsOn: ['parentParam'], // IDs dos parâmetros-pai
validation: ValidationRule( // opcional — validação inline
rule: ValidationRuleType.min,
value: '1',
errorMessage: 'Valor mínimo é 1',
),
)ReportParams é um typedef para Map<String, String>.