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,405 @@
1
+ # Print Layouts — Calling Them From Host-App Code
2
+
3
+ > **Scope.** This document covers triggering a print layout **from outside the UI Builder** — from a regular page, action handler, dialog, hook, or anywhere else in host-app code.
4
+ >
5
+ > The layout *itself* is designed in the **Print Layout Builder** at `/printBuilder` and persisted via the backend. This guide only covers how the host renders + prints a saved layout, not how to design one.
6
+ >
7
+ > If you're triggering print from inside the builder's Calculation runtime (`actionsConfig.code`, `viewerActions.code`, `onSubmit`, etc.), use the auto-injected `openPrintLayout(layoutId, data)` — see [`training/49-printLayout.md`](../training/49-printLayout.md) for the full Calculation-scope reference (data shape, when to fire, recipes for row / viewer / form-submit triggers, pitfalls). This doc is for everything else: regular host-app React, dialogs, hooks, pages outside any builder context.
8
+
9
+ ---
10
+
11
+ ## 1. Quick start
12
+
13
+ ```jsx
14
+ import { useRef } from 'react'
15
+ import PrintDialog from 'views/builder/viewer/PrintDialog'
16
+
17
+ export default function InvoicesPage() {
18
+ // 1. Allocate a ref the dialog will register itself on.
19
+ const printDialogRef = useRef(null)
20
+
21
+ // 2. Build the minimum viewerContext the dialog needs.
22
+ const viewerContext = {
23
+ printDialogRef,
24
+ data: {}, // becomes printData inside the layout
25
+ setData: () => {},
26
+ form: {},
27
+ setForm: () => {},
28
+ mode: 'preview',
29
+ isEditMode: false,
30
+ isPreviewMode: true,
31
+ isPrintContext: true,
32
+ }
33
+
34
+ // 3. Mount the dialog once. It stays hidden until you open it.
35
+ // 4. Open it from anywhere with the saved layout id + the data the
36
+ // layout should render against.
37
+ return (
38
+ <>
39
+ <Button
40
+ variant='contained'
41
+ onClick={() =>
42
+ printDialogRef.current?.open('invoice-layout-id', {
43
+ invoiceNumber: 'INV-1042',
44
+ customerName: 'Acme Corp',
45
+ lines: [{ sku: 'A', qty: 2 }, { sku: 'B', qty: 5 }],
46
+ })
47
+ }
48
+ >
49
+ Print Invoice
50
+ </Button>
51
+
52
+ <PrintDialog viewerContext={viewerContext} />
53
+ </>
54
+ )
55
+ }
56
+ ```
57
+
58
+ The dialog fetches the layout by `id`, renders the multi-zone preview, exposes a Print button, injects print CSS for `@page`, and on print fills in page numbers / clones fixed header & footer per page.
59
+
60
+ ---
61
+
62
+ ## 2. Prerequisites
63
+
64
+ ### 2.1 The layout exists on the backend
65
+
66
+ You design the layout in the host app's `/printBuilder` (the page wrapper for `PrintBuilderPage`), give it a title, and save it. The backend persists it as `{ id, title, value: JSON-stringified-schema }` under `Endpoints.PrintLayout.*`. The dialog fetches it on `open(layoutId, data)`.
67
+
68
+ ### 2.2 `<RoboByteFrontBuilderProvider>` at app root
69
+
70
+ `PrintDialog` uses `Services.GetService` to fetch the layout, which reads `apiURL` and `accessToken` from the provider. AG Grid (for `reportViewer` zones inside a layout) also reads its license from the provider. Without the provider, the fetch and any embedded grids fail silently.
71
+
72
+ ### 2.3 `next.config.js` bare-alias resolution
73
+
74
+ The import path is bare-aliased — `views/builder/viewer/PrintDialog`. Your host's `next.config.js` `NormalModuleReplacementPlugin` resolves `views/*` to the package's source when the importing file is inside the package, and to the host's source otherwise. See [INTEGRATION.md](../INTEGRATION.md). When importing `PrintDialog` from **host** code, you need to point the alias at the package explicitly because the importing file is in the host tree.
75
+
76
+ The simplest cross-tree import is to add this to your host's `package.json` `dependencies` and just import via package subpath:
77
+
78
+ ```js
79
+ // works wherever next-transpile-modules is active
80
+ import PrintDialog from 'robobyte-front-builder/src/views/builder/viewer/PrintDialog'
81
+ ```
82
+
83
+ Or expose it from your host via `next.config.js`:
84
+
85
+ ```js
86
+ // next.config.js — point bare 'views/builder/viewer/PrintDialog' at the package
87
+ const packageOwned = [
88
+ 'services/reportData/fetchReportData',
89
+ 'views/builder/viewer/PrintDialog', // ← add this
90
+ ]
91
+ packageOwned.forEach(mod => {
92
+ config.plugins.push(
93
+ new NormalModuleReplacementPlugin(
94
+ new RegExp(`^${mod.replace(/\//g, '\\/')}(\\.jsx?)?$`),
95
+ resource => { resource.request = path.join(builderSrc, mod) }
96
+ )
97
+ )
98
+ })
99
+ ```
100
+
101
+ After that, `import PrintDialog from 'views/builder/viewer/PrintDialog'` works from any host file.
102
+
103
+ ---
104
+
105
+ ## 3. How it works
106
+
107
+ The dialog is a **mounted-but-hidden** React component that exposes an imperative ref:
108
+
109
+ ```ts
110
+ printDialogRef.current = {
111
+ open(layoutId: string, data?: object): void, // fetch + open
112
+ openWithSchema(schema: object, data?: object): void, // open with a pre-loaded schema
113
+ close(): void,
114
+ }
115
+ ```
116
+
117
+ When `open(layoutId, data)` is called:
118
+
119
+ 1. The dialog calls `Endpoints.PrintLayout.Get.GetById?id=layoutId` and parses the schema.
120
+ 2. It stores `data` as the `printData` (read inside the layout as `data` in any Calculation / expression).
121
+ 3. It opens a full-screen `<Dialog>` showing a multi-page preview built by `PrintPreviewCanvas`, plus a "Print" button.
122
+ 4. Print CSS for `@page` (margins, paper size, header / footer height) is injected globally so `Ctrl+P` from inside the dialog respects the layout's settings.
123
+ 5. When the user clicks Print, the dialog closes, page numbers are filled in (`{page}` / `{pages}` tokens), fixed header / footer zones are cloned per body page, then `window.print()` is fired. An `afterprint` event listener cleans everything up.
124
+
125
+ Because the print mechanism uses the browser's native `window.print()`, the user gets the OS print dialog — no PDF library, no server round-trip. Saving as PDF is just "Save to PDF" in the OS print dialog.
126
+
127
+ ---
128
+
129
+ ## 4. The `data` you pass to `open(...)`
130
+
131
+ The second argument to `open` becomes the layout's `data` scope. Inside the layout (designed in the builder), expressions and Calculations read this object as `data`:
132
+
133
+ ```js
134
+ // In the layout, a label component's text expression:
135
+ data.invoiceNumber // → 'INV-1042'
136
+ data.lines.reduce((sum, l) => sum + l.qty, 0) // → 7
137
+ ```
138
+
139
+ There are no constraints on shape — pass whatever the layout's bindings expect. A typical pattern:
140
+
141
+ ```js
142
+ printDialogRef.current.open('invoice-layout', {
143
+ // header
144
+ invoiceNumber: order.id,
145
+ invoiceDate: formatDate(order.createdAt),
146
+ customerName: order.customer.name,
147
+ customerAddr: order.customer.address,
148
+
149
+ // body
150
+ lines: order.lines,
151
+ subtotal: order.subtotal,
152
+ tax: order.tax,
153
+ total: order.total,
154
+
155
+ // footer
156
+ notes: order.notes,
157
+ })
158
+ ```
159
+
160
+ > **Tip.** Keep a clear contract between the layout designer and the caller. The layout's Settings tab has a "Test Data" JSON field — the designer uses that to preview against representative data; that same shape is what the caller should pass at runtime.
161
+
162
+ ---
163
+
164
+ ## 5. Three mounting patterns
165
+
166
+ ### 5.1 Mount per page (simplest)
167
+
168
+ Allocate the ref + mount the dialog where you need it. Best when print is page-specific.
169
+
170
+ ```jsx
171
+ function OrdersPage() {
172
+ const printDialogRef = useRef(null)
173
+ const ctx = {
174
+ printDialogRef,
175
+ data: {}, setData: () => {}, form: {}, setForm: () => {},
176
+ mode: 'preview', isEditMode: false, isPreviewMode: true, isPrintContext: true,
177
+ }
178
+
179
+ const printOrder = (order) =>
180
+ printDialogRef.current?.open('order-layout-id', { order })
181
+
182
+ return (
183
+ <>
184
+ <OrdersTable onPrint={printOrder} />
185
+ <PrintDialog viewerContext={ctx} />
186
+ </>
187
+ )
188
+ }
189
+ ```
190
+
191
+ ### 5.2 Mount at app root (cleanest for multi-page apps)
192
+
193
+ If you want `printLayout(id, data)` callable from anywhere, mount once at the `_app.js` level and put the ref + helper in a context.
194
+
195
+ ```jsx
196
+ // host: contexts/PrintContext.jsx
197
+ import { createContext, useContext, useRef, useCallback } from 'react'
198
+ import PrintDialog from 'views/builder/viewer/PrintDialog'
199
+
200
+ const PrintCtx = createContext({ openPrintLayout: () => {} })
201
+
202
+ export function PrintProvider({ children }) {
203
+ const printDialogRef = useRef(null)
204
+
205
+ const openPrintLayout = useCallback((layoutId, data) => {
206
+ if (!printDialogRef.current) {
207
+ console.warn('[openPrintLayout] PrintDialog is not mounted yet')
208
+ return
209
+ }
210
+ printDialogRef.current.open(layoutId, data)
211
+ }, [])
212
+
213
+ const closePrintLayout = useCallback(() => {
214
+ printDialogRef.current?.close()
215
+ }, [])
216
+
217
+ const viewerContext = {
218
+ printDialogRef,
219
+ data: {}, setData: () => {}, form: {}, setForm: () => {},
220
+ mode: 'preview', isEditMode: false, isPreviewMode: true, isPrintContext: true,
221
+ }
222
+
223
+ return (
224
+ <PrintCtx.Provider value={{ openPrintLayout, closePrintLayout }}>
225
+ {children}
226
+ <PrintDialog viewerContext={viewerContext} />
227
+ </PrintCtx.Provider>
228
+ )
229
+ }
230
+
231
+ export const usePrintLayout = () => useContext(PrintCtx)
232
+ ```
233
+
234
+ ```jsx
235
+ // host: pages/_app.js
236
+ <RoboByteFrontBuilderProvider {...}>
237
+ <PrintProvider>
238
+ <Component {...pageProps} />
239
+ </PrintProvider>
240
+ </RoboByteFrontBuilderProvider>
241
+ ```
242
+
243
+ ```jsx
244
+ // any host page
245
+ import { usePrintLayout } from '@/contexts/PrintContext'
246
+
247
+ function InvoiceRow({ invoice }) {
248
+ const { openPrintLayout } = usePrintLayout()
249
+ return (
250
+ <Button onClick={() => openPrintLayout('invoice-layout', invoice)}>
251
+ Print
252
+ </Button>
253
+ )
254
+ }
255
+ ```
256
+
257
+ > This is the recommended pattern for any non-trivial host app — every page gets `usePrintLayout()` without re-mounting the dialog.
258
+
259
+ ### 5.3 With a preloaded schema (skip the fetch)
260
+
261
+ If your host already has the layout's schema in memory — e.g. from a custom backend bundle, a fixture, or a recently-saved layout — bypass the API fetch and call `openWithSchema(schema, data)`:
262
+
263
+ ```jsx
264
+ const someSchema = await fetch('/api/my-cached-layout').then(r => r.json())
265
+ printDialogRef.current.openWithSchema(someSchema, { invoiceId: 42 })
266
+ ```
267
+
268
+ This is what the Print Builder's "Test Print" button uses — the in-progress schema isn't on the server yet, so it gets pushed in directly.
269
+
270
+ ---
271
+
272
+ ## 6. Discovering and listing layouts
273
+
274
+ If users should pick a layout at runtime, two paths:
275
+
276
+ ### 6.1 Embed the built-in `PrintLayoutsList` page
277
+
278
+ ```jsx
279
+ import { PrintLayoutsList } from 'robobyte-front-builder'
280
+
281
+ <PrintLayoutsList />
282
+ ```
283
+
284
+ Renders the same list view as `/printBuilder/layouts` — browse, preview, delete.
285
+
286
+ ### 6.2 Roll your own with the endpoint
287
+
288
+ ```jsx
289
+ import { Services, Endpoints } from 'services/Endpoints'
290
+
291
+ async function loadLayouts() {
292
+ const r = await Services.GetService(Endpoints.PrintLayout.Get.GetAll, false, {})
293
+ return r?.data ?? [] // [{ id, title, value }, ...]
294
+ }
295
+ ```
296
+
297
+ `value` is the JSON-stringified schema. Pass `id` to `printDialogRef.current.open(id, data)` to render.
298
+
299
+ ---
300
+
301
+ ## 7. Recipes
302
+
303
+ ### 7.1 Print right after a save
304
+
305
+ ```js
306
+ async function saveAndPrint(orderDraft) {
307
+ const r = await PostService(Endpoints.Orders.Post.Create, true, orderDraft)
308
+ if (r?.data) {
309
+ openPrintLayout('order-layout', { ...orderDraft, id: r.data.id })
310
+ }
311
+ }
312
+ ```
313
+
314
+ ### 7.2 Print a filtered grid (no per-row template)
315
+
316
+ For "print the report exactly as it is on screen", let the user click the **PDF export** icon in the report toolbar — it uses `jspdf-autotable` and respects current filters / columns. Use `openPrintLayout` only when you have a designed template the data should populate.
317
+
318
+ ### 7.3 Bulk print one layout per selected row
319
+
320
+ ```js
321
+ function bulkPrint(rows) {
322
+ // Print one at a time — opening multiple PrintDialogs at once would race.
323
+ // Easiest pattern: concat into a single layout that accepts an array.
324
+ openPrintLayout('packing-slips', { slips: rows })
325
+ }
326
+ ```
327
+
328
+ The layout iterates `data.slips` via a `repeater` and the print engine paginates automatically.
329
+
330
+ ### 7.4 Programmatic close (e.g. cancel from a parent)
331
+
332
+ ```js
333
+ printDialogRef.current?.close()
334
+ // OR via the provider pattern:
335
+ const { closePrintLayout } = usePrintLayout()
336
+ closePrintLayout()
337
+ ```
338
+
339
+ ### 7.5 Print a layout from a `<reportViewer>` viewer action
340
+
341
+ If you've wired the `PrintProvider` (section 5.2), pull `openPrintLayout` from the hook inside your `viewerActions` `onClick`:
342
+
343
+ ```jsx
344
+ import { usePrintLayout } from '@/contexts/PrintContext'
345
+
346
+ function OrdersReport() {
347
+ const { openPrintLayout } = usePrintLayout()
348
+ const [api, setApi] = useState(null)
349
+
350
+ const viewerActions = [
351
+ {
352
+ label: 'Print Selected',
353
+ icon: 'PrintOutlined',
354
+ variant: 'contained',
355
+ onClick: () => {
356
+ const selected = api?.getSelectedRows() ?? []
357
+ if (selected.length === 0) return
358
+ openPrintLayout('order-layout', { orders: selected })
359
+ },
360
+ },
361
+ ]
362
+
363
+ return (
364
+ <ReportViewer pageId='ORDERS' setOutGridApi={setApi} viewerActions={viewerActions} />
365
+ )
366
+ }
367
+ ```
368
+
369
+ ---
370
+
371
+ ## 8. Pitfalls
372
+
373
+ - **Ref not registered yet.** `PrintDialog` registers `printDialogRef.current` in a `useEffect` after mount. Calling `open()` before the first render completes throws "PrintDialog is not mounted". In practice this only matters for SSR — wrap calls in `useEffect` or `requestAnimationFrame` if you must trigger on first paint.
374
+ - **Multiple mounted dialogs.** Each `<PrintDialog>` instance writes to its own ref. Mounting two with the same ref is undefined; mounting two with different refs is fine but you'll have stacking print CSS. Use the provider pattern (section 5.2) to enforce a single instance.
375
+ - **Provider missing.** Without `<RoboByteFrontBuilderProvider>`, `Services.GetService` has no base URL or auth — the layout fetch fails silently and the dialog stays in its loading state.
376
+ - **The `viewerContext` shape matters.** The dialog reads `printDialogRef` from it. The other fields (`data`, `setData`, `form`, …) are forwarded to every zone's render context. Pass at least the keys shown in section 1 — missing keys will show up as `undefined` inside the layout's expressions.
377
+ - **`data` is the layout's runtime scope, not arbitrary metadata.** Inside the layout, every Calculation and expression reads `data.x`. If you pass `{ order: {...} }`, expressions need to write `data.order.x`. Match the shape the designer designed against.
378
+ - **Saving as PDF is a browser print operation.** There's no programmatic "save to PDF" — it goes through the OS print dialog. Users on Chromium pick "Save as PDF"; users on Safari pick the print → PDF flow. If you need server-rendered PDFs, that's out of scope for this package.
379
+ - **`@page` settings only kick in after the first `open` of a session.** The CSS is injected on schema load. If you change the layout's `settings.margins` and call `open` again, the new CSS replaces the old. Don't try to set `@page` from outside the layout.
380
+ - **Print preview shows the AG Grid as a static HTML table.** When a layout includes a `reportViewer`, the print path fetches **all rows with `isPagination: false`** and renders a plain table (AG Grid virtualization breaks under `window.print()`). For very large reports, expect the print preview to take several seconds to populate.
381
+
382
+ ---
383
+
384
+ ## 9. Cheat sheet
385
+
386
+ 1. Import: `import PrintDialog from 'views/builder/viewer/PrintDialog'` (bare alias; add the package-owned plugin rule in `next.config.js` if you're importing from host code).
387
+ 2. Allocate `const printDialogRef = useRef(null)`.
388
+ 3. Mount `<PrintDialog viewerContext={{ printDialogRef, data: {}, setData: () => {}, form: {}, setForm: () => {}, mode: 'preview', isEditMode: false, isPreviewMode: true, isPrintContext: true }} />` once in your tree.
389
+ 4. Call `printDialogRef.current.open('layout-id', dataObject)` to fetch + show.
390
+ 5. Or `printDialogRef.current.openWithSchema(schema, dataObject)` if you already have the schema in memory.
391
+ 6. Call `printDialogRef.current.close()` to dismiss.
392
+ 7. For multi-page apps: wrap once in a `PrintProvider` and expose `usePrintLayout()` (section 5.2).
393
+ 8. The `dataObject` becomes `data` inside the layout — match the shape the designer designed against.
394
+
395
+ ---
396
+
397
+ ## Cross-references
398
+
399
+ - **In-builder usage** (`openPrintLayout` auto-injected into Calculation scope): [`training/49-printLayout.md`](../training/49-printLayout.md). Also covered briefly in README's *Calculation Scope Reference* and *Print Layout Builder* sections.
400
+ - **Designing layouts**: visit `/printBuilder` in the host app (the `PrintBuilderPage` re-export).
401
+ - **Listing / managing saved layouts**: `<PrintLayoutsList />` (re-export), or roll your own via `Endpoints.PrintLayout.Get.GetAll`.
402
+ - **Host-app integration**: [INTEGRATION.md](../INTEGRATION.md) for `next.config.js`, peer deps, provider setup.
403
+ - **AG Grid theme customization** (relevant for `reportViewer` zones inside a print layout): README → *AG Grid theme*.
404
+ - **`<ReportViewer>` host-app usage**: [docs/ReportViewer.md](./ReportViewer.md).
405
+ - **`fetchReportDataByPageId` host-app usage**: [docs/fetchReportData.md](./fetchReportData.md).
package/package.json CHANGED
@@ -1,7 +1,31 @@
1
1
  {
2
2
  "name": "robobyte-front-builder",
3
- "version": "1.0.25",
3
+ "version": "1.0.27",
4
4
  "description": "RoboByte low-code UI builder, Report builder, and navigation extension system",
5
+ "keywords": [
6
+ "low-code",
7
+ "ui-builder",
8
+ "report-builder",
9
+ "print-layout",
10
+ "next.js",
11
+ "react",
12
+ "ag-grid",
13
+ "mui",
14
+ "drag-and-drop",
15
+ "schema-driven",
16
+ "form-builder",
17
+ "navigation"
18
+ ],
19
+ "homepage": "https://github.com/Hossny37/RoboByteFrontBuilder#readme",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/Hossny37/RoboByteFrontBuilder.git"
23
+ },
24
+ "bugs": {
25
+ "url": "https://github.com/Hossny37/RoboByteFrontBuilder/issues"
26
+ },
27
+ "license": "MIT",
28
+ "author": "Hossny37",
5
29
  "main": "src/lib/index.js",
6
30
  "files": [
7
31
  "src",
@@ -9,7 +33,11 @@
9
33
  "styles",
10
34
  "next.config.js",
11
35
  "jsconfig.json",
36
+ "LICENSE",
12
37
  "INTEGRATION.md",
38
+ "docs",
39
+ "training/*.md",
40
+ "training/examples",
13
41
  "RoboByteBuilder_User_Manual.docx"
14
42
  ],
15
43
  "exports": {
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Centralized AG Grid theme configuration.
3
+ *
4
+ * Every <AgGridReact> in the package reads its theme from a single source —
5
+ * the React context provided by `AgGridThemeProvider` (wrapped automatically
6
+ * inside RoboByteFrontBuilderProvider). The default lives here, in code.
7
+ *
8
+ * Host apps can override theme params by passing `agGridTheme` to
9
+ * RoboByteFrontBuilderProvider:
10
+ *
11
+ * <RoboByteFrontBuilderProvider
12
+ * agGridTheme={{ accentColor: '#3b82f6', headerHeight: 32 }}
13
+ * ...
14
+ * />
15
+ *
16
+ * The override object is merged on top of DEFAULT_AG_THEME_PARAMS — host
17
+ * params win for the keys they set, and fall back to defaults otherwise.
18
+ *
19
+ * Future: this default will be replaced with a system-settings fetch so
20
+ * theme params can be tweaked at runtime without a code change. Until then,
21
+ * editing this file is the way to change the package-wide default.
22
+ */
23
+
24
+ import { themeQuartz } from 'ag-grid-community'
25
+
26
+ /**
27
+ * Default Quartz theme parameters used across the package.
28
+ * See https://www.ag-grid.com/react-data-grid/theming/ for the full list.
29
+ *
30
+ * Add new keys here as the package grows. Host apps can override any of
31
+ * them by passing `agGridTheme={{ <key>: <value> }}` to the provider.
32
+ */
33
+ export const DEFAULT_AG_THEME_PARAMS = Object.freeze({
34
+ accentColor: '#FF1185',
35
+ // headerHeight: 28,
36
+ // rowHeight: 34,
37
+ // fontFamily: 'inherit',
38
+ // fontSize: 13,
39
+ // borderRadius: 4,
40
+ })
41
+
42
+ /**
43
+ * Build a Quartz theme from optional overrides. Returns a fresh theme object
44
+ * (Quartz themes are immutable). Safe to call on every render — `withParams`
45
+ * caches by params identity.
46
+ *
47
+ * @param {Object} [overrides] - Quartz theme params to merge over the defaults.
48
+ * @returns {object} A ready-to-use AG Grid theme.
49
+ */
50
+ export function buildAgGridTheme(overrides) {
51
+ return themeQuartz.withParams({
52
+ ...DEFAULT_AG_THEME_PARAMS,
53
+ ...(overrides ?? {}),
54
+ })
55
+ }
56
+
57
+ /** Pre-built default theme — convenient when no override is in play. */
58
+ export const DEFAULT_AG_THEME = buildAgGridTheme()
@@ -0,0 +1,42 @@
1
+ /**
2
+ * AgGridThemeContext — single source of truth for the package-wide AG Grid
3
+ * theme. Wrapped automatically inside RoboByteFrontBuilderProvider.
4
+ *
5
+ * Consumers call `useAgGridTheme()` to get the resolved Quartz theme and pass
6
+ * it to <AgGridReact theme={agTheme} />.
7
+ */
8
+
9
+ import { createContext, useContext, useMemo } from 'react'
10
+ import { buildAgGridTheme, DEFAULT_AG_THEME } from './agGridTheme'
11
+
12
+ const AgGridThemeContext = createContext(DEFAULT_AG_THEME)
13
+
14
+ /**
15
+ * Wraps children with the AG Grid theme context.
16
+ *
17
+ * @param {Object} [params] - Quartz theme overrides (merged over defaults).
18
+ * @param {ReactNode} children
19
+ */
20
+ export function AgGridThemeProvider({ params, children }) {
21
+ // JSON.stringify on the params object is intentional — host apps that pass
22
+ // an inline object literal would otherwise rebuild the theme on every
23
+ // parent render. Stringify keys the memo by the actual params content.
24
+ const theme = useMemo(
25
+ () => buildAgGridTheme(params),
26
+ // eslint-disable-next-line react-hooks/exhaustive-deps
27
+ [JSON.stringify(params ?? null)]
28
+ )
29
+ return (
30
+ <AgGridThemeContext.Provider value={theme}>
31
+ {children}
32
+ </AgGridThemeContext.Provider>
33
+ )
34
+ }
35
+
36
+ /**
37
+ * Returns the live AG Grid theme. Falls back to the default theme when
38
+ * called outside RoboByteFrontBuilderProvider, so isolated grids still work.
39
+ */
40
+ export function useAgGridTheme() {
41
+ return useContext(AgGridThemeContext)
42
+ }
package/src/lib/index.js CHANGED
@@ -106,3 +106,10 @@ export { AuthContext } from '../context/AuthContext'
106
106
  // — both resolve to the same module thanks to the NormalModuleReplacementPlugin
107
107
  // in next.config.js, so the in-memory builderModelCache is shared.
108
108
  export { default as fetchReportDataByPageId } from '../services/reportData/fetchReportData'
109
+
110
+ // ── AG Grid theme ────────────────────────────────────────────────────────────
111
+ // Default theme params (host can override via RoboByteFrontBuilderProvider's
112
+ // `agGridTheme` prop), a builder function for ad-hoc themes, and the hook
113
+ // every internal grid uses to read the live theme.
114
+ export { DEFAULT_AG_THEME_PARAMS, buildAgGridTheme, DEFAULT_AG_THEME } from './agGridTheme'
115
+ export { AgGridThemeProvider, useAgGridTheme } from './agGridThemeContext'
@@ -49,6 +49,7 @@ import { Toaster } from 'react-hot-toast'
49
49
  import { ModuleRegistry } from 'ag-grid-community'
50
50
  import { AllEnterpriseModule, LicenseManager } from 'ag-grid-enterprise'
51
51
  import { NavigationExtensionProvider } from '../navigation/NavigationExtensionContext'
52
+ import { AgGridThemeProvider } from '../agGridThemeContext'
52
53
  import { configureRoboByte } from '../../services/config'
53
54
  import { AuthContext } from '../../context/AuthContext'
54
55
  import { setRouter } from '../../services/routerRef'
@@ -95,6 +96,17 @@ const RoboByteFrontBuilderProvider = ({
95
96
  navExtensions = [],
96
97
  endpoints = null,
97
98
  agGridLicenseKey = null,
99
+ /**
100
+ * Optional Quartz theme overrides applied to every <AgGridReact> in the
101
+ * package. Merged on top of DEFAULT_AG_THEME_PARAMS in `lib/agGridTheme.js`.
102
+ *
103
+ * Example:
104
+ * agGridTheme={{ accentColor: '#3b82f6', headerHeight: 32 }}
105
+ *
106
+ * Future: this will accept a "load from system settings" mode. For now
107
+ * pass a plain params object.
108
+ */
109
+ agGridTheme = null,
98
110
  toasterProps = {},
99
111
  }) => {
100
112
  // Apply URL + endpoint config synchronously before any child renders.
@@ -121,6 +133,7 @@ const RoboByteFrontBuilderProvider = ({
121
133
  return (
122
134
  <AuthContext.Provider value={authValue}>
123
135
  <NavigationExtensionProvider items={navExtensions}>
136
+ <AgGridThemeProvider params={agGridTheme}>
124
137
  <RouterSync />
125
138
  {children}
126
139
  {/* Package-owned Toaster — same react-hot-toast instance used by all
@@ -157,6 +170,7 @@ const RoboByteFrontBuilderProvider = ({
157
170
  }}
158
171
  {...toasterProps}
159
172
  />
173
+ </AgGridThemeProvider>
160
174
  </NavigationExtensionProvider>
161
175
  </AuthContext.Provider>
162
176
  )
@@ -1,7 +1,6 @@
1
1
  import { Box, Button, IconButton, Tooltip, Typography } from '@mui/material'
2
2
  import { useRef, useMemo, useCallback, useEffect } from 'react'
3
3
  import { AgGridReact } from 'ag-grid-react'
4
- import { themeQuartz } from 'ag-grid-community'
5
4
  import { AddOutlined, DeleteOutlined, TableChartOutlined } from '@mui/icons-material'
6
5
  import ViewerComponentWrapper from '../ViewerComponentWrapper'
7
6
  import RowActionsCell from './RowActionsCell'
@@ -9,9 +8,7 @@ import { resolveProps } from 'services/builderHelper/resolveProps'
9
8
  import { executeJSCode } from 'services/builderHelper/jsExecutor'
10
9
  import { processColumnDefinitions, processColumnsConfig } from 'views/genericTable/convertStringFunctions'
11
10
  import { AG_COMPONENTS } from './dataGridComponents'
12
-
13
- // ── Shared AG Grid theme (same as SGrid) ──────────────────────────────────────
14
- const agTheme = themeQuartz.withParams({ accentColor: '#FF1185' })
11
+ import { useAgGridTheme } from 'src/lib/agGridThemeContext'
15
12
 
16
13
  // ── Delete-column cell renderer ───────────────────────────────────────────────
17
14
  // Defined at module level so AG Grid never receives a new component reference.
@@ -49,6 +46,9 @@ export default function DataGridRenderer({ node, viewerContext }) {
49
46
  form, data, setData, dataRef, reportRefs, openDialog, closeDialog, isEditMode,
50
47
  } = viewerContext
51
48
 
49
+ // Centralized AG Grid theme — see lib/agGridTheme.js / RoboByteFrontBuilderProvider.
50
+ const agTheme = useAgGridTheme()
51
+
52
52
  const main = resolveProps(node, 'main', viewerContext)
53
53
 
54
54
  const {
@@ -115,7 +115,7 @@ import numeral from 'numeral'
115
115
  import CustomFilterDialog from "views/customFilter/CustomFilterDialog";
116
116
  import CustomStatusBar from "views/genericTable/statusBar/rowCountStatusBar";
117
117
  import StreamService from "services/StreamService";
118
- import {themeQuartz} from 'ag-grid-community';
118
+ import { useAgGridTheme } from 'src/lib/agGridThemeContext'
119
119
  import {ReportBuilderEndpoints} from "services/Endpoints/ReportBuilderEndpoints";
120
120
  import {debounce} from "lodash";
121
121
  import jsPDF from "jspdf";
@@ -330,10 +330,10 @@ const SGrid = props => {
330
330
  // ** State
331
331
  const appContext = useContext(SystemContext);
332
332
  const imageBaseUrl = appContext?.settings?.imagesStorageServer;
333
- const agTheme = themeQuartz
334
- .withParams({
335
- accentColor: "#FF1185"
336
- });
333
+ // Theme comes from RoboByteFrontBuilderProvider's agGridTheme prop, merged
334
+ // with DEFAULT_AG_THEME_PARAMS in lib/agGridTheme.js. Falls back to the
335
+ // bare default when used outside the provider.
336
+ const agTheme = useAgGridTheme()
337
337
 
338
338
  const {
339
339
  externalTimer,
@@ -37,7 +37,7 @@ import numeral from 'numeral'
37
37
  import CustomFilterDialog from "views/customFilter/CustomFilterDialog";
38
38
  import CustomStatusBar from "views/genericTable/statusBar/rowCountStatusBar";
39
39
  import StreamService from "services/StreamService";
40
- import {themeQuartz} from 'ag-grid-community';
40
+ import { useAgGridTheme } from 'src/lib/agGridThemeContext'
41
41
 
42
42
  // ** Utils Import
43
43
 
@@ -52,10 +52,8 @@ const defaultFinalRequest = {
52
52
  }
53
53
  const TAGGrid = props => {
54
54
  // ** State
55
- const agTheme = themeQuartz
56
- .withParams({
57
- accentColor: "#FF1185"
58
- });
55
+ // Theme comes from RoboByteFrontBuilderProvider's agGridTheme prop.
56
+ const agTheme = useAgGridTheme()
59
57
  const {
60
58
  groupEndPoint,
61
59
  streamEndPoint,