ui-soxo-bootstrap-core 2.6.54 → 2.6.55-dev.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.
@@ -1,5 +1,5 @@
1
1
  // src/components/SignatureCanvas.js
2
- import { useRef } from 'react';
2
+ import { useRef, useState } from 'react';
3
3
  import SignatureCanvas from 'react-signature-canvas';
4
4
  import { Button, message } from 'antd';
5
5
  import { useTranslation } from '../../../lib';
@@ -8,12 +8,22 @@ import './signature-pad.scss';
8
8
 
9
9
 
10
10
 
11
- const SignatureCanvasComponent = ({ onClear, onSaveAndAddSignature, btnloading }) => {
11
+ const SignatureCanvasComponent = ({
12
+ onClear,
13
+ onSaveAndAddSignature,
14
+ btnloading,
15
+ showUpload = true,
16
+ accept = 'image/png',
17
+ maxSizeMB = 5,
18
+ }) => {
12
19
  const sigCanvas = useRef({});
20
+ const fileInput = useRef(null);
21
+ const [uploadedName, setUploadedName] = useState(null);
13
22
  const { t, i18n } = useTranslation(); // To Translate to another language
14
23
 
15
24
  const clear = () => {
16
25
  sigCanvas.current.clear()
26
+ setUploadedName(null);
17
27
  if (onClear) onClear();
18
28
  };
19
29
 
@@ -34,6 +44,102 @@ const SignatureCanvasComponent = ({ onClear, onSaveAndAddSignature, btnloading }
34
44
  }
35
45
  };
36
46
 
47
+ /**
48
+ * Open the (hidden) file picker
49
+ */
50
+ const selectImage = () => {
51
+ if (fileInput.current) fileInput.current.click();
52
+ };
53
+
54
+ /**
55
+ * Draw the picked image on the pad, scaled to fit and centered,
56
+ * so that Save / Clear keep working exactly as with a drawn signature
57
+ *
58
+ * @param {string} dataUrl
59
+ */
60
+ const drawImageOnPad = (dataUrl) => {
61
+ const image = new Image();
62
+
63
+ image.onerror = () => message.error(t('Unable to load the selected image'));
64
+
65
+ image.onload = () => {
66
+ const canvas = sigCanvas.current.getCanvas();
67
+
68
+ if (!canvas || !image.naturalWidth || !image.naturalHeight) {
69
+ message.error(t('Unable to load the selected image'));
70
+ return;
71
+ }
72
+
73
+ // Size the canvas is drawn with, and its actual (device pixel) resolution
74
+ const drawWidth = canvas.offsetWidth || canvas.width;
75
+ const drawHeight = canvas.offsetHeight || canvas.height;
76
+ const ratio = canvas.width / drawWidth;
77
+
78
+ // Fit inside the pad without distorting the image
79
+ const scale = Math.min(
80
+ canvas.width / image.naturalWidth,
81
+ canvas.height / image.naturalHeight
82
+ );
83
+ const imageWidth = image.naturalWidth * scale;
84
+ const imageHeight = image.naturalHeight * scale;
85
+
86
+ const offscreen = document.createElement('canvas');
87
+ offscreen.width = canvas.width;
88
+ offscreen.height = canvas.height;
89
+ offscreen.getContext('2d').drawImage(
90
+ image,
91
+ (canvas.width - imageWidth) / 2,
92
+ (canvas.height - imageHeight) / 2,
93
+ imageWidth,
94
+ imageHeight
95
+ );
96
+
97
+ sigCanvas.current.clear();
98
+ sigCanvas.current.fromDataURL(offscreen.toDataURL('image/png'), {
99
+ width: drawWidth,
100
+ height: drawHeight,
101
+ ratio,
102
+ });
103
+ };
104
+
105
+ image.src = dataUrl;
106
+ };
107
+
108
+ /**
109
+ * Validate and read the picked image
110
+ *
111
+ * @param {Event} event
112
+ */
113
+ const onFileSelect = (event) => {
114
+ const file = event.target.files && event.target.files[0];
115
+
116
+ // Reset so that picking the same file again still fires a change
117
+ event.target.value = '';
118
+
119
+ if (!file) return;
120
+
121
+ // Only the types listed in `accept` are allowed, whatever the file dialog let through
122
+ const allowed = accept.split(',').map((type) => type.trim()).filter(Boolean);
123
+
124
+ if (!file.type || allowed.indexOf(file.type) < 0) {
125
+ message.error(`${t('Please select a valid image file')} (${allowed.join(', ')})`);
126
+ return;
127
+ }
128
+
129
+ if (file.size > maxSizeMB * 1024 * 1024) {
130
+ message.error(`${t('Image should be smaller than')} ${maxSizeMB}MB`);
131
+ return;
132
+ }
133
+
134
+ const reader = new FileReader();
135
+ reader.onerror = () => message.error(t('Unable to read the selected image'));
136
+ reader.onload = () => {
137
+ drawImageOnPad(reader.result);
138
+ setUploadedName(file.name);
139
+ };
140
+ reader.readAsDataURL(file);
141
+ };
142
+
37
143
  return (
38
144
  <div className='signature-pad'>
39
145
  <SignatureCanvas
@@ -47,9 +153,19 @@ const SignatureCanvasComponent = ({ onClear, onSaveAndAddSignature, btnloading }
47
153
  />
48
154
 
49
155
  <p>
50
- <small>Click<strong> Save </strong>to add signature and save the document</small>
156
+ <small>
157
+ {showUpload
158
+ ? <>Draw the signature or <strong>Upload Image</strong>, then click<strong> Save </strong>to add signature and save the document</>
159
+ : <>Click<strong> Save </strong>to add signature and save the document</>}
160
+ </small>
51
161
  </p>
52
162
 
163
+ {showUpload && uploadedName && (
164
+ <p className='signature-file-name'>
165
+ <small>{uploadedName}</small>
166
+ </p>
167
+ )}
168
+
53
169
  <div className='signature-actions'>
54
170
  <div className='left'>
55
171
  <Button
@@ -61,6 +177,16 @@ const SignatureCanvasComponent = ({ onClear, onSaveAndAddSignature, btnloading }
61
177
  >
62
178
  {t('Save')}
63
179
  </Button>
180
+ {showUpload && (
181
+ <Button
182
+ style={{ borderRadius: '2px' }}
183
+ type="secondary"
184
+ size="medium"
185
+ onClick={selectImage}
186
+ >
187
+ {t('Upload Image')}
188
+ </Button>
189
+ )}
64
190
  <Button
65
191
  style={{ borderRadius: '2px' }}
66
192
  type="secondary"
@@ -82,6 +208,17 @@ const SignatureCanvasComponent = ({ onClear, onSaveAndAddSignature, btnloading }
82
208
  {t('Add Signature')}
83
209
  </Button> */}
84
210
  </div>
211
+
212
+ {showUpload && (
213
+ <input
214
+ ref={fileInput}
215
+ type="file"
216
+ accept={accept}
217
+ className='signature-file-input'
218
+ style={{ display: 'none' }}
219
+ onChange={onFileSelect}
220
+ />
221
+ )}
85
222
  </div>
86
223
 
87
224
  );
@@ -1,6 +1,15 @@
1
1
  .signature-pad {
2
2
  padding: 20px;
3
3
 
4
+ .signature-file-input {
5
+ display: none;
6
+ }
7
+
8
+ .signature-file-name {
9
+ margin-bottom: 4px;
10
+ color: rgba(0, 0, 0, 0.45);
11
+ }
12
+
4
13
  .signature-actions {
5
14
  display: flex;
6
15
  justify-content: space-between;
@@ -211,7 +211,9 @@ function FormCreator({
211
211
  // Keep the same value preparation path for normal submit and search reset.
212
212
  fields.forEach((field) => {
213
213
 
214
- if (field.field && field.field.includes('date')) {
214
+ // Only actual date inputs are converted , a field can be named with date
215
+ // and still hold a plain value ( eg : date_filter_type select )
216
+ if (field.field && field.field.includes('date') && ['date', 'datetime'].indexOf(field.type) !== -1) {
215
217
 
216
218
  nextValues[field.field] = moment(nextValues[field.field]).valueOf();
217
219
 
@@ -530,7 +532,7 @@ function UserInput({ field, onUpload, selectedInformation, onChange, onSearchRes
530
532
  return (
531
533
  <Select defaultValue={defaultValue}
532
534
  required={field.required}
533
- style={{ width: '120' }}
535
+ style={{ minWidth: '160px' }}
534
536
  onChange={(value) => onChange(field, value)}
535
537
  >
536
538
  {field.options.map((option, key) => (
@@ -15,6 +15,9 @@ import MenuDashBoardComponent from '../../../../lib/elements/basic/menu-dashboar
15
15
 
16
16
  import ReportingTable from './reporting-table';
17
17
 
18
+ // Input parameter that decides which date column the report is filtered by
19
+ const DATE_FILTER_FIELD = 'date_filter_type';
20
+
18
21
  /**
19
22
  * ReportingDashboard component renders the dashboard and handles patient details,
20
23
  * configuration, and form layout for generating reports.
@@ -174,6 +177,9 @@ export default function ReportingDashboard({
174
177
  if (urlParams[record.field]) {
175
178
  if (record.type === 'date') {
176
179
  formContent[record.field] = moment.utc(urlParams[record.field]);
180
+ } else if (record.type !== 'search') {
181
+ // Restore plain values ( eg : select filters ) back from the url
182
+ formContent[record.field] = urlParams[record.field];
177
183
  }
178
184
 
179
185
  // return formContent;
@@ -201,6 +207,10 @@ export default function ReportingDashboard({
201
207
  break;
202
208
 
203
209
  default:
210
+ // Any other default is taken as the value itself ( eg : select filters )
211
+ if (record.default !== undefined && record.default !== null && record.type !== 'date') {
212
+ formContent[record.field] = record.default;
213
+ }
204
214
  break;
205
215
  }
206
216
  }
@@ -209,6 +219,11 @@ export default function ReportingDashboard({
209
219
  if (record.type === 'date' && !formContent[record.field]) {
210
220
  formContent[record.field] = moment().tz(process.env.REACT_APP_TIMEZONE);
211
221
  }
222
+
223
+ // For a static select with no value yet , preselect the first option
224
+ if (record.type === 'select' && Array.isArray(record.options) && formContent[record.field] === undefined) {
225
+ formContent[record.field] = record.options[0]?.valueMember;
226
+ }
212
227
  if (record.type === 'search') {
213
228
  if (!formContent[record.field]) formContent[record.field] = [];
214
229
  return {
@@ -248,8 +263,19 @@ export default function ReportingDashboard({
248
263
  // If enabled, clear the details array
249
264
  setDetails([]);
250
265
  } else {
266
+ // The date filter input is shown only when the filter is enabled in otherDetails .
267
+ // Otherwise it stays hidden holding its default value , so the report keeps filtering
268
+ // on the default date
269
+ const filterKey = otherDetails ? Object.keys(otherDetails).find((key) => key.toLowerCase().trim() === 'enableuserfilter') : null;
270
+
271
+ const isFilterEnabled = filterKey ? otherDetails[filterKey] === true || otherDetails[filterKey] === 'true' : false;
272
+
251
273
  // Keep all parameters with a type (including search) to render in FormCreator
252
- setDetails([...parameters.filter((ele) => ele.type)]);
274
+ setDetails([
275
+ ...parameters
276
+ .filter((ele) => ele.type)
277
+ .map((ele) => (ele.field === DATE_FILTER_FIELD ? { ...ele, visible: isFilterEnabled } : ele)),
278
+ ]);
253
279
  }
254
280
  }
255
281
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ui-soxo-bootstrap-core",
3
- "version": "2.6.54",
3
+ "version": "2.6.55-dev.1",
4
4
  "description": "All the Core Components for you to start",
5
5
  "keywords": [
6
6
  "all in one"