robobyte-front-builder 1.0.26 → 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 (81) hide show
  1. package/LICENSE +65 -0
  2. package/README.md +57 -0
  3. package/docs/ReportViewer.md +581 -0
  4. package/docs/fetchReportData.md +379 -0
  5. package/docs/printLayout.md +405 -0
  6. package/package.json +29 -1
  7. package/training/00-index.md +168 -0
  8. package/training/01-input.md +144 -0
  9. package/training/02-checkbox.md +107 -0
  10. package/training/03-dropdown.md +135 -0
  11. package/training/04-datepicker.md +139 -0
  12. package/training/05-radio.md +123 -0
  13. package/training/06-number.md +133 -0
  14. package/training/07-textarea.md +114 -0
  15. package/training/08-richtext.md +112 -0
  16. package/training/09-tag.md +110 -0
  17. package/training/10-time.md +107 -0
  18. package/training/11-toggle.md +108 -0
  19. package/training/12-signature.md +107 -0
  20. package/training/13-autocomplete.md +134 -0
  21. package/training/14-button.md +168 -0
  22. package/training/15-label.md +138 -0
  23. package/training/16-header.md +128 -0
  24. package/training/17-divider.md +96 -0
  25. package/training/18-image.md +105 -0
  26. package/training/19-link.md +108 -0
  27. package/training/20-banner.md +122 -0
  28. package/training/21-progress-circle.md +101 -0
  29. package/training/22-progress-line.md +93 -0
  30. package/training/23-menu.md +139 -0
  31. package/training/24-popover.md +114 -0
  32. package/training/25-layout.md +116 -0
  33. package/training/26-layout-cell.md +143 -0
  34. package/training/27-card.md +87 -0
  35. package/training/28-wizard.md +126 -0
  36. package/training/29-wizard-step.md +92 -0
  37. package/training/30-repeater.md +123 -0
  38. package/training/31-dialog.md +131 -0
  39. package/training/32-breadcrumb.md +121 -0
  40. package/training/33-dataGrid.md +129 -0
  41. package/training/34-dataTableViewer.md +115 -0
  42. package/training/35-reportViewer.md +673 -0
  43. package/training/36-viewRenderer.md +110 -0
  44. package/training/37-treeView.md +170 -0
  45. package/training/38-kpi-metric.md +148 -0
  46. package/training/39-kpi-trend.md +105 -0
  47. package/training/40-kpi-badge.md +112 -0
  48. package/training/41-kpi-statusDot.md +118 -0
  49. package/training/42-kpi-iconBox.md +114 -0
  50. package/training/43-kpi-gauge.md +143 -0
  51. package/training/44-kpi-bulletChart.md +126 -0
  52. package/training/45-kpi-colorScale.md +143 -0
  53. package/training/46-kpi-rating.md +125 -0
  54. package/training/47-kpi-countdown.md +151 -0
  55. package/training/48-fetchReportData.md +276 -0
  56. package/training/49-printLayout.md +215 -0
  57. package/training/examples/01-login-form.json +176 -0
  58. package/training/examples/02-contact-form.json +141 -0
  59. package/training/examples/03-kpi-cards-row.json +123 -0
  60. package/training/examples/04-settings-toggles.json +153 -0
  61. package/training/examples/05-user-profile-card.json +136 -0
  62. package/training/examples/06-date-range-filter.json +108 -0
  63. package/training/examples/07-search-bar-results.json +130 -0
  64. package/training/examples/08-notification-settings.json +131 -0
  65. package/training/examples/09-employee-profile-form.json +259 -0
  66. package/training/examples/10-invoice-form.json +241 -0
  67. package/training/examples/11-dashboard-overview.json +251 -0
  68. package/training/examples/12-registration-wizard.json +154 -0
  69. package/training/examples/13-product-catalog.json +168 -0
  70. package/training/examples/14-data-table-with-filters.json +180 -0
  71. package/training/examples/15-tabbed-profile.json +92 -0
  72. package/training/examples/16-kpi-full-row.json +203 -0
  73. package/training/examples/17-tree-detail-view.json +139 -0
  74. package/training/examples/18-employee-management.json +233 -0
  75. package/training/examples/19-sales-dashboard.json +272 -0
  76. package/training/examples/20-checkout-wizard.json +225 -0
  77. package/training/examples/21-analytics-page.json +222 -0
  78. package/training/examples/22-hr-onboarding.json +222 -0
  79. package/training/examples/23-document-browser.json +241 -0
  80. package/training/examples/24-order-management.json +290 -0
  81. package/training/examples/25-crm-contact-page.json +272 -0
@@ -0,0 +1,125 @@
1
+ # Component: kpi-rating
2
+
3
+ An interactive or read-only star/heart/circle rating component. Writes the selected value back to `data[valueKey]`.
4
+
5
+ ---
6
+
7
+ ## Props — Main Tab
8
+
9
+ ### Data
10
+ | Prop | Type | Options / Notes |
11
+ |------|------|-----------------|
12
+ | `valueKey` | expression | Data key for the current rating value. Clicking a star writes the new rating to `data[valueKey]`. |
13
+
14
+ ### Appearance
15
+ | Prop | Type | Options / Notes |
16
+ |------|------|-----------------|
17
+ | `max` | expression | Maximum rating (number of icons shown). E.g. `5`, `10`. |
18
+ | `icon` | select | `star` · `heart` · `circle` · `square` — Icon shape. |
19
+ | `color` | expression | Color of filled/active icons, e.g. `"#f5a623"`, `"primary.main"`. |
20
+ | `emptyColor` | expression | Color of unfilled icons, e.g. `"#e0e0e0"`. |
21
+ | `size` | expression | Icon size in px, e.g. `"28"`. |
22
+ | `gap` | expression | Gap between icons in px, e.g. `"4"`. |
23
+ | `align` | select | `left` · `center` · `right` |
24
+ | `readonly` | boolean | `true` prevents user interaction — display only. |
25
+
26
+ ### Actions
27
+ | Prop | Type | Notes |
28
+ |------|------|-------|
29
+ | `onChange` | kpi-action | Fires when the user selects a rating. Available: `newValue` (new rating number), `clickedValue` (alias). The new value is automatically written to `data[valueKey]` before the action runs. |
30
+
31
+ ---
32
+
33
+ ## Props — Style Tab
34
+
35
+ No dedicated style tab.
36
+
37
+ ---
38
+
39
+ ## Use Cases
40
+
41
+ **When to use:**
42
+ - Product reviews: user rates a product 1–5 stars.
43
+ - Feedback forms: rate your experience.
44
+ - Priority indicators with visual icons.
45
+ - Read-only display of an average rating score.
46
+
47
+ **When NOT to use:**
48
+ - Sliders with continuous values → use a number input with slider.
49
+ - Pure numeric display → use `kpi-metric`.
50
+ - Status with fixed categories → use `kpi-badge`.
51
+
52
+ ---
53
+
54
+ ## Schema Examples
55
+
56
+ ### Interactive 5-star rating
57
+ ```json
58
+ {
59
+ "type": "kpi-rating",
60
+ "props": {
61
+ "valueKey": "data.userRating",
62
+ "max": 5,
63
+ "icon": "star",
64
+ "color": "#f5a623",
65
+ "emptyColor": "#e0e0e0",
66
+ "size": "32",
67
+ "gap": "4",
68
+ "align": "center",
69
+ "readonly": false
70
+ }
71
+ }
72
+ ```
73
+
74
+ ### Read-only product rating display
75
+ ```json
76
+ {
77
+ "type": "kpi-rating",
78
+ "props": {
79
+ "valueKey": "data.product.avgRating",
80
+ "max": 5,
81
+ "icon": "star",
82
+ "color": "#fbc02d",
83
+ "emptyColor": "#e0e0e0",
84
+ "size": "20",
85
+ "readonly": true,
86
+ "align": "left"
87
+ }
88
+ }
89
+ ```
90
+
91
+ ### Heart rating with action
92
+ ```json
93
+ {
94
+ "type": "kpi-rating",
95
+ "props": {
96
+ "valueKey": "data.satisfaction",
97
+ "max": 5,
98
+ "icon": "heart",
99
+ "color": "#e91e63",
100
+ "size": "28",
101
+ "align": "center",
102
+ "onChange": {
103
+ "type": "custom",
104
+ "code": "submitSatisfactionScore({ score: newValue, sessionId: data.sessionId })"
105
+ }
106
+ }
107
+ }
108
+ ```
109
+
110
+ ### 10-point circle rating
111
+ ```json
112
+ {
113
+ "type": "kpi-rating",
114
+ "props": {
115
+ "valueKey": "data.npsScore",
116
+ "max": 10,
117
+ "icon": "circle",
118
+ "color": "primary.main",
119
+ "emptyColor": "#e0e0e0",
120
+ "size": "20",
121
+ "gap": "2",
122
+ "align": "left"
123
+ }
124
+ }
125
+ ```
@@ -0,0 +1,151 @@
1
+ # Component: kpi-countdown
2
+
3
+ A live countdown timer displaying days, hours, minutes, and seconds remaining until a target date/time. Fires an `onDone` action when it reaches zero.
4
+
5
+ ---
6
+
7
+ ## Props — Main Tab
8
+
9
+ ### Data
10
+ | Prop | Type | Options / Notes |
11
+ |------|------|-----------------|
12
+ | `targetDateKey` | expression | Data key or expression returning the target date. Accepts: ISO datetime string, ISO date string, epoch ms (number), epoch seconds (number < 1e11), or JS Date object. E.g. `"dealDeadline"` → `data.dealDeadline`. |
13
+ | `targetDate` | expression | Static target date when not using `targetDateKey`. E.g. `"2025-12-31T23:59:59"`. |
14
+ | `doneLabel` | expression | Text shown when the countdown reaches zero, e.g. `"Expired"`, `"Time's up!"`. |
15
+ | `label` | expression | Optional heading shown above the countdown digits. |
16
+
17
+ ### Segments
18
+ | Prop | Type | Options / Notes |
19
+ |------|------|-----------------|
20
+ | `showDays` | boolean | Show days counter. |
21
+ | `showHours` | boolean | Show hours counter. |
22
+ | `showMinutes` | boolean | Show minutes counter. |
23
+ | `showSeconds` | boolean | Show seconds counter. |
24
+
25
+ ### Unit Label Overrides
26
+ | Prop | Type | Options / Notes |
27
+ |------|------|-----------------|
28
+ | `labelDays` | expression | Override "days" label, e.g. `"d"`. |
29
+ | `labelHours` | expression | Override "hrs" label, e.g. `"h"`. |
30
+ | `labelMinutes` | expression | Override "min" label. |
31
+ | `labelSeconds` | expression | Override "sec" label. |
32
+
33
+ ### Layout
34
+ | Prop | Type | Options / Notes |
35
+ |------|------|-----------------|
36
+ | `size` | select | `small` · `medium` · `large` — Controls digit font scale. |
37
+ | `align` | select | `left` · `center` · `right` |
38
+ | `separator` | expression | Character between digit groups, e.g. `":"`. Default: `":"`. |
39
+
40
+ ### Colors
41
+ | Prop | Type | Options / Notes |
42
+ |------|------|-----------------|
43
+ | `color` | expression | Digit color. Overridden by thresholds. E.g. `"#1976d2"`. |
44
+ | `unitColor` | expression | Color of the unit labels (days/hrs/min/sec). Default: `text.disabled`. |
45
+ | `separatorColor` | expression | Color of the separator character. Default: `text.disabled`. |
46
+ | `doneLabelColor` | expression | Color of the done label. Default: `success.main`. |
47
+
48
+ ### Digit Style
49
+ | Prop | Type | Options / Notes |
50
+ |------|------|-----------------|
51
+ | `numberFontWeight` | select | `300` · `400` · `500` · `600` · `700` · `800` · `900` |
52
+
53
+ ### Label Style
54
+ | Prop | Type | Options / Notes |
55
+ |------|------|-----------------|
56
+ | `labelFontSize` | expression | Heading label font size. |
57
+ | `labelFontWeight` | select | `300` · `400` · `500` · `600` · `700` · `800` |
58
+ | `labelColor` | expression | Heading label color. Default: `text.secondary`. |
59
+
60
+ ### Thresholds
61
+ | Prop | Type | Notes |
62
+ |------|------|-------|
63
+ | `thresholds` | thresholds-editor | Applied on remaining seconds. E.g. `< 3600` → `error.main` (last hour turns red). |
64
+
65
+ ### On Done
66
+ | Prop | Type | Notes |
67
+ |------|------|-------|
68
+ | `onDoneCode` | code | Code run when countdown reaches zero. |
69
+
70
+ ---
71
+
72
+ ## Use Cases
73
+
74
+ **When to use:**
75
+ - Deal/offer expiry countdowns (e.g. flash sale).
76
+ - Appointment/meeting countdown.
77
+ - Deadline trackers for tasks or bids.
78
+ - Event date countdowns (conference, launch).
79
+
80
+ **When NOT to use:**
81
+ - Elapsed time (uptime) → display a label with calculated duration.
82
+ - Non-time-based progress → use `kpi-gauge` or `progress-line`.
83
+ - Static date display → use `label`.
84
+
85
+ ---
86
+
87
+ ## Schema Examples
88
+
89
+ ### Deal expiry countdown
90
+ ```json
91
+ {
92
+ "type": "kpi-countdown",
93
+ "props": {
94
+ "targetDateKey": "data.offerExpiry",
95
+ "label": "Offer Expires In",
96
+ "doneLabel": "Offer Expired",
97
+ "showDays": true,
98
+ "showHours": true,
99
+ "showMinutes": true,
100
+ "showSeconds": true,
101
+ "size": "large",
102
+ "align": "center",
103
+ "numberFontWeight": "700",
104
+ "color": "#1976d2",
105
+ "thresholds": [
106
+ { "operator": "<", "value": 3600, "color": "error.main" },
107
+ { "operator": "<", "value": 86400, "color": "warning.main" }
108
+ ],
109
+ "onDoneCode": "setData({ offerExpired: true }); showBanner('offerExpiredBanner')"
110
+ }
111
+ }
112
+ ```
113
+
114
+ ### Days-only event countdown
115
+ ```json
116
+ {
117
+ "type": "kpi-countdown",
118
+ "props": {
119
+ "targetDate": "2025-12-31",
120
+ "label": "Days Until Year End",
121
+ "showDays": true,
122
+ "showHours": false,
123
+ "showMinutes": false,
124
+ "showSeconds": false,
125
+ "size": "medium",
126
+ "align": "center",
127
+ "color": "#1976d2"
128
+ }
129
+ }
130
+ ```
131
+
132
+ ### Compact with custom separator
133
+ ```json
134
+ {
135
+ "type": "kpi-countdown",
136
+ "props": {
137
+ "targetDateKey": "data.meetingTime",
138
+ "showDays": false,
139
+ "showHours": true,
140
+ "showMinutes": true,
141
+ "showSeconds": true,
142
+ "separator": ":",
143
+ "size": "small",
144
+ "align": "left",
145
+ "labelDays": "d",
146
+ "labelHours": "h",
147
+ "labelMinutes": "m",
148
+ "labelSeconds": "s"
149
+ }
150
+ }
151
+ ```
@@ -0,0 +1,276 @@
1
+ # Helper: `fetchReportData` — Calculation-scope Reference Manual
2
+
3
+ > **Audience.** AI sessions emitting builder Calculation code (`actionsConfig.code`, `viewerActions.code`, `onClick`, `onLoad`, column functions, etc.). `fetchReportData` is **auto-injected** into every Calculation scope — you never `import` it in a builder schema.
4
+
5
+ Fetches report data by `pageId` **without any AG Grid dependency**. Resolves the report's builder model on first call (cached for the session), applies the supplied filter on top of the report's own filter, optionally evaluates `customFilterCode`, then issues `GenericGet` and returns a normalized result.
6
+
7
+ Use this whenever a Calculation needs **rows from a server-defined report** but the rows aren't rendered by a `reportViewer` on the page (e.g. dialog seed data, exports, conditional logic, dashboard counters, hidden lookups).
8
+
9
+ > ⚠️ Different from `<reportViewer>`. `<reportViewer>` renders + paginates + filters + ag-grids the data. `fetchReportData` is a **pure data fetch** — you get an array back and decide what to do with it.
10
+
11
+ ---
12
+
13
+ ## 1. Quick start
14
+
15
+ ```js
16
+ const result = await fetchReportData({ pageId: 'OPEN_INVOICES' })
17
+ if (result.success) {
18
+ console.log(`Got ${result.rows.length} of ${result.total} rows`)
19
+ }
20
+ ```
21
+
22
+ Minimum: `pageId`. Everything else has sane defaults.
23
+
24
+ ---
25
+
26
+ ## 2. Signature
27
+
28
+ `fetchReportData` is the alias bound into Calculation scope. The function it points to is the package's `fetchReportDataByPageId` (one and the same).
29
+
30
+ ```ts
31
+ await fetchReportData({
32
+ pageId, // string | number — REQUIRED
33
+ filter = {}, // see section 3
34
+ isDataOnly = false, // true → return rows array directly, no wrapper
35
+ dataAsObject = false, // true → expand underscore-paths into nested objects
36
+ isPagination = true, // false → fetch all matching rows
37
+ isSingle = false, // true → return only the first row (row, not array)
38
+ globalParams = null, // { 0: value, 1: value, ... } overrides selectionParams by index
39
+ page = 1, // 1-based page number
40
+ pageSize = 50, // rows per page
41
+ authContext = null, // host auth, used by customFilterCode
42
+ context = null, // host system context, used by customFilterCode
43
+ })
44
+ ```
45
+
46
+ > **`authContext` / `context`** — pass them along when calling from inside a Calculation if your filter uses `customFilterCode` that reads `authContext.user`. The builder runtime doesn't auto-thread them through (yet) — explicitly forward what you have. Most calls don't need either.
47
+
48
+ ---
49
+
50
+ ## 3. The `filter` parameter
51
+
52
+ Plain JS object. Three slots:
53
+
54
+ | Slot | Type | Purpose |
55
+ |---|---|---|
56
+ | `Tfilter` | `FilterItem[]` | Filters merged with the report's own filters. |
57
+ | `TFilter` | `FilterItem[]` | Alias accepted for legacy callers. Concatenated with `Tfilter`. |
58
+ | `customFilterCode` | `string` | JS source evaluated at fetch time; pushes filters into a local `customFilter` array. |
59
+
60
+ Anything **else** at the top level of `filter` is forwarded as-is to the backend request body. Useful for backend-specific knobs.
61
+
62
+ ### `FilterItem` shape
63
+
64
+ ```ts
65
+ {
66
+ path: string, // dotted field path, e.g. "Customer.RegionId"
67
+ friendlyName?: string,
68
+ value: any, // scalar | array (for In/NotIn/Between)
69
+ method: 'Equal' | 'NotEqual' | 'Greater' | 'GreaterOrEqual' |
70
+ 'Less' | 'LessOrEqual' | 'Contains' | 'StartsWith' |
71
+ 'EndsWith' | 'In' | 'NotIn' | 'IsNull' | 'IsNotNull' | 'Between',
72
+ orGroupName?: string, // group multiple items into an OR-block
73
+ }
74
+ ```
75
+
76
+ ### Null filtering
77
+
78
+ Items where `value === null || value === undefined || value === ''` are stripped **before** the request is sent. So you can build a filter from optional state without conditional guards:
79
+
80
+ ```js
81
+ const filter = {
82
+ Tfilter: [
83
+ { path: 'Status', value: data.status, method: 'Equal' }, // dropped if data.status is null
84
+ { path: 'OwnerId', value: data.ownerId, method: 'Equal' }, // same
85
+ { path: 'Year', value: 2026, method: 'Equal' }, // kept
86
+ ]
87
+ }
88
+ ```
89
+
90
+ ### `customFilterCode`
91
+
92
+ A JS string evaluated in a sandbox at request time. Push `FilterItem`s into `customFilter`. Scope:
93
+
94
+ | Var | Source |
95
+ |---|---|
96
+ | `authContext` | The `authContext` arg passed to `fetchReportData`. |
97
+ | `context` | The `context` arg passed to `fetchReportData`. |
98
+ | `filters` | The merged `Tfilter` array right before custom filters are added. Read-only. |
99
+ | `customFilter` | Empty `[]` to push items into. |
100
+
101
+ ```js
102
+ const filter = {
103
+ customFilterCode: `
104
+ if (authContext?.user?.shopId) {
105
+ customFilter.push({
106
+ path: 'ShopId',
107
+ value: authContext.user.shopId,
108
+ method: 'Equal',
109
+ })
110
+ }
111
+ `
112
+ }
113
+ ```
114
+
115
+ ---
116
+
117
+ ## 4. Return shape
118
+
119
+ ```ts
120
+ // Default — full wrapper:
121
+ {
122
+ success: true,
123
+ rows: RowType[] | RowType, // array OR single row (if isSingle)
124
+ total: number, // total rows on the server (paginated mode)
125
+ rawResponse: any, // exact backend response (use sparingly)
126
+ finalRequest: { // useful for debugging — what was actually sent
127
+ endpoint: string,
128
+ getType: 'Pagination' | 'NoPagination' | 'SqlPagination' | 'SqlNoPagination',
129
+ filter: object, // final filter payload
130
+ sourceModel: string,
131
+ params: { page, pageSize },
132
+ }
133
+ }
134
+
135
+ // On error:
136
+ { success: false, error: 'Report detailsOld not found' /* or Error */ }
137
+
138
+ // With isDataOnly: true:
139
+ RowType[] | RowType // bare rows; no wrapper
140
+ ```
141
+
142
+ ### Common shape variants
143
+
144
+ | Options | Returns |
145
+ |---|---|
146
+ | Defaults | `{ success, rows: Row[], total, ... }` |
147
+ | `isSingle: true` | `{ success, rows: Row, ... }` — first row, not an array |
148
+ | `isDataOnly: true` | `Row[]` (or `Row` if `isSingle`) — no wrapper |
149
+ | `isPagination: false` | All matching rows (use with care on large datasets) |
150
+ | `dataAsObject: true` | Rows have underscore-paths expanded to nested objects (`{ "Customer_Name": "..." }` → `{ Customer: { Name: "..." } }`) |
151
+
152
+ ---
153
+
154
+ ## 5. Recipes
155
+
156
+ ### 5.1 Count rows for a KPI
157
+
158
+ ```js
159
+ async function Calculation(form, data, setData) {
160
+ const r = await fetchReportData({
161
+ pageId: 'OPEN_TICKETS',
162
+ isPagination: true,
163
+ pageSize: 1, // we only care about `total`, not the rows
164
+ })
165
+ if (r.success) setData(prev => ({ ...prev, openTicketCount: r.total }))
166
+ }
167
+ ```
168
+
169
+ ### 5.2 Seed a dialog with one record
170
+
171
+ ```js
172
+ async function Calculation(form, data, setData, dataRef, reportRefs, openDialog) {
173
+ const r = await fetchReportData({
174
+ pageId: 'CUSTOMER_BY_ID',
175
+ isSingle: true,
176
+ filter: { Tfilter: [{ path: 'Id', value: rowData.Id, method: 'Equal' }] },
177
+ })
178
+ if (!r.success) { showToast('Lookup failed', 'error'); return }
179
+ openDialog('editCustomer', r.rows) // r.rows is a single object, not an array
180
+ }
181
+ ```
182
+
183
+ ### 5.3 Filtered list inside an onLoad action
184
+
185
+ ```js
186
+ async function Calculation(form, data, setData) {
187
+ const r = await fetchReportData({
188
+ pageId: 'ACTIVE_USERS',
189
+ filter: { Tfilter: [{ path: 'TenantId', value: data.tenantId, method: 'Equal' }] },
190
+ isPagination: false,
191
+ })
192
+ if (r.success) setData(prev => ({ ...prev, users: r.rows }))
193
+ }
194
+ ```
195
+
196
+ ### 5.4 Pivot-style "raw rows for chart"
197
+
198
+ ```js
199
+ async function Calculation(form, data, setData) {
200
+ const rows = await fetchReportData({
201
+ pageId: 'SALES_BY_MONTH',
202
+ isPagination: false,
203
+ isDataOnly: true, // skip the wrapper — we just want rows
204
+ dataAsObject: true, // expand `Region_Name` → `Region.Name`
205
+ })
206
+ setData(prev => ({ ...prev, chartData: rows }))
207
+ }
208
+ ```
209
+
210
+ ### 5.5 Forward auth context for tenant-scoped reports
211
+
212
+ ```js
213
+ async function Calculation(form, data, setData, dataRef, reportRefs, openDialog, closeDialog, urlParams, page) {
214
+ const r = await fetchReportData({
215
+ pageId: 'MY_SHOP_ORDERS',
216
+ filter: {
217
+ customFilterCode: `
218
+ if (authContext?.user?.shopId) {
219
+ customFilter.push({ path: 'ShopId', value: authContext.user.shopId, method: 'Equal' })
220
+ }
221
+ `,
222
+ },
223
+ authContext: page?.authContext, // forward whatever the parent page has
224
+ })
225
+ // ...
226
+ }
227
+ ```
228
+
229
+ ### 5.6 Override report selection params by index
230
+
231
+ ```js
232
+ const r = await fetchReportData({
233
+ pageId: 'SALES_BY_REGION',
234
+ globalParams: { 0: data.year, 1: data.region }, // 0-based positional, NOT keyed by name
235
+ })
236
+ ```
237
+
238
+ ### 5.7 Refresh a hidden lookup table on demand
239
+
240
+ ```js
241
+ async function refreshLookups(form, data, setData) {
242
+ const r = await fetchReportData({
243
+ pageId: 'CATEGORIES_LOOKUP',
244
+ isPagination: false,
245
+ isDataOnly: true,
246
+ dataAsObject: true,
247
+ })
248
+ setData(prev => ({ ...prev, categories: r }))
249
+ }
250
+ ```
251
+
252
+ ---
253
+
254
+ ## 6. Pitfalls
255
+
256
+ - **Forgetting `await`.** `fetchReportData` returns a `Promise`. Without `await`, `r.success` is `undefined` and the next line runs before the request completes. Always `await`.
257
+ - **`isSingle: true` returns the row, not an array.** `r.rows[0]` will crash; use `r.rows` directly.
258
+ - **`isPagination: false` on huge reports.** All rows are fetched in one request. Use it for lookups (≤ a few thousand rows). For large datasets, paginate.
259
+ - **Builder-model cache is per-session.** First call hits the metadata endpoint; subsequent calls with the same `pageId` reuse it. If the report definition changes mid-session, the user sees stale columns until refresh.
260
+ - **`globalParams` is index-keyed, not name-keyed.** Keys are 0-based positional indices into `selectionParams`. Wrong index → wrong override.
261
+ - **`filter.LocalTfilter` is stripped.** Only `Tfilter` / `TFilter` are honored at this level. (LocalTfilter is a UI-side concept that doesn't make sense for a direct fetch.)
262
+ - **`null` / `''` / `undefined` values are dropped.** This is usually convenient (no need to gate on optional state), but if you literally need to filter for `null`, use `method: 'IsNull'`, not `value: null`.
263
+ - **`customFilterCode` is a string, not a function.** Pushed `FilterItem`s must be plain objects. You can't reference page `data` directly inside — only `authContext`, `context`, `filters` are in scope. To filter by page state, build the filter array in JS **before** calling `fetchReportData` instead.
264
+
265
+ ---
266
+
267
+ ## 7. Cheat sheet
268
+
269
+ 1. `await fetchReportData({ pageId: '...' })` — that's the minimum.
270
+ 2. Build the filter as a plain JS object: `{ Tfilter: [{ path, value, method }] }`. Null values are auto-stripped.
271
+ 3. `isDataOnly: true` returns the bare rows array — skip the `r.success` ceremony when you trust the call.
272
+ 4. `isSingle: true` returns one row, not an array. `r.rows` is that row.
273
+ 5. `isPagination: false` to load everything; cap with `pageSize` if needed.
274
+ 6. `dataAsObject: true` to expand `Foo_Bar` flat keys into `Foo.Bar` nested objects.
275
+ 7. `globalParams: { 0: ..., 1: ... }` overrides specific `selectionParams` by index.
276
+ 8. `customFilterCode` runs server-side at request time — useful for auth-derived filters; otherwise build the filter object in JS.