bways-report
v0.1.5
Published
Enterprise-grade, metadata-driven reporting engine and visual page builder for Angular 18+ ERP applications.
Maintainers
Readme
BWays-Report — Integration & Developer Guide
bways-report is an enterprise-grade, metadata-driven reporting platform and visual dynamic page builder for Angular 18+ business & ERP applications.
It allows non-developers to visually design reporting pages with filters, KPI metric cards, charts, and grids. The report configuration is saved purely as JSON metadata (ReportDefinition), and the runtime engine dynamically instantiates the reactive forms, controls, data queries, and results grid on the fly.
Table of Contents
- Automated Setup (ng add)
- Quick Start (Rule of 1)
- Configuration Options & Zero-Backend Dev
- Implementing Host Adapters (Advanced)
- Routing & ERP Navigation Integration
- Drill-Down Drill-Through & Slide-Over Drawers
- Starter Blueprints Gallery & Branded Exports
1. Automated Setup (ng add)
Run the Angular CLI schematic to configure your application in seconds:
ng add bways-reportThis automatically installs required dependencies and adds provideReporting() to your src/app/app.config.ts.
2. Quick Start (Rule of 1)
A. 1-Line App Setup
In your Angular 18+ application:
// src/app/app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideReporting } from 'bways-report';
export const appConfig: ApplicationConfig = {
providers: [
// 1-line setup: automatically hooks up HTTP adapters, UltraGridComponent, and session headers
provideReporting({ apiBaseUrl: '/api/reporting' }),
],
};Zero-Backend / Local Storage Mode: For local prototyping, offline demos, or frontend-only dev, use:
provideReporting(withLocalStorage()); // seeds built-in blueprints and saves to browser localStorage
B. 1-Tag Runtime Page
Render any saved report with its dynamic filter form, auto-bound stored procedure results, top toolbar, export actions, and saved view presets:
import { Component } from '@angular/core';
import { RptReportComponent } from 'bways-report';
@Component({
standalone: true,
imports: [RptReportComponent],
template: `
<!-- 1 tag to run and render the full report page -->
<rpt-report [id]="'sales-ledger'" />
`,
})
export class SalesReportPage {}C. 1-Tag Visual Report Designer
Embed the full drag-and-drop report builder into your ERP settings or admin portal:
import { Component } from '@angular/core';
import { RptReportDesignerComponent } from 'bways-report';
@Component({
standalone: true,
imports: [RptReportDesignerComponent],
template: `
<!-- 1 tag to build, preview, or modify any report -->
<rpt-report-designer
[id]="'sales-ledger'"
(saved)="onReportSaved($event)"
/>
`,
})
export class ReportBuilderPage {
onReportSaved(definition: any) {
console.log('Report saved successfully:', definition);
}
}D. Saved Views & Presets in JSON
When users filter, reorder, group, or format columns in the grid, they can click "+ Save View" to persist their customized view preset. Presets are saved directly into the report's JSON:
{
"grid": {
"dataSourceId": "ds-sales",
"columns": [...],
"options": {
"presets": [
{
"id": "preset_1728200000",
"name": "Q3 High Value Clients",
"gridState": {
"filters": { "amount": { "operator": "gte", "value": 50000 } },
"columnOrder": ["invoiceId", "amount", "customerName"]
}
}
]
}
}
}Users can switch between views seamlessly from the top dropdown, keeping all formatting and custom filters intact.
Backend Integration: Mounting BwaysReportingModule in NestJS
To power the built-in HTTP adapters, mount BwaysReportingModule directly inside your existing NestJS host application:
// app.module.ts (in your NestJS backend)
import { Module } from '@nestjs/common';
import { BwaysReportingModule } from '@bways/reporting-backend';
@Module({
imports: [
BwaysReportingModule.forRoot({
security: {
allowedSchemas: ['dbo', 'reporting'],
disallowRawSql: true, // SQL injection prevention
validateEntitiesAgainstCatalog: true, // Checks sys.procedures catalog
},
}),
],
})
export class AppModule {}Option B: Custom Adapters (Full Manual Control)
If you don't use the standard backend endpoints, you can implement your own adapters:
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes),
provideBwaysReport({
gridComponent: BWaysGridComponent,
dataSourceAdapter: MyCustomDataSourceAdapter,
persistenceAdapter: MyCustomPersistenceAdapter,
masterDataProvider: MyCustomMasterDataProvider,
defaultPageSize: 25,
enableDesigner: true,
}),
],
};3. Implementing Host Adapters
The library is decoupled from your backend. You provide your business application's API handlers by extending three abstract contracts:
A. Data Source Adapter
Responsible for executing report queries and returning tabular result rows:
// src/app/services/erp-datasource.adapter.ts
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { ReportDataSourceAdapter, ReportDataSourceDefinition } from 'bways-report';
@Injectable({ providedIn: 'root' })
export class ErpDataSourceAdapter extends ReportDataSourceAdapter {
private readonly http = inject(HttpClient);
execute(
dataSource: ReportDataSourceDefinition,
parameters: Record<string, unknown>
): Observable<unknown[]> {
// Send report filter parameters to your backend API endpoint
return this.http.post<unknown[]>(dataSource.endpoint, parameters);
}
}B. Persistence Adapter
Responsible for saving, loading, listing, and publishing report metadata definitions (ReportDefinition):
// src/app/services/erp-persistence.adapter.ts
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import {
ReportPersistenceAdapter,
ReportDefinition,
ReportSummary,
} from 'bways-report';
@Injectable({ providedIn: 'root' })
export class ErpPersistenceAdapter extends ReportPersistenceAdapter {
private readonly http = inject(HttpClient);
private readonly baseUrl = '/api/reports';
load(reportId: string): Observable<ReportDefinition> {
return this.http.get<ReportDefinition>(`${this.baseUrl}/${reportId}`);
}
save(report: ReportDefinition): Observable<ReportDefinition> {
return this.http.post<ReportDefinition>(this.baseUrl, report);
}
list(): Observable<ReportSummary[]> {
return this.http.get<ReportSummary[]>(this.baseUrl);
}
delete(reportId: string): Observable<void> {
return this.http.delete<void>(`${this.baseUrl}/${reportId}`);
}
publish(reportId: string, version: number): Observable<void> {
return this.http.post<void>(`${this.baseUrl}/${reportId}/publish`, { version });
}
}C. Master Data Provider
Supplies option lists for dropdowns and lookups (e.g., Customers, Warehouses, Statuses):
// src/app/services/erp-masterdata.provider.ts
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { MasterDataProvider, DropdownOption } from 'bways-report';
@Injectable({ providedIn: 'root' })
export class ErpMasterDataProvider extends MasterDataProvider {
private readonly http = inject(HttpClient);
getOptions(
masterName: string,
filter?: Record<string, unknown>
): Observable<DropdownOption[]> {
return this.http.get<DropdownOption[]>(`/api/master-data/${masterName}`, {
params: filter as any,
});
}
}4. Routing & ERP Navigation Integration
Dynamic Route Generation
Mount all report runtime and designer routes with a single call in your app.routes.ts:
// src/app/app.routes.ts
import { Routes } from '@angular/router';
import { createReportRoutes } from 'bways-report';
export const routes: Routes = [
{
path: 'reports',
children: createReportRoutes({
// Optional: enforce auth or role guards
runtimeGuards: [],
designerGuards: [], // e.g. [AdminGuard]
enableDesignerRoutes: true, // creates 'new' and ':reportId/edit' routes
}),
},
];This instantly creates:
/reports/:reportId— RendersRoutedReportRuntimeComponent(resolves report viareportResolver, presents interactive filters, KPI metrics, chart, and result grid)./reports/:reportId/edit— RendersRoutedReportDesignerComponent(visual drag-and-drop builder loaded with current definition)./reports/new— RendersRoutedReportDesignerComponentinitialized with a blank template.
Categorized ERP Sidebar / Header Navigation
Use ReportMenuService to generate hierarchical sidebar menus grouped by category (e.g. Finance, Sales, Inventory):
// src/app/components/sidebar/erp-sidebar.component.ts
import { Component, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { ReportMenuService } from 'bways-report';
@Component({
selector: 'erp-sidebar',
standalone: true,
imports: [CommonModule, RouterModule],
template: `
<aside class="sidebar">
<input
type="search"
placeholder="Search reports..."
(input)="onSearch($event)"
/>
@for (group of menuService.menuGroups(); track group.category) {
<div class="menu-group">
<h4>{{ group.displayName }}</h4>
<ul>
@for (item of group.items; track item.id) {
<li>
<a [routerLink]="item.route" routerLinkActive="active">
{{ item.name }}
@if (item.isDraft) {
<span class="badge">Draft</span>
}
</a>
</li>
}
</ul>
</div>
}
</aside>
`,
})
export class ErpSidebarComponent {
readonly menuService = inject(ReportMenuService);
onSearch(event: Event): void {
const query = (event.target as HTMLInputElement).value;
this.menuService.setSearchQuery(query);
}
}5. Embedding Components Directly
If you prefer embedding components inside existing pages instead of routing:
Report Runtime (Interactive Report Page)
import { Component } from '@angular/core';
import { ReportRuntimeComponent, ReportDefinition } from 'bways-report';
@Component({
selector: 'my-report-page',
standalone: true,
imports: [ReportRuntimeComponent],
template: `
<!-- Option A: Load by ID via persistence adapter -->
<rpt-runtime [reportId]="'sales-register-2024'" />
<!-- Option B: Pass ReportDefinition directly -->
<!-- <rpt-runtime [report]="customDefinition" /> -->
`,
})
export class MyReportPageComponent {}Visual Drag-and-Drop Designer
import { Component } from '@angular/core';
import { DesignerComponent, ReportDefinition } from 'bways-report';
@Component({
selector: 'my-designer-page',
standalone: true,
imports: [DesignerComponent],
template: `
<rpt-designer
[initialReport]="reportDefinition"
(save)="onSaveReport($event)"
/>
`,
})
export class MyDesignerPageComponent {
reportDefinition: ReportDefinition = /* ... */;
onSaveReport(updatedReport: ReportDefinition): void {
console.log('Saved report schema:', updatedReport);
}
}6. Grid & Chart Bridge Integration (BWays-Grid)
Using BWays-Grid or Custom Grid
Provide your grid component via the GRID_COMPONENT token in provideBwaysReport():
provideBwaysReport({
gridComponent: BWaysGridComponent, // or AgGridWrapperComponent
})ReportGridBridgeComponent dynamically passes the data rows, columns configuration, sorting, and aggregate settings directly to your grid component. If no grid is provided, bways-report renders its high-performance built-in tabular grid with pagination and column sorting.
Using Chart Components
Provide a chart component wrapper (Chart.js, ApexCharts, Highcharts) via chartComponent:
provideBwaysReport({
chartComponent: MyHostChartComponent,
})If not provided, the built-in SVG visual chart renderer outputs crisp, responsive Bar charts, Donut charts, and Line/Area charts with zero external dependencies.
7. Theming & CSS Custom Properties
bways-report uses CSS custom properties (--rpt-*) throughout all components, ensuring seamless alignment with your ERP application's design system:
/* In your host application's styles.scss or theme file */
:root {
/* Brand Colors */
--rpt-primary: #2563eb;
--rpt-primary-hover: #1d4ed8;
--rpt-primary-focus: rgba(37, 99, 235, 0.25);
/* Surface & Text */
--rpt-surface-bg: #ffffff;
--rpt-bg-subtle: #f8fafc;
--rpt-text-color: #0f172a;
--rpt-text-muted: #64748b;
--rpt-border-color: #e2e8f0;
/* Geometry & Typography */
--rpt-radius-md: 6px;
--rpt-radius-lg: 12px;
--rpt-font-family: 'Inter', system-ui, sans-serif;
--rpt-font-size-base: 0.875rem;
/* Status Colors */
--rpt-success-color: #10b981;
--rpt-warning-color: #f59e0b;
--rpt-error-color: #ef4444;
}8. Registering Custom Host Controls
You can register custom Angular components (e.g., custom slider, multi-select, rich customer picker) into the ComponentRegistry so they appear in the Visual Designer palette and render dynamically at runtime:
import { ComponentRegistry, ComponentMetadata } from 'bways-report';
import { MyCustomPickerComponent } from './my-custom-picker.component';
export function configureCustomControls(registry: ComponentRegistry): void {
registry.register({
type: 'customer-picker',
displayName: 'Customer Picker',
category: 'input',
icon: 'person_search',
component: MyCustomPickerComponent,
createDefaultConfig: () => ({
id: 'cust-picker',
type: 'customer-picker',
label: 'Select Customer',
properties: { placeholder: 'Search customer...' },
layout: { columnSpan: 4 },
}),
propertySchema: [
{ key: 'label', label: 'Label', type: 'string', required: true, group: 'General' },
{ key: 'key', label: 'Field Name (Key)', type: 'string', required: true, group: 'General' },
{ key: 'required', label: 'Required', type: 'boolean', group: 'Validation' },
],
});
}9. Exporting Data (CSV & JSON)
Use ReportExportService for one-click file generation:
import { Component, inject } from '@angular/core';
import { ReportExportService } from 'bways-report';
@Component({
selector: 'export-actions',
standalone: true,
template: `
<button (click)="downloadCsv()">Download CSV</button>
<button (click)="downloadJson()">Download Metadata</button>
`,
})
export class ExportActionsComponent {
private readonly exportService = inject(ReportExportService);
downloadCsv(): void {
const data = [
{ invoiceNo: 'INV-001', customer: 'Acme Corp', total: 4950 },
{ invoiceNo: 'INV-002', customer: 'Globex Inc', total: 14080 },
];
this.exportService.exportCsv(data, {
fileName: 'invoices-export.csv',
columns: [
{ field: 'invoiceNo', header: 'Invoice #' },
{ field: 'customer', header: 'Customer' },
{
field: 'total',
header: 'Total ($)',
formatter: (val: number) => `$${val.toLocaleString()}`
},
],
addBom: true, // Includes UTF-8 BOM so Microsoft Excel renders international characters properly
});
}
downloadJson(): void {
this.exportService.exportJson(myReportDefinition, {
fileName: 'report-schema.json',
pretty: true,
});
}
}License
MIT © BWays Technologies
