robobyte-front-builder 1.0.25 → 1.0.27

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.
Files changed (90) hide show
  1. package/INTEGRATION.md +94 -10
  2. package/LICENSE +65 -0
  3. package/README.md +149 -21
  4. package/docs/ReportViewer.md +581 -0
  5. package/docs/fetchReportData.md +379 -0
  6. package/docs/printLayout.md +405 -0
  7. package/package.json +29 -1
  8. package/src/lib/agGridTheme.js +58 -0
  9. package/src/lib/agGridThemeContext.jsx +42 -0
  10. package/src/lib/index.js +7 -0
  11. package/src/lib/providers/RoboByteFrontBuilderProvider.jsx +14 -0
  12. package/src/views/builder/viewer/renderers/DataGridRenderer.jsx +4 -4
  13. package/src/views/genericTable/SGrid.js +5 -5
  14. package/src/views/genericTable/TAGGrid.js +3 -5
  15. package/src/views/rolePermissions/UpdateReportPermissionDialog.js +4 -0
  16. package/training/00-index.md +168 -0
  17. package/training/01-input.md +144 -0
  18. package/training/02-checkbox.md +107 -0
  19. package/training/03-dropdown.md +135 -0
  20. package/training/04-datepicker.md +139 -0
  21. package/training/05-radio.md +123 -0
  22. package/training/06-number.md +133 -0
  23. package/training/07-textarea.md +114 -0
  24. package/training/08-richtext.md +112 -0
  25. package/training/09-tag.md +110 -0
  26. package/training/10-time.md +107 -0
  27. package/training/11-toggle.md +108 -0
  28. package/training/12-signature.md +107 -0
  29. package/training/13-autocomplete.md +134 -0
  30. package/training/14-button.md +168 -0
  31. package/training/15-label.md +138 -0
  32. package/training/16-header.md +128 -0
  33. package/training/17-divider.md +96 -0
  34. package/training/18-image.md +105 -0
  35. package/training/19-link.md +108 -0
  36. package/training/20-banner.md +122 -0
  37. package/training/21-progress-circle.md +101 -0
  38. package/training/22-progress-line.md +93 -0
  39. package/training/23-menu.md +139 -0
  40. package/training/24-popover.md +114 -0
  41. package/training/25-layout.md +116 -0
  42. package/training/26-layout-cell.md +143 -0
  43. package/training/27-card.md +87 -0
  44. package/training/28-wizard.md +126 -0
  45. package/training/29-wizard-step.md +92 -0
  46. package/training/30-repeater.md +123 -0
  47. package/training/31-dialog.md +131 -0
  48. package/training/32-breadcrumb.md +121 -0
  49. package/training/33-dataGrid.md +129 -0
  50. package/training/34-dataTableViewer.md +115 -0
  51. package/training/35-reportViewer.md +673 -0
  52. package/training/36-viewRenderer.md +110 -0
  53. package/training/37-treeView.md +170 -0
  54. package/training/38-kpi-metric.md +148 -0
  55. package/training/39-kpi-trend.md +105 -0
  56. package/training/40-kpi-badge.md +112 -0
  57. package/training/41-kpi-statusDot.md +118 -0
  58. package/training/42-kpi-iconBox.md +114 -0
  59. package/training/43-kpi-gauge.md +143 -0
  60. package/training/44-kpi-bulletChart.md +126 -0
  61. package/training/45-kpi-colorScale.md +143 -0
  62. package/training/46-kpi-rating.md +125 -0
  63. package/training/47-kpi-countdown.md +151 -0
  64. package/training/48-fetchReportData.md +276 -0
  65. package/training/49-printLayout.md +215 -0
  66. package/training/examples/01-login-form.json +176 -0
  67. package/training/examples/02-contact-form.json +141 -0
  68. package/training/examples/03-kpi-cards-row.json +123 -0
  69. package/training/examples/04-settings-toggles.json +153 -0
  70. package/training/examples/05-user-profile-card.json +136 -0
  71. package/training/examples/06-date-range-filter.json +108 -0
  72. package/training/examples/07-search-bar-results.json +130 -0
  73. package/training/examples/08-notification-settings.json +131 -0
  74. package/training/examples/09-employee-profile-form.json +259 -0
  75. package/training/examples/10-invoice-form.json +241 -0
  76. package/training/examples/11-dashboard-overview.json +251 -0
  77. package/training/examples/12-registration-wizard.json +154 -0
  78. package/training/examples/13-product-catalog.json +168 -0
  79. package/training/examples/14-data-table-with-filters.json +180 -0
  80. package/training/examples/15-tabbed-profile.json +92 -0
  81. package/training/examples/16-kpi-full-row.json +203 -0
  82. package/training/examples/17-tree-detail-view.json +139 -0
  83. package/training/examples/18-employee-management.json +233 -0
  84. package/training/examples/19-sales-dashboard.json +272 -0
  85. package/training/examples/20-checkout-wizard.json +225 -0
  86. package/training/examples/21-analytics-page.json +222 -0
  87. package/training/examples/22-hr-onboarding.json +222 -0
  88. package/training/examples/23-document-browser.json +241 -0
  89. package/training/examples/24-order-management.json +290 -0
  90. package/training/examples/25-crm-contact-page.json +272 -0
@@ -0,0 +1,673 @@
1
+ # Component: `reportViewer` — Complete AI Reference Manual
2
+
3
+ > **Audience.** This document is written for an AI session that needs to **emit a builder schema** containing a `reportViewer`, or **embed `<ReportViewer />`** as a React component in code generated against `robobyte-front-builder`. Every prop, every code scope, and every gotcha is documented here so the model does not need to grep source.
4
+
5
+ Renders an AG Grid Enterprise–powered server-side report. The component fetches its column definitions and rows from the backend by `id` / `pageId`, applies server-side pagination, filtering, sorting, grouping, and aggregation, and exposes a toolbar with title, search, filter dialog, refresh, row actions, and user-defined "viewer actions". Use this whenever the data lives on the server.
6
+
7
+ > ⚠️ **Use `reportViewer` for server-fetched data.** Use `dataGrid` for client-side editable grids. Use `dataTableViewer` for static / already-in-memory arrays.
8
+
9
+ ---
10
+
11
+ ## 1. Quick start
12
+
13
+ Minimum viable schema — fetch a report by id and render at 70vh height:
14
+
15
+ ```json
16
+ {
17
+ "type": "reportViewer",
18
+ "props": {
19
+ "key": "employeesReport",
20
+ "id": "EMPLOYEE_LIST",
21
+ "height": "70vh"
22
+ }
23
+ }
24
+ ```
25
+
26
+ Note: `id` and/or `pageId` is required — without one the viewer renders the "No report selected" empty state.
27
+
28
+ ---
29
+
30
+ ## 2. Two ways to use it
31
+
32
+ ### As a builder component (most common)
33
+
34
+ Drop a `reportViewer` node into the schema. The UI Builder resolves props from the inspector, registers a `reportRef` under `props.main.key`, and renders the report inside `ViewerComponentWrapper`.
35
+
36
+ ```json
37
+ {
38
+ "type": "reportViewer",
39
+ "props": { "key": "ordersReport", "pageId": "ORDERS_BY_REGION" }
40
+ }
41
+ ```
42
+
43
+ ### As a React component (host-app code)
44
+
45
+ The same page is exported as a React component for inline embedding (Reports inside dialogs, drawers, custom dashboards). Import path:
46
+
47
+ ```jsx
48
+ import { ReportViewer } from 'robobyte-front-builder'
49
+
50
+ <ReportViewer
51
+ pageId="ORDERS_BY_REGION"
52
+ minimized={false}
53
+ noHeader={true}
54
+ filter={{ Tfilter: [{ path: 'RegionId', value: 4, method: 'Equal' }] }}
55
+ height="400px"
56
+ title="Q1 Orders"
57
+ caption="2026-01-01 → 2026-03-31"
58
+ />
59
+ ```
60
+
61
+ When embedded this way, `noHeader={true}` hides the standalone-page chrome (report-name + Studio button).
62
+
63
+ ---
64
+
65
+ ## 3. Complete props reference
66
+
67
+ All props are inspector fields on the `Main` tab. Types refer to the inspector field type (`expression` means: accepts a literal value OR a JS expression evaluated in the page's calculation scope).
68
+
69
+ ### 3.1 Identity & data source
70
+
71
+ | Prop | Type | Required | Description |
72
+ |---|---|---|---|
73
+ | `key` | expression | yes | Stable identifier. Used as `nodeName` in `reportRefs`. Pick something kebab/camel-cased and unique on the page. |
74
+ | `id` | expression | one of | Report ID (the backend's top-level report id). |
75
+ | `pageId` | expression | one of | Page/sub-report identifier (for multi-page reports). At least one of `id` / `pageId` must be set. |
76
+ | `sessionId` | expression | no | When set, the viewer hydrates filters / saved state from `localStorage` under this session id. Used to share filter state across navigations. |
77
+ | `globalParams` | expression | no | Object keyed by **selection-param index** → value. Overrides specific `selectionParams` defined in the report. Example: `{ 0: data.year, 1: data.month }`. |
78
+
79
+ ### 3.2 Filter
80
+
81
+ | Prop | Type | Description |
82
+ |---|---|---|
83
+ | `filter` | expression | External filter object merged with the report's own filters. **See section 4 for the full shape.** |
84
+
85
+ ### 3.3 Presentation (toolbar / chrome)
86
+
87
+ | Prop | Type | Description |
88
+ |---|---|---|
89
+ | `title` | expression | Bold heading rendered at the **start** of the report toolbar. Falls back to the report's own backend name if unset. |
90
+ | `caption` | expression | Small secondary text rendered under `title` (e.g. row count, time range). |
91
+ | `height` | expression | Grid height. CSS units. Default `"85vh"`. Common: `"70vh"`, `"500px"`, `"60vh"`. |
92
+ | `minimized` | boolean | Removes toolbar + paddings; useful when the viewer is embedded inside a Card or Dialog. |
93
+ | `noHeader` | boolean | Hides the report-name header (the row with name + Studio button). The toolbar with title/search/filter/refresh stays. |
94
+ | `isSingle` | boolean | `true` → return only the first row. Used for "single-record" reports. |
95
+ | `dataAsObject` | boolean | `true` → keyed-by-id object instead of an array (use the row's unique id field as the key). |
96
+
97
+ ### 3.4 Refresh control
98
+
99
+ | Prop | Type | Description |
100
+ |---|---|---|
101
+ | `refresh` | expression | Change this value to force a data reload. Typical pattern: `data.refreshTick` then `setData(prev => ({...prev, refreshTick: Date.now()}))`. |
102
+ | `externalTimer` | expression | Auto-refresh interval **in minutes**. Set to a numeric value; setting to `0` or `null` disables. |
103
+ | `isRerender` | boolean | When toggled, fully remounts the grid (resets internal state). |
104
+
105
+ ### 3.5 Columns & rows
106
+
107
+ | Prop | Type | Description |
108
+ |---|---|---|
109
+ | `extraCols` | `extra-cols-editor` | Array of additional AG Grid `colDef` objects appended to the end of the columns. See section 8. |
110
+ | `columnsConfig` | `columns-config-editor` | Per-field overrides on **existing** report columns, matched by `field`. See section 9. |
111
+
112
+ ### 3.6 Action arrays
113
+
114
+ | Prop | Type | Description |
115
+ |---|---|---|
116
+ | `actionsConfig` | `actions-config-editor` | **Row actions.** Appears as buttons in the row's Actions cell. See section 6. |
117
+ | `viewerActions` | `actions-config-editor` | **Page-level actions.** Appears as buttons in the toolbar after Filter / Refresh. See section 7. |
118
+
119
+ ### 3.7 Programmatic plumbing (set automatically; almost never set by hand)
120
+
121
+ | Prop | Type | Description |
122
+ |---|---|---|
123
+ | `updateRef` | ref | Per-instance ref tracking row mutations. Set by `ReportViewerRenderer`. |
124
+ | `nodeId` | string | Builder node id. Set by `ReportViewerRenderer`. |
125
+ | `reportRefs` | ref | Shared registry `{ [nodeName]: updateRef }`. Set by `ReportViewerRenderer`. |
126
+ | `setOutGridApi` | function | Callback receiving the AG Grid API instance. Used in custom React embeddings only — builder schemas don't set this. |
127
+ | `setData` | function | Callback receiving loaded rows. Builder schemas don't set this. |
128
+
129
+ ---
130
+
131
+ ## 4. The `filter` object — complete shape
132
+
133
+ Filter is merged from three sources at runtime: the **report's own** filter (backend-defined), the **user's local** filters (added in the in-grid UI), and **`filter` prop** (external). The final object passed to the API is `Tfilter` (all filters merged into one AND-group).
134
+
135
+ ### 4.1 Fields you can pass via `filter` prop
136
+
137
+ ```ts
138
+ {
139
+ // Filters that always apply (e.g. an outer "show only my records" constraint
140
+ // that the user cannot remove via the in-grid filter UI).
141
+ fixedTFilter?: FilterItem[],
142
+
143
+ // Local-style filters that DO appear in the in-grid filter UI; the user can
144
+ // remove or modify them.
145
+ LocalTfilter?: FilterItem[],
146
+
147
+ // JS code string evaluated to produce additional filter items at request
148
+ // time. See section 4.3.
149
+ customFilterCode?: string,
150
+ }
151
+ ```
152
+
153
+ ### 4.2 `FilterItem` shape
154
+
155
+ ```ts
156
+ {
157
+ path: string, // dotted path to the field, e.g. "Customer.RegionId"
158
+ friendlyName: string, // label shown in the UI (e.g. "Region")
159
+ value: any, // string | number | boolean | array (for In/NotIn)
160
+ method: FilterOp, // see operators table below
161
+ dtoClassName?: string, // optional class hint for the backend
162
+ modelClassName?: string,
163
+ afterSelect?: any, // implementation-specific
164
+ orGroupName?: string, // group multiple filters into an OR-block
165
+ }
166
+ ```
167
+
168
+ #### Operators (`method` values)
169
+
170
+ | `method` | Meaning | Value type |
171
+ |---|---|---|
172
+ | `Equal` | `field == value` | scalar |
173
+ | `NotEqual` | `field != value` | scalar |
174
+ | `Greater` | `field > value` | numeric / date |
175
+ | `GreaterOrEqual` | `field >= value` | numeric / date |
176
+ | `Less` | `field < value` | numeric / date |
177
+ | `LessOrEqual` | `field <= value` | numeric / date |
178
+ | `Contains` | substring match | string |
179
+ | `StartsWith` | prefix | string |
180
+ | `EndsWith` | suffix | string |
181
+ | `In` | `field IN (...values)` | array |
182
+ | `NotIn` | `field NOT IN (...values)` | array |
183
+ | `IsNull` | null check | — |
184
+ | `IsNotNull` | non-null check | — |
185
+ | `Between` | range | `[from, to]` |
186
+
187
+ ### 4.3 `customFilterCode`
188
+
189
+ A code string evaluated at request time. **Push items into the `customFilter` array.** Available scope:
190
+
191
+ | Variable | Source |
192
+ |---|---|
193
+ | `authContext` | The host's auth context (`user`, `accessToken`, claims). |
194
+ | `context` | The host's system context. |
195
+ | `data` | The page's reactive state. |
196
+ | `customFilter` | Empty `FilterItem[]` to push into. |
197
+
198
+ Example — restrict the report to the current user's shop:
199
+
200
+ ```js
201
+ const filter = {
202
+ customFilterCode: `
203
+ if (authContext?.user?.shopId) {
204
+ customFilter.push({
205
+ field: 'ShopId',
206
+ value: authContext.user.shopId,
207
+ method: 'Equal',
208
+ operation: 'Equal'
209
+ })
210
+ }
211
+ `
212
+ }
213
+ ```
214
+
215
+ Notes:
216
+ - The code runs **after** static filters are gathered, so it can read other context to compose conditional filters.
217
+ - The code can `return` either an array of `FilterItem`s OR an object `{ Tfilter: [...] }`. Pushing into `customFilter` is the simplest path.
218
+ - A common pattern is to keep the filter shape in `data` and have `customFilterCode` translate it server-side.
219
+
220
+ ---
221
+
222
+ ## 5. Title, caption, toolbar layout
223
+
224
+ The toolbar is a **single row** since the recent redesign:
225
+
226
+ ```
227
+ [Title / Caption stacked] [Search] [Filter] [Refresh] [viewerActions...]
228
+ ```
229
+
230
+ - **Left:** `title` (h6, bold) + `caption` (small secondary). Either may be omitted; if both are set, they stack tightly. Both clip with ellipsis on narrow widths.
231
+ - **Right (right-aligned group, gap-spaced):**
232
+ - **Search** — collapses to a bordered icon button by default. Click or focus to expand into a TextField with chip startAdornment + search icon endAdornment. Arrow keys / Home / End navigate the field-selector popover; Enter picks the highlighted field; Esc closes the popover.
233
+ - **PDF export** (icon button) — only when "Load all data" is on.
234
+ - **Filter** — outlined button. Opens the `CustomFilterDialog`.
235
+ - **Refresh** — outlined button. Toggles `localRefresh` to trigger a reload.
236
+ - **viewerActions** — user-defined buttons (section 7).
237
+
238
+ The **side panel** ("Templates" tool panel, opens from the right edge) holds:
239
+ - Display Settings: **Auto-refresh** select (Off, 30s, 1m, 2m, 5m, 15m, 30m), **Load all data** checkbox, **Export to Excel** button.
240
+ - Templates: save / save as new / edit current grid layout (columns, filters, sort).
241
+
242
+ ---
243
+
244
+ ## 6. Row actions (`actionsConfig`)
245
+
246
+ An array of action buttons rendered in each row's Actions cell. Each action runs its `code` as an `async function Calculation(form, data, setData, dataRef, reportRefs, openDialog, closeDialog, urlParams, page)`.
247
+
248
+ ### 6.1 Schema
249
+
250
+ ```ts
251
+ [
252
+ {
253
+ label: string, // button text
254
+ icon: string, // MUI icon name, e.g. "EditOutlined" — empty / unset = no icon
255
+ color: 'primary' | 'secondary' | 'success' | 'error' | 'warning' | 'info' | 'inherit',
256
+ variant: 'text' | 'outlined' | 'contained',
257
+ code: string, // async function body
258
+ confirmation: string?, // optional confirmation prompt before running
259
+ disabled: string | boolean? // expression that may return truthy to disable
260
+ }
261
+ ]
262
+ ```
263
+
264
+ ### 6.2 Scope available inside `code`
265
+
266
+ | Var | What it is |
267
+ |---|---|
268
+ | `form` | Form values (read-only snapshot). |
269
+ | `data` | Page reactive state. |
270
+ | `setData` | Setter for page state. |
271
+ | `dataRef` | Mutable non-reactive store. |
272
+ | `reportRefs` | Registry of all report `updateRef`s on the page. |
273
+ | `openDialog(key, data?)` / `closeDialog(key)` | Dialog control. |
274
+ | `rowData` | **Row-specific** — the current AG Grid row's data object. |
275
+ | `setRefValue`, `getRefValue`, `setRefRow`, `hasRefValue`, `clearRefRow`, `clearAllRef`, `getNodeRef`, `THIS_NODE` | Node-aware ref helpers. Pass `THIS_NODE` to target the current report; pass another report's nodeName to cross-target. |
276
+ | `page.data`, `page.setData`, `page.dataRef`, `page.reportRefs` | Parent page scope (when this report is inside a Dialog or embedded view). |
277
+ | `GetService`, `PostService`, `UpdateService`, `PatchService`, `DeleteService` | Auth-aware HTTP wrappers. |
278
+ | `showToast(message, type?)` | `'success' | 'error' | 'warning' | 'info' | 'loading'` |
279
+ | `router`, `urlParams`, `globalData`, `setGlobalData` | Navigation + global store. |
280
+
281
+ ### 6.3 Examples
282
+
283
+ Open an edit dialog seeded with the row:
284
+
285
+ ```json
286
+ {
287
+ "label": "Edit",
288
+ "icon": "EditOutlined",
289
+ "color": "primary",
290
+ "variant": "text",
291
+ "code": "openDialog('editEmployee', { id: rowData.Id, name: rowData.Name })"
292
+ }
293
+ ```
294
+
295
+ Delete with confirmation:
296
+
297
+ ```json
298
+ {
299
+ "label": "Delete",
300
+ "icon": "DeleteOutlined",
301
+ "color": "error",
302
+ "variant": "text",
303
+ "confirmation": "Delete this employee? This cannot be undone.",
304
+ "code": "await DeleteService(Endpoints.HR.Delete.Employee, true, {}, { id: rowData.Id }); reportRefs[THIS_NODE]?.current && (await reportRefs[THIS_NODE].current.refresh?.()); showToast('Deleted', 'success')"
305
+ }
306
+ ```
307
+
308
+ ---
309
+
310
+ ## 7. Viewer actions (`viewerActions`)
311
+
312
+ **New.** Page-level buttons rendered in the toolbar after Refresh. Same schema as `actionsConfig`, but `code` runs **without a row context** — `rowData` is not in scope.
313
+
314
+ Use these for:
315
+ - "New" / "Add" buttons that open a creation dialog.
316
+ - "Export PDF" / "Download" using custom logic.
317
+ - "Reset filters" / "Open in new tab" / etc.
318
+ - Any whole-report-level operation.
319
+
320
+ ### 7.1 Schema (identical to row actions, minus `rowData`)
321
+
322
+ ```json
323
+ "viewerActions": [
324
+ {
325
+ "label": "New Customer",
326
+ "icon": "AddOutlined",
327
+ "color": "primary",
328
+ "variant": "contained",
329
+ "code": "openDialog('newCustomer')"
330
+ },
331
+ {
332
+ "label": "Reset Filters",
333
+ "icon": "RestoreOutlined",
334
+ "color": "inherit",
335
+ "variant": "outlined",
336
+ "code": "setData(prev => ({ ...prev, customerFilter: {} }))"
337
+ }
338
+ ]
339
+ ```
340
+
341
+ ### 7.2 Scope inside viewer-action `code`
342
+
343
+ Identical to row actions **minus** `rowData`. Specifically: `form`, `data`, `setData`, `dataRef`, `reportRefs`, `openDialog`, `closeDialog`, `pageData`, `pageSetData`, `pageDataRef`, `pageReportRefs`, all `*Service` helpers, `showToast`, `router`, etc.
344
+
345
+ ### 7.3 Render rules
346
+
347
+ - Buttons are rendered in declared order.
348
+ - Defaults: `variant: 'outlined'`, `color: 'primary'`, no icon.
349
+ - `disabled` accepts a boolean or an expression that resolves to a boolean.
350
+
351
+ ---
352
+
353
+ ## 8. Extra columns (`extraCols`)
354
+
355
+ An array of complete AG Grid `colDef` objects **appended** to the end of the report's columns.
356
+
357
+ Common fields:
358
+
359
+ ```ts
360
+ {
361
+ headerName?: string,
362
+ field?: string, // server field path (use friendlyName-mapped names for report-defined columns)
363
+ pinned?: 'left' | 'right',
364
+ width?: number,
365
+ editable?: boolean | expression,
366
+ cellRenderer?: string | function,
367
+ valueGetter?: function,
368
+ valueSetter?: function,
369
+ cellStyle?: object | function,
370
+ comparator?: function,
371
+ // …any other AG Grid colDef field
372
+ }
373
+ ```
374
+
375
+ Functions can be written as JS strings and are evaluated in a context that has `reportRefs`, `nodeName`, `data`, `dataRef`, `setData`, `openDialog`, `closeDialog`.
376
+
377
+ Example — append a computed column:
378
+
379
+ ```json
380
+ "extraCols": [
381
+ {
382
+ "headerName": "Full Name",
383
+ "field": "FullName",
384
+ "valueGetter": "params.data.FirstName + ' ' + params.data.LastName",
385
+ "width": 200,
386
+ "pinned": "left"
387
+ }
388
+ ]
389
+ ```
390
+
391
+ ---
392
+
393
+ ## 9. Column overrides (`columnsConfig`)
394
+
395
+ Per-field overrides matched by `field` name to existing report columns. Doesn't add columns — it modifies them.
396
+
397
+ ```json
398
+ "columnsConfig": [
399
+ {
400
+ "field": "Salary",
401
+ "config": {
402
+ "editable": true,
403
+ "cellStyle": { "textAlign": "right" },
404
+ "valueFormatter": "params.value != null ? params.value.toLocaleString() : ''"
405
+ }
406
+ },
407
+ {
408
+ "field": "Status",
409
+ "config": {
410
+ "cellRenderer": "statusBadgeRenderer"
411
+ }
412
+ }
413
+ ]
414
+ ```
415
+
416
+ Inside string-typed functions you get the same scope as `extraCols` functions (`reportRefs`, `nodeName`, `data`, etc.).
417
+
418
+ ---
419
+
420
+ ## 10. The `reportRefs` API
421
+
422
+ Each report on a page registers an `updateRef` under its `key`. From any Calculation, you can interact with another report's data:
423
+
424
+ ```js
425
+ // Inside a row action or viewer action
426
+ const otherRef = reportRefs['ordersReport']?.current
427
+ // otherRef is an array of { rowId, field, oldValue, newValue, rowData, ... } for pending edits
428
+
429
+ // Common helpers (auto-injected into row-action scope):
430
+ setRefValue('ordersReport', rowId, 'Status', 'Approved')
431
+ getRefValue('ordersReport', rowId, 'Status')
432
+ clearRefRow('ordersReport', rowId)
433
+ clearAllRef('ordersReport')
434
+ getNodeRef('ordersReport') // raw updateRef
435
+ ```
436
+
437
+ Pass `THIS_NODE` (the sentinel) to target the **current** report from inside its own row action.
438
+
439
+ ---
440
+
441
+ ## 11. Refresh & timers
442
+
443
+ Three ways to trigger reload:
444
+
445
+ 1. **Bind to a reactive value.** Pass `refresh: data.refreshTick` and toggle it: `setData(p => ({...p, refreshTick: Date.now()}))`.
446
+ 2. **Auto-refresh via external timer.** Set `externalTimer` to a number of minutes. The viewer toggles its internal `localRefresh` every interval.
447
+ 3. **User clicks the Refresh button** in the toolbar.
448
+
449
+ ---
450
+
451
+ ## 12. Sessions (`sessionId`)
452
+
453
+ When `sessionId` is set, the viewer:
454
+ - Reads a previously-saved filter payload from `localStorage` (key derived from `sessionId`) on mount.
455
+ - Saves the user's filters / search chips / selected params to that key on change.
456
+
457
+ Use this to share filter state across navigations (e.g. a dashboard tile and a deep-link page that both display the same report should use the same `sessionId`).
458
+
459
+ ---
460
+
461
+ ## 13. Selection params (`globalParams`)
462
+
463
+ If the report's backend definition declares `selectionParams` (e.g. "Year", "Region"), pass `globalParams` as an object keyed by **0-based index** to override specific ones at runtime:
464
+
465
+ ```json
466
+ {
467
+ "id": "SALES_BY_REGION",
468
+ "globalParams": "{ 0: data.selectedYear, 1: data.selectedRegion }"
469
+ }
470
+ ```
471
+
472
+ `globalParams` keys missing from the object leave the report's defaults intact.
473
+
474
+ ---
475
+
476
+ ## 14. Common recipes
477
+
478
+ ### 14.1 Master / detail with two reports
479
+
480
+ Click a row in the master report → load detail rows scoped to that id:
481
+
482
+ ```json
483
+ {
484
+ "type": "layout",
485
+ "props": { "cols": 2 },
486
+ "children": [
487
+ {
488
+ "type": "layout-cell",
489
+ "children": [
490
+ {
491
+ "type": "reportViewer",
492
+ "props": {
493
+ "key": "customers",
494
+ "id": "CUSTOMER_LIST",
495
+ "actionsConfig": [
496
+ {
497
+ "label": "View Orders",
498
+ "icon": "OpenInNewOutlined",
499
+ "code": "setData(prev => ({ ...prev, selectedCustomerId: rowData.Id }))"
500
+ }
501
+ ]
502
+ }
503
+ }
504
+ ]
505
+ },
506
+ {
507
+ "type": "layout-cell",
508
+ "children": [
509
+ {
510
+ "type": "reportViewer",
511
+ "props": {
512
+ "key": "customerOrders",
513
+ "id": "ORDER_LIST",
514
+ "filter": "data.selectedCustomerId ? { Tfilter: [{ path: 'CustomerId', value: data.selectedCustomerId, method: 'Equal' }] } : { Tfilter: [] }"
515
+ }
516
+ }
517
+ ]
518
+ }
519
+ ]
520
+ }
521
+ ```
522
+
523
+ ### 14.2 Toolbar "New" + "Export PDF" via viewer actions
524
+
525
+ ```json
526
+ {
527
+ "type": "reportViewer",
528
+ "props": {
529
+ "key": "invoices",
530
+ "id": "INVOICE_LIST",
531
+ "title": "Invoices",
532
+ "caption": "All open invoices",
533
+ "viewerActions": [
534
+ {
535
+ "label": "New Invoice",
536
+ "icon": "AddOutlined",
537
+ "variant": "contained",
538
+ "code": "openDialog('newInvoice')"
539
+ },
540
+ {
541
+ "label": "Open Print Layout",
542
+ "icon": "PrintOutlined",
543
+ "code": "openPrintLayout('invoiceLayout', { filter: data.invoiceFilter })"
544
+ }
545
+ ]
546
+ }
547
+ }
548
+ ```
549
+
550
+ ### 14.3 Auto-refresh + bound external filter
551
+
552
+ ```json
553
+ {
554
+ "type": "reportViewer",
555
+ "props": {
556
+ "key": "liveSignups",
557
+ "id": "USER_SIGNUPS",
558
+ "externalTimer": 1,
559
+ "filter": "{ Tfilter: [{ path: 'CreatedAt', value: [data.fromDate, data.toDate], method: 'Between' }] }"
560
+ }
561
+ }
562
+ ```
563
+
564
+ ### 14.4 Tenant-scoped report via `customFilterCode`
565
+
566
+ ```json
567
+ {
568
+ "type": "reportViewer",
569
+ "props": {
570
+ "key": "myShopOrders",
571
+ "id": "ORDER_LIST",
572
+ "filter": "{ customFilterCode: \"if (authContext?.user?.shopId) customFilter.push({ path: 'ShopId', value: authContext.user.shopId, method: 'Equal' })\" }"
573
+ }
574
+ }
575
+ ```
576
+
577
+ ### 14.5 Inline edit + save via `columnsConfig` and a viewer action
578
+
579
+ ```json
580
+ {
581
+ "type": "reportViewer",
582
+ "props": {
583
+ "key": "prices",
584
+ "id": "PRICE_LIST",
585
+ "columnsConfig": [
586
+ { "field": "Price", "config": { "editable": true } }
587
+ ],
588
+ "viewerActions": [
589
+ {
590
+ "label": "Save Changes",
591
+ "icon": "SaveOutlined",
592
+ "variant": "contained",
593
+ "code": "const pending = reportRefs['prices']?.current ?? []; if (pending.length === 0) { showToast('Nothing to save', 'info'); return } await PostService(Endpoints.Pricing.Post.UpdateMany, true, { changes: pending }); showToast('Saved', 'success'); pending.length = 0"
594
+ }
595
+ ]
596
+ }
597
+ }
598
+ ```
599
+
600
+ ---
601
+
602
+ ## 15. Pitfalls
603
+
604
+ - **Forgetting `key`** — every report must have a unique `key`. Without it, `reportRefs` collides and cross-report helpers (`setRefValue` etc.) target the wrong table.
605
+ - **Using `dataGrid` for server data** — `dataGrid` is client-side. Performance dies on large datasets and pagination doesn't exist.
606
+ - **Filter passed as object literal in the inspector** — the inspector treats `filter` as an **expression**. Write `{ Tfilter: [...] }`, **not** `{"Tfilter": [...]}` (the latter is a JSON literal; you want a JS object). The same goes for `globalParams`.
607
+ - **`actionsConfig` `code` not awaiting async work** — if you call a `*Service` and don't `await`, the next line runs before the response. Always `await` HTTP calls.
608
+ - **Refresh races** — toggling `refresh` and `externalTimer` simultaneously can cause double-fetches. If you need precise timing, drive everything through `refresh` + `setData`.
609
+ - **Action button labels in RTL pages** — the toolbar is LTR by default but the page may be RTL; labels render fine either way, but icons appear on the visual "start" side of the button (left in LTR, right in RTL).
610
+ - **`globalParams` index, not key** — keys are 0-based positional indices into `selectionParams`, not names. Misindex and you'll silently override the wrong param.
611
+ - **Setting `setOutGridApi` from a builder schema** — it only makes sense in code-level embedding. Builder schemas don't have a place for raw function props.
612
+
613
+ ---
614
+
615
+ ## 16. Full schema examples
616
+
617
+ ### 16.1 Minimal
618
+
619
+ ```json
620
+ {
621
+ "type": "reportViewer",
622
+ "props": { "key": "list", "id": "EMPLOYEE_LIST" }
623
+ }
624
+ ```
625
+
626
+ ### 16.2 Heavily configured
627
+
628
+ ```json
629
+ {
630
+ "type": "reportViewer",
631
+ "props": {
632
+ "key": "ordersReport",
633
+ "id": "ORDER_LIST",
634
+ "pageId": "OPEN_ORDERS",
635
+ "title": "Open Orders",
636
+ "caption": "Real-time, refreshes every minute",
637
+ "height": "70vh",
638
+ "noHeader": true,
639
+ "externalTimer": 1,
640
+ "sessionId": "dashboard.orders",
641
+ "filter": "{ fixedTFilter: [{ path: 'Status', value: 'Open', method: 'Equal' }], customFilterCode: \"if (authContext?.user?.regionId) customFilter.push({ path: 'RegionId', value: authContext.user.regionId, method: 'Equal' })\" }",
642
+ "globalParams": "{ 0: data.year }",
643
+ "columnsConfig": [
644
+ { "field": "Amount", "config": { "editable": false, "cellStyle": { "textAlign": "right" }, "valueFormatter": "params.value?.toLocaleString()" } }
645
+ ],
646
+ "extraCols": [
647
+ { "headerName": "Margin", "field": "Margin", "valueGetter": "(params.data.Revenue - params.data.Cost) / params.data.Revenue", "valueFormatter": "(params.value * 100).toFixed(1) + '%'" }
648
+ ],
649
+ "actionsConfig": [
650
+ { "label": "View", "icon": "VisibilityOutlined", "color": "primary", "variant": "text", "code": "openDialog('orderDetail', { orderId: rowData.Id })" },
651
+ { "label": "Approve", "icon": "CheckOutlined", "color": "success", "variant": "text", "code": "await PostService(Endpoints.Orders.Post.Approve, true, { id: rowData.Id }); showToast('Approved', 'success'); setData(p => ({ ...p, refreshTick: Date.now() }))" },
652
+ { "label": "Cancel", "icon": "CloseOutlined", "color": "error", "variant": "text", "confirmation": "Cancel this order?", "code": "await DeleteService(Endpoints.Orders.Delete.Cancel, true, {}, { id: rowData.Id }); showToast('Cancelled', 'info')" }
653
+ ],
654
+ "viewerActions": [
655
+ { "label": "New Order", "icon": "AddOutlined", "color": "primary", "variant": "contained", "code": "openDialog('newOrder')" },
656
+ { "label": "Export Report", "icon": "DownloadOutlined", "code": "openPrintLayout('ordersReport', { filter: data.activeFilter })" }
657
+ ]
658
+ }
659
+ }
660
+ ```
661
+
662
+ ---
663
+
664
+ ## 17. Cheat sheet — minimum mental model
665
+
666
+ 1. **Use `reportViewer` when data lives on the server.** Use a unique `key`. Set `id` and/or `pageId`. That's enough.
667
+ 2. **Style the toolbar via `title` + `caption`.** Both are optional. Place row-level buttons in `actionsConfig`, page-level buttons in `viewerActions`.
668
+ 3. **Filter via the `filter` prop.** Three slots: `fixedTFilter` (always applied), `LocalTfilter` (user-removable), `customFilterCode` (server-evaluated JS to compose conditional filters from auth/context).
669
+ 4. **Modify columns** with `columnsConfig` (per-field overrides) and `extraCols` (appended new columns).
670
+ 5. **Cross-report ops** go through `reportRefs[otherReportKey]` or the `setRefValue` / `getRefValue` helpers (with `THIS_NODE` for self-targeting).
671
+ 6. **Refresh** by toggling `refresh`, by setting `externalTimer`, or by the user clicking Refresh.
672
+
673
+ If anything in this manual contradicts what's in [README.md](../README.md), README is the source of truth for the package surface — but for `reportViewer` specifically, this document is authoritative.