ERP-systemen zijn het hart van Delphi-ontwikkeling: klanten, orders, facturen, voorraadhoeveelheden, productieruns. De bedrijfslogica leeft al in Object Pascal. De database is aangesloten. De rapporten draaien. Wat vaak ontbreekt is een via de browser toegankelijk dashboard dat het management op een tablet kan openen zonder iets te hoeven installeren. Dit artikel laat zien hoe je precies dat bouwt met sgcHTML, aan de hand van een realistisch scenario: een verkoop-ERP met live omzetcijfers, een facturenraster, een bezorging-Kanban-bord en real-time push wanneer nieuwe orders binnenkomen.
Alle code compileert in Delphi 10.4 Sydney en later. Het eindresultaat is een enkel .exe-bestand dat het dashboard serveert op poort 8080, met Bootstrap 5-opmaak, een responsieve layout en WebSocket live-updates, en zonder JavaScript-bestanden die naast het binaire bestand geplaatst moeten worden.
De server instellen
sgcHTML omhult een TsgcWSHTTPServer van sgcWebSockets. Zet TsgcHTMLEngine_Server op de datamodule naast de HTTP-server en verbind ze met elkaar. De engine onderschept automatisch verzoeken voor statische assets (Bootstrap CSS/JS, Chart.js, htmx); al het overige gaat naar jouw OnCommandGet-handler.
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;
KPI-statkaarten
De bovenste rij van het dashboard toont vier KPI-kaarten: totale omzet, openstaande facturen, aantal vervallen facturen en het percentage tijdige leveringen. TsgcHTMLComponent_StatCard verzorgt de volledige weergave: achtergrondverloop, titel, grote waarde, trendpijl en een optioneel voettekstlabel.
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;
Omzettrend-grafiek
De omzetgrafiek beslaat de tweede rij. TsgcHTMLComponent_Chart accepteert labelreeksen en datasets met rand- en vulkleuren. LoadFromDataSet koppelt een queryresultaat aan grafiekdata met één aanroep: geef de dataset mee, het labelveld (maandnaam) en een of meer waardevelden (omzet, kosten).
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;
Gepagineerde factuurentabel
TsgcHTMLComponent_DataTable combineert een TsgcHTMLComponent_Grid en een TsgcHTMLComponent_Pagination tot één widget met een ingebouwd zoekvak, een rijentellabel, een optionele exportknop en configureerbare paginagroottes. LoadFromDataSet leest kolomnamen en typen automatisch uit; daarna kun je kopteksten en kolombreedten aanpassen via 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;
Kanban-bord voor orderverwerking
TsgcHTMLComponent_KanbanBoard stelt een meerkolomig, sleepbaar bord voor. Elke kolom bevat een verzameling kaarten; elke kaart heeft een titel, een optionele beschrijving, een toegewezen persoon, een tag en een kleur. In een ERP-context komen de kolommen op natuurlijke wijze overeen met orderstaten: Received, Picking, Packed, Shipped.
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;
De volledige pagina samenstellen
De vier secties worden samengebracht in één ServeDashboard-procedure die een pagina aanmaakt, elke builder aanroept, het resultaat inpakt in een Bootstrap-sjabloon en naar de respons schrijft:
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;
Live updates via WebSocket
Het dashboard zou statisch zijn zonder live data. sgcHTML bouwt voort op sgcWebSockets, waardoor het pushen van een fragmentupdate wanneer een nieuwe order wordt geplaatst slechts één broadcast-aanroep vereist. Aan de clientzijde bevat de statkaart een id-attribuut en de htmx WebSocket-extensie vervangt de inhoud wanneer een bericht binnenkomt.
// 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;
Het fragment vervangt de bijbehorende kaart in de browser direct, zonder het herladen van de pagina en zonder dat de gebruiker ergens op hoeft te klikken. Elke verbonden client ziet de update binnen milliseconden.
Wat je aan het einde hebt
Het volledige voorbeeld bestaat uit één Delphi-unit en een datamodule. Compileer en start: er verschijnt een via de browser toegankelijk ERP-dashboard op de geconfigureerde poort, met vier KPI-kaarten, een staafgrafiek van omzet versus kosten over 12 maanden, een doorzoekbare en gepagineerde factuurentabel en een meerkolomig Kanban-bord voor orderverwerking. Wanneer nieuwe orders worden geplaatst, worden de KPI-kaarten in real time bijgewerkt in elk verbonden browsertabblad. Het totale aantal geschreven front-endregels: nul.
Hetzelfde patroon werkt voor elke Delphi-bedrijfsapplicatie: voorraadbeheer, productieplanning, HR-systemen, vlootbeheer. Als de data in een TDataSet zit, kan die 's ochtends al op een live webdashboard staan.
Download de gratis proefversie van sgcHTML via esegece.com/products/sgchtml/download en bekijk de meegeleverde ERP-demo, Beheerconsole, Live Monitor en Klantenportal-applicaties voor een volledig werkend voorbeeld.
Vragen? Neem contact op. Je krijgt antwoord van de mensen die de code hebben geschreven.
