UI/UX

PO-UI: Construindo dashboards modernos integrados ao Protheus

Do banco de dados ao componente visual — como transformar dados do Protheus em dashboards que a diretoria quer usar.

O PO-UI é o framework de componentes do ecossistema TOTVS. Ele resolve metade do problema — a parte visual. A outra metade, que ninguém fala, é como extrair os dados do Protheus de forma performática e transformá-los em informação útil.

O que é PO-UI e por que usar

O PO-UI é uma biblioteca de componentes Angular criada pela TOTVS, seguindo o padrão TOTVS Design System. Ele já resolve:

  • Componentes prontos: tabelas, gráficos, filtros, modais, drawers.
  • Padrão visual: consistência com o ecossistema TOTVS.
  • Responsividade: mobile-first nativo.
  • Acessibilidade: WCAG 2.1 nos componentes principais.
Contexto

O PO-UI não substitui o ADVPL — ele consome APIs REST que o ADVPL expõe. O backend continua sendo Protheus; o frontend é Angular com PO-UI.

Arquitetura típica de um dashboard

Arquitetura Dashboard PO-UI
┌─────────────────────────────────────────────────┐
│  Frontend Angular + PO-UI                       │
│  ┌─────────────┐  ┌─────────────┐  ┌──────────┐│
│  │ po-chart    │  │ po-table    │  │ po-drawer││
│  │ (gráficos)  │  │ (dados)     │  │ (filtro) ││
│  └──────┬──────┘  └──────┬──────┘  └────┬─────┘│
│         │ HTTP            │ HTTP         │      │
└─────────┼─────────────────┼──────────────┼──────┘
          │                 │              │
┌─────────┼─────────────────┼──────────────┼──────┐
│  Backend Protheus (ADVPL)                      │
│  ┌──────┴──────┐  ┌───────┴─────┐  ┌────┴────┐│
│  │ WS REST     │  │ WS REST     │  │ WS REST ││
│  │ /dashboard  │  │ /vendas     │  │ /filtro ││
│  └──────┬──────┘  └───────┬─────┘  └────┬────┘│
│         │ SQL              │ SQL          │SQL  │
│         ▼                  ▼              ▼     │
│  ┌──────────────────────────────────────────────┐│
│  │  SQL Server (Protheus)                      ││
│  │  SC5, SC6, SF2, SD2, SA1, SB1...           ││
│  └──────────────────────────────────────────────┘│
└──────────────────────────────────────────────────┘

Componentes essenciais para dashboards

po-chart — Gráficos

O po-chart usa p-series (array de PoChartSerie) e p-type (PoChartType): barras, linhas, área, pizza e donut. Fixe a versão do PO-UI do seu projeto e confira a API daquela versão — nomes de propriedade mudam entre releases:

dashboard.component.ts — Gráfico de vendas
// Template: <po-chart [p-type]="chartType" [p-series]="chartSeries" [p-x-axis-quotes]="meses">
export class DashboardComponent implements OnInit {

  chartType: PoChartType = PoChartType.Bar;
  chartSeries: PoChartSerie[] = [];
  meses: string[] = [];

  constructor(private dashboardService: DashboardService) {}

  ngOnInit() {
    this.dashboardService.getVendasMes().subscribe(data => {
      this.meses = data.map(item => item.mes);
      this.chartSeries = [
        { label: 'Faturamento (R$)', data: data.map(item => item.faturamento) },
        { label: 'Pedidos', data: data.map(item => item.pedidos) }
      ];
    });
  }
}
Atenção

APIs antigas de chart (chartItems/chartColumns com POChartAxisType) não existem na versão atual. E ícones: POIcon está depreciado na v21 — use os ícones Animalia (an an-...).

po-table — Tabela de dados

Tabela de pedidos recentes
// Template PO-UI — o po-table tem p-striped; NÃO existe p-striped-columns
<po-page-default p-title="Dashboard de Pedidos">

  <po-widget p-title="Pedidos Recentes">
    <po-table
      [p-columns]="columns"
      [p-items]="pedidos"
      [p-loading]="loading"
      p-striped="true"
    >
    </po-table>
  </po-widget>

</po-page-default>

// Colunas — currency usa código ISO; labels usam color-01..color-12 + type
columns: PoTableColumn[] = [
  { property: 'numero', label: 'Pedido', type: 'string' },
  { property: 'cliente', label: 'Cliente', type: 'string' },
  { property: 'valor', label: 'Valor', type: 'currency', format: 'BRL' },
  { property: 'status', label: 'Status', type: 'label',
    labels: [
      { value: '1', label: 'Faturado', type: 'success', color: 'color-10' },
      { value: '2', label: 'Pendente', type: 'warning', color: 'color-08' }
    ]
  }
];

po-drawer — Filtros laterais

Em vez de poluir a tela com filtros, use o po-drawer para um painel lateral de filtragem:

Filtros com po-drawer
<po-drawer
  [p-hide-close]="false"
  [p-expanded]="drawerExpanded"
  p-title="Filtros"
  p-size="sm"
>
  <po-combo
    p-label="Filial"
    [p-options]="filiais"
    [(ngModel)]="filtro.filial"
  ></po-combo>

  <po-datepicker
    p-label="Período"
    [(ngModel)]="filtro.periodo"
  ></po-datepicker>

  <po-button
    p-label="Aplicar"
    (p-click)="aplicarFiltros()"
  ></po-button>
</po-drawer>

po-lookup — Traduzindo códigos

Para buscar códigos com modal de consulta, o po-lookup é um componente de input separado — não é uma propriedade de coluna do po-table. Em tabelas, o padrão mais robusto é o backend já devolver o nome resolvido:

po-lookup (filtro) + nome resolvido no backend
// 1. Filtro com po-lookup — componente próprio, com p-service-api
<po-lookup
  p-label="Cliente"
  [p-columns]="lookupColumns"
  [p-service-api]="'/api/v1/clientes'"
  [(ngModel)]="filtro.cliente"
></po-lookup>

// 2. Na tabela: coluna string — o backend já resolveu o nome
{ property: 'cliente', label: 'Cliente', type: 'string' }
// backend retorna: { "cliente": "001-01 - ACME LTDA" }

po-info — KPIs e valores resumidos

Para exibir valores de destaque (KPIs), o po-info é mais indicado que uma tabela. Atenção: o po-info só tem p-label e p-value (+ p-orientation) — não existe p-icon nem p-value-color. Se precisar de ícone, coloque o po-info dentro de um po-widget com ícone Animalia:

po-info — KPIs do dashboard
<po-widget p-title="Financeiro">
  <po-info
    class="po-md-4"
    p-label="Faturamento do Mês"
    [p-value]="faturamentoMes"
  ></po-info>

  <po-info
    class="po-md-4"
    p-label="Pedidos Ativos"
    [p-value]="pedidosAtivos"
  ></po-info>

  <po-info
    class="po-md-4"
    p-label="Ticket Médio"
    [p-value]="ticketMedio"
  ></po-info>
</po-widget>

Backend ADVPL: WS REST para dashboards

O frontend precisa de dados. O backend usa o framework WSRESTFUL/WSMETHOD com ::SetResponse() e retorna .T. — erros via SetRestFault():

ADVPL — WS REST Dashboard
#Include "protheus.ch" + "restful.ch" + "topconn.ch"

WsRestFul Dashboard Description "KPIs do dashboard v1" Format Application_Json
  WsMethod GET Description "KPIs agregados dos últimos 30 dias" WsSyntax "/api/v1/dashboard"
End WsRestFul

WsMethod GET WsService Dashboard
  Local oResponse := JsonObject():New()
  Local cQuery := ""

  // KPIs em uma query — colunas explícitas, filial, no-lock
  cQuery := "SELECT COUNT(DISTINCT SC5.C5_NUM) AS PEDIDOS, "
  cQuery += " SUM(SC6.C6_VALOR) AS FATURAMENTO, "
  cQuery += " COUNT(DISTINCT SC5.C5_CLIENTE+SC5.C5_LOJACLI) AS CLIENTES "
  cQuery += "FROM " + RetSqlName("SC5") + " SC5 "
  cQuery += "JOIN " + RetSqlName("SC6") + " SC6 "
  cQuery += " ON SC6.C6_FILIAL = SC5.C5_FILIAL "
  cQuery += "AND SC6.C6_NUM = SC5.C5_NUM "
  cQuery += "WHERE SC5.C5_FILIAL = '" + xFilial("SC5") + "' "
  cQuery += "AND SC5.C5_EMISSAO >= '" + Dtos(Date()-30) + "' "
  cQuery += "AND SC5.D_E_L_E_T_ = ' ' AND SC6.D_E_L_E_T_ = ' ' "
  cQuery += " %nolock%"

  cQuery := ChangeQuery(cQuery)
  TCQUERY cQuery NEW ALIAS "TMPDASH"

  // Campos numéricos podem chegar como texto — force o tipo
  TCSetField("TMPDASH", "FATURAMENTO", "N", 18, 2)

  oResponse["pedidos"]      := TMPDASH->PEDIDOS
  oResponse["faturamento"]  := TMPDASH->FATURAMENTO
  oResponse["clientes"]     := TMPDASH->CLIENTES
  TMPDASH->(dbCloseArea())

  ::SetContentType("application/json")
  ::SetResponse(oResponse:ToJson())
Return .T.
Atenção

Valide no SX3 físico do seu ambiente antes de presumir campos "padrão" — C6_VALOR pode não existir fisicamente em ambientes com leiaute customizado. E filtros vindos do front (ex.: código de cliente) devem ser validados por tipo/tamanho antes de entrar na query — nunca concatene entrada livre sem validação (ver artigo de segurança de APIs).

Performance: regras para dashboards rápidos

01
Uma query por widget

Não faça uma query gigante e divida no frontend. Cada widget deve ter sua própria query otimizada.

02
Cache com expiração real

Dados de dashboard não mudam a cada segundo. Implemente o cache em camada específica: FWCache/GetGlbValue com expiração no AppServer, ou HTTP cache no proxy reverso — não um "cache" genérico sem mecanismo definido.

03
Índices no SQL Server

Uma query de dashboard sem índices adequados trava em produção. Analise o execution plan antes de publicar.

04
Paginação no backend

Tabelas com +10k registros precisam de paginação server-side. No po-table isso é feito com p-show-more/(p-show-more), p-height + virtual scroll, ou p-service-api para carregar sob demanda — não existe um p-page solto.

Precisa de um dashboard PO-UI para sua empresa?

Falar com a ELP