ui-soxo-bootstrap-core 2.6.56 → 2.6.61

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.
@@ -363,6 +363,11 @@ export default function LandingApi({ history, CustomComponents, CustomModels, ap
363
363
  dispatch({ type: 'settings', payload: result.result.settings });
364
364
  }
365
365
 
366
+ // Role of the logged in user comes along with the menus response (NURA)
367
+ if (result.result.role) {
368
+ dispatch({ type: 'role', payload: result.result.role });
369
+ }
370
+
366
371
  // Reports length
367
372
  if (reports.length) {
368
373
  reportMenus = [
package/core/lib/Store.js CHANGED
@@ -73,6 +73,7 @@ const initialState = {
73
73
  defaultBranch: {},
74
74
  theme: initialTheme(), // Set the initial theme from themes.json
75
75
  settings: {},
76
+ role: {},
76
77
  };
77
78
 
78
79
  /**
@@ -231,6 +232,7 @@ export const GlobalProvider = ({ children, CustomModels,CustomComponents, appSet
231
232
  app: app,
232
233
  user: state.user,
233
234
  settings: state.settings,
235
+ role: state.role,
234
236
  dispatch: dispatch,
235
237
  twilio: state.twilio,
236
238
  isMobile,
@@ -308,6 +310,10 @@ export const AppReducer = (state, action) => {
308
310
  case 'settings':
309
311
  return { ...state, settings: action.payload };
310
312
 
313
+ // Role of the logged in user, received along with the menus response
314
+ case 'role':
315
+ return { ...state, role: action.payload };
316
+
311
317
  case 'slot':
312
318
  return { ...state, user: action.payload };
313
319
 
@@ -42,6 +42,7 @@ function ReferenceSelect({
42
42
  allowClear,
43
43
  sortKey, // Key for sorting
44
44
  sortOrder = 'asc', // default to ascending,
45
+ dbPtr, // db pointer to load the options from , defaults to the logged in db when not passed
45
46
  ...componentProps
46
47
  }) {
47
48
  let [options, setOptions] = useState([]);
@@ -87,8 +88,11 @@ function ReferenceSelect({
87
88
 
88
89
  // Case 1: 'nura-base' mode – used for general fetch via config
89
90
  if (mode === 'nura-base') {
91
+ // Point the request to the passed db , else keep the default db_ptr flow
92
+ const requestConfig = dbPtr ? { ...config, headers: { ...config?.headers, db_ptr: dbPtr } } : { ...config };
93
+
90
94
  // Fetch data from model using config
91
- model.get({ ...config }).then((response) => {
95
+ model.get(requestConfig).then((response) => {
92
96
  // Extract result from response or fallback to response directly
93
97
  let data = response.result || response;
94
98
 
@@ -1,7 +1,7 @@
1
- import React, { useState, useEffect, useMemo } from 'react';
1
+ import React, { useState, useEffect, useMemo, useContext } from 'react';
2
2
  import { Table, Skeleton, Input, Modal, message, Pagination } from 'antd';
3
3
  import { QrcodeOutlined } from '@ant-design/icons';
4
- import { ExportReactCSV, getExportData, Card, TableComponent, QrScanner } from './../../../../lib/';
4
+ import { ExportReactCSV, getExportData, Card, TableComponent, QrScanner, GlobalContext, safeJSON } from './../../../../lib/';
5
5
  import moment from 'moment-timezone';
6
6
  import { CoreScripts } from './../../../../models/';
7
7
  import Button from '../../../../lib/elements/basic/button/button';
@@ -93,6 +93,19 @@ export default function ReportingTable({
93
93
  const [internalConfig, setInternalConfig] = useState({});
94
94
  const [internalPagination, setInternalPagination] = useState({ current: 1, pageSize: 20, total: 0 });
95
95
 
96
+ const { role = {} } = useContext(GlobalContext);
97
+ // When any of the logged in user's roles has `enableUserFilter: true` in its other details,
98
+ // reports receive `enableUserFilter: true` so they can restrict rows to the user's own records.
99
+ // `role` may be a single role object or an array of roles.
100
+ const roles = Array.isArray(role) ? role : role ? [role] : [];
101
+ const isUserFilterEnabled = roles.some((r) => {
102
+ const details = r?.other_details;
103
+ // Some records store other_details with single quotes, e.g. "{'enableUserFilter':true}"
104
+ const parsed = safeJSON(details) || (typeof details === 'string' ? safeJSON(details.replace(/'/g, '"')) : null) || {};
105
+ return parsed.enableUserFilter === true;
106
+ });
107
+ const userFilter = isUserFilterEnabled ? { enableUserFilter: true } : {};
108
+
96
109
  // Independent mode is enabled when enough identifiers are present for the
97
110
  // table to load its own schema and data instead of relying on parent props.
98
111
  const shouldFetchData = !!(requestId || reportId || replacements?.submode || replacements?.mode);
@@ -189,6 +202,7 @@ export default function ReportingTable({
189
202
  page: currentPager.current,
190
203
  limit: currentPager.pageSize,
191
204
  ...(requestPayload?.body ? {} : { mode, submode }),
205
+ ...userFilter,
192
206
  },
193
207
  };
194
208
  // if (!isNuradesk) {
@@ -199,6 +213,7 @@ export default function ReportingTable({
199
213
  ...replacements,
200
214
  page: currentPager.current,
201
215
  limit: currentPager.pageSize,
216
+ ...userFilter,
202
217
  },
203
218
  };
204
219
  }
@@ -34,6 +34,8 @@ import './steps.scss';
34
34
  const TOUCH_NAV_HIDE_DELAY = 2800;
35
35
  const SWIPE_DISTANCE_THRESHOLD = 60;
36
36
  const SWIPE_VERTICAL_TOLERANCE = 80;
37
+ const ZOOM_MIN = 1;
38
+ const ZOOM_MAX = 3;
37
39
 
38
40
  /**
39
41
  * First-step CTA labels keyed by normalized process name.
@@ -148,7 +150,11 @@ export default function ProcessStepsPage({ match, CustomComponents = {}, ...prop
148
150
  const [realtimeStatus, setRealtimeStatus] = useState('idle');
149
151
  const [isTouchDevice, setIsTouchDevice] = useState(false);
150
152
  const [touchNavVisible, setTouchNavVisible] = useState(false);
153
+ const [zoom, setZoom] = useState(ZOOM_MIN);
151
154
 
155
+ const stageBodyRef = useRef(null);
156
+ const zoomLayerRef = useRef(null);
157
+ const pinchRef = useRef(null);
152
158
  const narrationUtteranceRef = useRef(null);
153
159
  const narrationAudioRef = useRef(null);
154
160
  const narrationAudioUrlRef = useRef(null);
@@ -1139,11 +1145,91 @@ export default function ProcessStepsPage({ match, CustomComponents = {}, ...prop
1139
1145
  return;
1140
1146
  }
1141
1147
 
1148
+ if (isStepFullscreen && event.touches.length >= 2) {
1149
+ // Two fingers = pinch. Cancel any pending swipe so it cannot navigate.
1150
+ touchStartRef.current = null;
1151
+ const stage = stageBodyRef.current;
1152
+ const rect = stage.getBoundingClientRect();
1153
+ const [first, second] = [event.touches[0], event.touches[1]];
1154
+ const midX = (first.clientX + second.clientX) / 2 - rect.left;
1155
+ const midY = (first.clientY + second.clientY) / 2 - rect.top;
1156
+ pinchRef.current = {
1157
+ left: rect.left,
1158
+ top: rect.top,
1159
+ distance: getTouchDistance(first, second),
1160
+ scale: zoom,
1161
+ nextScale: zoom,
1162
+ // Content point under the pinch midpoint, in unscaled coordinates.
1163
+ contentX: (stage.scrollLeft + midX) / zoom,
1164
+ contentY: (stage.scrollTop + midY) / zoom,
1165
+ };
1166
+ // Pinch writes to the DOM directly (no React re-render per frame).
1167
+ stage.style.overflow = 'auto';
1168
+ zoomLayerRef.current.style.willChange = 'transform';
1169
+ return;
1170
+ }
1171
+
1172
+ if (pinchRef.current || (isStepFullscreen && zoom > 1)) {
1173
+ // Zoomed in: horizontal drags scroll the content, not swipe between steps.
1174
+ return;
1175
+ }
1176
+
1142
1177
  const touch = event.touches[0];
1143
1178
  touchStartRef.current = { x: touch.clientX, y: touch.clientY };
1144
1179
  revealTouchNav();
1145
1180
  };
1146
1181
 
1182
+ const getTouchDistance = (first, second) => Math.hypot(first.clientX - second.clientX, first.clientY - second.clientY) || 1;
1183
+
1184
+ /**
1185
+ * onTouchMove for the stage body (full screen pinch-to-zoom).
1186
+ * - Scale follows the finger distance, clamped to ZOOM_MIN..ZOOM_MAX.
1187
+ * - Content is scaled from its top-left; the stage body scrolls natively
1188
+ * while zoomed, so scroll is set to keep the pinch midpoint anchored.
1189
+ * - Transform and scroll are written straight to the DOM; React state is
1190
+ * committed once, on touch end, so the heavy step tree is not re-rendered
1191
+ * on every touchmove.
1192
+ */
1193
+ const handleStageTouchMove = (event) => {
1194
+ const pinch = pinchRef.current;
1195
+ if (!pinch || !isStepFullscreen || event.touches.length < 2) {
1196
+ return;
1197
+ }
1198
+
1199
+ const stage = stageBodyRef.current;
1200
+ const [first, second] = [event.touches[0], event.touches[1]];
1201
+ const midX = (first.clientX + second.clientX) / 2 - pinch.left;
1202
+ const midY = (first.clientY + second.clientY) / 2 - pinch.top;
1203
+ const nextScale = Math.min(ZOOM_MAX, Math.max(ZOOM_MIN, pinch.scale * (getTouchDistance(first, second) / pinch.distance)));
1204
+
1205
+ pinch.nextScale = nextScale;
1206
+ zoomLayerRef.current.style.transform = `scale(${nextScale})`;
1207
+ stage.scrollLeft = pinch.contentX * nextScale - midX;
1208
+ stage.scrollTop = pinch.contentY * nextScale - midY;
1209
+ };
1210
+
1211
+ /** Commits the pinch result to state and hands styling back to React. */
1212
+ const finishPinch = () => {
1213
+ const pinch = pinchRef.current;
1214
+ pinchRef.current = null;
1215
+ if (!pinch) {
1216
+ return;
1217
+ }
1218
+
1219
+ const stage = stageBodyRef.current;
1220
+ const layer = zoomLayerRef.current;
1221
+ // Snap tiny zooms back to 100% so the view never rests at ~1.01.
1222
+ const finalScale = pinch.nextScale < 1.05 ? ZOOM_MIN : pinch.nextScale;
1223
+ stage.style.overflow = '';
1224
+ layer.style.willChange = '';
1225
+ layer.style.transform = finalScale === 1 ? '' : `scale(${finalScale})`;
1226
+ if (finalScale === 1) {
1227
+ stage.scrollLeft = 0;
1228
+ stage.scrollTop = 0;
1229
+ }
1230
+ setZoom(finalScale);
1231
+ };
1232
+
1147
1233
  /**
1148
1234
  * onTouchEnd for the stage body.
1149
1235
  * - Computes the horizontal/vertical delta against the stored touch origin.
@@ -1155,6 +1241,13 @@ export default function ProcessStepsPage({ match, CustomComponents = {}, ...prop
1155
1241
  * the user can continue tapping if they prefer.
1156
1242
  */
1157
1243
  const handleStageTouchEnd = (event) => {
1244
+ if (pinchRef.current) {
1245
+ // Pinch ends when a finger lifts; the leftover finger must not trigger a swipe.
1246
+ finishPinch();
1247
+ touchStartRef.current = null;
1248
+ return;
1249
+ }
1250
+
1158
1251
  const start = touchStartRef.current;
1159
1252
  touchStartRef.current = null;
1160
1253
 
@@ -1188,6 +1281,19 @@ export default function ProcessStepsPage({ match, CustomComponents = {}, ...prop
1188
1281
  }
1189
1282
  };
1190
1283
 
1284
+ /**
1285
+ * Zoom is per step and full-screen only: reset when leaving full screen or
1286
+ * moving to another step so the next view never opens pre-zoomed.
1287
+ */
1288
+ useEffect(() => {
1289
+ pinchRef.current = null;
1290
+ if (stageBodyRef.current) {
1291
+ stageBodyRef.current.scrollLeft = 0;
1292
+ stageBodyRef.current.scrollTop = 0;
1293
+ }
1294
+ setZoom(ZOOM_MIN);
1295
+ }, [isStepFullscreen, activeStep]);
1296
+
1191
1297
  /**
1192
1298
  * Cleanup any pending auto-hide timeout on unmount so the callback cannot
1193
1299
  * fire against a stale component and emit a React warning.
@@ -1446,9 +1552,12 @@ export default function ProcessStepsPage({ match, CustomComponents = {}, ...prop
1446
1552
  desktop event trees untouched.
1447
1553
  */}
1448
1554
  <div
1449
- className={`steps-stage-body${isTouchDevice ? ' is-swipe-enabled' : ''}`}
1555
+ ref={stageBodyRef}
1556
+ className={`steps-stage-body${isTouchDevice ? ' is-swipe-enabled' : ''}${isStepFullscreen && zoom > 1 ? ' is-zoomed' : ''}`}
1450
1557
  onTouchStart={isTouchDevice ? handleStageTouchStart : undefined}
1558
+ onTouchMove={isTouchDevice ? handleStageTouchMove : undefined}
1451
1559
  onTouchEnd={isTouchDevice ? handleStageTouchEnd : undefined}
1560
+ onTouchCancel={isTouchDevice ? handleStageTouchEnd : undefined}
1452
1561
  >
1453
1562
  {/*
1454
1563
  Floating prev/next arrow buttons.
@@ -1505,14 +1614,24 @@ export default function ProcessStepsPage({ match, CustomComponents = {}, ...prop
1505
1614
  </div> */}
1506
1615
 
1507
1616
  {/* <div className="steps-chat-step-component"> */}
1508
- {loading ? (
1509
- <div className="steps-chat-loading">
1510
- <div className="steps-chat-loading__card">
1511
- <Skeleton active title={{ width: '32%' }} paragraph={{ rows: 6, width: ['90%', '82%', '86%', '70%', '78%', '60%'] }} />
1617
+ {/*
1618
+ Zoom layer: scaled via transform only in full screen, so
1619
+ the normal view keeps its untouched layout.
1620
+ */}
1621
+ <div
1622
+ ref={zoomLayerRef}
1623
+ className="steps-zoom-layer"
1624
+ style={isStepFullscreen && zoom !== 1 ? { transform: `scale(${zoom})` } : undefined}
1625
+ >
1626
+ {loading ? (
1627
+ <div className="steps-chat-loading">
1628
+ <div className="steps-chat-loading__card">
1629
+ <Skeleton active title={{ width: '32%' }} paragraph={{ rows: 6, width: ['90%', '82%', '86%', '70%', '78%', '60%'] }} />
1630
+ </div>
1512
1631
  </div>
1513
- </div>
1514
- ) : null}
1515
- {!loading ? renderDynamicStep() : null}
1632
+ ) : null}
1633
+ {!loading ? renderDynamicStep() : null}
1634
+ </div>
1516
1635
  {/* </div> */}
1517
1636
  {/* </div> */}
1518
1637
  </div>
@@ -477,6 +477,23 @@
477
477
  .steps-stage-body.is-swipe-enabled {
478
478
  touch-action: pan-y;
479
479
  }
480
+ /* Zoomed in (full screen): scroll natively in both axes. */
481
+ .steps-stage-body.is-zoomed {
482
+ overflow: auto;
483
+ }
484
+ .steps-stage-body.is-swipe-enabled.is-zoomed {
485
+ touch-action: pan-x pan-y;
486
+ }
487
+
488
+ /* Wrapper for step content; transformed for full-screen zoom. */
489
+ .steps-zoom-layer {
490
+ flex: 1 1 auto;
491
+ min-height: 0;
492
+ min-width: 0;
493
+ display: flex;
494
+ flex-direction: column;
495
+ transform-origin: 0 0;
496
+ }
480
497
 
481
498
  /* ── Touch-device floating nav arrows ───────────────────── */
482
499
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ui-soxo-bootstrap-core",
3
- "version": "2.6.56",
3
+ "version": "2.6.61",
4
4
  "description": "All the Core Components for you to start",
5
5
  "keywords": [
6
6
  "all in one"