ui-soxo-bootstrap-core 2.6.15 → 2.6.16

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.
@@ -1,4 +1,4 @@
1
- import React, { useState, useEffect, useContext } from 'react';
1
+ import React, { useState, useEffect, useContext, useRef } from 'react';
2
2
 
3
3
  import { Route, Switch } from 'react-router-dom';
4
4
 
@@ -18,13 +18,33 @@ import PropTypes from 'prop-types';
18
18
 
19
19
  import { MenusAPI, CoreScripts } from '../../models';
20
20
 
21
+ const motivatingMessages = [
22
+ 'Setting things up for a great start...',
23
+ 'Good things are loading. Stay with us.',
24
+ 'Almost there. Preparing your workspace.',
25
+ 'You are one moment away from your dashboard.',
26
+ 'Getting everything ready for you.',
27
+ 'Great care takes a second. Loading now.',
28
+ ];
29
+
30
+ function getRandomMessage(previousMessage = '') {
31
+ const options = motivatingMessages.filter((message) => message !== previousMessage);
32
+
33
+ if (!options.length) {
34
+ return motivatingMessages[0];
35
+ }
36
+
37
+ const randomIndex = Math.floor(Math.random() * options.length);
38
+ return options[randomIndex];
39
+ }
40
+
21
41
  /**
22
42
  * Landing API
23
43
  *
24
44
  * @param {*} param0
25
45
  * @returns
26
46
  */
27
- export default function LandingApi({ history, CustomComponents, CustomModels, appSettings, ...props }) {
47
+ export default function LandingApi({ history, CustomComponents, CustomModels, appSettings, transitionPending = false, onHomeReady, ...props }) {
28
48
  const [loader, setLoader] = useState(false);
29
49
 
30
50
  // const [modules, setModules] = useState([]);
@@ -34,8 +54,11 @@ export default function LandingApi({ history, CustomComponents, CustomModels, ap
34
54
  const { dispatch, user = {} } = useContext(GlobalContext);
35
55
 
36
56
  const [allModules, setAllModules] = useState();
57
+ const homeReadyNotifiedRef = useRef(false);
58
+ const messageIntervalRef = useRef(null);
37
59
 
38
60
  const [meta, setMeta] = useState({});
61
+ const [loadingMessage, setLoadingMessage] = useState('');
39
62
 
40
63
  // const [reports, setReports] = useState([]);
41
64
 
@@ -70,6 +93,46 @@ export default function LandingApi({ history, CustomComponents, CustomModels, ap
70
93
  initializeUserMenus();
71
94
  }, []);
72
95
 
96
+ useEffect(() => {
97
+ if (!transitionPending) {
98
+ homeReadyNotifiedRef.current = false;
99
+ return;
100
+ }
101
+
102
+ if (!loader && allModules && !homeReadyNotifiedRef.current) {
103
+ homeReadyNotifiedRef.current = true;
104
+ if (typeof onHomeReady === 'function') {
105
+ onHomeReady();
106
+ }
107
+ }
108
+ }, [transitionPending, loader, allModules, onHomeReady]);
109
+
110
+ useEffect(() => {
111
+ if (!loader) {
112
+ setLoadingMessage('');
113
+
114
+ if (messageIntervalRef.current) {
115
+ clearInterval(messageIntervalRef.current);
116
+ messageIntervalRef.current = null;
117
+ }
118
+
119
+ return;
120
+ }
121
+
122
+ setLoadingMessage((previousMessage) => getRandomMessage(previousMessage));
123
+
124
+ messageIntervalRef.current = setInterval(() => {
125
+ setLoadingMessage((previousMessage) => getRandomMessage(previousMessage));
126
+ }, 2200);
127
+
128
+ return () => {
129
+ if (messageIntervalRef.current) {
130
+ clearInterval(messageIntervalRef.current);
131
+ messageIntervalRef.current = null;
132
+ }
133
+ };
134
+ }, [loader]);
135
+
73
136
  /**
74
137
  * Initialize the user menus
75
138
  */
@@ -245,6 +308,7 @@ export default function LandingApi({ history, CustomComponents, CustomModels, ap
245
308
  <Card className="skeleton-card">
246
309
  <div className="skeleton-wrapper">
247
310
  <Skeleton paragraph={{ rows: 4 }} />
311
+ <p className="motivating-text">{loadingMessage}</p>
248
312
  </div>
249
313
  </Card>
250
314
  ) : (
@@ -11,9 +11,31 @@
11
11
  }
12
12
 
13
13
  .skeleton-wrapper {
14
+ .motivating-text {
15
+ margin-top: 14px;
16
+ margin-bottom: 4px;
17
+ font-size: 14px;
18
+ line-height: 20px;
19
+ text-align: center;
20
+ color: #5e6d86;
21
+ font-weight: 500;
22
+ animation: skeletonTextFade 0.4s ease-in-out;
23
+ }
14
24
  }
15
25
 
16
26
  .wrapper-loader {
17
27
  margin: 20px;
18
28
  }
19
29
  }
30
+
31
+ @keyframes skeletonTextFade {
32
+ from {
33
+ opacity: 0;
34
+ transform: translateY(2px);
35
+ }
36
+
37
+ to {
38
+ opacity: 1;
39
+ transform: translateY(0);
40
+ }
41
+ }
@@ -13,6 +13,8 @@ export default function ActionButtons({
13
13
  steps,
14
14
  activeStep,
15
15
  isStepCompleted,
16
+ isFullscreen,
17
+ onToggleFullscreen,
16
18
  renderDynamicComponent,
17
19
  handlePrevious,
18
20
  handleNext,
@@ -35,6 +37,10 @@ export default function ActionButtons({
35
37
  Back
36
38
  </Button>
37
39
 
40
+ <Button type="default" onClick={onToggleFullscreen}>
41
+ {isFullscreen ? 'Exit Full Screen' : 'Switch to Full Screen'}
42
+ </Button>
43
+
38
44
  {/* Skip button */}
39
45
  {steps.length > 0 && steps[activeStep]?.allow_skip === 'Y' && (
40
46
  <Button type="default" onClick={handleSkip} disabled={activeStep === steps.length - 1}>
@@ -11,6 +11,7 @@
11
11
  overflow-y: auto;
12
12
  overflow-x: hidden;
13
13
  overscroll-behavior: contain;
14
+
14
15
  padding-bottom: 8px;
15
16
  }
16
17
 
@@ -27,7 +28,6 @@
27
28
  border-top: 1px solid #f0f0f0;
28
29
  box-shadow: 0 -2px 10px rgba(15, 23, 42, 0.04);
29
30
 
30
-
31
31
  width: 61%;
32
32
  padding: 10px;
33
33
  position: fixed;
@@ -37,9 +37,15 @@
37
37
  display: flex;
38
38
  border-radius: 4px;
39
39
  box-shadow: -1px -4px 10px 2px #f7f7f76e;
40
-
40
+ flex-wrap: wrap;
41
41
 
42
42
  .ant-btn {
43
43
  border-radius: 4px;
44
44
  }
45
- }
45
+ }
46
+
47
+ .process-steps-page.is-fullscreen .action-buttons-container {
48
+ width: calc(100% - 24px);
49
+ left: 12px;
50
+ right: 12px;
51
+ }
@@ -8,7 +8,7 @@
8
8
  * - Handles process submission and optional chaining to the next process.
9
9
  * - Provides a collapsible timeline view and action controls.
10
10
  */
11
- import React, { useEffect, useState } from 'react';
11
+ import React, { useEffect, useRef, useState } from 'react';
12
12
  import { Row, Col, Empty } from 'antd';
13
13
  import { Card } from './../../lib';
14
14
  import * as genericComponents from './../../lib';
@@ -36,8 +36,11 @@ export default function ProcessStepsPage({ match, CustomComponents = {}, ...prop
36
36
  const [timelineCollapsed, setTimelineCollapsed] = useState(false);
37
37
  const [showExternalWindow, setShowExternalWindow] = useState(false);
38
38
  const [externalWin, setExternalWin] = useState(null);
39
+ const [isFullscreen, setIsFullscreen] = useState(false);
40
+ const processStepsRef = useRef(null);
39
41
 
40
42
  const urlParams = Location.search();
43
+ const isConsultationMode = String(urlParams?.consultation).toLowerCase() === 'true';
41
44
  let processId = urlParams.processId;
42
45
  const [currentProcessId, setCurrentProcessId] = useState(processId);
43
46
  // Load process details based on the current process ID
@@ -271,13 +274,49 @@ export default function ProcessStepsPage({ match, CustomComponents = {}, ...prop
271
274
  };
272
275
  }, [activeStep, steps, externalWin]);
273
276
 
277
+ useEffect(() => {
278
+ const syncFullscreenState = () => {
279
+ setIsFullscreen(document.fullscreenElement === processStepsRef.current);
280
+ };
281
+
282
+ document.addEventListener('fullscreenchange', syncFullscreenState);
283
+ syncFullscreenState();
284
+
285
+ return () => {
286
+ document.removeEventListener('fullscreenchange', syncFullscreenState);
287
+ };
288
+ }, []);
289
+
290
+ const handleToggleFullscreen = async () => {
291
+ const element = processStepsRef.current;
292
+ if (!element) return;
293
+
294
+ try {
295
+ if (document.fullscreenElement === element) {
296
+ if (document.exitFullscreen) {
297
+ await document.exitFullscreen();
298
+ }
299
+ return;
300
+ }
301
+
302
+ if (element.requestFullscreen) {
303
+ await element.requestFullscreen();
304
+ }
305
+ } catch (error) {
306
+ console.error('Unable to toggle full screen for steps page:', error);
307
+ }
308
+ };
309
+
274
310
  /**
275
311
  * Renders the main process UI including timeline, step details,
276
312
  * and action buttons. This content is reused in both normal view
277
313
  * and external window view.
278
314
  */
279
- const renderContent = () => (
280
- <div className="process-steps-page">
315
+ const renderContent = (attachRef = true) => (
316
+ <div
317
+ ref={attachRef ? processStepsRef : null}
318
+ className={`process-steps-page ${isConsultationMode ? 'consultation-mode' : ''} ${isFullscreen ? 'is-fullscreen' : ''}`}
319
+ >
281
320
  <Card className="process-steps-card">
282
321
  <Row gutter={20} className="process-steps-row" align="stretch">
283
322
  <Col xs={24} sm={24} lg={timelineCollapsed ? 2 : 6} className="process-steps-timeline-col">
@@ -302,6 +341,8 @@ export default function ProcessStepsPage({ match, CustomComponents = {}, ...prop
302
341
  steps={steps}
303
342
  activeStep={activeStep}
304
343
  isStepCompleted={isStepCompleted}
344
+ isFullscreen={isFullscreen}
345
+ onToggleFullscreen={handleToggleFullscreen}
305
346
  renderDynamicComponent={DynamicComponent}
306
347
  handlePrevious={handlePrevious}
307
348
  handleNext={handleNext}
@@ -335,9 +376,9 @@ export default function ProcessStepsPage({ match, CustomComponents = {}, ...prop
335
376
  width={props.ExternalWindowWidth || 1000}
336
377
  height={props.ExternalWindowHeight || 1000}
337
378
  >
338
- {renderContent()}
379
+ {renderContent(false)}
339
380
  </ExternalWindow>
340
- {renderContent()}
381
+ {renderContent(true)}
341
382
  </>
342
383
  );
343
384
  }
@@ -3,6 +3,25 @@
3
3
  display: flex;
4
4
  flex-direction: column;
5
5
  overflow: hidden;
6
+
7
+ &.consultation-mode {
8
+ background: linear-gradient(180deg, #c8d2d6 0%, #d5d7d1 21%, #d9d8c6 40%, #c6cebe 58%, #a8c0c0 71%, #6e9fbb 84%, #367495 93%, #0a4d6e 100%);
9
+ padding: 12px;
10
+ }
11
+
12
+ &.is-fullscreen {
13
+ padding: 12px;
14
+ }
15
+ }
16
+
17
+ .process-steps-page.consultation-mode .process-steps-card {
18
+ border: none;
19
+ background: transparent;
20
+ box-shadow: none;
21
+ }
22
+
23
+ .process-steps-page.consultation-mode .process-steps-card > .ant-card-body {
24
+ padding: 10px;
6
25
  }
7
26
 
8
27
  .process-steps-card {
@@ -49,11 +68,11 @@
49
68
  flex: 1;
50
69
  max-height: 100%;
51
70
  overflow: hidden;
52
- background: #fff;
53
- border: 1px solid #e5ebf3;
54
- border-radius: 14px;
55
- box-shadow: 0 8px 20px rgba(15, 23, 42, 0.08);
56
- padding: 14px 16px;
71
+ // background: #fff;
72
+ // border: 1px solid #e5ebf3;
73
+ // border-radius: 14px;
74
+ // box-shadow: 0 8px 20px rgba(15, 23, 42, 0.08);
75
+ // padding: 14px 16px;
57
76
  position: relative;
58
77
  z-index: 2;
59
78
  }
@@ -84,8 +103,18 @@
84
103
  height: 100%;
85
104
  border: none !important;
86
105
  border-radius: 12px;
87
- background: linear-gradient(180deg, #f4f7fc 0%, #edf2f9 100%);
88
- box-shadow: inset 0 1px 2px rgba(255, 255, 255, 0.8), inset 0 8px 16px rgba(15, 23, 42, 0.08), inset 0 -2px 8px rgba(15, 23, 42, 0.04);
106
+ background: linear-gradient(135deg, color-mix(in srgb, var(--accent-color) 15%, var(--surface-color)), var(--surface-color));
107
+
108
+ border: 1px solid color-mix(in srgb, var(--accent-color) 30%, transparent);
109
+
110
+ box-shadow: 0 8px 20px color-mix(in srgb, var(--accent-color) 20%, transparent);
111
+
112
+ transition: all 0.3s ease;
113
+ // background: linear-gradient(180deg, #f4f7fc 0%, #edf2f9 100%);
114
+ // box-shadow:
115
+ // inset 0 1px 2px rgba(255, 255, 255, 0.8),
116
+ // inset 0 8px 16px rgba(15, 23, 42, 0.08),
117
+ // inset 0 -2px 8px rgba(15, 23, 42, 0.04);
89
118
  }
90
119
 
91
120
  .timeline-card .ant-card-body {
@@ -108,6 +137,35 @@
108
137
  transition: all 0.3s ease;
109
138
  height: 100%;
110
139
  overflow-y: auto;
140
+
141
+ scrollbar-width: thin; /* Firefox */
142
+ scrollbar-color: rgba(0, 0, 0, 0.25) transparent;
143
+
144
+ &::-webkit-scrollbar-thumb {
145
+ background: rgba(0, 0, 0, 0);
146
+ }
147
+
148
+ &:hover::-webkit-scrollbar-thumb {
149
+ background: rgba(0, 0, 0, 0.25);
150
+ }
151
+
152
+ &::-webkit-scrollbar {
153
+ width: 6px;
154
+ height: 6px;
155
+ }
156
+
157
+ &::-webkit-scrollbar-track {
158
+ background: transparent;
159
+ }
160
+
161
+ &::-webkit-scrollbar-thumb {
162
+ background: rgba(0, 0, 0, 0.25);
163
+ border-radius: 10px;
164
+ }
165
+
166
+ &::-webkit-scrollbar-thumb:hover {
167
+ background: rgba(0, 0, 0, 0.4);
168
+ }
111
169
  padding-right: 4px;
112
170
  }
113
171
 
@@ -119,7 +177,10 @@
119
177
  border-radius: 10px;
120
178
  background: #fff;
121
179
  padding: 10px 12px;
122
- transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
180
+ transition:
181
+ border-color 0.2s ease,
182
+ box-shadow 0.2s ease,
183
+ transform 0.2s ease;
123
184
  }
124
185
 
125
186
  .timeline-step:hover {
@@ -233,7 +294,11 @@
233
294
  color: #1f3f74;
234
295
  box-shadow: 0 6px 14px rgba(15, 23, 42, 0.2);
235
296
  z-index: 40;
236
- transition: background-color 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
297
+ transition:
298
+ background-color 0.2s ease,
299
+ border-color 0.2s ease,
300
+ box-shadow 0.2s ease,
301
+ transform 0.2s ease;
237
302
 
238
303
  &:hover {
239
304
  background: #f0f6ff;
@@ -265,9 +330,8 @@
265
330
  MOBILE & TABLET VIEW FIXES
266
331
  ============================ */
267
332
 
268
-
269
-
270
- @media (max-width: 992px) { // iPad & tablets
333
+ @media (max-width: 992px) {
334
+ // iPad & tablets
271
335
  .process-steps-page,
272
336
  .process-steps-row {
273
337
  min-height: auto;
@@ -275,6 +339,10 @@
275
339
  overflow: visible;
276
340
  }
277
341
 
342
+ .process-steps-page.consultation-mode {
343
+ padding: 8px;
344
+ }
345
+
278
346
  .process-steps-card,
279
347
  .process-steps-card .ant-card-body,
280
348
  .process-steps-content-col {
@@ -327,7 +395,8 @@
327
395
  }
328
396
  }
329
397
 
330
- @media (max-width: 768px) { // mobile screens
398
+ @media (max-width: 768px) {
399
+ // mobile screens
331
400
  .timeline-sidebar {
332
401
  gap: 8px;
333
402
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ui-soxo-bootstrap-core",
3
- "version": "2.6.15",
3
+ "version": "2.6.16",
4
4
  "description": "All the Core Components for you to start",
5
5
  "keywords": [
6
6
  "all in one"