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,581 @@
1
+ # `<ReportViewer>` — Standalone Component Reference (Host-App Usage)
2
+
3
+ > **Scope.** This document is for embedding `<ReportViewer>` as a React component **outside the UI Builder** — inside a regular page, dialog, drawer, or dashboard tile in a host Next.js app that consumes `robobyte-front-builder`.
4
+ >
5
+ > If you're emitting a **builder schema** (`{ "type": "reportViewer", "props": {…} }`), use [`training/35-reportViewer.md`](../training/35-reportViewer.md) instead — that document covers the inspector-resolved expression scope, `actionsConfig` / `viewerActions` Calculation runtime, `reportRefs` registry, and the `THIS_NODE` sentinel, none of which apply here.
6
+
7
+ In host-app code you write plain React. There is no Calculation runtime, no cross-report ref registry, no inspector. Row actions are plain AG Grid `cellRenderer` functions, viewer actions are plain `onClick` handlers, and you wire data through props and callbacks.
8
+
9
+ ---
10
+
11
+ ## 1. Quick start
12
+
13
+ ```jsx
14
+ import { ReportViewer } from 'robobyte-front-builder'
15
+
16
+ export default function OrdersPage() {
17
+ return (
18
+ <ReportViewer
19
+ pageId="ORDERS_BY_REGION"
20
+ height="70vh"
21
+ title="Orders"
22
+ caption="All regions"
23
+ />
24
+ )
25
+ }
26
+ ```
27
+
28
+ That's the minimum. The component:
29
+ - Calls `Endpoints.ReportBuilder.Post.GetReportDetails` on mount with the supplied `id` / `pageId`.
30
+ - Renders the toolbar, AG Grid Enterprise grid, and side tool panel.
31
+ - Handles pagination, filtering, sorting, grouping, aggregation, templates internally.
32
+
33
+ ---
34
+
35
+ ## 2. Prerequisites
36
+
37
+ ### 2.1 Provider in your `_app.js`
38
+
39
+ `<ReportViewer>` reads the API base URL, auth token, AG Grid license, **and the Quartz theme** from `<RoboByteFrontBuilderProvider>`. Without it, HTTP calls go to the wrong host and AG Grid throws a license warning. The `agGridTheme` prop (optional) lets you customize colors, header / row height, fonts, border radius, etc. across every grid the package renders.
40
+
41
+ ```jsx
42
+ import { RoboByteFrontBuilderProvider } from 'robobyte-front-builder'
43
+
44
+ export default function App({ Component, pageProps }) {
45
+ const auth = useYourAuth()
46
+ return (
47
+ <RoboByteFrontBuilderProvider
48
+ baseURL={process.env.NEXT_PUBLIC_API_BASE_URL}
49
+ apiURL={process.env.NEXT_PUBLIC_API_URL}
50
+ user={auth.user}
51
+ accessToken={auth.accessToken}
52
+ agGridLicenseKey={process.env.NEXT_PUBLIC_AG_GRID_LICENSE_KEY}
53
+ // Optional: theme overrides for every <AgGridReact> in the package.
54
+ // agGridTheme={{ accentColor: '#3b82f6', headerHeight: 32 }}
55
+ >
56
+ <Component {...pageProps} />
57
+ </RoboByteFrontBuilderProvider>
58
+ )
59
+ }
60
+ ```
61
+
62
+ ### 2.2 Peer dependencies installed in the host app
63
+
64
+ ```bash
65
+ npm install \
66
+ react react-dom next \
67
+ @mui/material @mui/icons-material @mui/lab @mui/system \
68
+ @mui/x-data-grid @mui/x-date-pickers @mui/x-internals @mui/x-virtualizer \
69
+ @emotion/react @emotion/styled \
70
+ ag-grid-community ag-grid-enterprise ag-grid-react \
71
+ xlsx react-hot-toast
72
+ ```
73
+
74
+ ### 2.3 `next.config.js` transpile + bare-alias plugin
75
+
76
+ See [INTEGRATION.md](../INTEGRATION.md) for the exact `next-transpile-modules` + `NormalModuleReplacementPlugin` setup. `<ReportViewer>` depends on internal bare-aliased imports (`services/*`, `views/*`) that the plugin resolves.
77
+
78
+ ---
79
+
80
+ ## 3. Props reference
81
+
82
+ Listed alphabetically. Where types overlap with the builder schema, the **value** here is a real JS object / function, **not** an expression string.
83
+
84
+ ### 3.1 Identity & data
85
+
86
+ | Prop | Type | Required | Description |
87
+ |---|---|---|---|
88
+ | `id` | string \| number | one of | Report id from the backend. |
89
+ | `pageId` | string \| number | one of | Page / sub-report id. **At least one of `id` / `pageId` must be set.** |
90
+ | `sessionId` | string | no | Persists user filter / search state to `localStorage` under this key across navigations. |
91
+ | `globalParams` | object | no | Object keyed by 0-based **index** into the report's `selectionParams`, e.g. `{ 0: year, 1: region }`. Overrides defaults at request time. |
92
+
93
+ ### 3.2 Filter
94
+
95
+ | Prop | Type | Description |
96
+ |---|---|---|
97
+ | `filter` | object | Merged into the request. **See section 4 for the full shape.** Plain JS object — not an expression string. |
98
+
99
+ ### 3.3 Presentation
100
+
101
+ | Prop | Type | Default | Description |
102
+ |---|---|---|---|
103
+ | `title` | string | report's name | Bold heading on the left of the toolbar. |
104
+ | `caption` | string | — | Small secondary text under `title`. |
105
+ | `height` | string | `'85vh'` | CSS height (`'500px'`, `'70vh'`, etc.). |
106
+ | `minimized` | boolean | `false` | Removes toolbar + paddings. Useful inside cards / popovers. |
107
+ | `noHeader` | boolean | `false` | Hides the standalone-page chrome (report-name + Studio button). Toolbar (title / search / filter / refresh / viewer actions) stays. |
108
+ | `isSingle` | boolean | `false` | Return only the first row. |
109
+ | `dataAsObject` | boolean | `false` | Returns data keyed by row's unique id rather than an array. |
110
+
111
+ ### 3.4 Refresh & timer
112
+
113
+ | Prop | Type | Description |
114
+ |---|---|---|
115
+ | `refresh` | any | Toggle / change this value to force a reload. Use a state value (`refreshTick`) and `setRefreshTick(Date.now())` to trigger. |
116
+ | `externalTimer` | number | Auto-refresh interval **in minutes**. `0` or `null` disables. |
117
+ | `isRerender` | boolean | Toggle to fully remount the grid (resets internal state). |
118
+
119
+ ### 3.5 Columns
120
+
121
+ | Prop | Type | Description |
122
+ |---|---|---|
123
+ | `extraCols` | `colDef[]` | AG Grid `colDef` objects appended to the report's columns. Plain functions are fine — `valueGetter`, `valueSetter`, `cellRenderer`, `cellStyle` etc. |
124
+ | `columnsConfig` | array | Per-field overrides matched by `field`: `[{ field, config: { editable, valueFormatter, ... } }, …]`. |
125
+
126
+ ### 3.6 Actions
127
+
128
+ | Prop | Type | Description |
129
+ |---|---|---|
130
+ | `actions` | function | AG Grid `cellRenderer` function for the row's Actions cell. Receives `params` (the AG Grid params object). You write whatever JSX you want. |
131
+ | `viewerActions` | `ViewerAction[]` | Page-level buttons rendered after Filter / Refresh. **See section 7.** |
132
+
133
+ ### 3.7 Programmatic hooks
134
+
135
+ | Prop | Type | Description |
136
+ |---|---|---|
137
+ | `setData` | `(rows) => void` | Callback fired when row data loads / changes. |
138
+ | `setOutGridApi` | `(api) => void` | Callback receiving the AG Grid API instance once `onGridReady` fires. Use it to read selected rows, refresh server-side, export, etc. |
139
+ | `setEventRowSelected` | `(event) => void` | Optional callback fired on every row-selection change. |
140
+ | `updateRef` | `React.MutableRefObject` | Per-instance ref tracking row mutations. Allocate with `useRef([])` if you want to read pending edits. |
141
+
142
+ > **`reportRefs`, `nodeId`, `THIS_NODE`, builder-context props** — these only matter inside the UI Builder. Don't pass them.
143
+
144
+ ---
145
+
146
+ ## 4. The `filter` object
147
+
148
+ Plain JS object passed each request. Three slots:
149
+
150
+ ```ts
151
+ {
152
+ // Filters always applied (the user cannot remove these via the UI).
153
+ fixedTFilter?: FilterItem[],
154
+
155
+ // Local-style filters; appear in the UI and can be removed by the user.
156
+ LocalTfilter?: FilterItem[],
157
+
158
+ // JS code string evaluated server-side at request time. Pushes additional
159
+ // FilterItems into a customFilter array. See 4.3.
160
+ customFilterCode?: string,
161
+ }
162
+ ```
163
+
164
+ ### 4.1 `FilterItem` shape
165
+
166
+ ```ts
167
+ {
168
+ path: string, // dotted field path, e.g. "Customer.RegionId"
169
+ friendlyName?: string, // label for UI display
170
+ value: any, // string | number | boolean | array (for In/NotIn/Between)
171
+ method: FilterOp,
172
+ orGroupName?: string, // optional, groups multiple items into an OR-block
173
+ }
174
+ ```
175
+
176
+ ### 4.2 Operator values (`method`)
177
+
178
+ `Equal` · `NotEqual` · `Greater` · `GreaterOrEqual` · `Less` · `LessOrEqual` · `Contains` · `StartsWith` · `EndsWith` · `In` · `NotIn` · `IsNull` · `IsNotNull` · `Between` (value is `[from, to]`).
179
+
180
+ ### 4.3 `customFilterCode` (advanced)
181
+
182
+ A code string evaluated by `SGrid` at request time. Scope:
183
+
184
+ | Var | Source |
185
+ |---|---|
186
+ | `authContext` | The host's auth context (`{ user, accessToken, … }`) from `RoboByteFrontBuilderProvider`. |
187
+ | `context` | The host's system context. |
188
+ | `data` | An empty `{}` in standalone mode (this hook is mainly useful inside the builder where `data` is the page's reactive state). |
189
+ | `customFilter` | An `[]` to push items into. |
190
+
191
+ Most host-app callers don't need `customFilterCode` — just build the filter object in JS from your React state and pass it as `filter`. Use `customFilterCode` only when the same filter rules must follow the report through a deep link / templated session.
192
+
193
+ ### 4.4 Building a filter from React state
194
+
195
+ ```jsx
196
+ const [region, setRegion] = useState(null)
197
+ const [status, setStatus] = useState('Open')
198
+
199
+ const filter = useMemo(() => ({
200
+ Tfilter: [
201
+ { path: 'Status', value: status, method: 'Equal' },
202
+ ...(region ? [{ path: 'RegionId', value: region, method: 'Equal' }] : []),
203
+ ],
204
+ }), [region, status])
205
+
206
+ return <ReportViewer pageId="ORDERS" filter={filter} height="60vh" />
207
+ ```
208
+
209
+ > **Tip.** `useMemo` matters — passing a fresh object literal on every parent render causes the grid to refetch unnecessarily.
210
+
211
+ ---
212
+
213
+ ## 5. Title / caption / toolbar layout
214
+
215
+ The toolbar is a single row:
216
+
217
+ ```
218
+ [Title / Caption] [Search] [Filter] [Refresh] [viewerActions…]
219
+ ```
220
+
221
+ - Pass `title` and `caption` as plain strings.
222
+ - Search collapses to an icon button when empty; expands on focus / when filters are active. Arrow keys navigate the field-selector popover; Enter picks the highlighted field.
223
+ - **Display Settings** (auto-refresh interval, "Load all data", "Export to Excel") live in the side panel under the Templates tool icon — the user toggles them, not you.
224
+
225
+ ---
226
+
227
+ ## 6. Row actions — custom `cellRenderer`
228
+
229
+ Inside the builder, `actionsConfig` is an array of `{ label, icon, code }`. Outside, you pass a complete `cellRenderer` function via `actions`. The function receives AG Grid's `params`; you write whatever React you want.
230
+
231
+ ```jsx
232
+ function OrderActionsCell({ params, onEdit, onCancel }) {
233
+ return (
234
+ <Box sx={{ display: 'flex', gap: 0.5 }}>
235
+ <IconButton size='small' onClick={() => onEdit(params.data)}>
236
+ <EditOutlined fontSize='small' />
237
+ </IconButton>
238
+ <IconButton size='small' color='error' onClick={() => onCancel(params.data.Id)}>
239
+ <DeleteOutlined fontSize='small' />
240
+ </IconButton>
241
+ </Box>
242
+ )
243
+ }
244
+
245
+ export default function OrdersPage() {
246
+ const handleEdit = (row) => setEditing(row)
247
+ const handleCancel = (id) => cancelOrder(id).then(refresh)
248
+
249
+ return (
250
+ <ReportViewer
251
+ pageId="ORDERS"
252
+ actions={(params) => (
253
+ <OrderActionsCell params={params} onEdit={handleEdit} onCancel={handleCancel} />
254
+ )}
255
+ />
256
+ )
257
+ }
258
+ ```
259
+
260
+ ---
261
+
262
+ ## 7. Viewer actions (`viewerActions`)
263
+
264
+ Page-level buttons rendered in the toolbar after Filter / Refresh. Each item is a plain object with an `onClick` you write yourself. **No code-string Calculation runtime.**
265
+
266
+ ### 7.1 `ViewerAction` shape
267
+
268
+ ```ts
269
+ {
270
+ key?: string, // React key — auto-generated if omitted
271
+ label: string, // button text
272
+ icon?: string, // MUI icon name resolved against @mui/icons-material
273
+ // (e.g. "AddOutlined", "DownloadOutlined")
274
+ color?: 'primary' | 'secondary' | 'success' | 'error' | 'warning' | 'info' | 'inherit',
275
+ variant?: 'text' | 'outlined' | 'contained', // default 'outlined'
276
+ disabled?: boolean,
277
+ onClick: () => void | Promise<void>,
278
+ }
279
+ ```
280
+
281
+ > `confirmation` is recognized by the builder runtime; in standalone mode just handle the confirm yourself inside `onClick` (e.g. `if (!window.confirm('Sure?')) return`).
282
+
283
+ ### 7.2 Example — "New" + "Export"
284
+
285
+ ```jsx
286
+ import { ReportViewer } from 'robobyte-front-builder'
287
+ import { useState, useCallback } from 'react'
288
+
289
+ export default function CustomersPage() {
290
+ const [refreshTick, setRefreshTick] = useState(0)
291
+ const [createOpen, setCreateOpen] = useState(false)
292
+
293
+ const viewerActions = [
294
+ {
295
+ label: 'New Customer',
296
+ icon: 'AddOutlined',
297
+ variant: 'contained',
298
+ color: 'primary',
299
+ onClick: () => setCreateOpen(true),
300
+ },
301
+ {
302
+ label: 'Refresh Manually',
303
+ icon: 'RefreshOutlined',
304
+ onClick: () => setRefreshTick(Date.now()),
305
+ },
306
+ {
307
+ label: 'Export CSV',
308
+ icon: 'DownloadOutlined',
309
+ onClick: () => gridApi?.exportDataAsCsv({ fileName: 'customers.csv' }),
310
+ },
311
+ ]
312
+
313
+ const [gridApi, setGridApi] = useState(null)
314
+
315
+ return (
316
+ <>
317
+ <ReportViewer
318
+ pageId="CUSTOMERS"
319
+ title="Customers"
320
+ caption="All accounts"
321
+ refresh={refreshTick}
322
+ viewerActions={viewerActions}
323
+ setOutGridApi={setGridApi}
324
+ />
325
+ <CreateCustomerDialog open={createOpen} onClose={() => setCreateOpen(false)} />
326
+ </>
327
+ )
328
+ }
329
+ ```
330
+
331
+ ---
332
+
333
+ ## 8. Refresh patterns
334
+
335
+ ### 8.1 Trigger on a button click
336
+
337
+ ```jsx
338
+ const [tick, setTick] = useState(0)
339
+
340
+ <ReportViewer pageId="X" refresh={tick} />
341
+ <Button onClick={() => setTick(t => t + 1)}>Reload</Button>
342
+ ```
343
+
344
+ ### 8.2 Auto-refresh every minute
345
+
346
+ ```jsx
347
+ <ReportViewer pageId="X" externalTimer={1} />
348
+ ```
349
+
350
+ ### 8.3 Refresh after a mutation
351
+
352
+ ```jsx
353
+ async function saveAndRefresh() {
354
+ await PostService(Endpoints.X.Post.Save, true, payload)
355
+ setTick(t => t + 1)
356
+ }
357
+ ```
358
+
359
+ > **Don't mix `refresh` and `externalTimer` for the same window** — both fire and you'll see double-fetches. Pick one source of truth.
360
+
361
+ ---
362
+
363
+ ## 9. Reading loaded data
364
+
365
+ Pass `setData` to get a callback every time the report loads new rows:
366
+
367
+ ```jsx
368
+ const [rows, setRows] = useState([])
369
+
370
+ <ReportViewer
371
+ pageId="X"
372
+ setData={setRows}
373
+ />
374
+
375
+ // Below the grid:
376
+ <Typography variant='caption'>{rows.length} rows loaded</Typography>
377
+ ```
378
+
379
+ ### 9.1 Combining with `dataAsObject`
380
+
381
+ `dataAsObject: true` returns rows keyed by their server-side unique id:
382
+
383
+ ```jsx
384
+ const [rowsById, setRowsById] = useState({})
385
+ <ReportViewer pageId="X" dataAsObject setData={setRowsById} />
386
+
387
+ // rowsById['ord_123'] ← O(1) lookup by id
388
+ ```
389
+
390
+ ### 9.2 Single-record reports
391
+
392
+ `isSingle: true` returns only the first row. Useful for "current user", "summary", etc.
393
+
394
+ ```jsx
395
+ const [me, setMe] = useState(null)
396
+ <ReportViewer pageId="ME" isSingle minimized setData={setMe} />
397
+ ```
398
+
399
+ ---
400
+
401
+ ## 10. AG Grid API access
402
+
403
+ `setOutGridApi` gives you the live API. Common uses:
404
+
405
+ ```jsx
406
+ const [api, setApi] = useState(null)
407
+
408
+ <ReportViewer pageId="X" setOutGridApi={setApi} />
409
+
410
+ // Selected rows:
411
+ const selected = api?.getSelectedRows() ?? []
412
+
413
+ // Refresh server-side without re-mounting:
414
+ api?.refreshServerSide({ purge: true })
415
+
416
+ // Export visible rows to CSV:
417
+ api?.exportDataAsCsv({ fileName: 'x.csv' })
418
+
419
+ // Apply a column state programmatically:
420
+ api?.applyColumnState({ state: [{ colId: 'CreatedAt', sort: 'desc' }] })
421
+ ```
422
+
423
+ ---
424
+
425
+ ## 11. Templates
426
+
427
+ The Templates tool panel (side panel) is fully self-contained — the user opens it via the icon at the right edge of the grid, saves their column layout + filters + sort, and switches between templates. Nothing for the host to wire.
428
+
429
+ ---
430
+
431
+ ## 12. Common recipes
432
+
433
+ ### 12.1 Report inside a Dialog
434
+
435
+ ```jsx
436
+ import { Dialog, DialogTitle, DialogContent } from '@mui/material'
437
+
438
+ <Dialog open={open} onClose={onClose} maxWidth='xl' fullWidth>
439
+ <DialogTitle>Customer Orders</DialogTitle>
440
+ <DialogContent sx={{ p: 0 }}>
441
+ <ReportViewer
442
+ pageId="CUSTOMER_ORDERS"
443
+ filter={{ Tfilter: [{ path: 'CustomerId', value: customerId, method: 'Equal' }] }}
444
+ height='70vh'
445
+ noHeader
446
+ />
447
+ </DialogContent>
448
+ </Dialog>
449
+ ```
450
+
451
+ ### 12.2 Two reports, master/detail
452
+
453
+ ```jsx
454
+ const [selectedCustomerId, setSelectedCustomerId] = useState(null)
455
+
456
+ <Grid container spacing={2}>
457
+ <Grid item xs={6}>
458
+ <ReportViewer
459
+ pageId="CUSTOMERS"
460
+ title="Customers"
461
+ actions={(params) => (
462
+ <IconButton size='small' onClick={() => setSelectedCustomerId(params.data.Id)}>
463
+ <OpenInNewOutlined fontSize='small' />
464
+ </IconButton>
465
+ )}
466
+ />
467
+ </Grid>
468
+ <Grid item xs={6}>
469
+ <ReportViewer
470
+ pageId="ORDERS"
471
+ title='Orders'
472
+ caption={selectedCustomerId ? `Customer ${selectedCustomerId}` : 'Pick a customer'}
473
+ filter={selectedCustomerId
474
+ ? { Tfilter: [{ path: 'CustomerId', value: selectedCustomerId, method: 'Equal' }] }
475
+ : { Tfilter: [{ path: 'Id', value: -1, method: 'Equal' }] }} // force empty
476
+ height='60vh'
477
+ />
478
+ </Grid>
479
+ </Grid>
480
+ ```
481
+
482
+ ### 12.3 Bound to URL query params (deep-link friendly)
483
+
484
+ ```jsx
485
+ import { useRouter } from 'next/router'
486
+
487
+ const router = useRouter()
488
+ const region = router.query.region
489
+
490
+ const filter = useMemo(() => (
491
+ region
492
+ ? { Tfilter: [{ path: 'Region', value: region, method: 'Equal' }] }
493
+ : {}
494
+ ), [region])
495
+
496
+ return (
497
+ <ReportViewer
498
+ pageId="SALES"
499
+ title='Sales'
500
+ caption={region ? `Region: ${region}` : 'All regions'}
501
+ filter={filter}
502
+ sessionId={`sales-${region ?? 'all'}`} // separate filter state per region
503
+ />
504
+ )
505
+ ```
506
+
507
+ ### 12.4 Custom toolbar action that triggers a refresh
508
+
509
+ ```jsx
510
+ const [tick, setTick] = useState(0)
511
+ const refresh = useCallback(() => setTick(Date.now()), [])
512
+
513
+ const viewerActions = [
514
+ {
515
+ label: 'Sync Now',
516
+ icon: 'CloudDownloadOutlined',
517
+ variant: 'contained',
518
+ onClick: async () => {
519
+ try {
520
+ await PostService(Endpoints.Sync.Post.Trigger, true, {})
521
+ refresh()
522
+ } catch (e) {
523
+ console.error(e)
524
+ }
525
+ },
526
+ },
527
+ ]
528
+
529
+ <ReportViewer pageId="LIVE_FEED" refresh={tick} viewerActions={viewerActions} />
530
+ ```
531
+
532
+ ### 12.5 Embed with pre-filtered data and no chrome (tile / widget)
533
+
534
+ ```jsx
535
+ <Card sx={{ p: 2 }}>
536
+ <Typography variant='h6'>Top 5 customers this week</Typography>
537
+ <ReportViewer
538
+ pageId="TOP_CUSTOMERS"
539
+ filter={{ Tfilter: [{ path: 'Range', value: 'week', method: 'Equal' }] }}
540
+ height='320px'
541
+ minimized
542
+ isSingle={false}
543
+ />
544
+ </Card>
545
+ ```
546
+
547
+ ---
548
+
549
+ ## 13. Pitfalls
550
+
551
+ - **Forgetting `<RoboByteFrontBuilderProvider>`** — `apiURL` / `accessToken` / AG Grid license come from there. Without it the report 404s on the API call and the grid throws a license banner. Wrap once at `_app.js` level.
552
+ - **`filter` object identity** — passing `filter={{ Tfilter: [...] }}` inline rebuilds the object every render and triggers refetches. Memoize with `useMemo`.
553
+ - **Mixing `refresh` with `externalTimer`** — both reload independently. Pick one strategy per report.
554
+ - **`viewerActions` icon string** — the value must match an export from `@mui/icons-material` exactly (e.g. `'AddOutlined'`, not `'add-outlined'`). Unknown icon names render the button without an icon, no error.
555
+ - **`actions` is a function, not an array** — in standalone mode `actions` is an AG Grid `cellRenderer` callback. If you tried passing a JSON array (`actionsConfig` style), nothing renders. Use the function form shown in section 6.
556
+ - **`globalParams` is index-keyed** — keys are 0-based positional indices into the report's `selectionParams`, not names. Wrong index → wrong override.
557
+ - **Setting `nodeId` / `reportRefs`** — these are builder-internal. Don't pass them; they'll either be ignored or interfere with the standalone grid.
558
+ - **Mounting two `<ReportViewer>` with the same `sessionId`** — they share saved filter state, which usually isn't what you want. Suffix with a discriminator (`sessionId={`orders-${tenantId}`}`).
559
+ - **Resetting `data` to refresh** — `<ReportViewer>` doesn't take a `data` prop in standalone mode. To force a reload, change `refresh` or `isRerender`. Re-rendering the parent with new props alone won't refetch.
560
+
561
+ ---
562
+
563
+ ## 14. Cheat sheet
564
+
565
+ 1. **Import**: `import { ReportViewer } from 'robobyte-front-builder'`.
566
+ 2. **Minimum props**: `pageId` (or `id`) + `height`.
567
+ 3. **Filter from React state**: build a plain `{ Tfilter: [...] }` object, memoize, pass via `filter`.
568
+ 4. **Refresh on demand**: bump a state value passed as `refresh`. Auto-refresh: `externalTimer={minutes}`.
569
+ 5. **Row actions**: pass `actions={(params) => <YourCell params={params} />}`.
570
+ 6. **Page-level buttons**: `viewerActions={[{ label, icon, variant, onClick }, …]}`.
571
+ 7. **Read loaded rows**: pass `setData`. Read the AG Grid API: pass `setOutGridApi`.
572
+ 8. **Wrap your app once in `<RoboByteFrontBuilderProvider>`** at `_app.js` level — that's where the API URL, token, and AG Grid license come from.
573
+
574
+ ---
575
+
576
+ ## Cross-references
577
+
578
+ - **Schema-level / AI-builder usage** of the same component: [`training/35-reportViewer.md`](../training/35-reportViewer.md).
579
+ - **Package installation, peer deps, `next.config.js`**: [`INTEGRATION.md`](../INTEGRATION.md).
580
+ - **All `RoboByteFrontBuilderProvider` props**: [`README.md`](../README.md).
581
+ - **AG Grid theme customization** (the `agGridTheme` prop and `useAgGridTheme()` hook): README → *AG Grid theme*.