ui-soxo-bootstrap-core 2.5.0 → 2.5.1

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.
@@ -18,64 +18,69 @@ import { message } from 'antd';
18
18
  * @param {Function} onMaximize - Callback when window is maximized
19
19
  */
20
20
  export function ExternalWindow({
21
- children,
22
- onClose,
23
- title = 'New Window',
24
- width = 600,
25
- height = 400,
26
- left,
27
- top,
28
- copyStyles = true,
29
- centerScreen = false,
30
- shortcuts = {},
31
- onMinimize,
32
- onMaximize
21
+ children,
22
+ onClose,
23
+ onWindowReady,
24
+ title = 'New Window',
25
+ width = 600,
26
+ height = 400,
27
+ left,
28
+ top,
29
+ copyStyles = true,
30
+ centerScreen = false,
31
+ shortcuts = {},
32
+ onMinimize,
33
+ onMaximize,
33
34
  }) {
34
- const [container, setContainer] = useState(null);
35
- const windowRef = useRef(null);
36
-
37
- // Default shortcuts
38
- const defaultShortcuts = useMemo(() => ({
39
- close: 'Escape',
40
- focus: 'Ctrl+Shift+F',
41
- ...shortcuts
42
- }), [shortcuts]);
43
-
44
- // Calculate window position
45
- const { posX, posY } = useMemo(() => {
46
- let posX = left;
47
- let posY = top;
48
-
49
- if (centerScreen) {
50
- const screenWidth = window.screen.availWidth;
51
- const screenHeight = window.screen.availHeight;
52
-
53
- if (centerScreen === 'both' || centerScreen === 'horizontal') {
54
- posX = (screenWidth - width) / 2;
55
- }
56
- if (centerScreen === 'both' || centerScreen === 'vertical') {
57
- posY = (screenHeight - height) / 2;
58
- }
59
- }
60
-
61
- return {
62
- posX: posX ?? 200,
63
- posY: posY ?? 200
64
- };
65
- }, [left, top, width, height, centerScreen]);
66
-
67
- const windowFeatures = useMemo(() =>
68
- `width=${width},height=${height},left=${posX},top=${posY},resizable=yes,scrollbars=yes`,
69
- [width, height, posX, posY]
70
- );
71
-
72
- // Initialize window document
73
- const initializeWindow = useCallback((win) => {
74
- const doc = win.document;
75
-
76
- // Write minimal HTML structure (like your working example)
77
- doc.open();
78
- doc.write(`
35
+ const [container, setContainer] = useState(null);
36
+ const windowRef = useRef(null);
37
+
38
+ // Default shortcuts
39
+ const defaultShortcuts = useMemo(
40
+ () => ({
41
+ close: 'Escape',
42
+ focus: 'Ctrl+Shift+F',
43
+ ...shortcuts,
44
+ }),
45
+ [shortcuts]
46
+ );
47
+
48
+ // Calculate window position
49
+ const { posX, posY } = useMemo(() => {
50
+ let posX = left;
51
+ let posY = top;
52
+
53
+ if (centerScreen) {
54
+ const screenWidth = window.screen.availWidth;
55
+ const screenHeight = window.screen.availHeight;
56
+
57
+ if (centerScreen === 'both' || centerScreen === 'horizontal') {
58
+ posX = (screenWidth - width) / 2;
59
+ }
60
+ if (centerScreen === 'both' || centerScreen === 'vertical') {
61
+ posY = (screenHeight - height) / 2;
62
+ }
63
+ }
64
+
65
+ return {
66
+ posX: posX ?? 200,
67
+ posY: posY ?? 200,
68
+ };
69
+ }, [left, top, width, height, centerScreen]);
70
+
71
+ const windowFeatures = useMemo(
72
+ () => `width=${width},height=${height},left=${posX},top=${posY},resizable=yes,scrollbars=yes`,
73
+ [width, height, posX, posY]
74
+ );
75
+
76
+ // Initialize window document
77
+ const initializeWindow = useCallback(
78
+ (win) => {
79
+ const doc = win.document;
80
+
81
+ // Write minimal HTML structure (like your working example)
82
+ doc.open();
83
+ doc.write(`
79
84
  <!DOCTYPE html>
80
85
  <html>
81
86
  <head>
@@ -112,114 +117,120 @@ export function ExternalWindow({
112
117
  </body>
113
118
  </html>
114
119
  `);
115
- doc.close();
116
-
117
- return doc.getElementById('root');
118
- }, [title]);
119
-
120
- // Copy styles from parent window
121
- const copyStylesToWindow = useCallback((targetWindow) => {
122
- if (!copyStyles) return;
123
-
124
- const fragment = targetWindow.document.createDocumentFragment();
125
-
126
- // Copy all stylesheets from parent
127
- Array.from(document.styleSheets).forEach(sheet => {
128
- try {
129
- if (sheet.href) {
130
- // External stylesheet
131
- const link = targetWindow.document.createElement('link');
132
- link.rel = 'stylesheet';
133
- link.href = sheet.href;
134
- fragment.appendChild(link);
135
- } else if (sheet.cssRules) {
136
- // Inline stylesheet
137
- const style = targetWindow.document.createElement('style');
138
- const cssText = Array.from(sheet.cssRules).map(r => r.cssText).join('\n');
139
- style.textContent = cssText;
140
- fragment.appendChild(style);
141
- }
142
- } catch (e) {
143
- // Silently ignore cross-origin stylesheet errors
144
- console.warn('Could not copy stylesheet:', e);
145
- }
146
- });
147
-
148
- targetWindow.document.head.appendChild(fragment);
149
- }, [copyStyles]);
150
-
151
- // Keyboard shortcut handler
152
- const createKeyDownHandler = useCallback((win) => (e) => {
153
- const key = [
154
- e.ctrlKey && 'Ctrl',
155
- e.shiftKey && 'Shift',
156
- e.altKey && 'Alt',
157
- e.metaKey && 'Meta',
158
- e.key
159
- ].filter(Boolean).join('+');
160
-
161
- if (defaultShortcuts.close && key === defaultShortcuts.close) {
162
- e.preventDefault();
163
- onClose();
164
- } else if (defaultShortcuts.focus && key === defaultShortcuts.focus) {
165
- e.preventDefault();
166
- win.focus();
167
- } else if (defaultShortcuts.minimize && key === defaultShortcuts.minimize) {
168
- e.preventDefault();
169
- win.blur();
170
- onMinimize?.();
171
- } else if (defaultShortcuts.maximize && key === defaultShortcuts.maximize) {
172
- e.preventDefault();
173
- win.moveTo(0, 0);
174
- win.resizeTo(screen.availWidth, screen.availHeight);
175
- onMaximize?.();
120
+ doc.close();
121
+
122
+ return doc.getElementById('root');
123
+ },
124
+ [title]
125
+ );
126
+
127
+ // Copy styles from parent window
128
+ const copyStylesToWindow = useCallback(
129
+ (targetWindow) => {
130
+ if (!copyStyles) return;
131
+
132
+ const fragment = targetWindow.document.createDocumentFragment();
133
+
134
+ // Copy all stylesheets from parent
135
+ Array.from(document.styleSheets).forEach((sheet) => {
136
+ try {
137
+ if (sheet.href) {
138
+ // External stylesheet
139
+ const link = targetWindow.document.createElement('link');
140
+ link.rel = 'stylesheet';
141
+ link.href = sheet.href;
142
+ fragment.appendChild(link);
143
+ } else if (sheet.cssRules) {
144
+ // Inline stylesheet
145
+ const style = targetWindow.document.createElement('style');
146
+ const cssText = Array.from(sheet.cssRules)
147
+ .map((r) => r.cssText)
148
+ .join('\n');
149
+ style.textContent = cssText;
150
+ fragment.appendChild(style);
151
+ }
152
+ } catch (e) {
153
+ // Silently ignore cross-origin stylesheet errors
154
+ console.warn('Could not copy stylesheet:', e);
176
155
  }
177
- }, [defaultShortcuts, onClose, onMinimize, onMaximize]);
178
-
179
- useEffect(() => {
180
- const win = window.open('', '', windowFeatures);
181
-
182
- if (!win) {
183
- message.error('Please allow popups for this site');
184
- onClose();
185
- return;
186
- }
187
-
188
- windowRef.current = win;
189
-
190
- // Initialize window document structure
191
- const root = initializeWindow(win);
192
-
193
- // Copy styles from parent (async to not block)
194
- requestAnimationFrame(() => {
195
- copyStylesToWindow(win);
196
- });
197
-
198
- // Set container for portal immediately
199
- setContainer(root);
200
-
201
- // Setup keyboard shortcuts
202
- const handleKeyDown = createKeyDownHandler(win);
203
- window.addEventListener('keydown', handleKeyDown);
204
- win.addEventListener('keydown', handleKeyDown);
205
-
206
- // Handle window close
207
- const handleClose = () => {
208
- setContainer(null);
209
- onClose();
210
- };
211
- win.addEventListener('beforeunload', handleClose);
212
-
213
- // Cleanup
214
- return () => {
215
- window.removeEventListener('keydown', handleKeyDown);
216
- win.removeEventListener('keydown', handleKeyDown);
217
- win.removeEventListener('beforeunload', handleClose);
218
- if (!win.closed) win.close();
219
- };
220
- // Only re-run if window features change, not on every render
221
- // eslint-disable-next-line react-hooks/exhaustive-deps
222
- }, [windowFeatures]);
223
-
224
- return container ? createPortal(children, container) : null;
225
- }
156
+ });
157
+
158
+ targetWindow.document.head.appendChild(fragment);
159
+ },
160
+ [copyStyles]
161
+ );
162
+
163
+ // Keyboard shortcut handler
164
+ const createKeyDownHandler = useCallback(
165
+ (win) => (e) => {
166
+ const key = [e.ctrlKey && 'Ctrl', e.shiftKey && 'Shift', e.altKey && 'Alt', e.metaKey && 'Meta', e.key].filter(Boolean).join('+');
167
+
168
+ if (defaultShortcuts.close && key === defaultShortcuts.close) {
169
+ e.preventDefault();
170
+ onClose();
171
+ } else if (defaultShortcuts.focus && key === defaultShortcuts.focus) {
172
+ e.preventDefault();
173
+ win.focus();
174
+ } else if (defaultShortcuts.minimize && key === defaultShortcuts.minimize) {
175
+ e.preventDefault();
176
+ win.blur();
177
+ onMinimize?.();
178
+ } else if (defaultShortcuts.maximize && key === defaultShortcuts.maximize) {
179
+ e.preventDefault();
180
+ win.moveTo(0, 0);
181
+ win.resizeTo(screen.availWidth, screen.availHeight);
182
+ onMaximize?.();
183
+ }
184
+ },
185
+ [defaultShortcuts, onClose, onMinimize, onMaximize]
186
+ );
187
+
188
+ useEffect(() => {
189
+ const win = window.open('', '', windowFeatures);
190
+
191
+ if (!win) {
192
+ message.error('Please allow popups for this site');
193
+ onClose();
194
+ return;
195
+ }
196
+ // 🔥 GIVE PARENT ACCESS + FOCUS
197
+ onWindowReady?.(win);
198
+
199
+ windowRef.current = win;
200
+
201
+ // Initialize window document structure
202
+ const root = initializeWindow(win);
203
+
204
+ // Copy styles from parent (async to not block)
205
+ requestAnimationFrame(() => {
206
+ copyStylesToWindow(win);
207
+ });
208
+
209
+ // Set container for portal immediately
210
+ setContainer(root);
211
+
212
+ // Setup keyboard shortcuts
213
+ const handleKeyDown = createKeyDownHandler(win);
214
+ window.addEventListener('keydown', handleKeyDown);
215
+ win.addEventListener('keydown', handleKeyDown);
216
+
217
+ // Handle window close
218
+ const handleClose = () => {
219
+ setContainer(null);
220
+ onClose();
221
+ };
222
+ win.addEventListener('beforeunload', handleClose);
223
+
224
+ // Cleanup
225
+ return () => {
226
+ window.removeEventListener('keydown', handleKeyDown);
227
+ win.removeEventListener('keydown', handleKeyDown);
228
+ win.removeEventListener('beforeunload', handleClose);
229
+ if (!win.closed) win.close();
230
+ };
231
+ // Only re-run if window features change, not on every render
232
+ // eslint-disable-next-line react-hooks/exhaustive-deps
233
+ }, [windowFeatures]);
234
+
235
+ return container ? createPortal(children, container) : null;
236
+ }
@@ -16,8 +16,6 @@ import PopQueryDashboard from './dashboard/components/pop-query-dashboard/pop-qu
16
16
 
17
17
  import HomePageAPI from './../pages/homepage-api/homepage-api';
18
18
 
19
- import { Profile, ChangePassword } from './../lib';
20
-
21
19
  import ReportingDashboard from '../modules/reporting/components/reporting-dashboard/reporting-dashboard';
22
20
 
23
21
  import ChangeInfo from './Informations/change-info/change-info';
@@ -34,9 +32,7 @@ export {
34
32
  DashboardCard,
35
33
  PopQueryDashboard,
36
34
  HomePageAPI,
37
- Profile,
38
35
  ReportingDashboard,
39
- ChangePassword,
40
36
  ChangeInfo,
41
37
  };
42
38
 
@@ -1,6 +1,6 @@
1
1
  import React, { useState, useEffect, useContext, useRef } from 'react';
2
2
 
3
- import { Table, Skeleton, Input, Tag, Modal, message, Pagination } from 'antd';
3
+ import { Table, Skeleton, Input, Tag, Modal, message, Pagination, Tooltip } from 'antd';
4
4
 
5
5
  import { QrcodeOutlined } from '@ant-design/icons';
6
6
 
@@ -106,8 +106,17 @@ export default function ReportingDashboard({
106
106
  setPatients([]);
107
107
  const fetchId = idOverride || id;
108
108
  await CoreScripts.getRecord({ id: fetchId, dbPtr }).then(async ({ result }) => {
109
+ // Check if display columns are provided from backend
110
+ if (result.display_columns) {
111
+ // Parse and set columns from stored JSON
112
+
113
+ setColumns(JSON.parse(result.display_columns));
114
+ } else {
115
+ // Reset columns if no display columns exist
116
+
117
+ setColumns([]);
118
+ }
109
119
  await prepareInputParameters(result);
110
- setColumns(JSON.parse(result.display_columns));
111
120
 
112
121
  if (result.summary_columns) {
113
122
  setSummaryColumns(JSON.parse(result.summary_columns));
@@ -160,11 +169,12 @@ export default function ReportingDashboard({
160
169
  if (urlParams.script_id) scriptId.current = urlParams.script_id;
161
170
 
162
171
  let otherDetails = record.other_details1 ? JSON.parse(record.other_details1) : null;
163
- parameters = JSON.parse(record.input_parameters);
172
+
173
+ parameters = record.input_parameters ? JSON.parse(record.input_parameters) : null;
164
174
 
165
175
  let formContent = {};
166
176
 
167
- parameters = await parameters.map((record) => {
177
+ parameters = await parameters?.map((record) => {
168
178
  // Only if the url params does have a matching value ,
169
179
  // we should not consider the default value
170
180
 
@@ -289,6 +299,17 @@ export default function ReportingDashboard({
289
299
  }
290
300
  // Update patients
291
301
  setPatients(resultDetails || []);
302
+ // Check if columns are not yet defined
303
+ if (columns.length === 0 && resultDetails.length > 0) {
304
+ // Create columns dynamically from resultDetails keys
305
+ setColumns((prev) => {
306
+ if (prev.length > 0) return prev;
307
+ return Object.keys(resultDetails[0]).map((key) => ({
308
+ title: key,
309
+ field: key,
310
+ }));
311
+ });
312
+ }
292
313
 
293
314
  if (result.length) {
294
315
  // Set Pgination data into URL
@@ -565,7 +586,6 @@ function GuestList({
565
586
  attributes,
566
587
  fetchReportData,
567
588
  }) {
568
- console.log(attributes, '_____________________________________');
569
589
  /**
570
590
  * @param {*} propValues
571
591
  */
@@ -683,7 +703,7 @@ function GuestList({
683
703
  redirectLink = redirectLink.replace(new RegExp('@' + replacement.field + ';', 'g'), record[replacement.field]);
684
704
  });
685
705
 
686
- return <Link to={`${redirectLink}`}>View</Link>;
706
+ return <Link to={`${redirectLink}`}>{entry.display_name_link ? entry.display_name_link : 'View'}</Link>;
687
707
  } else if (entry.field === 'custom') {
688
708
  // Make all the components in modules available for use in custom column of core script
689
709
  // var genericComponents = require('./../../../../../../../nura-api-new/nura-desk/src/modules');
@@ -737,7 +757,7 @@ function GuestList({
737
757
 
738
758
  // If the column type is 'span', render the field inside a <span> with inline color style
739
759
  } else if (entry.columnType === 'span') {
740
- return <span style={{ color: record.color_code }}>{record[entry.field]}</span>;
760
+ return <span style={{ color: record.color_code, overflowWrap: 'break-word', WebkitLineClamp: 3 }}>{record[entry.field]}</span>;
741
761
  }
742
762
  } else {
743
763
  /**
@@ -769,14 +789,25 @@ function GuestList({
769
789
  }
770
790
  } else {
771
791
  //If the value is neither 'Y' nor 'N', return the actual field value
772
- return <span style={{ color: textColor }}>{record[entry.field]}</span>;
792
+ return <span style={{ color: textColor, whiteSpace: 'pre-wrap', overflowWrap: 'break-word' }}>{record[entry.field]}</span>;
773
793
  }
774
794
  }
775
795
  }
776
796
  },
777
797
  field: entry.field,
778
- title: entry.title,
798
+ // title: entry.title,
799
+ // title: (
800
+ // <Tooltip title={entry.title}>
801
+ // {entry.title}
802
+ // </Tooltip>
803
+ // ),
804
+ title: (
805
+ <Tooltip title={entry.tooltip || entry.title}>
806
+ <span>{entry.title}</span>
807
+ </Tooltip>
808
+ ),
779
809
  key: entry.field,
810
+ width: entry.width ? parseInt(entry.width) : 160,
780
811
  fixed: entry.isFixedColumn ? entry.isFixedColumn : null, // Conditionally setting the 'fixed' key to 'left' if 'isColumnStatic' is true; otherwise, setting it to null.
781
812
  // Check if filtering is enabled and patients is an array
782
813
  filters:
@@ -800,7 +831,6 @@ function GuestList({
800
831
  // Return the value from record.props if it exists
801
832
  return description && record[description] ? record[description] : null;
802
833
  }
803
-
804
834
  return record[entry.field];
805
835
  },
806
836
  // Add align property based on column type
@@ -884,13 +914,21 @@ function GuestList({
884
914
  // setData(data);
885
915
 
886
916
  // Define export data
887
- let exportDatas = getExportData(patients, cols);
917
+ // Sanitize cols for export to ensure titles are strings
918
+ const exportCols = cols.map((col) => {
919
+ if (col.title && typeof col.title === 'object' && col.title.props) {
920
+ return { ...col, title: col.title.props.title };
921
+ }
922
+ return col;
923
+ });
924
+
925
+ let exportDatas = getExportData(patients, exportCols);
888
926
 
889
927
  if (exportDatas.exportDataColumns.length && exportDatas.exportDataHeaders.length) {
890
928
  setExportData({ exportDatas });
891
929
  }
892
930
  }
893
- }, [patients]);
931
+ }, [patients, columns]);
894
932
 
895
933
  let filtered;
896
934
 
@@ -1042,7 +1080,7 @@ function GuestList({
1042
1080
  ) : (
1043
1081
  <TableComponent
1044
1082
  size="small"
1045
- scroll={{ x: true }}
1083
+ scroll={{ x: 'max-content' }}
1046
1084
  rowKey={(record) => record.OpNo}
1047
1085
  dataSource={filtered ? filtered : patients} // In case if there is no filtered values we can use patient data
1048
1086
  columns={cols}
@@ -1162,3 +1200,5 @@ function GuestList({
1162
1200
  // </Card>
1163
1201
  // );
1164
1202
  // }
1203
+ // );
1204
+ // }
@@ -3,9 +3,10 @@
3
3
  * Handles navigation and action controls for a multi-step process,
4
4
  * including dynamic content rendering and process completion actions.
5
5
  */
6
- import React from 'react';
6
+ import React, { useState } from 'react';
7
7
  import { Skeleton } from 'antd';
8
8
  import { Button } from '../../lib';
9
+ import './action-buttons.scss';
9
10
 
10
11
  export default function ActionButtons({
11
12
  loading,
@@ -21,63 +22,50 @@ export default function ActionButtons({
21
22
  nextProcessId,
22
23
  timelineCollapsed,
23
24
  }) {
25
+ const [showNextProcess, setShowNextProcess] = useState(false);
26
+
24
27
  return (
25
28
  <>
26
29
  <div style={{ minHeight: 300 }}>{loading ? <Skeleton active /> : renderDynamicComponent()}</div>
27
30
  <>
28
- <div style={{ marginTop: 20, display: 'flex', justifyContent: 'flex-start', gap: '10px' }}>
31
+ <div className="action-buttons-container">
29
32
  {/* Back button */}
30
- <Button disabled={activeStep === 0} onClick={handlePrevious} style={{ marginRight: 8, borderRadius: 4 }}>
33
+ <Button disabled={activeStep === 0} onClick={handlePrevious}>
31
34
  Back
32
35
  </Button>
33
36
 
34
37
  {/* Skip button */}
35
38
  {steps.length > 0 && steps[activeStep]?.allow_skip === 'Y' && (
36
- <Button
37
- type="default"
38
- onClick={handleSkip}
39
- style={{
40
- borderRadius: 4,
41
- }}
42
- disabled={activeStep === steps.length - 1}
43
- >
39
+ <Button type="default" onClick={handleSkip} disabled={activeStep === steps.length - 1}>
44
40
  Skip
45
41
  </Button>
46
42
  )}
47
43
 
48
44
  {/* Next / Finish / Start Next */}
49
45
  {steps[activeStep]?.order_seqtype === 'E' ? (
50
- nextProcessId?.next_process_id ? (
51
- <Button
52
- type="primary"
53
- style={{
54
- borderRadius: 4,
55
- }}
56
- onClick={handleStartNextProcess}
57
- >
58
- Start Next {nextProcessId.next_process_name}
59
- </Button>
60
- ) : (
61
- <Button
62
- type="primary"
63
- style={{
64
- borderRadius: 4,
65
- }}
66
- onClick={handleFinish}
67
- >
68
- Finish
69
- </Button>
70
- )
46
+ <>
47
+ {!showNextProcess && (
48
+ <Button
49
+ type="primary"
50
+ onClick={async () => {
51
+ const success = await handleFinish();
52
+ if (success && nextProcessId?.next_process_id) {
53
+ setShowNextProcess(true);
54
+ }
55
+ }}
56
+ >
57
+ Finish
58
+ </Button>
59
+ )}
60
+
61
+ {showNextProcess && nextProcessId?.next_process_id && (
62
+ <Button type="primary" onClick={handleStartNextProcess}>
63
+ Start {nextProcessId.next_process_name}
64
+ </Button>
65
+ )}
66
+ </>
71
67
  ) : (
72
- <Button
73
- type="primary"
74
- // shape="round"
75
- style={{
76
- borderRadius: 4,
77
- }}
78
- disabled={activeStep === steps.length - 1 || !isStepCompleted}
79
- onClick={handleNext}
80
- >
68
+ <Button type="primary" disabled={activeStep === steps.length - 1 || !isStepCompleted} onClick={handleNext}>
81
69
  Next →
82
70
  </Button>
83
71
  )}
@@ -0,0 +1,16 @@
1
+ .action-buttons-container {
2
+ margin-top: 38px;
3
+ display: flex;
4
+ justify-content: flex-start;
5
+ gap: 10px;
6
+ position: sticky;
7
+ bottom: 0;
8
+ z-index: 1000;
9
+ background: #fff;
10
+ padding: 10px 0;
11
+ border-top: 1px solid #f0f0f0;
12
+
13
+ .ant-btn {
14
+ border-radius: 4px;
15
+ }
16
+ }
@@ -20,22 +20,25 @@ import './steps.scss';
20
20
  import TimelinePanel from './timeline';
21
21
  import { ExternalWindow } from '../../components';
22
22
 
23
- export default function ProcessStepsPage({ processId, match, CustomComponents = {}, ...props }) {
23
+ export default function ProcessStepsPage({ match, CustomComponents = {}, ...props }) {
24
24
  const allComponents = { ...genericComponents, ...CustomComponents };
25
25
 
26
26
  const [loading, setLoading] = useState(false);
27
27
  const [steps, setSteps] = useState([]);
28
28
  const [activeStep, setActiveStep] = useState(0);
29
29
  const [isStepCompleted, setIsStepCompleted] = useState(false);
30
- const [currentProcessId, setCurrentProcessId] = useState(processId);
30
+
31
31
  const [nextProcessId, setNextProcessId] = useState(null);
32
32
  const [stepStartTime, setStepStartTime] = useState(null);
33
33
  const [processStartTime, setProcessStartTime] = useState(null);
34
34
  const [processTimings, setProcessTimings] = useState([]);
35
35
  const [timelineCollapsed, setTimelineCollapsed] = useState(true);
36
36
  const [showExternalWindow, setShowExternalWindow] = useState(false);
37
- const urlParams = Location.search();
37
+ const [externalWin, setExternalWin] = useState(null);
38
38
 
39
+ const urlParams = Location.search();
40
+ let processId = urlParams.processId;
41
+ const [currentProcessId, setCurrentProcessId] = useState(processId);
39
42
  // Load process details based on the current process ID
40
43
  useEffect(() => {
41
44
  loadProcess(currentProcessId);
@@ -204,7 +207,9 @@ export default function ProcessStepsPage({ processId, match, CustomComponents =
204
207
  */
205
208
  const handleFinish = async () => {
206
209
  const final = recordStepTime();
207
- if (await handleProcessSubmit(final)) props.history?.goBack();
210
+ const success = await handleProcessSubmit(final);
211
+ if (success && !nextProcessId) props.history?.goBack();
212
+ return success;
208
213
  };
209
214
  /**
210
215
  * Start Next Process
@@ -236,31 +241,34 @@ export default function ProcessStepsPage({ processId, match, CustomComponents =
236
241
 
237
242
  return <Component {...step.config} {...props} step={step} params={urlParams} onStepComplete={() => setIsStepCompleted(true)} />;
238
243
  };
239
- /**
240
- * Keyboard Navigation
241
- * - Enables left and right arrow keys for step navigation.
242
- * - Prevents navigation beyond step boundaries.
243
- * - Cleans up event listeners on unmount.
244
- */
244
+
245
245
  useEffect(() => {
246
246
  const handleKeyDown = (event) => {
247
- // Handle Left Arrow key press to go to the previous step
248
- if (event.key === 'ArrowLeft') {
249
- if (activeStep > 0) {
250
- handlePrevious();
251
- }
247
+ if (event.key === 'ArrowLeft' && activeStep > 0) {
248
+ handlePrevious();
252
249
  }
253
- // Handle Right Arrow key press to go to the next step
254
- else if (event.key === 'ArrowRight') {
255
- if (activeStep < steps.length - 1) {
256
- handleNext();
257
- }
250
+
251
+ if (event.key === 'ArrowRight' && activeStep < steps.length - 1) {
252
+ handleNext();
258
253
  }
259
254
  };
260
255
 
261
- window.addEventListener('keydown', handleKeyDown);
262
- return () => window.removeEventListener('keydown', handleKeyDown);
263
- }, [activeStep, steps, handlePrevious, handleNext]);
256
+ // main window (document!)
257
+ document.addEventListener('keydown', handleKeyDown);
258
+
259
+ // external window (document!)
260
+ if (externalWin && externalWin.document) {
261
+ externalWin.document.addEventListener('keydown', handleKeyDown);
262
+ }
263
+
264
+ return () => {
265
+ document.removeEventListener('keydown', handleKeyDown);
266
+
267
+ if (externalWin && externalWin.document) {
268
+ externalWin.document.removeEventListener('keydown', handleKeyDown);
269
+ }
270
+ };
271
+ }, [activeStep, steps, externalWin]);
264
272
 
265
273
  /**
266
274
  * Renders the main process UI including timeline, step details,
@@ -268,41 +276,43 @@ export default function ProcessStepsPage({ processId, match, CustomComponents =
268
276
  * and external window view.
269
277
  */
270
278
  const renderContent = () => (
271
- <Card>
272
- <Row gutter={20}>
273
- <Col xs={24} sm={24} lg={timelineCollapsed ? 2 : 6}>
274
- <TimelinePanel
275
- loading={loading}
276
- steps={steps}
277
- activeStep={activeStep}
278
- timelineCollapsed={timelineCollapsed}
279
- handleTimelineClick={handleTimelineClick}
280
- setTimelineCollapsed={setTimelineCollapsed}
281
- />
282
- </Col>
279
+ <div>
280
+ <Card>
281
+ <Row gutter={20}>
282
+ <Col xs={24} sm={24} lg={timelineCollapsed ? 2 : 6}>
283
+ <TimelinePanel
284
+ loading={loading}
285
+ steps={steps}
286
+ activeStep={activeStep}
287
+ timelineCollapsed={timelineCollapsed}
288
+ handleTimelineClick={handleTimelineClick}
289
+ setTimelineCollapsed={setTimelineCollapsed}
290
+ />
291
+ </Col>
283
292
 
284
- <Col xs={24} sm={24} lg={timelineCollapsed ? 21 : 18}>
285
- <div style={{ marginBottom: 20 }}>
286
- <h2 style={{ margin: 0, fontSize: 16, fontWeight: 600 }}>{steps[activeStep]?.step_name}</h2>
287
- <p style={{ margin: 0, color: '#666' }}>{steps[activeStep]?.step_description}</p>
288
- </div>
289
- <ActionButtons
290
- loading={loading}
291
- steps={steps}
292
- activeStep={activeStep}
293
- isStepCompleted={isStepCompleted}
294
- renderDynamicComponent={DynamicComponent}
295
- handlePrevious={handlePrevious}
296
- handleNext={handleNext}
297
- handleSkip={handleSkip}
298
- handleFinish={handleFinish}
299
- handleStartNextProcess={handleStartNextProcess}
300
- nextProcessId={nextProcessId}
301
- timelineCollapsed={timelineCollapsed}
302
- />
303
- </Col>
304
- </Row>
305
- </Card>
293
+ <Col xs={24} sm={24} lg={timelineCollapsed ? 21 : 18}>
294
+ <div style={{ marginBottom: 20 }}>
295
+ <h2 style={{ margin: 0, fontSize: 16, fontWeight: 600 }}>{steps[activeStep]?.step_name}</h2>
296
+ <p style={{ margin: 0, color: '#666' }}>{steps[activeStep]?.step_description}</p>
297
+ </div>
298
+ <ActionButtons
299
+ loading={loading}
300
+ steps={steps}
301
+ activeStep={activeStep}
302
+ isStepCompleted={isStepCompleted}
303
+ renderDynamicComponent={DynamicComponent}
304
+ handlePrevious={handlePrevious}
305
+ handleNext={handleNext}
306
+ handleSkip={handleSkip}
307
+ handleFinish={handleFinish}
308
+ handleStartNextProcess={handleStartNextProcess}
309
+ nextProcessId={nextProcessId}
310
+ timelineCollapsed={timelineCollapsed}
311
+ />
312
+ </Col>
313
+ </Row>
314
+ </Card>
315
+ </div>
306
316
  );
307
317
  /**
308
318
  * Renders content in both the main window and an external window
@@ -312,6 +322,10 @@ export default function ProcessStepsPage({ processId, match, CustomComponents =
312
322
  return (
313
323
  <>
314
324
  <ExternalWindow
325
+ onWindowReady={(win) => {
326
+ setExternalWin(win);
327
+ win.focus();
328
+ }}
315
329
  title={steps[activeStep]?.step_name || 'Process Step'}
316
330
  onClose={() => setShowExternalWindow(false)}
317
331
  // left={window.screenX + window.outerWidth}
@@ -1,7 +1,8 @@
1
1
  .timeline-card .ant-card-body {
2
- padding: 20px;
2
+ // padding: 20px;
3
+ border: none;
3
4
  min-height: 400px;
4
- position: fixed; /* For positioning the arrow */
5
+ // position: fixed; /* For positioning the arrow */
5
6
  }
6
7
 
7
8
  .timeline-sidebar {
@@ -46,7 +47,7 @@
46
47
 
47
48
  .vertical-line {
48
49
  width: 2px;
49
- height: 40px;
50
+ height: 20px;
50
51
  background: #d9d9d9;
51
52
  }
52
53
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ui-soxo-bootstrap-core",
3
- "version": "2.5.0",
3
+ "version": "2.5.1",
4
4
  "description": "All the Core Components for you to start",
5
5
  "keywords": [
6
6
  "all in one"