nexabase-report 0.4.21 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +181 -469
- package/dist/{html2pdf-23Ab3AOC.js → html2pdf-BM387Lw6.js} +2 -2
- package/dist/{index-BdWKxQHc.js → index-Dp2g4-Q2.js} +16431 -16234
- package/dist/{index.es-DlySepqf.js → index.es-CIgwm7xe.js} +2 -2
- package/dist/{jspdf.es.min-BoOLuS4X.js → jspdf.es.min-Hv7XZ2or.js} +1 -1
- package/dist/lib/styles/_designer.css +1 -1
- package/dist/nexabase-report.es.js +1 -1
- package/dist/nexabase-report.umd.js +153 -153
- package/examples/integration-vue.html +50 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,108 +1,59 @@
|
|
|
1
1
|
# nexabase-report
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
[](LICENSE)
|
|
5
|
-
[](https://www.typescriptlang.org/)
|
|
6
|
-
[](https://vuejs.org/)
|
|
7
|
-
|
|
8
|
-
> Librería Vue 3 + TypeScript para diseñar y visualizar reportes tipo banded (inspirada en Stimulsoft / DevExpress). Incluye **diseñador WYSIWYG** multilingüe (ES/EN/PT), **visor como Custom Element** framework-agnostic, y **exportación 100% cliente** a PDF, Excel, Word y CSV.
|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
## Características
|
|
13
|
-
|
|
14
|
-
| Característica | Descripción |
|
|
15
|
-
|----------------|-------------|
|
|
16
|
-
| **Custom Element** | `<nexa-viewer>` funciona en Vue, React, Angular, Svelte, Blazor o HTML puro |
|
|
17
|
-
| **Diseñador WYSIWYG** | `NexaDesigner.vue` — ribbon, lienzo con snap-to-grid, sidebars, i18n |
|
|
18
|
-
| **Visor standalone** | Sin dependencia de Vue en el consumidor |
|
|
19
|
-
| **Exportación 100% cliente** | PDF (html2pdf.js + fallback jsPDF vectorial), Excel (SheetJS), Word (docx), CSV (UTF-8 BOM) |
|
|
20
|
-
| **Motor de paginación** | Algoritmo en dos fases: streaming + particionado por página, con splitting de tablas a través de páginas |
|
|
21
|
-
| **Renderers** | Texto, imágenes, barras, QR, shapes, drill-down, charts (ECharts), crosstabs, subreportes, widgets |
|
|
22
|
-
| **Watermarks** | Modo tile (texto repetido en grilla) o single posicionado (center, top-left, bottom-right, etc.) |
|
|
23
|
-
| **TOC** | Tabla de contenidos auto-generada en página 1 con enlaces a secciones |
|
|
24
|
-
| **Dashboard** | Modo de visualización con grilla de widgets |
|
|
25
|
-
| **Gráficos** | ECharts (barras, líneas, pastel, área, scatter, radar, etc.) |
|
|
26
|
-
| **Tablas dinámicas** | Crosstabs con agregaciones (sum, count, avg, min, max) |
|
|
27
|
-
| **Códigos QR / Barras** | jsbarcode + qrcode con bindings a datos |
|
|
28
|
-
| **Formato condicional** | Reglas por operador (eq, gt, contains, between, regex, etc.) |
|
|
29
|
-
| **Master-Detail** | DataBand con DetailBand hijo, filtro por campo clave |
|
|
30
|
-
| **Drill-Down** | Navegación a reporte destino con paso de parámetros |
|
|
31
|
-
| **Subreportes** | Anidamiento vía definición embebida o remota (API REST) |
|
|
32
|
-
| **Motor de expresiones** | Seguro (sin `eval`): `FormatNumber`, `FormatDate`, `IIF`, `ISNULL`, agregaciones, `SUBSTRING`, etc. |
|
|
33
|
-
| **Parámetros** | Diálogo de entrada con tipos (text, number, date, boolean, select), validación, skip opcional |
|
|
34
|
-
| **Shapes** | Rectángulos, elipses, líneas y flechas |
|
|
35
|
-
| **Búsqueda** | Highlight inline con navegación entre resultados |
|
|
36
|
-
| **Zoom** | Zoom +/- 10%, fitToWidth, fitToPage, modo continuo/página simple |
|
|
37
|
-
| **i18n** | Visor y diseñador en español, inglés, portugués |
|
|
38
|
-
|
|
39
|
-
---
|
|
40
|
-
|
|
41
|
-
## Instalación
|
|
3
|
+
> Professional report designer and viewer — framework-agnostic, client-side PDF/Excel/Word export, banded report model inspired by Stimulsoft & DevExpress.
|
|
42
4
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/nexabase-report)
|
|
6
|
+
[](https://www.npmjs.com/package/nexabase-report)
|
|
7
|
+
[](https://bundlephobia.com/package/nexabase-report)
|
|
8
|
+
[](https://github.com/nexabase/nexabase-report/blob/main/LICENSE)
|
|
46
9
|
|
|
47
|
-
|
|
10
|
+
## Features
|
|
48
11
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
12
|
+
- **WYSIWYG Designer** — drag & drop bands, elements, charts, crosstabs, barcodes, QR codes
|
|
13
|
+
- **Framework-agnostic Viewer** — Custom Element (`<nexa-viewer>`) works with Vue, React, Angular, Blazor, jQuery, or plain HTML
|
|
14
|
+
- **Client-side Export** — PDF (selectable text), Excel, Word, CSV — no server required
|
|
15
|
+
- **Banded Report Model** — ReportHeader, PageHeader/Footer, DataBand, GroupHeader/Footer, ReportFooter
|
|
16
|
+
- **Charts & Crosstabs** — ECharts integration, dynamic pivot tables with aggregations
|
|
17
|
+
- **Dashboard Designer** — widgets (chart, gauge, indicator, table, filter, pivot, text, image)
|
|
18
|
+
- **Expression Engine** — safe evaluation without `eval` — `FormatNumber`, `IIF`, `ISNULL`, `UPPER`, etc.
|
|
19
|
+
- **Master-Detail & Drill-down** — nested data sources, subreports, clickable drill-through
|
|
20
|
+
- **Conditional Formatting** — dynamic styles based on data values
|
|
21
|
+
- **Parameters** — typed report parameters with validation dialog
|
|
22
|
+
- **i18n** — built-in Spanish, English, Portuguese
|
|
52
23
|
|
|
53
|
-
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install nexabase-report
|
|
28
|
+
```
|
|
54
29
|
|
|
55
|
-
##
|
|
30
|
+
## Quick Start
|
|
56
31
|
|
|
57
|
-
### HTML
|
|
32
|
+
### Plain HTML / Any Framework
|
|
58
33
|
|
|
59
34
|
```html
|
|
60
35
|
<!DOCTYPE html>
|
|
61
36
|
<html>
|
|
62
37
|
<head>
|
|
63
|
-
<link rel="stylesheet" href="
|
|
38
|
+
<link rel="stylesheet" href="node_modules/nexabase-report/dist/style.css">
|
|
64
39
|
</head>
|
|
65
40
|
<body>
|
|
66
41
|
<nexa-viewer id="viewer" minimal></nexa-viewer>
|
|
67
42
|
|
|
68
|
-
<script src="
|
|
43
|
+
<script src="node_modules/nexabase-report/dist/nexabase-report.umd.js"></script>
|
|
69
44
|
<script>
|
|
70
|
-
NexaReport.registerNexaReport();
|
|
71
|
-
|
|
72
45
|
const viewer = document.getElementById('viewer');
|
|
73
|
-
viewer.definition = { /*
|
|
74
|
-
viewer.data = [ /*
|
|
46
|
+
viewer.definition = { /* report JSON */ };
|
|
47
|
+
viewer.data = [ /* array of records */ ];
|
|
75
48
|
</script>
|
|
76
49
|
</body>
|
|
77
50
|
</html>
|
|
78
51
|
```
|
|
79
52
|
|
|
80
|
-
### React
|
|
81
|
-
|
|
82
|
-
```tsx
|
|
83
|
-
import { useEffect, useRef } from 'react';
|
|
84
|
-
import { registerNexaReport } from 'nexabase-report';
|
|
85
|
-
import 'nexabase-report/style.css';
|
|
86
|
-
|
|
87
|
-
registerNexaReport();
|
|
88
|
-
|
|
89
|
-
export function ReportViewer({ definition, data }: { definition: any; data: any[] }) {
|
|
90
|
-
const ref = useRef<any>(null);
|
|
91
|
-
|
|
92
|
-
useEffect(() => {
|
|
93
|
-
if (!ref.current) return;
|
|
94
|
-
ref.current.definition = definition;
|
|
95
|
-
ref.current.data = data;
|
|
96
|
-
}, [definition, data]);
|
|
97
|
-
|
|
98
|
-
return <nexa-viewer ref={ref} minimal />;
|
|
99
|
-
}
|
|
100
|
-
```
|
|
101
|
-
|
|
102
53
|
### Vue 3
|
|
103
54
|
|
|
104
55
|
```vue
|
|
105
|
-
<script setup
|
|
56
|
+
<script setup>
|
|
106
57
|
import { registerNexaReport } from 'nexabase-report';
|
|
107
58
|
import 'nexabase-report/style.css';
|
|
108
59
|
|
|
@@ -110,473 +61,234 @@ registerNexaReport();
|
|
|
110
61
|
</script>
|
|
111
62
|
|
|
112
63
|
<template>
|
|
113
|
-
<nexa-viewer
|
|
114
|
-
:definition="definition"
|
|
115
|
-
:data="data"
|
|
116
|
-
minimal
|
|
117
|
-
style="display:block;height:700px"
|
|
118
|
-
/>
|
|
64
|
+
<nexa-viewer :definition="reportDef" :data="reportData" minimal />
|
|
119
65
|
</template>
|
|
120
66
|
```
|
|
121
67
|
|
|
122
|
-
###
|
|
68
|
+
### React
|
|
123
69
|
|
|
124
|
-
```
|
|
125
|
-
import {
|
|
70
|
+
```tsx
|
|
71
|
+
import { useEffect, useRef } from 'react';
|
|
72
|
+
import 'nexabase-report';
|
|
126
73
|
import 'nexabase-report/style.css';
|
|
127
74
|
|
|
128
|
-
|
|
75
|
+
function ReportViewer({ definition, data }: { definition: any; data: any[] }) {
|
|
76
|
+
const ref = useRef<any>(null);
|
|
129
77
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
async ngOnInit() {
|
|
139
|
-
this.reportDef = await fetch('/assets/report.json').then(r => r.json());
|
|
140
|
-
this.reportData = await fetch('/api/data').then(r => r.json());
|
|
141
|
-
}
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (ref.current) {
|
|
80
|
+
ref.current.definition = definition;
|
|
81
|
+
ref.current.data = data;
|
|
82
|
+
}
|
|
83
|
+
}, [definition, data]);
|
|
84
|
+
|
|
85
|
+
return <nexa-viewer ref={ref} minimal style={{ width: '100%', height: '100%' }} />;
|
|
142
86
|
}
|
|
143
87
|
```
|
|
144
88
|
|
|
145
|
-
### Blazor
|
|
89
|
+
### Blazor
|
|
146
90
|
|
|
147
91
|
```razor
|
|
148
|
-
@page "/reporte/{Id:int}"
|
|
149
|
-
@inject IJSRuntime JS
|
|
150
|
-
|
|
151
|
-
<link rel="stylesheet" href="_content/nexabase-report/style.css" />
|
|
152
|
-
<script src="_content/nexabase-report/nexabase-report.umd.js"></script>
|
|
153
|
-
|
|
154
92
|
<nexa-viewer id="viewer" minimal></nexa-viewer>
|
|
155
93
|
|
|
156
94
|
@code {
|
|
157
|
-
[Parameter] public int Id { get; set; }
|
|
158
|
-
|
|
159
95
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
160
96
|
{
|
|
161
|
-
if (
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
97
|
+
if (firstRender)
|
|
98
|
+
{
|
|
99
|
+
await JS.InvokeVoidAsync("initViewer", reportDef, data);
|
|
100
|
+
}
|
|
165
101
|
}
|
|
166
102
|
}
|
|
103
|
+
```
|
|
167
104
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
v.definition = definition;
|
|
176
|
-
v.data = data;
|
|
177
|
-
};
|
|
178
|
-
</script>
|
|
105
|
+
```javascript
|
|
106
|
+
// wwwroot/report.js
|
|
107
|
+
function initViewer(def, data) {
|
|
108
|
+
const viewer = document.getElementById('viewer');
|
|
109
|
+
viewer.definition = def;
|
|
110
|
+
viewer.data = data;
|
|
111
|
+
}
|
|
179
112
|
```
|
|
180
113
|
|
|
181
|
-
|
|
114
|
+
## Viewer API
|
|
182
115
|
|
|
183
|
-
|
|
116
|
+
### Props
|
|
184
117
|
|
|
185
|
-
|
|
118
|
+
| Prop | Type | Description |
|
|
119
|
+
|------|------|-------------|
|
|
120
|
+
| `definition` | `NexaReportDefinition \| string` | Report definition (object or JSON string) |
|
|
121
|
+
| `data` | `any[] \| Record<string, any[]>` | Data — array for single datasource, object for multiple |
|
|
122
|
+
| `minimal` | `boolean` | Hide toolbar and sidebar |
|
|
123
|
+
| `parameters` | `Record<string, any>` | Pre-fill parameter values (skips dialog) |
|
|
124
|
+
| `skipParamsDialog` | `boolean` | Don't show parameter input dialog |
|
|
125
|
+
| `currentPage` | `number` | Externally controlled page number |
|
|
126
|
+
| `locale` | `'es' \| 'en' \| 'pt'` | UI language |
|
|
127
|
+
| `licenseKey` | `string` | License key (optional) |
|
|
186
128
|
|
|
187
|
-
|
|
188
|
-
- **Lienzo** — snap-to-grid (5 px), rulers, selección múltiple, arrastrar/redimensionar
|
|
189
|
-
- **Panel de propiedades** — secciones: apariencia, datos, bordes, formato condicional
|
|
190
|
-
- **Diccionario de datos** — campos del datasource con drag al lienzo
|
|
191
|
-
- **Multilingüe** — español, inglés, portugués, cambiable desde la barra
|
|
192
|
-
- **Import/Export** — definiciones JSON
|
|
193
|
-
- **Deshacer/Rehacer** — Ctrl+Z / Ctrl+Y
|
|
194
|
-
- **Clipboard** — copiar/pegar elementos entre bandas
|
|
129
|
+
### Methods
|
|
195
130
|
|
|
196
|
-
|
|
131
|
+
| Method | Returns | Description |
|
|
132
|
+
|--------|---------|-------------|
|
|
133
|
+
| `exportPdf()` | `Promise<void>` | Export to PDF and download |
|
|
134
|
+
| `exportPdfAsBlob()` | `Promise<Blob>` | Export to PDF as Blob (for ZIP, upload, etc.) |
|
|
135
|
+
| `exportExcel()` | `Promise<void>` | Export to Excel and download |
|
|
136
|
+
| `exportWord()` | `Promise<void>` | Export to Word and download |
|
|
137
|
+
| `exportCsv()` | `Promise<void>` | Export to CSV and download |
|
|
138
|
+
| `printReport()` | `void` | Print the report |
|
|
139
|
+
| `updateData(data)` | `void` | Update data without re-rendering definition |
|
|
197
140
|
|
|
198
|
-
|
|
199
|
-
<script setup lang="ts">
|
|
200
|
-
import { NexaDesigner } from 'nexabase-report';
|
|
201
|
-
import 'nexabase-report/style.css';
|
|
202
|
-
import { ref } from 'vue';
|
|
141
|
+
### Events
|
|
203
142
|
|
|
204
|
-
|
|
205
|
-
|
|
143
|
+
| Event | Payload | Description |
|
|
144
|
+
|-------|---------|-------------|
|
|
145
|
+
| `data-request` | `{ alias: string }` | Request data for a datasource |
|
|
146
|
+
| `drill-click` | `{ element, sourceData, targetReportId, parameters }` | Drill-down element clicked |
|
|
147
|
+
| `subreport-toggle` | `{ subReportId, collapsed }` | Subreport expanded/collapsed |
|
|
148
|
+
| `page-change` | `{ page: number }` | Page changed |
|
|
206
149
|
|
|
207
|
-
|
|
208
|
-
console.log('Reporte guardado:', reportDef);
|
|
209
|
-
}
|
|
210
|
-
</script>
|
|
150
|
+
## Batch Export (ZIP)
|
|
211
151
|
|
|
212
|
-
|
|
213
|
-
<NexaDesigner ref="designerRef" :locale="locale" @save="onSave" style="height: 100vh" />
|
|
214
|
-
</template>
|
|
215
|
-
```
|
|
152
|
+
Export multiple reports to a single ZIP file without showing the viewer:
|
|
216
153
|
|
|
217
|
-
|
|
154
|
+
```javascript
|
|
155
|
+
import { registerNexaReport } from 'nexabase-report';
|
|
156
|
+
import JSZip from 'jszip';
|
|
218
157
|
|
|
219
|
-
|
|
220
|
-
|------|------|---------|-------------|
|
|
221
|
-
| `locale` | `'es' \| 'en' \| 'pt'` | `'es'` | Idioma del diseñador |
|
|
222
|
-
| `initialReport` | `NexaReportDefinition` | — | Reporte a editar (nuevo si no se pasa) |
|
|
158
|
+
registerNexaReport();
|
|
223
159
|
|
|
224
|
-
|
|
160
|
+
async function exportBatch(reportDef, invoices) {
|
|
161
|
+
// Create hidden viewer
|
|
162
|
+
const viewer = document.createElement('nexa-viewer');
|
|
163
|
+
viewer.style.display = 'none';
|
|
164
|
+
viewer.minimal = true;
|
|
165
|
+
viewer.definition = reportDef;
|
|
166
|
+
document.body.appendChild(viewer);
|
|
225
167
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
| `save` | `NexaReportDefinition` | Usuario presionó guardar |
|
|
168
|
+
await customElements.whenDefined('nexa-viewer');
|
|
169
|
+
await new Promise(r => setTimeout(r, 300));
|
|
229
170
|
|
|
230
|
-
|
|
171
|
+
const zip = new JSZip();
|
|
231
172
|
|
|
232
|
-
|
|
173
|
+
for (const invoice of invoices) {
|
|
174
|
+
viewer.updateData(invoice.items);
|
|
175
|
+
await new Promise(r => setTimeout(r, 500));
|
|
176
|
+
const blob = await viewer.exportPdfAsBlob();
|
|
177
|
+
zip.file(invoice.name + '.pdf', blob);
|
|
178
|
+
}
|
|
233
179
|
|
|
234
|
-
|
|
180
|
+
const zipBlob = await zip.generateAsync({ type: 'blob' });
|
|
235
181
|
|
|
236
|
-
|
|
182
|
+
// Download
|
|
183
|
+
const url = URL.createObjectURL(zipBlob);
|
|
184
|
+
const a = document.createElement('a');
|
|
185
|
+
a.href = url;
|
|
186
|
+
a.download = 'invoices.zip';
|
|
187
|
+
a.click();
|
|
188
|
+
URL.revokeObjectURL(url);
|
|
237
189
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
showToolbar
|
|
242
|
-
showThumbs
|
|
243
|
-
locale="es"
|
|
244
|
-
currentPage="1"
|
|
245
|
-
skipParamsDialog
|
|
246
|
-
></nexa-viewer>
|
|
190
|
+
// Cleanup
|
|
191
|
+
document.body.removeChild(viewer);
|
|
192
|
+
}
|
|
247
193
|
```
|
|
248
194
|
|
|
249
|
-
|
|
195
|
+
## Designer
|
|
196
|
+
|
|
197
|
+
The designer is a Vue 3 component for building reports visually:
|
|
250
198
|
|
|
251
199
|
```vue
|
|
252
|
-
<script setup
|
|
253
|
-
import
|
|
200
|
+
<script setup>
|
|
201
|
+
import NexaDesigner from 'nexabase-report/src/lib/designer/NexaDesigner.vue';
|
|
254
202
|
import 'nexabase-report/style.css';
|
|
255
|
-
|
|
256
|
-
registerNexaReport();
|
|
257
203
|
</script>
|
|
258
204
|
|
|
259
205
|
<template>
|
|
260
|
-
<
|
|
261
|
-
:definition="reportDef"
|
|
262
|
-
:data="reportData"
|
|
263
|
-
:parameters="params"
|
|
264
|
-
locale="es"
|
|
265
|
-
@page-change="onPageChange"
|
|
266
|
-
@drill-click="onDrillClick"
|
|
267
|
-
/>
|
|
206
|
+
<NexaDesigner :report="reportDefinition" @save="handleSave" />
|
|
268
207
|
</template>
|
|
269
208
|
```
|
|
270
209
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
| Prop | Tipo | Default | Descripción |
|
|
274
|
-
|------|------|---------|-------------|
|
|
275
|
-
| `definition` | `string \| NexaReportDefinition` | — | Definición del reporte (objeto o JSON string) |
|
|
276
|
-
| `data` | `string \| any[] \| Record<string, any[]>` | — | Datos: array simple (alias "main"), objeto multi-alias, o JSON string |
|
|
277
|
-
| `parameters` | `Record<string, any>` | — | Valores iniciales de parámetros |
|
|
278
|
-
| `minimal` | `boolean` | `false` | Oculta toolbar y thumbnails |
|
|
279
|
-
| `showToolbar` | `boolean` | `true` | Forzar toolbar visible (útil en minimal) |
|
|
280
|
-
| `showThumbs` | `boolean` | `true` | Forzar miniaturas visibles (útil en minimal) |
|
|
281
|
-
| `skipParamsDialog` | `boolean` | `false` | Salta el diálogo de parámetros al cargar |
|
|
282
|
-
| `currentPage` | `number` | `1` | Página inicial (soporta v-model) |
|
|
283
|
-
| `locale` | `string` | `'es'` | Idioma: `'es'`, `'en'`, `'pt'` |
|
|
284
|
-
| `apiBaseUrl` | `string` | — | URL base para subreportes/drill-down remotos |
|
|
285
|
-
| `apiKey` | `string` | — | API Key para requests al backend |
|
|
286
|
-
|
|
287
|
-
### Métodos (acceso vía ref o DOM)
|
|
288
|
-
|
|
289
|
-
```ts
|
|
290
|
-
const v = document.querySelector('nexa-viewer');
|
|
291
|
-
|
|
292
|
-
// Exportación
|
|
293
|
-
await v.exportPdf(); // pdf (html2pdf.js — fallback jsPDF vectorial)
|
|
294
|
-
await v.exportPdfWithBookmarks(); // pdf con marcadores
|
|
295
|
-
await v.exportExcel(); // .xlsx (SheetJS)
|
|
296
|
-
await v.exportWord(); // .docx
|
|
297
|
-
await v.exportCsv(); // .csv con UTF-8 BOM
|
|
298
|
-
|
|
299
|
-
// Navegación
|
|
300
|
-
v.goToPage(5);
|
|
301
|
-
|
|
302
|
-
// Datos
|
|
303
|
-
v.updateData(nuevosDatos);
|
|
304
|
-
|
|
305
|
-
// Parámetros
|
|
306
|
-
v.applyParams();
|
|
307
|
-
v.applyParamsWithValidation();
|
|
308
|
-
v.validateParams(); // retorna errores
|
|
309
|
-
|
|
310
|
-
// Utilidad
|
|
311
|
-
v.getDictionaryFields(); // retorna campos disponibles
|
|
312
|
-
|
|
313
|
-
// Propiedades de lectura
|
|
314
|
-
v.pageNumber; // número actual
|
|
315
|
-
v.totalPages; // total de páginas
|
|
316
|
-
v.paramValues; // valores actuales de parámetros
|
|
317
|
-
v.showParamsDialog; // estado del diálogo
|
|
318
|
-
v.paramValidationErrors; // errores de validación
|
|
319
|
-
```
|
|
320
|
-
|
|
321
|
-
### Eventos
|
|
322
|
-
|
|
323
|
-
```ts
|
|
324
|
-
v.addEventListener('page-change', e => console.log(e.detail.page));
|
|
325
|
-
v.addEventListener('drill-click', e => console.log(e.detail));
|
|
326
|
-
v.addEventListener('subreport-toggle', e => console.log(e.detail));
|
|
327
|
-
v.addEventListener('data-request', e => console.log(e.detail.alias));
|
|
328
|
-
```
|
|
329
|
-
|
|
330
|
-
---
|
|
331
|
-
|
|
332
|
-
## Arquitectura del visor
|
|
210
|
+
## Report Model
|
|
333
211
|
|
|
334
|
-
|
|
212
|
+
Reports use a banded structure:
|
|
335
213
|
|
|
214
|
+
```json
|
|
215
|
+
{
|
|
216
|
+
"metadata": { "name": "Sales Report", "version": "1.0" },
|
|
217
|
+
"layout": {
|
|
218
|
+
"page": { "format": "A4", "orientation": "portrait", "margins": { "top": 2, "right": 2, "bottom": 2, "left": 2 } },
|
|
219
|
+
"bands": [
|
|
220
|
+
{ "id": "header", "type": "ReportHeader", "height": 60, "elements": [...] },
|
|
221
|
+
{ "id": "data", "type": "DataBand", "height": 30, "dataSource": "main", "elements": [...] },
|
|
222
|
+
{ "id": "footer", "type": "PageFooter", "height": 30, "elements": [...] }
|
|
223
|
+
]
|
|
224
|
+
},
|
|
225
|
+
"dataSources": [{ "id": "ds1", "alias": "main", "collection": "sales" }]
|
|
226
|
+
}
|
|
336
227
|
```
|
|
337
|
-
<nexa-viewer>
|
|
338
|
-
├── Toolbar (condicional)
|
|
339
|
-
│ ├── Exportación: PDF, Excel, Word, CSV, imprimir
|
|
340
|
-
│ ├── Zoom: −, %, +, fitToWidth, fitToPage
|
|
341
|
-
│ └── Paginación: ◀, input/total, ▶, búsqueda, info
|
|
342
|
-
│
|
|
343
|
-
├── Sidebar (condicional, 220px)
|
|
344
|
-
│ ├── Pestaña Pages — miniaturas vía html2canvas
|
|
345
|
-
│ ├── Pestaña TOC — tabla de contenidos con navegación
|
|
346
|
-
│ └── Pestaña Params — formulario de parámetros (text, number, date, boolean, select)
|
|
347
|
-
│
|
|
348
|
-
├── Viewport (scrollable)
|
|
349
|
-
│ └── Pages root (transform: scale(Z%) )
|
|
350
|
-
│ ├── Dashboard: grilla de DashboardWidgetRenderer
|
|
351
|
-
│ └── Reporte: v-for pages
|
|
352
|
-
│ └── report-page (sombra, fondo blanco)
|
|
353
|
-
│ ├── Watermark (tile o single posicionado)
|
|
354
|
-
│ ├── TOC page (si página 1 y generateTOC)
|
|
355
|
-
│ └── Bands
|
|
356
|
-
│ ├── DataBand tabular → Table, Chart, Crosstab, SubReport
|
|
357
|
-
│ └── DataBand iterativa → Text, Image, Barcode, QR, Shape, DrillDown
|
|
358
|
-
│
|
|
359
|
-
├── Modal DrillDown (reporte anidado vía API)
|
|
360
|
-
└── Modal About
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
### PaginationEngine
|
|
364
|
-
|
|
365
|
-
El motor de paginación es una clase (`src/lib/viewer/services/PaginationEngine.ts`) que opera en **dos fases**:
|
|
366
228
|
|
|
367
|
-
|
|
368
|
-
1. Emite `ReportHeader` primero
|
|
369
|
-
2. Bandas estáticas (ni DataBand ni DetailBand)
|
|
370
|
-
3. Para cada `DataBand`:
|
|
371
|
-
- **Tabular** (Table, Chart, Crosstab, SubReport): emite la banda completa con todas las filas en `rows`
|
|
372
|
-
- **Iterativa** (una instancia por fila): emite una banda por fila, intercalando GroupHeader/GroupFooter según agrupación y procesando DetailBand hijos (master-detail)
|
|
373
|
-
4. Emite `ReportFooter` al final
|
|
229
|
+
### Band Types
|
|
374
230
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
- Asigna coordenadas Y absolutas para posicionamiento CSS
|
|
384
|
-
- Cada ~10 páginas cede el hilo (`setTimeout`) para no bloquear en datasets grandes
|
|
231
|
+
| Type | Description |
|
|
232
|
+
|------|-------------|
|
|
233
|
+
| `ReportHeader` | Renders once, on the first page |
|
|
234
|
+
| `PageHeader` | Renders at the top of every page |
|
|
235
|
+
| `DataBand` | Iterates over datasource records |
|
|
236
|
+
| `GroupHeader` / `GroupFooter` | Renders when group key changes |
|
|
237
|
+
| `PageFooter` | Renders at the bottom of every page |
|
|
238
|
+
| `ReportFooter` | Renders once, on the last page |
|
|
385
239
|
|
|
386
|
-
|
|
240
|
+
### Element Types
|
|
387
241
|
|
|
388
|
-
|
|
242
|
+
`Text`, `Image`, `Barcode`, `QRCode`, `Rectangle`, `Ellipse`, `Line`, `Arrow`, `Table`, `Chart`, `Crosstab`, `SubReport`, `DrillDown`
|
|
389
243
|
|
|
390
|
-
|
|
391
|
-
|----------|---------|-------------|
|
|
392
|
-
| TextRenderer | `viewer/renderers/TextRenderer.vue` | Texto plano con soporte de highlight de búsqueda |
|
|
393
|
-
| ImageRenderer | `viewer/renderers/ImageRenderer.vue` | `<img>` con resolución de binding/URL |
|
|
394
|
-
| BarcodeRenderer | `viewer/renderers/BarcodeRenderer.vue` | SVG vía jsbarcode (CODE128 default) |
|
|
395
|
-
| QRCodeRenderer | `viewer/renderers/QRCodeRenderer.vue` | QR vía librería qrcode |
|
|
396
|
-
| ShapeRenderer | `viewer/renderers/ShapeRenderer.vue` | Rectángulo, elipse, línea, flecha (CSS inline) |
|
|
397
|
-
| DrillDownRenderer | `viewer/renderers/DrillDownRenderer.vue` | Elemento clickeable que emite `drill-click` |
|
|
398
|
-
| FallbackRenderer | `viewer/renderers/FallbackRenderer.vue` | Muestra tipo no soportado |
|
|
399
|
-
| SubreportRenderer | `viewer/renderers/SubreportRenderer.vue` | `<nexa-viewer>` anidado (embebido o remoto) |
|
|
400
|
-
| ChartRenderer | `viewer/ChartRenderer.vue` | ECharts (barras, líneas, pastel, etc.) |
|
|
401
|
-
| CrosstabRenderer | `viewer/CrosstabRenderer.vue` | Tabla dinámica con agrupación fila/columna |
|
|
402
|
-
| DashboardWidgetRenderer | `viewer/DashboardWidgetRenderer.vue` | Widgets en modo dashboard |
|
|
244
|
+
## Expression Engine
|
|
403
245
|
|
|
404
|
-
|
|
246
|
+
Safe evaluation without `eval`. Supports:
|
|
405
247
|
|
|
406
|
-
|
|
248
|
+
- **Bindings:** `{{field}}` — resolves from data row
|
|
249
|
+
- **Expressions:** `{[expr]}` — evaluated with context
|
|
250
|
+
- **System variables:** `{[Page]}`, `{[TotalPages]}`, `{[Today]}`, `{[RowNumber]}`, `{[TotalRows]}`, `{[EvenRow]}`, `{[OddRow]}`
|
|
251
|
+
- **Functions:** `FormatNumber`, `FormatDate`, `FormatCurrency`, `IIF`, `ISNULL`, `UPPER`, `LOWER`, `SUBSTRING`, `ABS`, `CEIL`, `FLOOR`, `ROUND`, `LEN`, `TRIM`, `CONCAT`
|
|
407
252
|
|
|
408
|
-
|
|
409
|
-
- `zoomIn()` / `zoomOut()` — ±10 puntos
|
|
410
|
-
- `fitToWidth()` — escala al ancho del viewport
|
|
411
|
-
- `fitToPage()` — escala al alto y ancho
|
|
412
|
-
- Modos: `continuous` (scroll vertical) / `single` (página por página)
|
|
413
|
-
- Atajo: Ctrl+`+`, Ctrl+`-`, Ctrl+`0`
|
|
253
|
+
## Integration with NexaBase
|
|
414
254
|
|
|
415
|
-
|
|
255
|
+
Works out of the box with [NexaBase](https://nexabase.dev) backend:
|
|
416
256
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
- Navegación: `nextMatch()` / `prevMatch()` con scroll automático
|
|
420
|
-
- Highlight: `getHighlightParts(text)` retorna segmentos marcados/no marcados
|
|
421
|
-
- Input de búsqueda en toolbar con atajo de teclado
|
|
257
|
+
```javascript
|
|
258
|
+
import { nexaService } from 'nexabase-report';
|
|
422
259
|
|
|
423
|
-
|
|
260
|
+
// Connect
|
|
261
|
+
nexaService.connect('https://your-nexabase.url', 'your-api-key');
|
|
424
262
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
### TOC (Tabla de Contenidos)
|
|
430
|
-
|
|
431
|
-
- Generación automática si `generateTOC: true` en la definición
|
|
432
|
-
- Se Renderiza en página 1
|
|
433
|
-
- Cada ítem: texto + dots + número de página
|
|
434
|
-
- Navegación: click → `goToPage(n)` con scroll
|
|
435
|
-
|
|
436
|
-
### Dashboard
|
|
437
|
-
|
|
438
|
-
Si `documentType === 'Dashboard'`, el reporte se Renderiza como grilla de widgets (`DashboardWidgetRenderer`) en lugar de páginas con bandas.
|
|
439
|
-
|
|
440
|
-
---
|
|
441
|
-
|
|
442
|
-
## Formato de datos
|
|
443
|
-
|
|
444
|
-
### Array simple
|
|
445
|
-
|
|
446
|
-
```json
|
|
447
|
-
[
|
|
448
|
-
{ "id": 1, "nombre": "Juan Pérez", "total": 150000 },
|
|
449
|
-
{ "id": 2, "nombre": "María López", "total": 230000 }
|
|
450
|
-
]
|
|
263
|
+
// Fetch data and render
|
|
264
|
+
const data = await nexaService.listDocuments('sales');
|
|
265
|
+
viewer.data = data;
|
|
451
266
|
```
|
|
452
267
|
|
|
453
|
-
|
|
268
|
+
## Examples
|
|
454
269
|
|
|
455
|
-
|
|
270
|
+
See `examples/` directory in the repository for working samples:
|
|
271
|
+
- `integration-vue.html` — Vue 3 + UMD build
|
|
272
|
+
- `integration-react.html` — React + UMD build
|
|
273
|
+
- `integration-plain.html` — Plain HTML + UMD build
|
|
456
274
|
|
|
457
|
-
|
|
458
|
-
{
|
|
459
|
-
"clientes": [
|
|
460
|
-
{ "id": 1, "nombre": "Juan", "ciudad": "Bogotá" }
|
|
461
|
-
],
|
|
462
|
-
"pedidos": [
|
|
463
|
-
{ "cliente_id": 1, "producto": "Camisa", "cantidad": 3 }
|
|
464
|
-
]
|
|
465
|
-
}
|
|
466
|
-
```
|
|
275
|
+
## Development
|
|
467
276
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
fechaInicio: '2024-01-01',
|
|
475
|
-
fechaFin: '2024-12-31',
|
|
476
|
-
categoria: 'Electronics'
|
|
477
|
-
};
|
|
277
|
+
```bash
|
|
278
|
+
pnpm install
|
|
279
|
+
pnpm run dev # Start dev server
|
|
280
|
+
pnpm run build # Build library
|
|
281
|
+
pnpm run test # Run tests (199 tests)
|
|
282
|
+
pnpm run test:watch # Watch mode
|
|
478
283
|
```
|
|
479
284
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
## Variables y expresiones
|
|
483
|
-
|
|
484
|
-
### Variables de sistema
|
|
485
|
-
|
|
486
|
-
| Variable | Descripción | Ejemplo |
|
|
487
|
-
|----------|-------------|---------|
|
|
488
|
-
| `{{Page}}` | Página actual | `1` |
|
|
489
|
-
| `{{TotalPages}}` | Total de páginas | `5` |
|
|
490
|
-
| `{{Today}}` | Fecha actual (YYYY-MM-DD) | `2026-05-13` |
|
|
491
|
-
| `{{Now}}` | Fecha y hora actual | `2026-05-13 14:30:00` |
|
|
492
|
-
| `{{Year}}` | Año actual | `2026` |
|
|
493
|
-
| `{{Month}}` | Mes actual | `5` |
|
|
494
|
-
| `{{Day}}` | Día actual | `13` |
|
|
495
|
-
| `{{ReportName}}` | Nombre del reporte | `Ventas Mensuales` |
|
|
496
|
-
| `{{RowNumber}}` | Número de fila (1-based) | `1` |
|
|
497
|
-
| `{{TotalRows}}` | Total de filas | `50` |
|
|
498
|
-
| `{{EvenRow}}` / `{{OddRow}}` | Fila par / impar | `true` / `false` |
|
|
499
|
-
| `{{FirstRow}}` / `{{LastRow}}` | Primera / última fila | `true` |
|
|
500
|
-
| `{{GroupKey}}` | Clave del grupo actual | `Electronics` |
|
|
501
|
-
| `{{GroupCount}}` | Registros en el grupo actual | `12` |
|
|
502
|
-
|
|
503
|
-
### Expresiones `{[...]}`
|
|
504
|
-
|
|
505
|
-
| Expresión | Resultado |
|
|
506
|
-
|-----------|-----------|
|
|
507
|
-
| `{[FormatNumber(precio, 'es-ES')]}` | `1.234,56` |
|
|
508
|
-
| `{[FormatDate(fecha, 'dd/MM/yyyy')]}` | `15/01/2024` |
|
|
509
|
-
| `{[FormatCurrency(total, 'USD')]}` | `$1,234.56` |
|
|
510
|
-
| `{[IIF(total > 100000, 'ALTO', 'BAJO')]}` | `ALTO` |
|
|
511
|
-
| `{[ISNULL(cliente, 'Sin nombre')]}` | `Sin nombre` |
|
|
512
|
-
| `{[UPPER(nombre)]}` | `JUAN PÉREZ` |
|
|
513
|
-
| `{[LOWER(nombre)]}` | `juan pérez` |
|
|
514
|
-
| `{[SUBSTRING(texto, 0, 3)]}` | `Hel` |
|
|
515
|
-
| `{[LENGTH(nombre)]}` | `10` |
|
|
516
|
-
| `{[TRIM(texto)]}` | sin espacios |
|
|
517
|
-
| `{[ABS(-5)]}` | `5` |
|
|
518
|
-
| `{[CEIL(5.3)]}` | `6` |
|
|
519
|
-
| `{[FLOOR(5.9)]}` | `5` |
|
|
520
|
-
| `{[SUM(cantidad)]}` | suma |
|
|
521
|
-
| `{[COUNT(id)]}` | conteo |
|
|
522
|
-
| `{[AVG(precio)]}` | promedio |
|
|
523
|
-
| `{[MIN(precio)]}` | mínimo |
|
|
524
|
-
| `{[MAX(precio)]}` | máximo |
|
|
525
|
-
| `{[CONCAT(nombre, ' - ', ciudad)]}` | `Juan - Bogotá` |
|
|
526
|
-
| `{[REPLACE(texto, 'a', 'o')]}` | reemplazo |
|
|
527
|
-
|
|
528
|
-
---
|
|
529
|
-
|
|
530
|
-
## Ejemplos
|
|
531
|
-
|
|
532
|
-
Los reportes de ejemplo están en `examples/` y son compatibles con diseñador y visor.
|
|
533
|
-
|
|
534
|
-
| Archivo | Descripción |
|
|
535
|
-
|---------|-------------|
|
|
536
|
-
| `report-factura.json` | Factura con ítems, cliente y totales |
|
|
537
|
-
| `factura_de_recolección_de_residuos.json` | Factura ambiental multi-DataSource |
|
|
538
|
-
| `report-factura-residuos.json` | Factura de residuos con GroupHeader/Footer |
|
|
539
|
-
| `report-anexo-factura.json` | Anexo con tabla expandida |
|
|
540
|
-
| `report-ventas-logo-tabla.json` | Corporativo con logo y tabla agrupada |
|
|
541
|
-
| `report-agrupado-por-cliente.json` | GroupHeader + agrupación por cliente |
|
|
542
|
-
| `report-productos.json` | Lista simple de productos |
|
|
543
|
-
| `report-crosstab-categoria-mes.json` | Crosstab categoría × mes |
|
|
544
|
-
| `report-grafico-ventas-por-mes.json` | Gráfico de barras ECharts |
|
|
545
|
-
| `report-master-detail-ordenes.json` | Master-Detail con subreportes |
|
|
546
|
-
| `report-codigos-qr-barcode.json` | Códigos QR y barras |
|
|
547
|
-
|
|
548
|
-
---
|
|
549
|
-
|
|
550
|
-
## Publicación
|
|
285
|
+
## Publishing
|
|
551
286
|
|
|
552
287
|
```bash
|
|
553
|
-
pnpm run
|
|
554
|
-
pnpm version patch|minor|major
|
|
288
|
+
pnpm run release:minor # Bump minor version
|
|
555
289
|
pnpm publish --access public
|
|
556
290
|
```
|
|
557
291
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
```html
|
|
561
|
-
<script src="https://cdn.jsdelivr.net/npm/nexabase-report/dist/nexabase-report.umd.js"></script>
|
|
562
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/nexabase-report/dist/style.css">
|
|
563
|
-
```
|
|
564
|
-
|
|
565
|
-
Ver [`docs/PUBLISHING.md`](docs/PUBLISHING.md) para CI/CD y registro privado.
|
|
566
|
-
|
|
567
|
-
---
|
|
568
|
-
|
|
569
|
-
## Documentación adicional
|
|
570
|
-
|
|
571
|
-
- [API completa del Viewer](docs/VIEWER_API.md) — props, métodos, eventos, tipos
|
|
572
|
-
- [API REST](docs/API_REST.md) — endpoints serverless
|
|
573
|
-
- [Integración externa](docs/EXTERNAL_INTEGRATION.md) — ASP.NET, Django, Laravel, Rails
|
|
574
|
-
- [Plan de QA](docs/QA_PLAN.md) — checklist de pruebas manuales
|
|
575
|
-
- [Checklist de regresión exportación](docs/EXPORT_REGRESSION.md) — PDF, Excel, Word, CSV
|
|
576
|
-
- [Guía de publicación npm](docs/PUBLISHING.md) — build, versionado, CI/CD
|
|
577
|
-
|
|
578
|
-
---
|
|
579
|
-
|
|
580
|
-
## Licencia
|
|
292
|
+
## License
|
|
581
293
|
|
|
582
294
|
MIT © NexaBase Team
|