nexabase-report 0.1.1 → 0.1.2
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 +253 -95
- package/dist/favicon.ico +0 -0
- package/dist/{html2canvas-MMn8dUQK.js → html2canvas-Bt7A8WrM.js} +1 -1
- package/dist/{html2canvas-Dmt1hL9C.js → html2canvas-ChtVTMFS.js} +2 -2
- package/dist/{html2pdf-BumfBzwC.js → html2pdf-DsfBPN4L.js} +3 -3
- package/dist/{index-j9lAJl17.js → index-D3l3xYdG.js} +20787 -20620
- package/dist/{index.es-BQC8FNVO.js → index.es-CmGmMwAx.js} +2 -2
- package/dist/{jspdf.es.min-DIevzj7_.js → jspdf.es.min-M0h3R1kR.js} +2 -2
- package/dist/logo.png +0 -0
- package/dist/nexabase-report.es.js +1 -1
- package/dist/nexabase-report.umd.js +146 -146
- package/dist/nexabase_report_logo_base.png +0 -0
- package/dist/nexabase_report_logo_base_transparente.png +0 -0
- package/dist/nexabase_report_logo_var1.png +0 -0
- package/dist/nexabase_report_logo_var2.png +0 -0
- package/dist/style.css +1001 -429
- package/examples/dashboards/activity_dashboard.json +80 -0
- package/examples/dashboards/inventory_dashboard.json +147 -0
- package/examples/dashboards/sales_dashboard.json +162 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,6 +1,48 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
# nexabase-report
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/nexabase-report)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
|
|
6
|
+
> Librería Vue 3 + TypeScript para diseñar y visualizar reportes tipo banded (inspirada en Stimulsoft / DevExpress). Incluye **diseñador WYSIWYG**, **visor framework-agnostic** como Custom Element, y **exportación 100% cliente** a PDF, Excel, Word y CSV.
|
|
7
|
+
|
|
8
|
+
## Tabla de contenidos
|
|
9
|
+
|
|
10
|
+
- [Características](#características)
|
|
11
|
+
- [Instalación](#instalación)
|
|
12
|
+
- [Uso rápido](#uso-rápido)
|
|
13
|
+
- [React](#react)
|
|
14
|
+
- [Vue 3](#vue-3)
|
|
15
|
+
- [Angular](#angular)
|
|
16
|
+
- [HTML puro (CDN)](#html-puro-cdn)
|
|
17
|
+
- [API del Viewer](#api-del-viewer)
|
|
18
|
+
- [Props](#props)
|
|
19
|
+
- [Métodos](#métodos)
|
|
20
|
+
- [Eventos](#eventos)
|
|
21
|
+
- [Formato de datos](#formato-de-datos)
|
|
22
|
+
- [Variables de sistema](#variables-de-sistema)
|
|
23
|
+
- [Funciones de expresiones](#funciones-de-expresiones)
|
|
24
|
+
- [Exportación programática](#exportación-programática)
|
|
25
|
+
- [Solución de problemas](#solución-de-problemas)
|
|
26
|
+
- [Documentación adicional](#documentación-adicional)
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Características
|
|
31
|
+
|
|
32
|
+
| Característica | Descripción |
|
|
33
|
+
|----------------|-------------|
|
|
34
|
+
| **Custom Element** | `<nexa-viewer>` funciona en Vue, React, Angular, Svelte o HTML puro |
|
|
35
|
+
| **Diseñador WYSIWYG** | Arrastra y suelta bandas, textos, tablas, imágenes, gráficos |
|
|
36
|
+
| **Exportación cliente** | PDF (vectorial con jsPDF), Excel (SheetJS), Word (docx), CSV |
|
|
37
|
+
| **Gráficos** | ECharts integrado (barras, líneas, pastel, etc.) |
|
|
38
|
+
| **Tablas dinámicas** | Crosstabs (pivot tables) nativos |
|
|
39
|
+
| **Códigos QR / Barras** | jsbarcode + qrcode |
|
|
40
|
+
| **Formato condicional** | Reglas visuales por campo y valor |
|
|
41
|
+
| **Master-Detail** | Subreportes anidados |
|
|
42
|
+
| **Motor de expresiones** | Seguro (sin `eval`): `FormatNumber`, `IIF`, `ISNULL`, etc. |
|
|
43
|
+
| **Sin backend requerido** | Los datos se pasan directamente por props |
|
|
44
|
+
|
|
45
|
+
---
|
|
4
46
|
|
|
5
47
|
## Instalación
|
|
6
48
|
|
|
@@ -8,55 +50,17 @@ Librería para diseñar y visualizar reportes tipo banded (inspirada en Stimulso
|
|
|
8
50
|
npm install nexabase-report
|
|
9
51
|
```
|
|
10
52
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
El Viewer se expone como Custom Element: `<nexa-viewer />`.
|
|
14
|
-
|
|
15
|
-
## Flujo real (NexaBase): cargar JSON exportado + pasar data
|
|
16
|
-
|
|
17
|
-
1. Exporta el reporte desde NexaBase y guarda el JSON en tu app (por ejemplo en `public/report.json`).
|
|
18
|
-
2. Carga ese JSON por `fetch()` y asígnalo a `definition`.
|
|
19
|
-
3. Pasa tus datos del sistema en `data`.
|
|
20
|
-
|
|
21
|
-
### 1) Vue 3
|
|
53
|
+
También necesitarás importar los estilos globales **una vez** en tu app:
|
|
22
54
|
|
|
23
55
|
```ts
|
|
24
|
-
import { registerNexaReport } from 'nexabase-report';
|
|
25
56
|
import 'nexabase-report/style.css';
|
|
26
|
-
|
|
27
|
-
registerNexaReport();
|
|
28
57
|
```
|
|
29
58
|
|
|
30
|
-
|
|
31
|
-
<script setup lang="ts">
|
|
32
|
-
import { onMounted, ref } from 'vue';
|
|
59
|
+
---
|
|
33
60
|
|
|
34
|
-
|
|
35
|
-
const data = ref<any[]>([]);
|
|
61
|
+
## Uso rápido
|
|
36
62
|
|
|
37
|
-
|
|
38
|
-
const res = await fetch('/report.json');
|
|
39
|
-
definition.value = await res.json();
|
|
40
|
-
|
|
41
|
-
data.value = [
|
|
42
|
-
{ nombre: 'Producto A', precio: 100, cantidad: 2 },
|
|
43
|
-
{ nombre: 'Producto B', precio: 50, cantidad: 5 },
|
|
44
|
-
];
|
|
45
|
-
});
|
|
46
|
-
</script>
|
|
47
|
-
|
|
48
|
-
<template>
|
|
49
|
-
<nexa-viewer
|
|
50
|
-
:definition="definition"
|
|
51
|
-
:data="data"
|
|
52
|
-
:parameters="{ fechaDesde: '2026-01-01' }"
|
|
53
|
-
minimal
|
|
54
|
-
skip-params-dialog
|
|
55
|
-
/>
|
|
56
|
-
</template>
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
### 2) React
|
|
63
|
+
### React
|
|
60
64
|
|
|
61
65
|
```tsx
|
|
62
66
|
import { useEffect, useRef, useState } from 'react';
|
|
@@ -66,18 +70,17 @@ import 'nexabase-report/style.css';
|
|
|
66
70
|
registerNexaReport();
|
|
67
71
|
|
|
68
72
|
export function ReportViewer() {
|
|
69
|
-
const ref = useRef<
|
|
73
|
+
const ref = useRef<HTMLElement>(null);
|
|
70
74
|
const [definition, setDefinition] = useState<any>(null);
|
|
71
75
|
const [data, setData] = useState<any[]>([]);
|
|
72
|
-
const [parameters] = useState<any>({ fechaDesde: '2026-01-01' });
|
|
73
76
|
|
|
74
77
|
useEffect(() => {
|
|
75
78
|
(async () => {
|
|
76
79
|
const res = await fetch('/report.json');
|
|
77
80
|
setDefinition(await res.json());
|
|
78
81
|
setData([
|
|
79
|
-
{ nombre: 'Producto A', precio: 100
|
|
80
|
-
{ nombre: 'Producto B', precio: 50
|
|
82
|
+
{ nombre: 'Producto A', precio: 100 },
|
|
83
|
+
{ nombre: 'Producto B', precio: 50 },
|
|
81
84
|
]);
|
|
82
85
|
})();
|
|
83
86
|
}, []);
|
|
@@ -86,87 +89,242 @@ export function ReportViewer() {
|
|
|
86
89
|
if (!ref.current) return;
|
|
87
90
|
ref.current.definition = definition;
|
|
88
91
|
ref.current.data = data;
|
|
89
|
-
|
|
90
|
-
}, [definition, data, parameters]);
|
|
92
|
+
}, [definition, data]);
|
|
91
93
|
|
|
92
94
|
return <nexa-viewer ref={ref} minimal />;
|
|
93
95
|
}
|
|
94
96
|
```
|
|
95
97
|
|
|
96
|
-
### 3
|
|
98
|
+
### Vue 3
|
|
99
|
+
|
|
100
|
+
```vue
|
|
101
|
+
<script setup lang="ts">
|
|
102
|
+
import { ref, watch } from 'vue';
|
|
103
|
+
import { registerNexaReport } from 'nexabase-report';
|
|
104
|
+
import 'nexabase-report/style.css';
|
|
105
|
+
|
|
106
|
+
registerNexaReport();
|
|
97
107
|
|
|
98
|
-
|
|
108
|
+
const props = defineProps<{ definition: any; data: any[] }>();
|
|
109
|
+
const viewerRef = ref<HTMLElement | null>(null);
|
|
110
|
+
|
|
111
|
+
watch(
|
|
112
|
+
() => [props.definition, props.data],
|
|
113
|
+
() => {
|
|
114
|
+
const el = viewerRef.value as any;
|
|
115
|
+
if (!el) return;
|
|
116
|
+
el.definition = props.definition;
|
|
117
|
+
el.data = props.data;
|
|
118
|
+
},
|
|
119
|
+
{ immediate: true }
|
|
120
|
+
);
|
|
121
|
+
</script>
|
|
122
|
+
|
|
123
|
+
<template>
|
|
124
|
+
<nexa-viewer ref="viewerRef" minimal style="display:block;height:100%" />
|
|
125
|
+
</template>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Angular
|
|
99
129
|
|
|
100
130
|
```ts
|
|
101
|
-
import {
|
|
131
|
+
import { Component, ElementRef, ViewChild, AfterViewInit } from '@angular/core';
|
|
132
|
+
import { registerNexaReport } from 'nexabase-report';
|
|
133
|
+
import 'nexabase-report/style.css';
|
|
134
|
+
|
|
135
|
+
registerNexaReport();
|
|
102
136
|
|
|
103
137
|
@Component({
|
|
104
138
|
selector: 'app-root',
|
|
105
|
-
template: '<nexa-viewer #viewer></nexa-viewer>',
|
|
139
|
+
template: '<nexa-viewer #viewer minimal></nexa-viewer>',
|
|
106
140
|
})
|
|
107
141
|
export class AppComponent implements AfterViewInit {
|
|
108
|
-
@ViewChild('viewer', { static: true }) viewerRef!: ElementRef<
|
|
142
|
+
@ViewChild('viewer', { static: true }) viewerRef!: ElementRef<HTMLElement>;
|
|
109
143
|
|
|
110
144
|
async ngAfterViewInit() {
|
|
111
|
-
const
|
|
145
|
+
const el = this.viewerRef.nativeElement as any;
|
|
112
146
|
const res = await fetch('/assets/report.json');
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
{ nombre: 'Producto A', precio: 100, cantidad: 2 },
|
|
116
|
-
{ nombre: 'Producto B', precio: 50, cantidad: 5 },
|
|
117
|
-
];
|
|
118
|
-
v.minimal = true;
|
|
119
|
-
v.skipParamsDialog = true;
|
|
147
|
+
el.definition = await res.json();
|
|
148
|
+
el.data = [{ nombre: 'Producto A', precio: 100 }];
|
|
120
149
|
}
|
|
121
150
|
}
|
|
122
151
|
```
|
|
123
152
|
|
|
124
|
-
###
|
|
153
|
+
### HTML puro (CDN)
|
|
125
154
|
|
|
126
155
|
```html
|
|
127
|
-
|
|
128
|
-
<
|
|
129
|
-
<
|
|
130
|
-
|
|
131
|
-
</
|
|
156
|
+
<!DOCTYPE html>
|
|
157
|
+
<html>
|
|
158
|
+
<head>
|
|
159
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/nexabase-report@latest/dist/style.css">
|
|
160
|
+
</head>
|
|
161
|
+
<body>
|
|
162
|
+
<nexa-viewer id="viewer" minimal></nexa-viewer>
|
|
163
|
+
|
|
164
|
+
<script src="https://cdn.jsdelivr.net/npm/nexabase-report@latest/dist/nexabase-report.umd.js"></script>
|
|
165
|
+
<script>
|
|
166
|
+
window.NexaReport.registerNexaReport();
|
|
167
|
+
const v = document.getElementById('viewer');
|
|
168
|
+
v.definition = { /* ... */ };
|
|
169
|
+
v.data = [ /* ... */ ];
|
|
170
|
+
</script>
|
|
171
|
+
</body>
|
|
172
|
+
</html>
|
|
173
|
+
```
|
|
132
174
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## API del Viewer
|
|
178
|
+
|
|
179
|
+
### Props
|
|
180
|
+
|
|
181
|
+
| Prop | Tipo | Default | Descripción |
|
|
182
|
+
|------|------|---------|-------------|
|
|
183
|
+
| `definition` | `string \| object` | — | Definición del reporte (JSON o objeto) |
|
|
184
|
+
| `data` | `string \| any[] \| Record<string, any[]>` | — | Datos del reporte |
|
|
185
|
+
| `parameters` | `Record<string, any>` | `{}` | Valores de parámetros |
|
|
186
|
+
| `minimal` | `boolean \| string` | `false` | Oculta la toolbar de exportación |
|
|
187
|
+
| `skipParamsDialog` | `boolean` | `false` | Salta el diálogo de parámetros |
|
|
188
|
+
| `showToolbar` | `boolean \| string` | `true` | Muestra/oculta toolbar |
|
|
189
|
+
| `showThumbs` | `boolean \| string` | `true` | Muestra/oculta panel de miniaturas |
|
|
190
|
+
| `currentPage` | `number` | `1` | Página actual (control externo) |
|
|
191
|
+
|
|
192
|
+
### Métodos (vía ref DOM)
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
const viewer = document.querySelector('nexa-viewer');
|
|
196
|
+
|
|
197
|
+
await viewer.exportPdf();
|
|
198
|
+
await viewer.exportExcel();
|
|
199
|
+
await viewer.exportWord();
|
|
200
|
+
await viewer.exportCsv();
|
|
201
|
+
|
|
202
|
+
viewer.goToPage(3);
|
|
203
|
+
viewer.updateData({ main: [...] });
|
|
139
204
|
```
|
|
140
205
|
|
|
141
|
-
|
|
206
|
+
### Eventos
|
|
142
207
|
|
|
143
|
-
|
|
208
|
+
```ts
|
|
209
|
+
viewer.addEventListener('page-change', (e) => console.log(e.detail));
|
|
210
|
+
viewer.addEventListener('drill-click', (e) => console.log(e.detail));
|
|
211
|
+
```
|
|
144
212
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Formato de datos
|
|
216
|
+
|
|
217
|
+
### Single DataSource (array simple)
|
|
218
|
+
|
|
219
|
+
```json
|
|
220
|
+
[
|
|
221
|
+
{ "id": 1, "nombre": "Juan", "ventas": 1500 },
|
|
222
|
+
{ "id": 2, "nombre": "María", "ventas": 2300 }
|
|
223
|
+
]
|
|
148
224
|
```
|
|
149
225
|
|
|
150
|
-
|
|
226
|
+
### Multiple DataSources (objeto con alias)
|
|
151
227
|
|
|
152
|
-
```
|
|
153
|
-
|
|
228
|
+
```json
|
|
229
|
+
{
|
|
230
|
+
"clientes": [
|
|
231
|
+
{ "id": 1, "nombre": "Juan" }
|
|
232
|
+
],
|
|
233
|
+
"pedidos": [
|
|
234
|
+
{ "id": 101, "cliente_id": 1, "total": 500 }
|
|
235
|
+
]
|
|
236
|
+
}
|
|
154
237
|
```
|
|
155
238
|
|
|
156
|
-
|
|
239
|
+
---
|
|
157
240
|
|
|
158
|
-
|
|
241
|
+
## Variables de sistema
|
|
159
242
|
|
|
160
|
-
|
|
243
|
+
| Variable | Descripción | Ejemplo |
|
|
244
|
+
|----------|-------------|---------|
|
|
245
|
+
| `{{Page}}` | Página actual | `1` |
|
|
246
|
+
| `{{TotalPages}}` | Total de páginas | `5` |
|
|
247
|
+
| `{{Today}}` | Fecha actual | `2024-01-15` |
|
|
248
|
+
| `{{Now}}` | Fecha y hora actual | `2024-01-15 14:30` |
|
|
249
|
+
| `{{ReportName}}` | Nombre del reporte | `Ventas Mensuales` |
|
|
250
|
+
| `{{RowNumber}}` | Número de fila | `1` |
|
|
251
|
+
| `{{TotalRows}}` | Total de filas | `50` |
|
|
252
|
+
| `{{EvenRow}}` / `{{OddRow}}` | Fila par/impar | `true` / `false` |
|
|
161
253
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Funciones de expresiones
|
|
257
|
+
|
|
258
|
+
Usar sintaxis `{[...]}`:
|
|
259
|
+
|
|
260
|
+
```
|
|
261
|
+
{[FormatNumber(precio, 'es-ES')]} → 1.234,56
|
|
262
|
+
{[FormatDate(fecha, 'dd/MM/yyyy')]} → 15/01/2024
|
|
263
|
+
{[FormatCurrency(total, 'USD')]} → $1,234.56
|
|
264
|
+
{[IIF(total > 100, 'OK', 'Bajo')]} → OK
|
|
265
|
+
{[ISNULL(campo, 'Sin dato')]} → Sin dato
|
|
266
|
+
{[UPPER(nombre)]} → JUAN
|
|
267
|
+
{[SUBSTRING(texto, 1, 3)]} → pri
|
|
268
|
+
{[ABS(-5)]} → 5
|
|
269
|
+
{[CEIL(5.3)]} → 6
|
|
270
|
+
{[FLOOR(5.9)]} → 5
|
|
167
271
|
```
|
|
168
272
|
|
|
169
|
-
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## Exportación programática
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
// React / Vue / Angular
|
|
279
|
+
const viewer = viewerRef.current;
|
|
280
|
+
await viewer.exportPdf();
|
|
281
|
+
|
|
282
|
+
// HTML puro
|
|
283
|
+
const viewer = document.getElementById('viewer');
|
|
284
|
+
await viewer.exportPdf();
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## Solución de problemas
|
|
290
|
+
|
|
291
|
+
### `<nexa-viewer>` no se renderiza
|
|
292
|
+
|
|
293
|
+
Asegúrate de llamar `registerNexaReport()` **antes** de montar el componente, e importar `nexabase-report/style.css`.
|
|
294
|
+
|
|
295
|
+
### TypeScript: `'nexa-viewer' is not a known element`
|
|
296
|
+
|
|
297
|
+
En React/Vue/Angular, el custom element no está en el JSX/TSX estándar. Agrega declaraciones de tipo locales:
|
|
298
|
+
|
|
299
|
+
```ts
|
|
300
|
+
declare module 'nexabase-report' {
|
|
301
|
+
export function registerNexaReport(): void;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
declare global {
|
|
305
|
+
namespace JSX {
|
|
306
|
+
interface IntrinsicElements {
|
|
307
|
+
'nexa-viewer': any;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
En Angular usa `CUSTOM_ELEMENTS_SCHEMA` en tu `@Component`.
|
|
314
|
+
|
|
315
|
+
### Los datos no aparecen
|
|
316
|
+
|
|
317
|
+
Verifica que `dataSource` en la definición del reporte coincida con el alias de tus datos. El primer datasource suele tener alias `"main"`.
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
## Documentación adicional
|
|
322
|
+
|
|
323
|
+
- [API del Viewer](docs/VIEWER_API.md)
|
|
324
|
+
- [Integración externa](docs/EXTERNAL_INTEGRATION.md)
|
|
325
|
+
- [Plan de QA](docs/QA_PLAN.md)
|
|
326
|
+
- [Guía de publicación](docs/PUBLISHING.md)
|
|
327
|
+
|
|
328
|
+
## Licencia
|
|
170
329
|
|
|
171
|
-
|
|
172
|
-
- Integración externa: [docs/EXTERNAL_INTEGRATION.md](docs/EXTERNAL_INTEGRATION.md)
|
|
330
|
+
MIT © NexaBase Team
|
package/dist/favicon.ico
ADDED
|
Binary file
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as c } from "./index-
|
|
2
|
-
import { r as f } from "./html2canvas-
|
|
1
|
+
import { a as c } from "./index-D3l3xYdG.js";
|
|
2
|
+
import { r as f } from "./html2canvas-Bt7A8WrM.js";
|
|
3
3
|
function l(r, n) {
|
|
4
4
|
for (var o = 0; o < n.length; o++) {
|
|
5
5
|
const e = n[o];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { g as De, a as Ke, c as Te } from "./index-
|
|
2
|
-
import { j as Ge } from "./jspdf.es.min-
|
|
3
|
-
import { r as Be } from "./html2canvas-
|
|
1
|
+
import { g as De, a as Ke, c as Te } from "./index-D3l3xYdG.js";
|
|
2
|
+
import { j as Ge } from "./jspdf.es.min-M0h3R1kR.js";
|
|
3
|
+
import { r as Be } from "./html2canvas-Bt7A8WrM.js";
|
|
4
4
|
function Ue(ge, we) {
|
|
5
5
|
for (var me = 0; me < we.length; me++) {
|
|
6
6
|
const ce = we[me];
|