Sistemas ERP são o coração do desenvolvimento em Delphi: clientes, pedidos, faturas, níveis de estoque, execuções de produção. A lógica de negócios já vive em Object Pascal. O banco de dados está conectado. Os relatórios funcionam. O que frequentemente falta é um dashboard acessível pelo navegador que a gerência possa abrir em um tablet sem instalar nada. Este artigo mostra como construir exatamente isso com o sgcHTML, partindo de um cenário realista: um ERP de vendas com valores de receita ao vivo, uma grade de faturas, um quadro Kanban de entregas e atualizações em tempo real quando novos pedidos chegam.
Todo o código compila no Delphi 10.4 Sydney e versões posteriores. O resultado final é um único .exe que serve o dashboard na porta 8080, com estilo Bootstrap 5, layout responsivo e atualizações ao vivo via WebSocket, sem nenhum arquivo JavaScript para implantar junto ao binário.
Configurando o servidor
O sgcHTML envolve um TsgcWSHTTPServer do sgcWebSockets. Adicione o TsgcHTMLEngine_Server ao módulo de dados ao lado do servidor HTTP e conecte-os. O motor intercepta automaticamente as requisições de ativos estáticos (Bootstrap CSS/JS, Chart.js, htmx); todo o resto vai para o seu handler OnCommandGet.
uses
sgcWebSocket_Server, sgcHTML_Engine_Server,
sgcHTML_Template_Bootstrap, sgcHTML_Page;
type
TERP_Server = class(TDataModule)
HTTPServer: TsgcWSHTTPServer;
HTMLEngine: TsgcHTMLEngine_Server;
private
procedure HandleGet(AContext: TIdContext;
AReq: TIdHTTPRequestInfo; AResp: TIdHTTPResponseInfo);
public
procedure Start(aPort: Integer);
end;
procedure TERP_Server.Start(aPort: Integer);
begin
HTTPServer.Port := aPort;
HTMLEngine.Server := HTTPServer;
HTTPServer.OnCommandGet := HandleGet;
HTTPServer.Active := True;
end;
procedure TERP_Server.HandleGet(AContext: TIdContext;
AReq: TIdHTTPRequestInfo; AResp: TIdHTTPResponseInfo);
begin
if AReq.Document = '/' then
ServeDashboard(AResp)
else if AReq.Document = '/invoices' then
ServeInvoices(AResp)
else if AReq.Document = '/kanban' then
ServeKanban(AResp)
else
AResp.ResponseNo := 404;
end;
Cartões de KPI
A primeira linha do dashboard exibe quatro cartões de KPI: receita total, faturas em aberto, quantidade de faturas vencidas e taxa de entrega no prazo. O TsgcHTMLComponent_StatCard cuida de toda a apresentação visual: gradiente de fundo, título, valor em destaque, seta de tendência e rótulo de rodapé opcional.
uses
sgcHTML_Component_StatCard;
procedure TERP_Server.BuildKPIRow(aPage: TsgcHTMLPage; aDB: TFDConnection);
function MakeCard(aPage: TsgcHTMLPage; const aTitle, aValue: string;
aColor: TsgcHTMLStatColor; aTrend: TsgcHTMLStatTrend;
const aTrendValue, aFooter: string; aOrder: Integer): TsgcHTMLComponent_StatCard;
begin
Result := TsgcHTMLComponent_StatCard.Create(nil);
Result.PageBuilder := aPage.PageBuilder;
Result.Section := 'kpi';
Result.SectionOrder := aOrder;
Result.ColumnWidth := cw3;
Result.Title := aTitle;
Result.Value := aValue;
Result.Color := aColor;
Result.Trend := aTrend;
Result.TrendValue := aTrendValue;
Result.FooterText := aFooter;
Result.Gradient := sgBlueViolet;
end;
var
oQ: TFDQuery;
begin
oQ := TFDQuery.Create(nil);
try
oQ.Connection := aDB;
oQ.SQL.Text := 'SELECT SUM(total) FROM invoices WHERE YEAR(issued)=YEAR(NOW())';
oQ.Open;
MakeCard(aPage, 'YTD Revenue', FormatCurr('$#,##0', oQ.Fields[0].AsFloat),
scPrimary, stUp, '+14%', 'vs last year', 1);
oQ.Close;
oQ.SQL.Text := 'SELECT COUNT(*) FROM invoices WHERE status=''open''';
oQ.Open;
MakeCard(aPage, 'Open Invoices', oQ.Fields[0].AsString,
scInfo, stNeutral, '', 'awaiting payment', 2);
oQ.Close;
oQ.SQL.Text := 'SELECT COUNT(*) FROM invoices WHERE status=''overdue''';
oQ.Open;
MakeCard(aPage, 'Overdue', oQ.Fields[0].AsString,
scDanger, stDown, '-3', 'vs last month', 3);
oQ.Close;
oQ.SQL.Text := 'SELECT ROUND(100.0*SUM(on_time)/COUNT(*),1) FROM deliveries';
oQ.Open;
MakeCard(aPage, 'On-Time Delivery', oQ.Fields[0].AsString + '%',
scSuccess, stUp, '+2pp', 'last 30 days', 4);
oQ.Close;
finally
oQ.Free;
end;
end;
Gráfico de tendência de receita
O gráfico de receita ocupa a segunda linha. O TsgcHTMLComponent_Chart aceita arrays de rótulos e conjuntos de dados com cores de borda e preenchimento. O LoadFromDataSet mapeia o resultado de uma consulta para os dados do gráfico com uma única chamada: passe o dataset, o campo de rótulo (nome do mês) e um ou mais campos de valor (receita, custo).
uses
sgcHTML_Component_Chart;
procedure TERP_Server.BuildRevenueChart(aPage: TsgcHTMLPage; aDB: TFDConnection);
var
oChart: TsgcHTMLComponent_Chart;
oQ: TFDQuery;
begin
oChart := TsgcHTMLComponent_Chart.Create(nil);
oChart.PageBuilder := aPage.PageBuilder;
oChart.Section := 'charts';
oChart.SectionTitle := 'Revenue';
oChart.SectionOrder := 1;
oChart.ColumnWidth := cw8;
oChart.ChartType := ctBar;
oChart.Title := 'Monthly Revenue vs Cost (last 12 months)';
oChart.ShowLegend := True;
oChart.Stacked := False;
oChart.Responsive := True;
oQ := TFDQuery.Create(nil);
try
oQ.Connection := aDB;
oQ.SQL.Text :=
'SELECT DATE_FORMAT(issued,''%b'') AS month, ' +
' SUM(total) AS revenue, ' +
' SUM(cost) AS cost ' +
'FROM invoices ' +
'WHERE issued >= DATE_SUB(NOW(), INTERVAL 12 MONTH) ' +
'GROUP BY YEAR(issued), MONTH(issued) ' +
'ORDER BY YEAR(issued), MONTH(issued)';
oQ.Open;
// One call maps the query to labels + two datasets
oChart.LoadFromDataSet(oQ, 'month', ['revenue', 'cost']);
finally
oQ.Free;
end;
end;
Tabela de faturas paginada
O TsgcHTMLComponent_DataTable combina um TsgcHTMLComponent_Grid e um TsgcHTMLComponent_Pagination em um único widget com caixa de pesquisa integrada, rótulo de contagem de linhas, botão de exportação opcional e tamanhos de página configuráveis. O LoadFromDataSet lê os nomes e tipos das colunas automaticamente; você pode substituir os rótulos do cabeçalho e as larguras de coluna depois através de Grid.Columns.
uses
sgcHTML_Component_DataTable;
procedure TERP_Server.BuildInvoiceTable(aPage: TsgcHTMLPage; aDB: TFDConnection);
var
oTable: TsgcHTMLComponent_DataTable;
oQ: TFDQuery;
begin
oTable := TsgcHTMLComponent_DataTable.Create(nil);
oTable.PageBuilder := aPage.PageBuilder;
oTable.Section := 'invoices';
oTable.SectionTitle := 'Invoices';
oTable.SectionOrder := 1;
oTable.ColumnWidth := cw12;
oTable.Title := 'Recent Invoices';
oTable.ShowSearch := True;
oTable.ShowExport := True;
oTable.ShowPageSize := True;
oTable.PageSizes := '10,25,50';
oQ := TFDQuery.Create(nil);
try
oQ.Connection := aDB;
oQ.SQL.Text :=
'SELECT number, customer, issued, due, total, status ' +
'FROM invoices ' +
'ORDER BY issued DESC ' +
'LIMIT 200';
oQ.Open;
oTable.LoadFromDataSet(oQ, 20);
finally
oQ.Free;
end;
end;
Quadro Kanban para atendimento de pedidos
O TsgcHTMLComponent_KanbanBoard representa um quadro com múltiplas colunas e suporte a arrastar e soltar. Cada coluna contém uma coleção de cartões; cada cartão possui título, descrição opcional, responsável, tag e cor. Em um contexto de ERP, as colunas se mapeiam naturalmente aos estados dos pedidos: Recebido, Separação, Embalado, Enviado.
uses
sgcHTML_Component_KanbanBoard;
procedure TERP_Server.BuildKanban(aPage: TsgcHTMLPage; aDB: TFDConnection);
var
oBoard: TsgcHTMLComponent_KanbanBoard;
oCol: TsgcHTMLKanbanColumn;
oQ: TFDQuery;
vStatus: string;
begin
oBoard := TsgcHTMLComponent_KanbanBoard.Create(nil);
oBoard.PageBuilder := aPage.PageBuilder;
oBoard.Section := 'kanban';
oBoard.SectionTitle := 'Order Fulfilment';
oBoard.SectionOrder := 1;
oBoard.ColumnWidth := cw12;
// Create the four columns
for vStatus in ['Received', 'Picking', 'Packed', 'Shipped'] do
begin
oCol := oBoard.Columns.Add;
oCol.Title := vStatus;
oCol.Color := hcLight;
end;
oQ := TFDQuery.Create(nil);
try
oQ.Connection := aDB;
oQ.SQL.Text :=
'SELECT order_no, customer, qty_total, assigned_to, status ' +
'FROM orders ' +
'WHERE status IN (''Received'',''Picking'',''Packed'',''Shipped'') ' +
'ORDER BY created DESC LIMIT 100';
oQ.Open;
while not oQ.Eof do
begin
// Find the column whose title matches the order status
oCol := oBoard.Columns.FindByTitle(oQ.FieldByName('status').AsString);
if Assigned(oCol) then
oCol.AddCard(
oQ.FieldByName('order_no').AsString,
oQ.FieldByName('customer').AsString + ' — ' +
oQ.FieldByName('qty_total').AsString + ' items',
hcLight,
oQ.FieldByName('assigned_to').AsString
);
oQ.Next;
end;
finally
oQ.Free;
end;
end;
Montando a página completa
As quatro seções são montadas por um único procedimento ServeDashboard que cria uma página, chama cada construtor, envolve o resultado em um template Bootstrap e grava na resposta:
procedure TERP_Server.ServeDashboard(AResp: TIdHTTPResponseInfo);
var
oPage: TsgcHTMLPage;
oTemplate: TsgcHTMLTemplate_Bootstrap;
begin
oPage := TsgcHTMLPage.Create(nil);
oTemplate := TsgcHTMLTemplate_Bootstrap.Create(nil);
try
BuildKPIRow(oPage, FDB);
BuildRevenueChart(oPage, FDB);
BuildInvoiceTable(oPage, FDB);
BuildKanban(oPage, FDB);
oTemplate.Title := 'ERP Dashboard';
oTemplate.Page := oPage;
oTemplate.DarkMode := False;
oTemplate.BodyClass := 'bg-light';
AResp.ContentType := 'text/html; charset=utf-8';
AResp.ContentText := oTemplate.GetHTML;
AResp.ResponseNo := 200;
finally
oPage.Free;
oTemplate.Free;
end;
end;
Atualizações ao vivo via WebSocket
O dashboard seria estático sem dados ao vivo. O sgcHTML é construído sobre o sgcWebSockets, portanto enviar uma atualização de fragmento quando um novo pedido é registrado é uma única chamada de broadcast. No lado do cliente, o cartão de KPI possui um atributo id e a extensão WebSocket do htmx substitui seu conteúdo quando uma mensagem chega.
// Called from the order-processing thread when a new order is saved
procedure TERP_Server.OnNewOrderSaved(aOrder: TERPOrder);
var
oCard: TsgcHTMLComponent_StatCard;
begin
// Rebuild the "Open Invoices" KPI card with the updated count
oCard := TsgcHTMLComponent_StatCard.Create(nil);
try
oCard.CardID := 'kpi-open-invoices';
oCard.Title := 'Open Invoices';
oCard.Value := IntToStr(FDB.OpenInvoiceCount);
oCard.Color := scInfo;
oCard.Trend := stUp;
// Broadcast the new card HTML to all connected dashboard clients
HTTPServer.Broadcast(oCard.HTML, '/dashboard');
finally
oCard.Free;
end;
end;
O fragmento substitui o cartão correspondente no navegador imediatamente, sem recarregar a página e sem que o usuário precise clicar em nada. Cada cliente conectado vê a atualização em milissegundos.
O que você obtém ao final
O exemplo completo é uma única unit Delphi e um módulo de dados. Compile e execute: um dashboard ERP acessível pelo navegador aparece na porta configurada, com quatro cartões de KPI, um gráfico de barras de receita versus custo dos últimos 12 meses, uma tabela de faturas pesquisável e paginada e um quadro Kanban com múltiplas colunas para atendimento de pedidos. Quando novos pedidos são registrados, os cartões de KPI atualizam em tempo real em todas as abas do navegador conectadas. Total de código front-end escrito: zero linhas.
O mesmo padrão funciona para qualquer aplicação de negócios em Delphi: gestão de estoque, programação de produção, sistemas de RH, rastreamento de frotas. Se os dados estão em um TDataSet, podem estar em um dashboard web ao vivo em uma manhã.
Baixe a versão de avaliação gratuita do sgcHTML em esegece.com/products/sgchtml/download e explore os aplicativos de demonstração incluídos: ERP Demo, Admin Console, Live Monitor e Customer Portal, para um exemplo completo e funcional.
Dúvidas? Entre em contato. Você receberá uma resposta das pessoas que escreveram o código.
