robobyte-front-builder 1.0.21 → 1.0.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,15 +1,50 @@
1
1
  # robobyte-front-builder
2
2
 
3
- A low-code UI builder, Report builder, Print Layout designer, and Navigation extension system for Next.js applications.
3
+ A low-code **UI Builder**, **Report Builder**, **Print Layout Designer**, and **Navigation Extension API** for Next.js applications.
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/robobyte-front-builder)](https://www.npmjs.com/package/robobyte-front-builder)
6
6
 
7
7
  ---
8
8
 
9
+ ## Table of Contents
10
+
11
+ 1. [What's included](#whats-included)
12
+ 2. [Requirements](#requirements)
13
+ 3. [Installation](#installation)
14
+ 4. [Peer dependencies](#peer-dependencies)
15
+ 5. [next.config.js setup](#nextconfigjs-setup)
16
+ 6. [Provider setup](#provider-setup-_appjs)
17
+ 7. [Builder pages](#builder-pages)
18
+ 8. [Navigation Extension API](#navigation-extension-api)
19
+ 9. [Provider props reference](#provider-props-reference)
20
+ 10. [fetchReportDataByPageId](#fetchreportdatabypageid)
21
+ 11. [ReportViewer as a component](#reportviewer-as-a-component)
22
+ 12. [Data Grid component](#data-grid-component)
23
+ 13. [Dialog component](#dialog-component)
24
+ 14. [Popover component](#popover-component)
25
+ 15. [Excel Upload component](#excel-upload-component)
26
+ 16. [Wizard component](#wizard-component)
27
+ 17. [Repeater component](#repeater-component)
28
+ 18. [Menu component](#menu-component)
29
+ 19. [View Renderer component](#view-renderer-component)
30
+ 20. [Layout Grid component](#layout-grid-component)
31
+ 21. [Breadcrumb component](#breadcrumb-component)
32
+ 22. [Print Layout Builder](#print-layout-builder)
33
+ 23. [Calculation Scope Reference](#calculation-scope-reference)
34
+ 24. [KPI Component Actions](#kpi-component-actions)
35
+ 25. [Global Data Store](#global-data-store)
36
+ 26. [Dark / Light theme](#dark--light-theme)
37
+ 27. [Syncing local changes](#syncing-local-changes)
38
+ 28. [Troubleshooting](#troubleshooting)
39
+ 29. [Publishing](#publishing)
40
+ 30. [Changelog](#changelog)
41
+
42
+ ---
43
+
9
44
  ## What's included
10
45
 
11
46
  - **UI Builder** — drag-and-drop canvas for building data-driven views with 50+ components
12
- - **Report Builder** — design and preview paginated reports
47
+ - **Report Builder** — design and preview paginated reports with AG Grid
13
48
  - **Print Layout Designer** — multi-zone print templates (header / body pages / footer)
14
49
  - **Navigator Builder** — configure sidebar navigation
15
50
  - **Viewer** — production read-only renderer for saved UI Builder views
@@ -17,6 +52,7 @@ A low-code UI builder, Report builder, Print Layout designer, and Navigation ext
17
52
  - **KPI Components** — 12 ready-to-use metric visualisations (gauge, trend, bullet chart, rating, countdown, …)
18
53
  - **Timer Engine** — configurable auto-refresh timers per view
19
54
  - **Undo / Redo / Copy / Paste** — full clipboard and history support in the builder
55
+ - **Global Data Store** — cross-route reactive state that survives client-side navigations
20
56
 
21
57
  ---
22
58
 
@@ -38,9 +74,11 @@ A low-code UI builder, Report builder, Print Layout designer, and Navigation ext
38
74
  npm install robobyte-front-builder
39
75
  ```
40
76
 
41
- ### Required peer dependencies
77
+ ---
78
+
79
+ ## Peer dependencies
42
80
 
43
- These must be installed in the **host app** (they must be shared as a single instance):
81
+ Only the libraries that **must be shared as a single instance** are declared as `peerDependencies` — React, MUI, Emotion, and AG Grid. Everything else ships inside the package so the host app doesn't need to install them manually.
44
82
 
45
83
  ```bash
46
84
  npm install \
@@ -52,152 +90,826 @@ npm install \
52
90
  xlsx react-hot-toast
53
91
  ```
54
92
 
55
- > Everything else (`dayjs`, `lodash`, `recharts`, etc.) ships inside the package and does not need to be installed in the host app.
93
+ > **Why only these?** React and MUI use context and module registries that break if two copies exist in the same app (hooks errors, theme not applied, AG Grid license warnings). `xlsx` is required by the Excel Upload component and is resolved from the host app's `node_modules` because the builder source is transpiled in the host's webpack context. Everything else `dayjs`, `lodash`, `recharts`, etc. can safely run from the package's own `node_modules` copy with no side effects.
56
94
 
57
95
  ---
58
96
 
59
- ## Setup
60
-
61
- ### 1. next.config.js
97
+ ## `next.config.js` setup
62
98
 
63
- Add the package to `transpilePackages` so Next.js compiles the source-first package:
99
+ Two things are required: **transpile** the package and set up the **context-aware bare-alias webpack plugin** so that bare imports like `services/*`, `context/*`, etc. resolve to the correct source tree.
64
100
 
65
101
  ```js
66
- /** @type {import('next').NextConfig} */
67
- const nextConfig = {
68
- transpilePackages: ['robobyte-front-builder'],
69
- }
70
-
71
- module.exports = nextConfig
102
+ const path = require('path')
103
+ const withTM = require('next-transpile-modules')(['robobyte-front-builder'])
104
+
105
+ const hostSrc = path.resolve(__dirname, 'src')
106
+ const builderSrc = path.resolve(__dirname, 'node_modules/robobyte-front-builder/src')
107
+
108
+ module.exports = withTM({
109
+ webpack(config, { isServer }) {
110
+ if (isServer) {
111
+ config.resolve.conditionNames = ['require', 'node', 'default']
112
+ }
113
+
114
+ const { NormalModuleReplacementPlugin } = require('webpack')
115
+
116
+ // Package-owned services — always resolved from the package
117
+ const packageOwnedServices = [
118
+ 'services/reportData/fetchReportData',
119
+ ]
120
+ packageOwnedServices.forEach(mod => {
121
+ config.plugins.push(
122
+ new NormalModuleReplacementPlugin(
123
+ new RegExp(`^${mod.replace(/\//g, '\\/')}(\\.js)?$`),
124
+ resource => { resource.request = path.join(builderSrc, mod) }
125
+ )
126
+ )
127
+ })
128
+
129
+ // Context-aware bare alias routing
130
+ config.plugins.push(
131
+ new NormalModuleReplacementPlugin(
132
+ /^(services|views|context|src|pages)(\/|$)/,
133
+ resource => {
134
+ const isBuilder =
135
+ resource.context.includes('robobyte-front-builder') ||
136
+ resource.context.includes('RoboByteFrontBuilder')
137
+ const root = isBuilder ? builderSrc : hostSrc
138
+ resource.request = resource.request.replace(
139
+ /^(services|views|context|src|pages)(\/|$)/,
140
+ (_, prefix, sep) =>
141
+ prefix === 'src'
142
+ ? root + sep
143
+ : path.join(root, prefix) + sep
144
+ )
145
+ }
146
+ )
147
+ )
148
+
149
+ return config
150
+ },
151
+ })
72
152
  ```
73
153
 
74
- ### 2. Wrap your app
154
+ > **Restart the dev server** whenever `next.config.js` changes hot-reload does not apply to webpack plugin changes.
75
155
 
76
- In `pages/_app.js`, wrap your tree with the provider:
156
+ ---
157
+
158
+ ## Provider setup (`_app.js`)
159
+
160
+ Wrap the component tree with `RoboByteFrontBuilderProvider`. Bridge your host auth context into it so the package attaches auth headers to every API call.
77
161
 
78
162
  ```jsx
79
163
  import { RoboByteFrontBuilderProvider } from 'robobyte-front-builder'
80
164
 
81
- export default function App({ Component, pageProps }) {
165
+ function RoboByteBridge({ children }) {
166
+ const auth = useContext(YourAuthContext)
82
167
  return (
83
168
  <RoboByteFrontBuilderProvider
84
- baseURL="https://your-api.example.com"
85
- apiURL="https://your-api.example.com/api"
86
- getAccessToken={() => yourAuthModule.getToken()}
87
- getUser={() => yourAuthModule.getCurrentUser()}
169
+ baseURL={process.env.NEXT_PUBLIC_API_BASE_URL}
170
+ apiURL={process.env.NEXT_PUBLIC_API_URL}
171
+ user={auth.user}
172
+ accessToken={auth.accessToken}
173
+ agGridLicenseKey={process.env.NEXT_PUBLIC_AG_GRID_LICENSE_KEY}
88
174
  >
89
- <Component {...pageProps} />
175
+ {children}
90
176
  </RoboByteFrontBuilderProvider>
91
177
  )
92
178
  }
179
+
180
+ export default function App({ Component, pageProps }) {
181
+ const getLayout = Component.getLayout ?? (page => page)
182
+ return (
183
+ <YourAuthProvider>
184
+ <RoboByteBridge>
185
+ {getLayout(<Component {...pageProps} />)}
186
+ </RoboByteBridge>
187
+ </YourAuthProvider>
188
+ )
189
+ }
93
190
  ```
94
191
 
95
- ### 3. Add pages
192
+ ---
96
193
 
97
- Create page files in your host app that re-export the builder pages:
194
+ ## Builder pages
98
195
 
99
- ```js
100
- // pages/ui-builder/[id].js
101
- export { default } from 'robobyte-front-builder/UIBuilderPage'
102
- // or:
103
- import { UIBuilderPage } from 'robobyte-front-builder'
104
- export default UIBuilderPage
105
- ```
196
+ All builder routes live under the `/builders/` prefix. Create a thin wrapper page in your host app for each route.
106
197
 
107
- | Host page path | Export to use |
198
+ | Route | Package export |
108
199
  |---|---|
109
- | `pages/ui-builder/[id].js` | `UIBuilderPage` |
110
- | `pages/ui-builder/views/index.js` | `ViewsList` |
111
- | `pages/viewer/[id].js` | `ViewerPage` |
112
- | `pages/report-builder/[id].js` | `ReportBuilderPage` |
113
- | `pages/report-builder/reports/index.js` | `ReportsList` |
114
- | `pages/report-builder/viewer/index.js` | `ReportViewer` |
115
- | `pages/navigator-builder/index.js` | `NavigatorBuilderPage` |
116
- | `pages/print-builder/index.js` | `PrintBuilderPage` |
117
- | `pages/print-builder/layouts/index.js` | `PrintLayoutsList` |
200
+ | `/builders/ui` | `UIBuilderPage` |
201
+ | `/builders/ui/views` | `ViewsList` |
202
+ | `/builders/report` | `ReportBuilderPage` |
203
+ | `/builders/report/list` | `ReportsList` |
204
+ | `/builders/report/viewer` | `ReportViewer` |
205
+ | `/builders/report/reportsPermissions` | `ReportsCard` |
206
+ | `/builders/navigator` | `NavigatorBuilderPage` |
207
+ | `/viewer/[id]` | `ViewerPage` |
208
+ | `/printBuilder` | `PrintBuilderPage` |
209
+ | `/printBuilder/layouts` | `PrintLayoutsList` |
210
+
211
+ ### Page wrapper pattern
212
+
213
+ Use explicit import + static property assignment. A bare `export { X as default }` does **not** reliably carry `getLayout`, `acl`, `authGuard` across package boundaries in Next.js.
214
+
215
+ ```jsx
216
+ // pages/builders/report/viewer/index.jsx
217
+ import { ReportViewer } from 'robobyte-front-builder'
218
+ import BlankLayout from 'src/@core/layouts/BlankLayout'
219
+ import PermissionsSubjects from 'src/configs/Permissions/PermissionsSubjects.json'
220
+
221
+ ReportViewer.getLayout = page => <BlankLayout>{page}</BlankLayout>
222
+ ReportViewer.acl = { action: 'view', subject: PermissionsSubjects.Free }
223
+ ReportViewer.authGuard = true
224
+ ReportViewer.guestGuard = false
225
+ export default ReportViewer
226
+ ```
227
+
228
+ ### Viewer page (`/viewer/[id]`)
229
+
230
+ ```jsx
231
+ import { useContext } from 'react'
232
+ import { ViewerPage, SystemContext } from 'robobyte-front-builder'
233
+ import { YourNavContext } from 'src/context/YourNavContext'
234
+
235
+ function SystemContextBridge({ children }) {
236
+ const { nav } = useContext(YourNavContext)
237
+ return (
238
+ <SystemContext.Provider value={{ nav }}>
239
+ {children}
240
+ </SystemContext.Provider>
241
+ )
242
+ }
243
+
244
+ export default function ViewerRoute() {
245
+ return (
246
+ <SystemContextBridge>
247
+ <ViewerPage />
248
+ </SystemContextBridge>
249
+ )
250
+ }
251
+
252
+ ViewerRoute.authGuard = true
253
+ ViewerRoute.guestGuard = false
254
+ ```
118
255
 
119
256
  ---
120
257
 
121
258
  ## Navigation Extension API
122
259
 
123
- Inject nav items into the builder's sidebar from anywhere in the host app without touching this package:
260
+ ### Option A Static items via the Provider
261
+
262
+ ```jsx
263
+ <RoboByteFrontBuilderProvider
264
+ navExtensions={[
265
+ {
266
+ id: 'host-analytics',
267
+ title: 'Analytics',
268
+ icon: 'ChartBar',
269
+ type: 'static',
270
+ path: '/analytics',
271
+ insertAt: 'end',
272
+ },
273
+ ]}
274
+ >
275
+ ...
276
+ </RoboByteFrontBuilderProvider>
277
+ ```
278
+
279
+ ### Option B — Dynamic items via `useNavExtension` hook
124
280
 
125
281
  ```jsx
126
282
  import { useNavExtension } from 'robobyte-front-builder'
127
283
 
128
- export default function MyFeatureModule() {
129
- useNavExtension({
130
- key: 'my-feature',
131
- label: 'My Feature',
132
- icon: <MyIcon />,
133
- href: '/my-feature',
134
- order: 10,
135
- })
284
+ function MyFeaturePlugin() {
285
+ useNavExtension(
286
+ [{ id: 'my-feature', title: 'My Feature', icon: 'StarOutline', type: 'static', path: '/my-feature', insertAt: 'start' }],
287
+ 'my-feature-plugin'
288
+ )
289
+ return null
290
+ }
291
+ ```
292
+
293
+ ### Nav item shape
294
+
295
+ | Field | Type | Required | Description |
296
+ |---|---|---|---|
297
+ | `id` | string | yes | Stable unique identifier |
298
+ | `title` | string | yes | Display label |
299
+ | `icon` | string | no | mdi-material-ui icon name |
300
+ | `type` | `'static'` \| `'dynamic'` \| `'external'` | yes | Route type |
301
+ | `path` | string | for `static` | Absolute path |
302
+ | `viewId` | number | for `dynamic` | Registered view ID |
303
+ | `externalUrl` | string | for `external` | Full URL, opens in new tab |
304
+ | `insertAt` | `'start'` \| `'end'` \| number \| `{ afterId }` \| `{ beforeId }` | no | Placement (default `'end'`) |
305
+ | `children` | NavItem[] | no | Collapsible group |
306
+ | `action` / `subject` | string | no | CASL ACL visibility |
307
+
308
+ ---
309
+
310
+ ## Provider props reference
311
+
312
+ | Prop | Type | Description |
313
+ |---|---|---|
314
+ | `baseURL` | string | Root API server URL |
315
+ | `apiURL` | string | `/api` prefix URL |
316
+ | `user` | object | Current user object from your auth context |
317
+ | `accessToken` | string | Bearer token attached to every service call |
318
+ | `agGridLicenseKey` | string | AG Grid Enterprise license key — the provider calls `LicenseManager.setLicenseKey()` and registers all enterprise modules internally |
319
+ | `navExtensions` | array | Static nav items to inject into the sidebar |
320
+ | `endpoints` | object | Full-URL overrides per endpoint group + name |
321
+
322
+ > **AG Grid note:** Do not call `LicenseManager` or `ModuleRegistry` in the host app — the provider handles it entirely.
323
+
324
+ ---
325
+
326
+ ## `fetchReportDataByPageId`
136
327
 
137
- return <div>...</div>
328
+ Fetches report data by `pageId` without any AG Grid dependency. Always imported from the package path — the `NormalModuleReplacementPlugin` in `next.config.js` routes it to the package's canonical implementation.
329
+
330
+ ```js
331
+ import fetchReportDataByPageId from 'services/reportData/fetchReportData'
332
+
333
+ const result = await fetchReportDataByPageId({
334
+ pageId, // required
335
+ filter, // { Tfilter, TFilter, customFilterCode, ... }
336
+ isDataOnly, // return raw rows only (default false)
337
+ dataAsObject, // key rows by unique id (default false)
338
+ isPagination, // server-side pagination (default true)
339
+ isSingle, // return first row only (default false)
340
+ globalParams, // override selectionParams by index
341
+ page, // page number (default 1)
342
+ pageSize, // page size (default 50)
343
+ authContext,
344
+ context,
345
+ })
346
+
347
+ if (result.success) {
348
+ console.log(result.rows)
349
+ console.log(result.total)
350
+ }
351
+ ```
352
+
353
+ ### `customFilterCode`
354
+
355
+ ```js
356
+ const filter = {
357
+ customFilterCode: `
358
+ if (authContext?.user?.shopId) {
359
+ customFilter.push({ field: 'ShopId', value: authContext.user.shopId, operation: 'Equal' })
360
+ }
361
+ `
138
362
  }
139
363
  ```
140
364
 
141
365
  ---
142
366
 
143
- ## RoboByteFrontBuilderProvider props
367
+ ## ReportViewer as a component
368
+
369
+ `ReportViewer` can be rendered inline anywhere — not only as a full page:
370
+
371
+ ```jsx
372
+ import { ReportViewer } from 'robobyte-front-builder'
373
+
374
+ <ReportViewer
375
+ pageId="your-page-id"
376
+ minimized={true}
377
+ noHeader={true}
378
+ filter={{ Tfilter: [...] }}
379
+ height="400px"
380
+ />
381
+ ```
144
382
 
145
383
  | Prop | Type | Description |
146
384
  |---|---|---|
147
- | `baseURL` | `string` | Base URL for file/asset requests |
148
- | `apiURL` | `string` | API base URL for all data endpoints |
149
- | `getAccessToken` | `() => string` | Called before each request to get the auth token |
150
- | `getUser` | `() => object` | Returns the current user object |
151
- | `navExtensions` | `array` | Static nav items to inject (alternative to `useNavExtension`) |
152
- | `theme` | `object` | MUI theme override |
385
+ | `id` | string | Report ID |
386
+ | `pageId` | string | Report page ID |
387
+ | `filter` | object | External filter |
388
+ | `minimized` | bool | Removes padding and box shadow |
389
+ | `noHeader` | bool | Hides the report title and studio button |
390
+ | `height` | string | Grid height (default `85vh`) |
391
+ | `isSingle` | bool | Return only the first row |
392
+ | `dataAsObject` | bool | Key rows by unique id |
393
+ | `setData` | function | Callback receiving the loaded rows |
394
+ | `setOutGridApi` | function | Callback receiving the AG Grid API instance |
395
+ | `refresh` | any | Change this value to force a data reload |
396
+ | `actions` | array | Custom row action buttons |
397
+ | `columnsConfig` | array | Column overrides |
398
+ | `globalParams` | array | Override selectionParams by index |
399
+ | `sessionId` | string | Load filter payload from localStorage |
400
+ | `updateRef` | ref | Ref for programmatic updates |
401
+
402
+ ---
403
+
404
+ ## Data Grid component
405
+
406
+ The **Data Grid** wraps AG Grid Enterprise and is configurable entirely from the builder inspector.
407
+
408
+ ### Inspector — Main tab
409
+
410
+ | Field | Description |
411
+ |---|---|
412
+ | `Key` | Reference key for `reportRefs` access |
413
+ | `Data Key` | Key to read/write row data in page `data` |
414
+ | `Columns` | Expression returning an AG Grid `colDef[]` array |
415
+ | `Extra Columns` | Visual editor for adding new columns on top of data-driven ones |
416
+ | `Columns Config` | Visual editor for overriding existing column properties by `field` name |
417
+ | `Row Actions` | Visual editor for per-row action buttons |
418
+ | `Add Button Label` | Label for the add-row button (default `"Add"`) |
419
+ | `New Row Template` | Code returning the default object for a new row |
420
+ | `Read Only` | Expression — when truthy, disables all editing |
421
+ | `Show Add Button` | Toggle the add-row button |
422
+ | `Show Delete Col` | Toggle the delete-row column |
423
+ | `Height` | Grid height expression (e.g. `'400px'`, `'60vh'`) |
424
+ | `Row Selection` | `none` / `single` / `multiple` |
425
+ | `Pagination` | Enable client-side pagination |
426
+ | `Page Size` | Rows per page |
427
+
428
+ ### `reportRefs` API
429
+
430
+ ```js
431
+ const grid = reportRefs['myGridKey']
432
+ grid.getRows() // current row data array
433
+ grid.setRows(arr) // replace all rows
434
+ grid.addRow(obj) // append a row
435
+ grid.updateRow(index, obj) // update a row by index
436
+ grid.deleteRow(index) // delete a row by index
437
+ grid.getGridApi() // raw AG Grid API instance
438
+ ```
439
+
440
+ ---
441
+
442
+ ## Dialog component
443
+
444
+ Renders a hidden MUI Dialog opened programmatically via `openDialog(key)` / `closeDialog(key)`.
445
+
446
+ ### Inspector — Main tab
447
+
448
+ | Field | Description |
449
+ |---|---|
450
+ | `Key` | Identifier used to open/close — must be unique on the page |
451
+ | `View ID` | When set, renders a full embedded view inside the dialog body |
452
+ | `Title` | Dialog title bar text (expression) |
453
+ | `Hide Title Bar` | Hides the entire title bar |
454
+ | `Max Width` | `xs` / `sm` / `md` / `lg` / `xl` |
455
+ | `Full Width` | Stretch to max width breakpoint |
456
+ | `Full Screen` | Full-screen mode |
457
+ | `Show Close Button` | Shows the × icon |
458
+ | `Close on Backdrop` | Closes when user clicks outside |
459
+ | `Action Buttons` | Footer buttons, each with its own Calculation code |
460
+
461
+ ### Usage
462
+
463
+ ```js
464
+ // Open with optional seed data
465
+ openDialog('confirmDelete')
466
+ openDialog('editUser', { userId: row.id, name: row.name })
467
+
468
+ // Close
469
+ closeDialog('confirmDelete')
470
+ ```
471
+
472
+ ### Isolated data store
473
+
474
+ Each dialog gets its own `data` / `setData` scope. To read or write the parent page state from inside a dialog use `pageData` and `pageSetData`:
475
+
476
+ ```js
477
+ // Inside a dialog action:
478
+ pageSetData(prev => ({ ...prev, lastEdited: data.userId }))
479
+ ```
480
+
481
+ ---
482
+
483
+ ## Popover component
484
+
485
+ A self-contained trigger (button, icon button, or text) that opens a floating panel. No `openDialog` call needed.
486
+
487
+ ### Inspector — Main tab
488
+
489
+ | Field | Description |
490
+ |---|---|
491
+ | `Trigger Type` | `button` / `icon-button` / `text` |
492
+ | `Trigger Label` | Label text |
493
+ | `Placement` | `bottom-start`, `bottom`, `bottom-end`, `top-start`, etc. |
494
+ | `Trigger On` | `click` (default) or `hover` |
495
+ | `Controlled Open` | Expression — overrides the internal open/close state |
496
+ | `Close On Content Click` | Closes when any child inside is clicked |
153
497
 
154
498
  ---
155
499
 
156
- ## Advanced: accessing builder context
500
+ ## Excel Upload component
501
+
502
+ Lets users upload `.xlsx`, `.xls`, or `.csv` files, map columns to field names, and access the result in actions.
503
+
504
+ ### Display variants
505
+
506
+ | Variant | Description |
507
+ |---|---|
508
+ | `dropzone` | Large dashed drop zone with drag & drop |
509
+ | `button` | Compact outlined button |
510
+ | `icon` | Icon-only button |
511
+
512
+ ### Actions
513
+
514
+ | Event | `newValue` | Notes |
515
+ |---|---|---|
516
+ | `onUpload` | `{ fileName, headers, rowCount }` | Fires after parsing, before the mapping dialog |
517
+ | `onMapped` | `Array<object>` | Fires before data is saved — **return a modified array** to override |
518
+ | `onClear` | `[]` | Fires when the user clears the data |
157
519
 
158
- For host apps that need to read or drive builder state programmatically:
520
+ ### `reportRefs` API
159
521
 
160
522
  ```js
161
- import { useBuilder } from 'robobyte-front-builder'
523
+ const uploader = reportRefs['myUploaderKey']
524
+ uploader.rows // current mapped rows
525
+ uploader.setRows(arr) // replace stored rows
526
+ uploader.clear() // clear all stored rows
527
+ ```
528
+
529
+ ---
530
+
531
+ ## Wizard component
532
+
533
+ A MUI Stepper with optional validation, async lifecycle hooks, and controlled or uncontrolled step state.
162
534
 
163
- const { schema, dispatch } = useBuilder()
535
+ ### Inspector props Wizard
536
+
537
+ | Prop | Type | Default | Description |
538
+ |---|---|---|---|
539
+ | `orientation` | `'horizontal'` \| `'vertical'` | `'horizontal'` | Stepper orientation |
540
+ | `linear` | boolean | `true` | Prevent skipping ahead |
541
+ | `hideNavigation` | boolean | `false` | Hide Back / Next / Finish buttons |
542
+ | `key` | string | `'wizardStep'` | `data` key written with the current step index |
543
+ | `activeStep` | number \| expression | — | When set, wizard is controlled by `data[key]` |
544
+ | `onStepChange` | JS action | — | Receives `{ from, to, direction, goToStep, nextStep, prevStep }` |
545
+ | `onFinish` | JS action | — | Runs when user clicks Finish on the last step |
546
+
547
+ ### Inspector props — Wizard Step
548
+
549
+ | Prop | Type | Description |
550
+ |---|---|---|
551
+ | `label` | string | Step label in the stepper header |
552
+ | `optional` | boolean | Marks the step as optional |
553
+ | `onEnter` | JS action | Fires when this step becomes active |
554
+ | `onNext` | JS action | Return `false` to block navigation (validation) |
555
+
556
+ ### Navigation helpers
557
+
558
+ ```js
559
+ goToStep(n) // jump to step n (0-based)
560
+ nextStep() // advance one step
561
+ prevStep() // go back one step
164
562
  ```
165
563
 
166
564
  ---
167
565
 
168
- ## Local development
566
+ ## Repeater component
567
+
568
+ Renders a list of identical template items from a static array, a dynamic count, or a fetched API endpoint.
569
+
570
+ | Mode | How to configure |
571
+ |---|---|
572
+ | Static items | Set `items` to a static or expression array |
573
+ | Count | Set `count` to a number; optionally `dataKey` to store values |
574
+ | Endpoint | Set `endpoint` + `serviceId` or `widgetId` |
575
+
576
+ ### Expressions inside a Repeater
577
+
578
+ | Variable | Value |
579
+ |---|---|
580
+ | `dataItem` | The current array element |
581
+ | `itemIndex` | Zero-based item index |
582
+ | `form[key]` | The current item's scoped form value |
583
+
584
+ ---
585
+
586
+ ## Menu component
587
+
588
+ Renders a MUI Tabs bar in two modes: **navigation mode** (items-based, each tab is a link/action) and **tab-panel mode** (tabs-based, each tab owns a drop zone).
589
+
590
+ | Prop | Type | Description |
591
+ |---|---|---|
592
+ | `items` | array | Navigation items `{ title, icon?, href? }` — enables navigation mode |
593
+ | `tabs` | array | Tab panel items `{ label, icon? }` — enables tab-panel mode |
594
+ | `vertical` | boolean | Render tabs vertically |
595
+ | `justified` | boolean | Stretch tabs to fill full width |
596
+
597
+ ---
598
+
599
+ ## View Renderer component
600
+
601
+ Embeds a saved builder view inline inside another view, with isolated or shared state.
602
+
603
+ | Prop | Type | Description |
604
+ |---|---|---|
605
+ | `viewId` | string | **Required.** ID of the saved view to embed |
606
+ | `isolated` | boolean | `true` = own data/form/refs, `false` = shares parent state |
607
+ | `initialData` | expression | Seed data passed on mount (isolated mode only) |
608
+ | `externalData` | expression | Additional data merged on every parent re-render |
609
+ | `heightMode` | `'auto'` \| `'fixed'` | `'fixed'` enables a pixel height with scroll |
610
+ | `height` | number | Pixel height when `heightMode` is `'fixed'` |
611
+
612
+ ---
613
+
614
+ ## Layout Grid component
615
+
616
+ A CSS Grid container whose column count, gap, and per-cell styling are configured in the builder inspector.
617
+
618
+ | Prop | Type | Default | Description |
619
+ |---|---|---|---|
620
+ | `cols` | number | `2` | Number of equal-width columns |
621
+ | `gap` | number px | `8` | Column gap |
622
+ | `rowGap` | number px | same as `gap` | Row gap |
623
+ | `cellMinHeight` | number/string | — | Default min-height for every cell |
624
+ | `cellPadding` | number/string | — | Inner padding for every cell |
625
+ | `cellBackgroundColor` | color | — | Background color for every cell |
626
+ | `cellBorder` | string | — | CSS border shorthand for every cell |
627
+ | `cellBorderRadius` | number/string | — | Border radius for every cell |
628
+ | `cellBoxShadow` | string | — | Box-shadow for every cell |
629
+ | `cellJustifyContent` | string | — | Flexbox `justify-content` inside every cell |
630
+ | `cellAlignItems` | string | — | Flexbox `align-items` inside every cell |
631
+
632
+ ---
633
+
634
+ ## Breadcrumb component
169
635
 
170
- To link the package locally without publishing:
636
+ Renders a MUI Breadcrumbs navigation trail.
171
637
 
172
638
  ```json
173
- // host app package.json
174
- "robobyte-front-builder": "file:../../RoboByteFrontBuilder"
639
+ {
640
+ "type": "breadcrumb",
641
+ "props": {
642
+ "main": {
643
+ "items": {
644
+ "valueType": "value",
645
+ "value": [
646
+ { "label": "Home", "href": "/" },
647
+ { "label": "Reports", "href": "/reports" },
648
+ { "label": "Q1 2025" }
649
+ ]
650
+ }
651
+ }
652
+ }
653
+ }
654
+ ```
655
+
656
+ ---
657
+
658
+ ## Print Layout Builder
659
+
660
+ > ⚠️ **Preview — needs more testing.** Functional but not production-ready.
661
+
662
+ Design reusable print layouts with configurable headers, footers, multi-page bodies, watermarks, and page numbering.
663
+
664
+ ### Backend API endpoints required
665
+
666
+ | Method | URL | Description |
667
+ |---|---|---|
668
+ | `POST` | `UiBuilderModule/PrintLayout` | Create a new layout |
669
+ | `PUT` | `UiBuilderModule/PrintLayout` | Update an existing layout |
670
+ | `GET` | `UiBuilderModule/PrintLayout/GetAll` | List all layouts |
671
+ | `GET` | `UiBuilderModule/PrintLayout/GetById?id=<id>` | Fetch by ID |
672
+ | `DELETE` | `UiBuilderModule/PrintLayout?id=<id>` | Delete a layout |
673
+
674
+ ### Triggering print from a view
675
+
676
+ ```js
677
+ openPrintLayout('your-layout-id', {
678
+ invoiceNumber: data.invoiceNumber,
679
+ customerName: data.customer.name,
680
+ lines: data.lines,
681
+ })
682
+ ```
683
+
684
+ ### Page numbering format
685
+
686
+ Use `{page}` and `{pages}` tokens in the Format field:
687
+
688
+ ```
689
+ Page {page} of {pages} → Page 1 of 4
690
+ ```
691
+
692
+ ---
693
+
694
+ ## Calculation Scope Reference
695
+
696
+ Every Calculation function receives the same set of variables:
697
+
698
+ ### Core state
699
+
700
+ | Variable | Description |
701
+ |---|---|
702
+ | `form` | Form field values (read-only snapshot) |
703
+ | `data` | Reactive page state |
704
+ | `setData` | Write page state — `setData(prev => ({ ...prev, key: value }))` |
705
+ | `dataRef` | Mutable non-reactive store |
706
+ | `reportRefs` | Map of `{ [key]: ref }` for every Data Grid / Excel Upload on the page |
707
+
708
+ ### Dialog / layout
709
+
710
+ | Variable | Description |
711
+ |---|---|
712
+ | `openDialog(key, data?)` | Open a dialog, optionally seeding its isolated state |
713
+ | `closeDialog(key)` | Close a dialog |
714
+ | `openPrintLayout(layoutId, data?)` | Open the print preview |
715
+ | `closePrintLayout()` | Close the active print preview |
716
+
717
+ ### Navigation & routing
718
+
719
+ | Variable | Description |
720
+ |---|---|
721
+ | `router` | Next.js router — `router.push('/path')`, `router.query`, etc. |
722
+ | `urlParams` | `{ query, pathname, asPath }` snapshot |
723
+
724
+ ### Global store
725
+
726
+ | Variable | Description |
727
+ |---|---|
728
+ | `globalData` | Cross-route global state (read) |
729
+ | `setGlobalData(updater)` | Write global state — shallow-merges by default |
730
+
731
+ ### Services
732
+
733
+ | Variable | Description |
734
+ |---|---|
735
+ | `GetService` | Authenticated GET |
736
+ | `PostService` | Authenticated POST |
737
+ | `UpdateService` | Authenticated PUT |
738
+ | `PatchService` | Authenticated PATCH |
739
+ | `DeleteService` | Authenticated DELETE |
740
+ | `fetchReportData` | Fetch report data by `pageId` |
741
+
742
+ ### Helpers
743
+
744
+ | Variable | Description |
745
+ |---|---|
746
+ | `showToast(message, type?)` | `'success'` \| `'error'` \| `'warning'` \| `'info'` \| `'loading'` |
747
+
748
+ ### Parent page scope (inside dialogs / embedded views)
749
+
750
+ | Variable | Description |
751
+ |---|---|
752
+ | `page.data` | Parent page's reactive state |
753
+ | `page.setData` | Write to the parent page's state |
754
+ | `page.dataRef` | Parent page's non-reactive store |
755
+ | `page.reportRefs` | Parent page's grid refs |
756
+
757
+ ### Repeater-only
758
+
759
+ | Variable | Description |
760
+ |---|---|
761
+ | `dataItem` | The current repeated item |
762
+ | `itemIndex` | Zero-based iteration index |
763
+
764
+ ---
765
+
766
+ ## KPI Component Actions
767
+
768
+ KPI components support interaction events configured in the inspector Main tab → Actions section.
769
+
770
+ | Component | Event | Click-context variables |
771
+ |---|---|---|
772
+ | **Metric** | `onClick` | `clickedValue` |
773
+ | **Rating** | `onChange` | `newValue`, `clickedValue` |
774
+ | **Chart** | `onDataClick` | `clickedItem`, `clickedValue`, `clickedField`, `clickedLabel`, `clickedIndex` |
775
+ | **HeatmapGrid** | `onCellClick` | `clickedItem`, `clickedValue`, `clickedRow`, `clickedCol`, `clickedRowIndex`, `clickedColIndex` |
776
+ | **Timeline** | `onItemClick` | `clickedItem`, `clickedIndex`, `clickedLabel`, `clickedStatus`, `clickedValue` |
777
+ | **StepStage** | `onStepClick` | `clickedItem`, `clickedIndex`, `clickedId`, `clickedLabel`, `clickedStatus` |
778
+ | **TagList** | `onTagClick` | `clickedItem`, `clickedTag`, `clickedLabel`, `clickedValue`, `clickedIndex` |
779
+
780
+ ---
781
+
782
+ ## Global Data Store
783
+
784
+ A module-level reactive singleton that survives client-side navigations.
785
+
786
+ ### In Calculation functions
787
+
788
+ ```js
789
+ // Read
790
+ console.log(globalData.selectedUser)
791
+
792
+ // Write
793
+ setGlobalData(prev => ({ ...prev, selectedUser: clickedItem }))
794
+
795
+ // Navigate — globalData persists to the next page
796
+ router.push('/user-detail')
797
+ ```
798
+
799
+ ### In React components
800
+
801
+ ```jsx
802
+ import { useGlobalStore } from 'robobyte-front-builder'
803
+
804
+ function MyComponent() {
805
+ const { globalData, setGlobalData } = useGlobalStore()
806
+ return <p>Selected: {globalData.selectedUser?.name}</p>
807
+ }
808
+ ```
809
+
810
+ ### Resetting on logout
811
+
812
+ ```js
813
+ import { resetGlobalData } from 'robobyte-front-builder'
814
+
815
+ function handleLogout() {
816
+ resetGlobalData()
817
+ // ... your auth logout
818
+ }
819
+ ```
820
+
821
+ ### API
822
+
823
+ | Export | Description |
824
+ |---|---|
825
+ | `useGlobalStore()` | React hook — `{ globalData, setGlobalData }` |
826
+ | `getGlobalData()` | Synchronous read from non-React code |
827
+ | `setGlobalData(updater)` | Write from anywhere |
828
+ | `subscribeGlobal(fn)` | Subscribe to changes — returns unsubscribe function |
829
+ | `resetGlobalData()` | Reset to `{}` and notify all subscribers |
830
+
831
+ ---
832
+
833
+ ## Dark / Light theme
834
+
835
+ Both the UI Builder and Print Layout Builder support dark and light modes (default: dark). A toggle button (☀️ / 🌙) is available in each builder's toolbar. The choice is persisted in `localStorage` under `rbb:builderThemeMode`. The theme is scoped to the builder pages only and does not affect the rest of the host application.
836
+
837
+ ---
838
+
839
+ ## Syncing local changes
840
+
841
+ When using `file:` path, `node_modules/robobyte-front-builder` is a **copy**, not a symlink. After editing source files, sync manually:
842
+
843
+ ```bash
844
+ SRC=RoboByteFrontBuilder/src
845
+ PKG=YourApp/node_modules/robobyte-front-builder/src
846
+
847
+ cp $SRC/lib/index.js $PKG/lib/index.js
848
+ cp $SRC/lib/providers/RoboByteFrontBuilderProvider.jsx $PKG/lib/providers/RoboByteFrontBuilderProvider.jsx
849
+ cp $SRC/services/reportData/fetchReportData.js $PKG/services/reportData/fetchReportData.js
850
+ # ... add other files as needed
175
851
  ```
176
852
 
177
- Run `npm install` once, then Next.js hot-reload picks up changes automatically.
853
+ Hot-reload picks up all file changes automatically. Only `next.config.js` changes require a dev server restart.
854
+
855
+ ---
856
+
857
+ ## Troubleshooting
858
+
859
+ **"Module not found: Can't resolve 'xlsx'"**
860
+ → Install `xlsx` in the host app: `npm install xlsx --legacy-peer-deps`
861
+
862
+ **"Cannot find module 'services/...'"**
863
+ → Ensure the `NormalModuleReplacementPlugin` is configured in `next.config.js`
864
+
865
+ **AG Grid table not showing / license warning**
866
+ → Pass `agGridLicenseKey` to `RoboByteFrontBuilderProvider`. Do not call `LicenseManager` or `ModuleRegistry` in the host app.
867
+
868
+ **AG Grid error #200 — IntegratedChartsModule not registered**
869
+ → The provider registers `IntegratedChartsModule.with(AgChartsEnterpriseModule)` automatically. Ensure `ag-charts-enterprise` is installed in the package.
870
+
871
+ **ReportViewer renders at half width**
872
+ → The outer `Box` in `reportViewer/index.js` must use `display: 'block'`, not `display: 'flex'`.
873
+
874
+ **Static props (`getLayout`, `acl`) not picked up from package exports**
875
+ → Use the explicit import + assignment pattern. Bare `export { X as default }` does not carry static properties across package boundaries.
876
+
877
+ **Duplicate React / MUI hook errors**
878
+ → Pin `react`, `react-dom`, and `@mui/material` to the host app's copies via `config.resolve.alias` in `next.config.js`.
879
+
880
+ **Old routes returning 404**
881
+ → Check that host-app pages exist under `/builders/...` and that old paths have `getServerSideProps = () => ({ notFound: true })` stubs.
178
882
 
179
883
  ---
180
884
 
181
885
  ## Publishing
182
886
 
183
887
  ```bash
184
- npm version patch # bug fix → 1.0.x
185
- npm version minor # feature → 1.x.0
186
- npm version major # breaking → x.0.0
888
+ npm login # one-time setup
889
+
890
+ npm version patch # 1.0.x — bug fixes
891
+ npm version minor # 1.x.0 — new features
892
+ npm version major # x.0.0 — breaking changes
893
+
187
894
  npm publish
188
895
  ```
189
896
 
897
+ Dry run (verify what will be published without actually publishing):
898
+ ```bash
899
+ npm publish --dry-run
900
+ ```
901
+
190
902
  ---
191
903
 
192
904
  ## Changelog
193
905
 
194
906
  ### 1.0.21
195
- - Added Timer Engine — configurable auto-refresh timers per view (interval, cron, manual trigger)
907
+ - Added Timer Engine — configurable auto-refresh timers per view
196
908
  - Added full Undo / Redo history with keyboard shortcuts (Ctrl+Z / Ctrl+Y)
197
909
  - Added Cut / Copy / Paste with system clipboard support (Ctrl+X / Ctrl+C / Ctrl+V)
198
910
  - Added Undo / Redo buttons to the viewer toolbar
199
- - Expanded KPI component suite: Gauge, BulletChart, ColorScale, Rating, Countdown, AvatarGroup, StepStage
200
- - Print Layout designer: multi-zone canvas (header / body / footer), `@page` CSS, print dialog
911
+ - Expanded KPI suite: Gauge, BulletChart, ColorScale, Rating, Countdown, AvatarGroup, StepStage
912
+ - Print Layout designer: multi-zone canvas, `@page` CSS, print dialog
201
913
 
202
914
  ### 1.0.20
203
915
  - KPI components: Metric, Trend, Badge, StatusDot, IconBox, Sparkline, MiniBarChart, Donut, Funnel, HeatmapGrid, TagList, Timeline, ComparisonBars
@@ -209,3 +921,8 @@ npm publish
209
921
  - Navigation Extension API
210
922
  - AG Grid and MUI data grid integration
211
923
  - Drag-and-drop canvas with 50+ components
924
+ - Global Data Store
925
+
926
+ ---
927
+
928
+ *Full user manual: [RoboByteBuilder_User_Manual.docx](./RoboByteBuilder_User_Manual.docx) (included in the package)*