robobyte-front-builder 1.0.26 → 1.0.28

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 (88) hide show
  1. package/INTEGRATION.md +6 -0
  2. package/LICENSE +65 -0
  3. package/README.md +166 -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/index.js +14 -0
  9. package/src/lib/muiTheme.js +655 -0
  10. package/src/lib/providers/RoboByteFrontBuilderProvider.jsx +45 -1
  11. package/src/pages/_app.js +1 -0
  12. package/src/pages/printBuilder/index.jsx +26 -19
  13. package/src/pages/viewBuilder/index.jsx +29 -19
  14. package/training/00-index.md +168 -0
  15. package/training/01-input.md +144 -0
  16. package/training/02-checkbox.md +107 -0
  17. package/training/03-dropdown.md +135 -0
  18. package/training/04-datepicker.md +139 -0
  19. package/training/05-radio.md +123 -0
  20. package/training/06-number.md +133 -0
  21. package/training/07-textarea.md +114 -0
  22. package/training/08-richtext.md +112 -0
  23. package/training/09-tag.md +110 -0
  24. package/training/10-time.md +107 -0
  25. package/training/11-toggle.md +108 -0
  26. package/training/12-signature.md +107 -0
  27. package/training/13-autocomplete.md +134 -0
  28. package/training/14-button.md +168 -0
  29. package/training/15-label.md +138 -0
  30. package/training/16-header.md +128 -0
  31. package/training/17-divider.md +96 -0
  32. package/training/18-image.md +105 -0
  33. package/training/19-link.md +108 -0
  34. package/training/20-banner.md +122 -0
  35. package/training/21-progress-circle.md +101 -0
  36. package/training/22-progress-line.md +93 -0
  37. package/training/23-menu.md +139 -0
  38. package/training/24-popover.md +114 -0
  39. package/training/25-layout.md +116 -0
  40. package/training/26-layout-cell.md +143 -0
  41. package/training/27-card.md +87 -0
  42. package/training/28-wizard.md +126 -0
  43. package/training/29-wizard-step.md +92 -0
  44. package/training/30-repeater.md +123 -0
  45. package/training/31-dialog.md +131 -0
  46. package/training/32-breadcrumb.md +121 -0
  47. package/training/33-dataGrid.md +129 -0
  48. package/training/34-dataTableViewer.md +115 -0
  49. package/training/35-reportViewer.md +673 -0
  50. package/training/36-viewRenderer.md +110 -0
  51. package/training/37-treeView.md +170 -0
  52. package/training/38-kpi-metric.md +148 -0
  53. package/training/39-kpi-trend.md +105 -0
  54. package/training/40-kpi-badge.md +112 -0
  55. package/training/41-kpi-statusDot.md +118 -0
  56. package/training/42-kpi-iconBox.md +114 -0
  57. package/training/43-kpi-gauge.md +143 -0
  58. package/training/44-kpi-bulletChart.md +126 -0
  59. package/training/45-kpi-colorScale.md +143 -0
  60. package/training/46-kpi-rating.md +125 -0
  61. package/training/47-kpi-countdown.md +151 -0
  62. package/training/48-fetchReportData.md +276 -0
  63. package/training/49-printLayout.md +215 -0
  64. package/training/examples/01-login-form.json +176 -0
  65. package/training/examples/02-contact-form.json +141 -0
  66. package/training/examples/03-kpi-cards-row.json +123 -0
  67. package/training/examples/04-settings-toggles.json +153 -0
  68. package/training/examples/05-user-profile-card.json +136 -0
  69. package/training/examples/06-date-range-filter.json +108 -0
  70. package/training/examples/07-search-bar-results.json +130 -0
  71. package/training/examples/08-notification-settings.json +131 -0
  72. package/training/examples/09-employee-profile-form.json +259 -0
  73. package/training/examples/10-invoice-form.json +241 -0
  74. package/training/examples/11-dashboard-overview.json +251 -0
  75. package/training/examples/12-registration-wizard.json +154 -0
  76. package/training/examples/13-product-catalog.json +168 -0
  77. package/training/examples/14-data-table-with-filters.json +180 -0
  78. package/training/examples/15-tabbed-profile.json +92 -0
  79. package/training/examples/16-kpi-full-row.json +203 -0
  80. package/training/examples/17-tree-detail-view.json +139 -0
  81. package/training/examples/18-employee-management.json +233 -0
  82. package/training/examples/19-sales-dashboard.json +272 -0
  83. package/training/examples/20-checkout-wizard.json +225 -0
  84. package/training/examples/21-analytics-page.json +222 -0
  85. package/training/examples/22-hr-onboarding.json +222 -0
  86. package/training/examples/23-document-browser.json +241 -0
  87. package/training/examples/24-order-management.json +290 -0
  88. package/training/examples/25-crm-contact-page.json +272 -0
@@ -0,0 +1,379 @@
1
+ # `fetchReportDataByPageId` — Standalone Reference (Host-App Usage)
2
+
3
+ > **Scope.** This document is for calling `fetchReportDataByPageId` directly from host-app code — pages, hooks, action handlers, server-side logic, anywhere outside the UI Builder.
4
+ >
5
+ > If you're emitting builder Calculation code, use [`training/48-fetchReportData.md`](../training/48-fetchReportData.md) instead — that variant explains the auto-injected `fetchReportData` symbol, the Calculation scope, and the conventions of in-builder action code.
6
+
7
+ A pure data fetcher by report `pageId`. No AG Grid. No React. Resolves the report's builder model (cached for the session), applies your filter on top of the report's own, optionally evaluates `customFilterCode`, calls `GenericGet`, and returns a normalized result.
8
+
9
+ Use it whenever a host page needs **rows from a server-defined report** but you're not embedding a `<ReportViewer>` — e.g. seeding a form, computing a KPI counter, exporting to PDF, prefetching lookup tables, building a custom chart on top of report data.
10
+
11
+ ---
12
+
13
+ ## 1. Quick start
14
+
15
+ ```js
16
+ import { fetchReportDataByPageId } from 'robobyte-front-builder'
17
+
18
+ const result = await fetchReportDataByPageId({ pageId: 'OPEN_INVOICES' })
19
+ if (result.success) {
20
+ console.log(`Got ${result.rows.length} of ${result.total} rows`)
21
+ }
22
+ ```
23
+
24
+ That's the minimum. `pageId` is the only required parameter.
25
+
26
+ ---
27
+
28
+ ## 2. Prerequisites
29
+
30
+ ### 2.1 Import paths — recommended vs legacy
31
+
32
+ **Recommended** (since v1.0.23):
33
+
34
+ ```js
35
+ import { fetchReportDataByPageId } from 'robobyte-front-builder'
36
+ ```
37
+
38
+ **Legacy bare-alias path** (still supported):
39
+
40
+ ```js
41
+ import fetchReportDataByPageId from 'services/reportData/fetchReportData'
42
+ ```
43
+
44
+ The bare-alias path only works because the host's `next.config.js` includes a `NormalModuleReplacementPlugin` rule (`packageOwnedServices`) that routes `services/reportData/fetchReportData` to the package's source. Both paths resolve to **the same module instance**, so the in-memory `builderModelCache` is shared regardless of which one you use. Pick the package import for new code — it doesn't depend on the special webpack rule and is easier to read.
45
+
46
+ ### 2.2 The provider supplies API URL + auth
47
+
48
+ `fetchReportDataByPageId` calls `Services.PostService` internally, which reads the API base URL and `accessToken` from `<RoboByteFrontBuilderProvider>` (set up at `_app.js` level). Without the provider, requests go nowhere useful and the call returns `{ success: false }`.
49
+
50
+ ---
51
+
52
+ ## 3. Signature
53
+
54
+ ```ts
55
+ import { fetchReportDataByPageId } from 'robobyte-front-builder'
56
+
57
+ await fetchReportDataByPageId({
58
+ pageId, // string | number — REQUIRED
59
+ filter = {}, // see section 5
60
+ isDataOnly = false, // true → return rows array directly, no wrapper
61
+ dataAsObject = false, // true → expand underscore-paths into nested objects
62
+ isPagination = true, // false → fetch all matching rows
63
+ isSingle = false, // true → return first row only (row, not array)
64
+ globalParams = null, // { 0: value, 1: value, ... } overrides selectionParams by index
65
+ page = 1, // 1-based page number
66
+ pageSize = 50, // rows per page
67
+ authContext = null, // see section 5.3
68
+ context = null, // see section 5.3
69
+ })
70
+ ```
71
+
72
+ Returns a `Promise`. **Always `await`** — see pitfalls.
73
+
74
+ ---
75
+
76
+ ## 4. Return shape
77
+
78
+ ```ts
79
+ // Default — full wrapper
80
+ {
81
+ success: true,
82
+ rows: RowType[] | RowType, // array OR single row (if isSingle)
83
+ total: number, // total rows on server (paginated mode)
84
+ rawResponse: any, // exact backend response
85
+ finalRequest: { // useful for debugging — what was actually sent
86
+ endpoint: string,
87
+ getType: 'Pagination' | 'NoPagination' | 'SqlPagination' | 'SqlNoPagination',
88
+ filter: object,
89
+ sourceModel: string,
90
+ params: { page, pageSize },
91
+ }
92
+ }
93
+
94
+ // On error
95
+ { success: false, error: 'Report detailsOld not found' /* or Error object */ }
96
+
97
+ // With isDataOnly: true
98
+ RowType[] | RowType // bare rows; no wrapper at all
99
+ ```
100
+
101
+ Variants:
102
+
103
+ | Options | Returns |
104
+ |---|---|
105
+ | Defaults | `{ success, rows: Row[], total, ... }` |
106
+ | `isSingle: true` | `{ success, rows: Row, total, ... }` — `rows` is a single object |
107
+ | `isDataOnly: true` | `Row[]` (or `Row` if `isSingle`) — no wrapper |
108
+ | `isPagination: false` | All matching rows fetched in one request |
109
+ | `dataAsObject: true` | `Foo_Bar` flat keys expanded into `Foo.Bar` nested objects |
110
+
111
+ ---
112
+
113
+ ## 5. The `filter` parameter
114
+
115
+ Plain JS object. Three special slots; anything else at the top level is forwarded to the backend as-is.
116
+
117
+ | Slot | Type | Purpose |
118
+ |---|---|---|
119
+ | `Tfilter` | `FilterItem[]` | Filters merged with the report's own. |
120
+ | `TFilter` | `FilterItem[]` | Alias accepted for legacy callers. |
121
+ | `customFilterCode` | `string` | JS source evaluated at fetch time. |
122
+
123
+ ### 5.1 `FilterItem` shape
124
+
125
+ ```ts
126
+ {
127
+ path: string, // dotted field path, e.g. "Customer.RegionId"
128
+ friendlyName?: string,
129
+ value: any, // scalar | array (for In/NotIn/Between)
130
+ method: 'Equal' | 'NotEqual' | 'Greater' | 'GreaterOrEqual' |
131
+ 'Less' | 'LessOrEqual' | 'Contains' | 'StartsWith' |
132
+ 'EndsWith' | 'In' | 'NotIn' | 'IsNull' | 'IsNotNull' | 'Between',
133
+ orGroupName?: string, // group multiple items into an OR-block
134
+ }
135
+ ```
136
+
137
+ ### 5.2 Null-value stripping
138
+
139
+ Items where `value === null || value === undefined || value === ''` are dropped before the request is sent. So you can build a filter object from optional state without conditional guards:
140
+
141
+ ```js
142
+ const filter = {
143
+ Tfilter: [
144
+ { path: 'Status', value: status, method: 'Equal' }, // dropped if status is null
145
+ { path: 'OwnerId', value: ownerId, method: 'Equal' }, // dropped if ownerId is null
146
+ { path: 'Year', value: 2026, method: 'Equal' }, // kept
147
+ ]
148
+ }
149
+ ```
150
+
151
+ ### 5.3 `customFilterCode` (advanced)
152
+
153
+ A code string evaluated server-side at request time. Useful when the filter rule depends on the auth context and you want the rule to follow the report regardless of the caller. Available scope:
154
+
155
+ | Var | Source |
156
+ |---|---|
157
+ | `authContext` | The `authContext` arg you pass to `fetchReportDataByPageId`. |
158
+ | `context` | The `context` arg you pass. |
159
+ | `filters` | The merged `Tfilter` array. Read-only. |
160
+ | `customFilter` | Empty `[]` to push items into. |
161
+
162
+ ```js
163
+ const result = await fetchReportDataByPageId({
164
+ pageId: 'MY_ORDERS',
165
+ filter: {
166
+ customFilterCode: `
167
+ if (authContext?.user?.shopId) {
168
+ customFilter.push({ path: 'ShopId', value: authContext.user.shopId, method: 'Equal' })
169
+ }
170
+ `,
171
+ },
172
+ authContext: { user }, // pass it explicitly
173
+ })
174
+ ```
175
+
176
+ > **In host-app code, you usually don't need `customFilterCode`.** Just build the filter object in plain JS from your React state and pass it. `customFilterCode` is for cases where the filter rule should travel with the report (e.g. server-side report definitions that include security constraints).
177
+
178
+ ---
179
+
180
+ ## 6. Recipes
181
+
182
+ ### 6.1 Fetch once on mount
183
+
184
+ ```jsx
185
+ import { useEffect, useState } from 'react'
186
+ import { fetchReportDataByPageId } from 'robobyte-front-builder'
187
+
188
+ export function useTickets() {
189
+ const [data, setData] = useState(null)
190
+ const [loading, setLoading] = useState(true)
191
+ const [error, setError] = useState(null)
192
+
193
+ useEffect(() => {
194
+ let cancelled = false
195
+ setLoading(true)
196
+ fetchReportDataByPageId({ pageId: 'OPEN_TICKETS', isPagination: false })
197
+ .then(r => {
198
+ if (cancelled) return
199
+ if (r.success) setData(r.rows)
200
+ else setError(r.error)
201
+ })
202
+ .finally(() => { if (!cancelled) setLoading(false) })
203
+
204
+ return () => { cancelled = true }
205
+ }, [])
206
+
207
+ return { data, loading, error }
208
+ }
209
+ ```
210
+
211
+ ### 6.2 KPI counter (just the total, no rows)
212
+
213
+ ```js
214
+ async function loadOpenInvoiceCount() {
215
+ const r = await fetchReportDataByPageId({
216
+ pageId: 'OPEN_INVOICES',
217
+ isPagination: true,
218
+ pageSize: 1, // we only need `total`, not the rows
219
+ })
220
+ return r.success ? r.total : 0
221
+ }
222
+ ```
223
+
224
+ ### 6.3 Filter built from React state, memoized
225
+
226
+ ```jsx
227
+ import { useMemo, useEffect, useState } from 'react'
228
+
229
+ function FilteredReportData({ regionId, status }) {
230
+ const [rows, setRows] = useState([])
231
+
232
+ const filter = useMemo(() => ({
233
+ Tfilter: [
234
+ { path: 'Status', value: status, method: 'Equal' },
235
+ { path: 'RegionId', value: regionId, method: 'Equal' },
236
+ ],
237
+ }), [regionId, status])
238
+
239
+ useEffect(() => {
240
+ let cancelled = false
241
+ fetchReportDataByPageId({ pageId: 'ORDERS', filter, isPagination: false })
242
+ .then(r => { if (!cancelled && r.success) setRows(r.rows) })
243
+ return () => { cancelled = true }
244
+ }, [filter])
245
+
246
+ return <Table rows={rows} />
247
+ }
248
+ ```
249
+
250
+ > `useMemo` matters — a fresh `filter` object on every parent render would refetch every render.
251
+
252
+ ### 6.4 Single record by id (for an Edit dialog)
253
+
254
+ ```js
255
+ async function loadCustomer(id) {
256
+ const r = await fetchReportDataByPageId({
257
+ pageId: 'CUSTOMER_BY_ID',
258
+ isSingle: true,
259
+ filter: { Tfilter: [{ path: 'Id', value: id, method: 'Equal' }] },
260
+ })
261
+ if (!r.success || !r.rows) throw new Error('not found')
262
+ return r.rows // single object, not an array
263
+ }
264
+ ```
265
+
266
+ ### 6.5 Bulk export to CSV / Excel
267
+
268
+ ```js
269
+ import * as XLSX from 'xlsx'
270
+
271
+ async function exportAll() {
272
+ const r = await fetchReportDataByPageId({
273
+ pageId: 'ORDERS',
274
+ isPagination: false, // fetch every row
275
+ dataAsObject: true, // expand nested keys for readable headers
276
+ isDataOnly: true, // skip the wrapper
277
+ })
278
+ const ws = XLSX.utils.json_to_sheet(r)
279
+ const wb = XLSX.utils.book_new()
280
+ XLSX.utils.book_append_sheet(wb, ws, 'Orders')
281
+ XLSX.writeFile(wb, 'orders.xlsx')
282
+ }
283
+ ```
284
+
285
+ ### 6.6 Pagination cursor in a hook
286
+
287
+ ```jsx
288
+ function usePaginatedReport(pageId, filter) {
289
+ const [page, setPage] = useState(1)
290
+ const [rows, setRows] = useState([])
291
+ const [total, setTotal] = useState(0)
292
+ const [loading, setLoading] = useState(false)
293
+
294
+ useEffect(() => {
295
+ let cancelled = false
296
+ setLoading(true)
297
+ fetchReportDataByPageId({ pageId, filter, page, pageSize: 50 })
298
+ .then(r => {
299
+ if (cancelled) return
300
+ if (r.success) { setRows(r.rows); setTotal(r.total) }
301
+ })
302
+ .finally(() => { if (!cancelled) setLoading(false) })
303
+ return () => { cancelled = true }
304
+ }, [pageId, filter, page])
305
+
306
+ return { rows, total, loading, page, setPage }
307
+ }
308
+ ```
309
+
310
+ ### 6.7 Server-side selection-param override
311
+
312
+ ```js
313
+ const r = await fetchReportDataByPageId({
314
+ pageId: 'SALES_BY_REGION',
315
+ globalParams: { 0: year, 1: regionId }, // 0-based positional, NOT keyed by name
316
+ })
317
+ ```
318
+
319
+ ### 6.8 Forwarding auth context for `customFilterCode`
320
+
321
+ ```js
322
+ import { useAuth } from 'your-auth-lib'
323
+
324
+ function Component() {
325
+ const auth = useAuth()
326
+ // ...
327
+ const r = await fetchReportDataByPageId({
328
+ pageId: 'MY_SHOP_REPORT',
329
+ filter: {
330
+ customFilterCode: `
331
+ if (authContext?.user?.shopId) {
332
+ customFilter.push({ path: 'ShopId', value: authContext.user.shopId, method: 'Equal' })
333
+ }
334
+ `,
335
+ },
336
+ authContext: { user: auth.user },
337
+ })
338
+ }
339
+ ```
340
+
341
+ ---
342
+
343
+ ## 7. Pitfalls
344
+
345
+ - **Forgetting `await`.** Returns a `Promise`. Calling `r.success` on the unresolved Promise is always `undefined`.
346
+ - **Importing from the wrong path.** Use `import { fetchReportDataByPageId } from 'robobyte-front-builder'`. The legacy bare-alias `'services/reportData/fetchReportData'` also works (the `NormalModuleReplacementPlugin` routes it to the same file), but anything else — relative paths, deep-import paths into `node_modules` — bypasses the dedupe rules and risks loading a second module instance with its own empty `builderModelCache`.
347
+ - **`isSingle: true` returns the row, not an array.** `r.rows[0]` will crash; use `r.rows` directly.
348
+ - **`isPagination: false` on a huge report.** All rows in one request. Use it for lookups (≤ a few thousand rows). For unbounded datasets, paginate.
349
+ - **Filter `null`/`''`/`undefined` items are dropped.** This is usually convenient (you can pass partial state with no conditional guards). But if you literally want to filter for null, use `method: 'IsNull'` — don't pass `value: null`.
350
+ - **`globalParams` is index-keyed.** Keys are 0-based positional indices into `selectionParams`, not names.
351
+ - **The builder-model cache lives for the page session.** First call to a given `pageId` hits the metadata endpoint; subsequent calls reuse it. If you change the report definition mid-session, the cache stays stale until full reload. Hard-refresh the tab if you need fresh metadata.
352
+ - **`filter` object identity in React.** When passing `filter` to a `useEffect`, memoize it with `useMemo`. A fresh literal each render triggers a refetch each render.
353
+ - **`customFilterCode` cannot reference React state.** It's a code string evaluated server-side with `authContext`, `context`, `filters`, `customFilter` in scope — nothing else. If you need state-based filtering, build the filter array in JS before the call.
354
+ - **Missing provider.** Without `<RoboByteFrontBuilderProvider>` wrapping your app, `Services.PostService` has no base URL or auth token; the request goes to the wrong host or 401s.
355
+
356
+ ---
357
+
358
+ ## 8. Cheat sheet
359
+
360
+ 1. `import { fetchReportDataByPageId } from 'robobyte-front-builder'` (recommended). The legacy bare-alias `'services/reportData/fetchReportData'` still works and resolves to the same module.
361
+ 2. `await fetchReportDataByPageId({ pageId: '...' })` — that's the minimum.
362
+ 3. Build the `filter` object in plain JS — `{ Tfilter: [{ path, value, method }] }`. Null values are auto-stripped.
363
+ 4. Memoize the `filter` with `useMemo` when passing into `useEffect`.
364
+ 5. `isDataOnly: true` returns the bare rows — skip the `r.success` ceremony when you trust the call.
365
+ 6. `isSingle: true` returns one row. `r.rows` is that row.
366
+ 7. `isPagination: false` for full datasets; combine with `isDataOnly` for clean export logic.
367
+ 8. `dataAsObject: true` to expand flat `Foo_Bar` keys into nested `Foo.Bar` objects.
368
+ 9. `globalParams: { 0: ..., 1: ... }` overrides selection params by index.
369
+ 10. Wrap the call with cancellation in `useEffect` (`let cancelled = false; … return () => { cancelled = true }`).
370
+
371
+ ---
372
+
373
+ ## Cross-references
374
+
375
+ - **In-builder Calculation usage of the same function** (auto-injected as `fetchReportData`): [`training/48-fetchReportData.md`](../training/48-fetchReportData.md).
376
+ - **`<ReportViewer>` host-app component** (when you want the data **rendered** rather than fetched): [`docs/ReportViewer.md`](./ReportViewer.md).
377
+ - **Package installation, peer deps, `next.config.js`**: [`INTEGRATION.md`](../INTEGRATION.md).
378
+ - **All `RoboByteFrontBuilderProvider` props**: [`README.md`](../README.md).
379
+ - **AG Grid theme customization** (only matters if the fetched rows end up rendered in a grid downstream): README → *AG Grid theme*.