ui-soxo-bootstrap-core 2.6.15 → 2.6.17

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.
@@ -0,0 +1,294 @@
1
+ # 📦 NPM Release Workflow — Task-Based Development → Develop → Master
2
+
3
+ This guide describes the complete workflow for publishing **task-based development builds** and **stable releases** using GitHub Actions, Git tags, and npm versioning.
4
+
5
+ Incorrect versioning or incorrect tags will break the publish pipeline — follow the process exactly.
6
+
7
+ ---
8
+
9
+ # 📘 Table of Contents
10
+
11
+ - Overview
12
+ - Branch Strategy
13
+ - Task Branch Workflow
14
+ - Versioning Rules
15
+ - Development Release Workflow (`develop`)
16
+ - Promotion Workflow (Dev → Master)
17
+ - Publishing via GitHub Release UI
18
+ - How GitHub Action Detects Release Type
19
+ - Summary Table
20
+ - Common Mistakes & Fixes
21
+
22
+ ---
23
+
24
+ # 🧩 Overview
25
+
26
+ We maintain two release channels:
27
+
28
+ | Channel | Branch | Tag | Purpose |
29
+ | ------------------- | ------- | -------------- | ------------------- |
30
+ | Stable (`latest`) | master | `vX.Y.Z` | Production release |
31
+ | Development (`dev`) | develop | `vX.Y.Z-dev.N` | QA/internal testing |
32
+
33
+ Releases are triggered using Git tags:
34
+
35
+ ```
36
+ npm version
37
+ ```
38
+
39
+ ---
40
+
41
+ # 🌿 Branch Strategy
42
+
43
+ ## `master`
44
+
45
+ - Production-ready code
46
+ - Publishes **stable** builds
47
+ - Versions never include a `dev` suffix
48
+
49
+ ## `develop`
50
+
51
+ - Used for QA/internal testing
52
+ - Publishes **development** builds
53
+ - Versions always include `-dev.N`
54
+
55
+ ## Task Branches
56
+
57
+ - Create from `master`
58
+ - Naming: `task/<JIRA-ID>-<description>`
59
+ - After completing work, merge into `develop`
60
+
61
+ ---
62
+
63
+ # 🏗️ Task Branch Workflow
64
+
65
+ ### 1. Create a task branch
66
+
67
+ ```bash
68
+ git checkout master
69
+ git pull origin master
70
+ git checkout -b task/<JIRA-ID>-<description>
71
+ ```
72
+
73
+ ### 2. Work on the task
74
+
75
+ ```bash
76
+ git add .
77
+ git commit -m "TASK-123: Implement XYZ"
78
+ ```
79
+
80
+ ### 3. Merge into develop
81
+
82
+ ```bash
83
+ git checkout develop
84
+ git pull origin develop
85
+ git merge task/<JIRA-ID>-<description>
86
+ ```
87
+
88
+ ---
89
+
90
+ # 🔧 Versioning Rules
91
+
92
+ ## Development Versions
93
+
94
+ ```
95
+ X.Y.Z-dev.N
96
+ ```
97
+
98
+ Example:
99
+
100
+ ```
101
+ 2.4.30-dev.0
102
+ ```
103
+
104
+ ## Stable Versions
105
+
106
+ ```
107
+ X.Y.Z
108
+ ```
109
+
110
+ ## Rules
111
+
112
+ - Each version must be unique
113
+ - Dev versions do **not** convert into stable versions
114
+ - Git tag must exactly match `package.json` version
115
+
116
+ ---
117
+
118
+ # 🟦 Development Release Workflow (`develop`)
119
+
120
+ ### 1. Switch to develop
121
+
122
+ ```bash
123
+ git checkout develop
124
+ git pull origin develop
125
+ ```
126
+
127
+ ### 2. Create a new development version
128
+
129
+ ```bash
130
+ npm version prerelease --preid=dev
131
+ ```
132
+
133
+ ### 3. Push commit + tag
134
+
135
+ ```bash
136
+ git push --follow-tags
137
+ ```
138
+
139
+ ### 4. GitHub Action publishes dev build
140
+
141
+ ```
142
+ npm publish --tag dev
143
+ ```
144
+
145
+ ### 5. Install the dev build
146
+
147
+ ```bash
148
+ npm install ui-soxo-bootstrap-core@dev
149
+ ```
150
+
151
+ ---
152
+
153
+ # 🟩 Promotion Workflow (Dev → Master)
154
+
155
+ ### 1. Switch to master
156
+
157
+ ```bash
158
+ git checkout master
159
+ git pull origin master
160
+ ```
161
+
162
+ ### 2. Merge develop into master
163
+
164
+ ```bash
165
+ git merge develop
166
+ ```
167
+
168
+ ### 3. Bump the stable version
169
+
170
+ ```bash
171
+ npm version patch
172
+ # or
173
+ npm version minor
174
+ # or
175
+ npm version major
176
+ ```
177
+
178
+ ### 4. Push with tags
179
+
180
+ ```bash
181
+ git push --follow-tags
182
+ ```
183
+
184
+ ### 5. GitHub Action publishes stable build
185
+
186
+ ```
187
+ npm publish
188
+ ```
189
+
190
+ ### 6. Install stable version
191
+
192
+ ```bash
193
+ npm install ui-soxo-bootstrap-core
194
+ # or
195
+ npm install ui-soxo-bootstrap-core@<version>
196
+ ```
197
+
198
+ ---
199
+
200
+ # 🟧 Publishing via GitHub Release UI
201
+
202
+ ### Important
203
+
204
+ **The tag must match the `package.json` version exactly**, including the `v` prefix.
205
+
206
+ Example:
207
+
208
+ ```
209
+ package.json: "version": "2.4.31-dev.0"
210
+ GitHub Tag: v2.4.31-dev.0
211
+ ```
212
+
213
+ ---
214
+
215
+ ## Steps
216
+
217
+ ### 1. Ensure `develop` branch is up to date
218
+
219
+ Push your changes.
220
+
221
+ ### 2. Ensure version contains a dev suffix
222
+
223
+ If not:
224
+
225
+ ```bash
226
+ npm version prerelease --preid=dev
227
+ git push --follow-tags
228
+ ```
229
+
230
+ ### 3. Open GitHub → Releases → Draft a new release
231
+
232
+ ### 4. Create tag matching the version
233
+
234
+ Example:
235
+
236
+ ```
237
+ v2.4.31-dev.0
238
+ ```
239
+
240
+ ### 5. Select target branch = `develop`
241
+
242
+ ### 6. Title = tag version
243
+
244
+ Description is optional.
245
+
246
+ ### 7. Publish the release
247
+
248
+ Triggers:
249
+
250
+ ```
251
+ npm publish --tag dev
252
+ ```
253
+
254
+ ---
255
+
256
+ # ⚙️ How GitHub Action Detects Release Type
257
+
258
+ If version contains `dev`:
259
+
260
+ ```
261
+ npm publish --tag dev
262
+ ```
263
+
264
+ Otherwise:
265
+
266
+ ```
267
+ npm publish
268
+ ```
269
+
270
+ ---
271
+
272
+ # 📊 Summary Table
273
+
274
+ | Step | Branch | Command | Tag | Publish Type |
275
+ | ---------------------- | ------- | ------------------------------------ | -------------- | ------------ |
276
+ | Create task branch | master | `git checkout -b task/<ID>` | — | — |
277
+ | Merge task → develop | develop | `git merge task/<ID>` | — | — |
278
+ | Dev version | develop | `npm version prerelease --preid=dev` | `vX.Y.Z-dev.N` | dev |
279
+ | Publish dev build | develop | `git push --follow-tags` | — | dev |
280
+ | Merge develop → master | master | `git merge develop` | — | — |
281
+ | Stable version | master | `npm version patch/minor/major` | `vX.Y.Z` | latest |
282
+ | Publish stable build | master | `git push --follow-tags` | — | latest |
283
+
284
+ ---
285
+
286
+ # ⚠️ Common Mistakes & Fixes
287
+
288
+ | Mistake | Issue | Fix |
289
+ | ------------------------- | ------------------ | ---------------------------- |
290
+ | Tag doesn't match version | Publish fails | Always use `npm version` |
291
+ | Manual tag creation | Version mismatch | Avoid manual tagging |
292
+ | Dev publish from master | Wrong channel | Verify branch before tagging |
293
+ | Not pushing tags | Workflow won’t run | Use `git push --follow-tags` |
294
+ | Merging incomplete tasks | Breaks dev builds | Test before merging |
@@ -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
+ }
@@ -1,5 +1,5 @@
1
1
  import React, { useState, useCallback, useEffect } from 'react';
2
- import { Space, Popconfirm, Input, Drawer, Skeleton, Collapse, message, Tag } from 'antd';
2
+ import { Space, Popconfirm, Input, Drawer, Skeleton, Collapse, message, Tag, Empty } from 'antd';
3
3
  import { ReloadOutlined, DeleteOutlined, EditOutlined, PlusCircleFilled, CopyOutlined } from '@ant-design/icons';
4
4
  import { Button, Card, Switch, DraggableWrapper } from '../../../../lib';
5
5
  // for draggable menu list import { DndProvider } from "react-dnd";
@@ -203,7 +203,7 @@ const MenuLists = ({ model, match, relativeAdd = false, additional_queries = [],
203
203
  model.delete(rec).then(loadMenus);
204
204
  };
205
205
 
206
- const filtered = records.filter((r) => r.name?.toUpperCase().includes(query.toUpperCase()));
206
+ const filtered = records.filter((r) => r.caption?.toUpperCase().includes(query.toUpperCase()));
207
207
 
208
208
  const visibleItems = filtered;
209
209
 
@@ -362,50 +362,53 @@ const MenuLists = ({ model, match, relativeAdd = false, additional_queries = [],
362
362
  <Card>
363
363
  <DndProvider backend={HTML5Backend}>
364
364
  <Collapse accordion>
365
- {visibleItems.map((item, index) => (
366
- <Panel
367
- key={item.id}
368
- header={
369
- <DraggableWrapper
370
- id={item.id}
371
- index={index}
372
- movePanel={movePanel}
373
- item={item}
374
- dragEnabled={dragMode}
375
- level={1}
376
- parentId={null}
377
- onCrossLevelMove={handleCrossLevelMove}
378
- canAcceptChildren={true}
379
- />
380
- }
381
- // only show arrow if sub_menus exist
382
- showArrow={item.sub_menus && item.sub_menus.length > 0}
383
- // disable panel
384
- // collapsible={item.sub_menus && item.sub_menus.length > 0 ? 'header' : 'disabled'}
385
- extra={panelActions(item, model, setSelectedRecord, setDrawerTitle, setDrawerVisible, deleteRecord)}
386
- >
387
- {item.sub_menus && item.sub_menus.length > 0 && (
388
- <NestedMenu
389
- parentId={item.id}
390
- step={item.step + 1}
391
- items={item.sub_menus || []}
392
- model={model}
393
- dragMode={dragMode}
394
- setSelectedRecord={setSelectedRecord}
395
- setDrawerTitle={setDrawerTitle}
396
- setDrawerVisible={setDrawerVisible}
397
- deleteRecord={deleteRecord}
398
- level={2}
399
- onCrossLevelMove={handleCrossLevelMove}
400
- onChange={(subMenus) => {
401
- const updated = records.map((r) => (r.id === item.id ? { ...r, sub_menus: subMenus } : r));
402
- setRecords(updated);
403
- setOrderChanged(true);
404
- }}
405
- />
406
- )}
407
- </Panel>
408
- ))}
365
+ {visibleItems && visibleItems.length > 0 ? (
366
+ visibleItems.map((item, index) => (
367
+ <Panel
368
+ key={item.id}
369
+ header={
370
+ <DraggableWrapper
371
+ id={item.id}
372
+ index={index}
373
+ movePanel={movePanel}
374
+ item={item}
375
+ dragEnabled={dragMode}
376
+ level={1}
377
+ parentId={null}
378
+ onCrossLevelMove={handleCrossLevelMove}
379
+ canAcceptChildren={true}
380
+ />
381
+ }
382
+ showArrow={item.sub_menus && item.sub_menus.length > 0}
383
+ extra={panelActions(item, model, setSelectedRecord, setDrawerTitle, setDrawerVisible, deleteRecord)}
384
+ >
385
+ {item.sub_menus && item.sub_menus.length > 0 && (
386
+ <NestedMenu
387
+ parentId={item.id}
388
+ step={item.step + 1}
389
+ items={item.sub_menus || []}
390
+ model={model}
391
+ dragMode={dragMode}
392
+ setSelectedRecord={setSelectedRecord}
393
+ setDrawerTitle={setDrawerTitle}
394
+ setDrawerVisible={setDrawerVisible}
395
+ deleteRecord={deleteRecord}
396
+ level={2}
397
+ onCrossLevelMove={handleCrossLevelMove}
398
+ onChange={(subMenus) => {
399
+ const updated = records.map((r) => (r.id === item.id ? { ...r, sub_menus: subMenus } : r));
400
+ setRecords(updated);
401
+ setOrderChanged(true);
402
+ }}
403
+ />
404
+ )}
405
+ </Panel>
406
+ ))
407
+ ) : (
408
+ <div style={{ textAlign: 'center', padding: '40px 0' }}>
409
+ <Empty description="No Menu Items" />
410
+ </div>
411
+ )}
409
412
  </Collapse>
410
413
  </DndProvider>
411
414
  </Card>
@@ -27,9 +27,13 @@ function getExportDefinition(entry, record) {
27
27
  * @param {boolean} root0.isFixedIndex
28
28
  * @param {Object} root0.CustomComponents
29
29
  * @param {Function} root0.refresh
30
+ * @param {Object} [root0.otherDetails={}] - Optional details from the report configuration.
31
+ * @param {boolean} [root0.otherDetails.isFilterEnabled] - Fallback to enable filtering on all columns.
32
+ * @param {boolean} [root.otherDetails.isSortingEnabled] - Fallback to enable sorting on all columns.
33
+ * @param {boolean} [root0.otherDetails.isHeaderWrapEnabled] - Fallback to enable header text wrapping for all columns.
30
34
  * @returns {Array}
31
35
  */
32
- export default function buildDisplayColumns({ columns = [], patients = [], isFixedIndex, CustomComponents, refresh }) {
36
+ export default function buildDisplayColumns({ columns = [], patients = [], isFixedIndex, CustomComponents, refresh, otherDetails = {} }) {
33
37
  const displayColumns = [
34
38
  {
35
39
  title: '#',
@@ -42,6 +46,11 @@ export default function buildDisplayColumns({ columns = [], patients = [], isFix
42
46
  ];
43
47
 
44
48
  columns.forEach((entry, index) => {
49
+ const isFilterEnabled = entry.isFilterEnabled || otherDetails?.isFilterEnabled;
50
+ const isSortingEnabled = entry.isSortingEnabled || otherDetails?.isSortingEnabled;
51
+ const isHeaderWrapEnabled = otherDetails?.isHeaderWrapEnabled;
52
+
53
+ const titleStyle = isHeaderWrapEnabled ? { whiteSpace: 'pre-wrap', overflowWrap: 'break-word' } : {};
45
54
  displayColumns.push({
46
55
  render: (record) =>
47
56
  renderDisplayCell({
@@ -52,20 +61,26 @@ export default function buildDisplayColumns({ columns = [], patients = [], isFix
52
61
  }),
53
62
  field: entry.field,
54
63
  title: (
55
- <Tooltip title={entry.tooltip || entry.title}>
56
- <span>{entry.title}</span>
64
+ <Tooltip
65
+ title={entry.tooltip || entry.title}
66
+ overlayInnerStyle={{
67
+ whiteSpace: 'normal',
68
+ overflowWrap: 'break-word',
69
+ }}
70
+ >
71
+ <span style={titleStyle}>{entry.title}</span>
57
72
  </Tooltip>
58
73
  ),
59
74
  key: entry.field || `display_column_${index}`,
60
75
  width: entry.width ? parseInt(entry.width, 10) : 160,
61
76
  fixed: entry.isFixedColumn ? entry.isFixedColumn : null,
62
77
  filters:
63
- entry.isFilterEnabled && Array.isArray(patients)
78
+ isFilterEnabled && Array.isArray(patients)
64
79
  ? [...new Set(patients.map((item) => item[entry.field]).filter(Boolean))].map((value) => ({ text: value, value }))
65
80
  : null,
66
- onFilter: entry.isFilterEnabled ? (value, record) => record[entry.field] === value : null,
67
- sorter: entry.isSortingEnabled ? (a, b) => String(a[entry.field]).localeCompare(String(b[entry.field])) : null,
68
- filterSearch: entry.isFilterEnabled ? entry.isFilterEnabled : false,
81
+ onFilter: isFilterEnabled ? (value, record) => record[entry.field] === value : null,
82
+ sorter: isSortingEnabled ? (a, b) => String(a[entry.field]).localeCompare(String(b[entry.field])) : null,
83
+ filterSearch: isFilterEnabled ? isFilterEnabled : false,
69
84
  exportDefinition: (record) => getExportDefinition(entry, record),
70
85
  align: entry.type === 'number' ? 'right' : 'left',
71
86
  });
@@ -299,7 +299,7 @@ export default function ReportingDashboard({
299
299
  // Update patients
300
300
  setPatients(resultDetails || []);
301
301
  console.log(parsedColumns);
302
-
302
+
303
303
  // Check if columns are not yet defined
304
304
  if (parsedColumns.length === 0 && resultDetails.length > 0) {
305
305
  // Create columns dynamically from resultDetails keys
@@ -599,6 +599,7 @@ function GuestList({
599
599
 
600
600
  const { isMobile, dispatch } = useContext(GlobalContext);
601
601
  const [single, setSingle] = useState({});
602
+ const otherDetails = config.other_details1 ? JSON.parse(config.other_details1) : {};
602
603
 
603
604
  // const [view, setView] = useState(isMobile ? true : false); //Need to check this condition
604
605
  const cols = buildDisplayColumns({
@@ -607,6 +608,7 @@ function GuestList({
607
608
  isFixedIndex,
608
609
  CustomComponents,
609
610
  refresh,
611
+ otherDetails
610
612
  });
611
613
 
612
614
  /**
@@ -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.17",
4
4
  "description": "All the Core Components for you to start",
5
5
  "keywords": [
6
6
  "all in one"