robobyte-front-builder 1.0.23 → 1.0.25

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "robobyte-front-builder",
3
- "version": "1.0.23",
3
+ "version": "1.0.25",
4
4
  "description": "RoboByte low-code UI builder, Report builder, and navigation extension system",
5
5
  "main": "src/lib/index.js",
6
6
  "files": [
package/src/lib/index.js CHANGED
@@ -98,3 +98,11 @@ export { SystemContext, SystemProvider } from '../context/SystemContext'
98
98
  // RoboByteFrontBuilderProvider's user / accessToken props.
99
99
  // There is no AuthProvider in this package; use your own host auth provider.
100
100
  export { AuthContext } from '../context/AuthContext'
101
+
102
+ // ── Helpers ──────────────────────────────────────────────────────────────────
103
+ // fetchReportDataByPageId: pure data fetcher by report `pageId`, no AG Grid
104
+ // dependency. Host code can either import from this package surface (the
105
+ // recommended path) or via the bare alias 'services/reportData/fetchReportData'
106
+ // — both resolve to the same module thanks to the NormalModuleReplacementPlugin
107
+ // in next.config.js, so the in-memory builderModelCache is shared.
108
+ export { default as fetchReportDataByPageId } from '../services/reportData/fetchReportData'
@@ -1,5 +1,5 @@
1
1
  import React, {useEffect, useState} from 'react'
2
- import {Box, CircularProgress, Typography, Paper, IconButton, CardContent, Tooltip} from '@mui/material'
2
+ import {Box, CircularProgress, Typography, Paper, IconButton, CardContent, Tooltip, Popover, Chip} from '@mui/material'
3
3
  import {useRouter} from 'next/router'
4
4
  import SGrid from 'views/genericTable/SGrid'
5
5
  import {Endpoints, Services} from 'services/Endpoints'
@@ -8,7 +8,7 @@ import CardHeader from "@mui/material/CardHeader";
8
8
  import Grid from "@mui/material/Grid";
9
9
  import {Plus} from "mdi-material-ui";
10
10
  import TAGGrid from "views/genericTable/TAGGrid";
11
- import {SettingsOutlined, AssessmentOutlined} from "@mui/icons-material";
11
+ import {SettingsOutlined, AssessmentOutlined, BugReportOutlined, ContentCopyOutlined} from "@mui/icons-material";
12
12
  import UpdateReportPermissionDialog from "views/rolePermissions/UpdateReportPermissionDialog";
13
13
  import {getReportSession} from 'src/services/helper/reportSessionHelper'
14
14
  import BlankLayout from '../../../../lib/layouts/BlankLayout';
@@ -48,6 +48,12 @@ const ReportViewer = (params) => {
48
48
  // Page-level toolbar buttons rendered after Filter / Refresh.
49
49
  // Built by ReportViewerRenderer with bound onClick handlers.
50
50
  viewerActions,
51
+ // Debug — when truthy, renders a small floating BugReport icon at the
52
+ // top-right corner of the report area. Clicking it opens a popover
53
+ // showing the resolved ids (pageId, id, builderMetadata.id) and other
54
+ // useful runtime info. Hidden entirely when `debug` is falsy, so
55
+ // production builds incur zero cost.
56
+ debug,
51
57
  // nodeId / reportRefs are injected by the UI builder's ReportViewerRenderer.
52
58
  // nodeId — the builder schema node ID for this ReportViewer instance
53
59
  // reportRefs — the shared registry { [nodeId]: updateRef } for all reports on the page
@@ -64,6 +70,8 @@ const ReportViewer = (params) => {
64
70
  const [builderMetadata, setBuilderMetadata] = useState(null)
65
71
  const [sessionPayload, setSessionPayload] = useState(null)
66
72
  const [payloadVersion, setPayloadVersion] = useState(0)
73
+ // Debug popover anchor — only used when `debug` prop is truthy.
74
+ const [debugAnchor, setDebugAnchor] = useState(null)
67
75
 
68
76
  useEffect(() => {
69
77
  // Prefer localStorage payload via sessionId (set by SGrid)
@@ -176,13 +184,137 @@ const ReportViewer = (params) => {
176
184
  setPayloadVersion(v => v + 1);
177
185
  }, [id, router.query?.id, pageId, router.query?.pageId, sessionPayload])
178
186
 
187
+ // ── Debug snapshot — gathered fresh on every render so the popover always
188
+ // reflects the current resolved state. Cheap to build (just reads
189
+ // existing state / props) so no memo needed.
190
+ const resolvedPageId = pageId ?? sessionPayload?.pageId ?? router.query?.pageId
191
+ const resolvedId = id ?? sessionPayload?.id ?? router.query?.id
192
+ const debugFields = debug ? [
193
+ { label: 'pageId', value: resolvedPageId },
194
+ { label: 'id (prop)', value: resolvedId },
195
+ { label: 'builder model id', value: builderModel?.id },
196
+ { label: 'report name', value: builderMetadata?.name },
197
+ { label: 'nodeId', value: nodeId },
198
+ { label: 'sessionId', value: sessionId },
199
+ { label: 'payload version', value: payloadVersion },
200
+ { label: 'session payload', value: sessionPayload ? '✓ loaded' : '—' },
201
+ { label: 'isSingle', value: String(Boolean(isSingle)) },
202
+ { label: 'dataAsObject', value: String(Boolean(dataAsObject)) },
203
+ { label: 'isRerender', value: String(Boolean(isRerender)) },
204
+ { label: 'externalTimer', value: externalTimer ?? '—' },
205
+ { label: 'height', value: height ?? '85vh' },
206
+ { label: 'globalParams', value: globalParams ? JSON.stringify(globalParams) : '—' },
207
+ ] : []
208
+
209
+ const copyToClipboard = (value) => {
210
+ if (value == null) return
211
+ try { navigator.clipboard?.writeText(String(value)) } catch (_) {}
212
+ }
213
+
179
214
  return (
180
215
  <Box
181
216
  sx={{
182
217
  display: 'block',
183
218
  width: '100%',
219
+ position: 'relative',
184
220
  }}
185
221
  >
222
+ {/* ── Debug floater ────────────────────────────────────────────────────
223
+ Renders only when the `debug` prop is truthy. A small icon button at
224
+ the top-right corner that opens a key/value popover. Useful when a
225
+ report appears broken — confirm pageId/id resolved correctly, the
226
+ builder model loaded, the right sessionPayload was picked, etc. */}
227
+ {debug && (
228
+ <>
229
+ <Tooltip title='Report debug info' placement='left'>
230
+ <IconButton
231
+ size='small'
232
+ onClick={(e) => setDebugAnchor(e.currentTarget)}
233
+ sx={{
234
+ position: 'absolute',
235
+ top: 4,
236
+ insetInlineEnd: 4,
237
+ zIndex: 10,
238
+ bgcolor: 'warning.light',
239
+ color: 'warning.contrastText',
240
+ opacity: 0.9,
241
+ '&:hover': { bgcolor: 'warning.main', opacity: 1 },
242
+ boxShadow: 1,
243
+ }}
244
+ >
245
+ <BugReportOutlined fontSize='small' />
246
+ </IconButton>
247
+ </Tooltip>
248
+
249
+ <Popover
250
+ open={Boolean(debugAnchor)}
251
+ anchorEl={debugAnchor}
252
+ onClose={() => setDebugAnchor(null)}
253
+ anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
254
+ transformOrigin={{ vertical: 'top', horizontal: 'right' }}
255
+ PaperProps={{ sx: { p: 0, minWidth: 320, maxWidth: 480 } }}
256
+ >
257
+ <Box sx={{ px: 2, py: 1, bgcolor: 'warning.light', display: 'flex', alignItems: 'center', gap: 1 }}>
258
+ <BugReportOutlined fontSize='small' sx={{ color: 'warning.contrastText' }} />
259
+ <Typography variant='subtitle2' sx={{ color: 'warning.contrastText', fontWeight: 700, flex: 1 }}>
260
+ Report Debug
261
+ </Typography>
262
+ <Chip
263
+ size='small'
264
+ label={isLoading ? 'loading' : builderModel ? 'ready' : 'idle'}
265
+ color={isLoading ? 'info' : builderModel ? 'success' : 'default'}
266
+ sx={{ height: 18, fontSize: 10 }}
267
+ />
268
+ </Box>
269
+ <Box sx={{ p: 1.5, display: 'flex', flexDirection: 'column', gap: 0.5 }}>
270
+ {debugFields.map(({ label, value }) => (
271
+ <Box
272
+ key={label}
273
+ sx={{
274
+ display: 'grid',
275
+ gridTemplateColumns: '130px 1fr auto',
276
+ alignItems: 'center',
277
+ gap: 1,
278
+ px: 1,
279
+ py: 0.5,
280
+ borderRadius: 0.5,
281
+ '&:hover': { bgcolor: 'action.hover' },
282
+ }}
283
+ >
284
+ <Typography variant='caption' sx={{ color: 'text.secondary', fontFamily: 'monospace' }}>
285
+ {label}
286
+ </Typography>
287
+ <Typography
288
+ variant='body2'
289
+ sx={{
290
+ fontFamily: 'monospace',
291
+ fontSize: 12,
292
+ color: value == null || value === '—' ? 'text.disabled' : 'text.primary',
293
+ overflow: 'hidden',
294
+ textOverflow: 'ellipsis',
295
+ whiteSpace: 'nowrap',
296
+ }}
297
+ title={String(value ?? '')}
298
+ >
299
+ {value ?? '—'}
300
+ </Typography>
301
+ <Tooltip title='Copy' placement='left'>
302
+ <IconButton
303
+ size='small'
304
+ onClick={() => copyToClipboard(value)}
305
+ disabled={value == null || value === '—'}
306
+ sx={{ p: 0.25 }}
307
+ >
308
+ <ContentCopyOutlined sx={{ fontSize: 13 }} />
309
+ </IconButton>
310
+ </Tooltip>
311
+ </Box>
312
+ ))}
313
+ </Box>
314
+ </Popover>
315
+ </>
316
+ )}
317
+
186
318
  {isLoading ? (
187
319
  <Box
188
320
  sx={{
@@ -9,6 +9,11 @@ export const REPORT_VIEWER_MAIN_FIELDS = [
9
9
  { name: 'title', label: 'Title', type: 'expression' },
10
10
  { name: 'caption', label: 'Caption', type: 'expression' },
11
11
 
12
+ // Debug — when true, a small BugReport icon appears at the report's
13
+ // top-right corner; clicking it opens a popover listing the resolved
14
+ // pageId / id / builder model id / sessionId / and other runtime info.
15
+ // Hidden entirely when false / unset — zero cost in production schemas.
16
+ { name: 'debug', label: 'Debug', type: 'boolean' },
12
17
  { name: 'minimized', label: 'Minimized', type: 'boolean' },
13
18
  { name: 'isRerender', label: 'Is Rerender', type: 'boolean' },
14
19
  { name: 'height', label: 'Height', type: 'expression' },