@visns-studio/visns-components 5.0.4 → 5.0.6

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.
package/package.json CHANGED
@@ -75,7 +75,7 @@
75
75
  "react-dom": "^17.0.0 || ^18.0.0"
76
76
  },
77
77
  "name": "@visns-studio/visns-components",
78
- "version": "5.0.4",
78
+ "version": "5.0.6",
79
79
  "description": "Various packages to assist in the development of our Custom Applications.",
80
80
  "main": "src/index.js",
81
81
  "files": [
@@ -1,11 +1,28 @@
1
- import React, { useEffect, useState, useCallback } from 'react';
1
+ import React, { useCallback } from 'react';
2
2
  import CustomFetch from './Fetch';
3
3
  import SelectList from './SelectList';
4
4
  import AsyncSelect from 'react-select/async';
5
+ import AsyncCreatableSelect from 'react-select/async-creatable';
5
6
  import { debounce } from 'lodash';
6
7
  import styles from './styles/AsyncSelect.module.scss';
8
+ import { components } from 'react-select';
9
+
10
+ // Custom Option component to add a second line of text
11
+ const CustomOption = (props) => (
12
+ <components.Option {...props}>
13
+ <div>
14
+ {props.data.label}
15
+ {props.data.description && props.data.description !== '' && (
16
+ <div style={{ fontSize: '0.85em', color: '#888' }}>
17
+ {props.data.description}
18
+ </div>
19
+ )}
20
+ </div>
21
+ </components.Option>
22
+ );
7
23
 
8
24
  function VisnsAsyncSelect({
25
+ className,
9
26
  fields = [],
10
27
  inputValue,
11
28
  onChange,
@@ -14,6 +31,7 @@ function VisnsAsyncSelect({
14
31
  placeholder,
15
32
  settings,
16
33
  style,
34
+ isCreatable = false, // New prop to toggle creatable functionality
17
35
  }) {
18
36
  const loadSuggestedOptions = useCallback(
19
37
  debounce((inputValue, callback) => {
@@ -31,7 +49,6 @@ function VisnsAsyncSelect({
31
49
  return new Promise((resolve, reject) => {
32
50
  let data = [];
33
51
 
34
- // Prepare payload
35
52
  let payload = {
36
53
  where: [
37
54
  {
@@ -41,38 +58,50 @@ function VisnsAsyncSelect({
41
58
  ],
42
59
  };
43
60
 
44
- // Check and set fields from settings if fields prop is null or empty
45
61
  if (fields && fields.length > 0) {
46
62
  payload.fields = fields;
47
63
  } else if (settings.fields && settings.fields.length > 0) {
48
64
  payload.fields = settings.fields;
49
65
  }
50
66
 
51
- // Check and set orderBy from settings if orderBy prop is null or empty
52
67
  if (orderBy) {
53
68
  payload.orderBy = orderBy;
54
69
  } else if (settings.orderBy) {
55
70
  payload.orderBy = settings.orderBy;
56
71
  }
57
72
 
58
- // Fetch data
59
73
  CustomFetch(
60
74
  settings.url,
61
75
  'POST',
62
76
  payload,
63
77
  function (result) {
64
78
  result.data.forEach((a) => {
79
+ let mainLabel = a.label || a[Object.keys(a)[0]];
80
+
81
+ if (settings.fields && settings.fields.length > 2) {
82
+ const additionalFields =
83
+ settings.fields.slice(2);
84
+ const additionalLabels = additionalFields
85
+ .filter(
86
+ (field) =>
87
+ field !== settings.descriptionKey
88
+ )
89
+ .map((field) => a[field] || '')
90
+ .join(' ');
91
+ mainLabel += ` ${additionalLabels}`;
92
+ }
93
+
65
94
  let _optionItem = {
66
95
  value: a.id,
67
- label: a.label,
96
+ label: mainLabel,
97
+ description: settings.descriptionKey
98
+ ? a[settings.descriptionKey]
99
+ : '',
68
100
  };
69
101
 
70
102
  Object.keys(a).forEach((b) => {
71
- if (b !== 'id') {
72
- _optionItem = {
73
- ..._optionItem,
74
- [b]: a[b],
75
- };
103
+ if (b !== 'id' && b !== 'label') {
104
+ _optionItem[b] = a[b];
76
105
  }
77
106
  });
78
107
 
@@ -87,17 +116,92 @@ function VisnsAsyncSelect({
87
116
  );
88
117
  });
89
118
  } else {
90
- return Promise.resolve([]); // Return empty array if input is less than 3 characters
119
+ return Promise.resolve([]);
91
120
  }
92
121
  };
93
122
 
123
+ const handleCreateOption = async (inputValue) => {
124
+ if (
125
+ settings.creatableConfig &&
126
+ settings.creatableConfig.url &&
127
+ settings.creatableConfig.method
128
+ ) {
129
+ let payload;
130
+ let firstNameField, lastNameField;
131
+
132
+ if (
133
+ settings.creatableConfig.type === 'name' &&
134
+ Array.isArray(settings.creatableConfig.fields)
135
+ ) {
136
+ [firstNameField, lastNameField] =
137
+ settings.creatableConfig.fields;
138
+
139
+ const nameParts = inputValue.trim().split(' ');
140
+ let firstname = '';
141
+ let lastname = '';
142
+
143
+ if (nameParts.length === 1) {
144
+ firstname = nameParts[0];
145
+ } else if (nameParts.length === 2) {
146
+ firstname = nameParts[0];
147
+ lastname = nameParts[1];
148
+ } else {
149
+ firstname = nameParts[0];
150
+ lastname = nameParts.slice(1).join(' ');
151
+ }
152
+
153
+ payload = {
154
+ [firstNameField || 'firstname']: firstname,
155
+ [lastNameField || 'lastname']: lastname,
156
+ };
157
+ } else {
158
+ payload = { label: inputValue };
159
+ }
160
+
161
+ const newOption = await CustomFetch(
162
+ settings.creatableConfig.url,
163
+ settings.creatableConfig.method,
164
+ payload
165
+ );
166
+
167
+ if (newOption && newOption.data && newOption.data.data) {
168
+ const newOptionData = newOption.data.data;
169
+ const option = {
170
+ value: newOptionData.id,
171
+ label:
172
+ settings.creatableConfig.type === 'name'
173
+ ? `${
174
+ newOptionData[firstNameField] ||
175
+ newOptionData.firstname
176
+ } ${
177
+ newOptionData[lastNameField] ||
178
+ newOptionData.lastname
179
+ }`
180
+ : newOptionData.label || inputValue,
181
+ description: settings.descriptionKey
182
+ ? newOptionData[settings.descriptionKey]
183
+ : 'Placeholder text for description',
184
+ ...newOptionData,
185
+ };
186
+
187
+ onChange(option, { action: 'create-option' }, settings.id); // Directly call onChange with new option
188
+ }
189
+ }
190
+ };
191
+
192
+ const SelectComponent = isCreatable ? AsyncCreatableSelect : AsyncSelect;
193
+
94
194
  return (
95
- <AsyncSelect
195
+ <SelectComponent
96
196
  isClearable
97
197
  cacheOptions
98
198
  loadOptions={loadSuggestedOptions}
99
199
  defaultOptions
100
- className={styles.visns__ms}
200
+ className={
201
+ className
202
+ ? `${styles.visns__ms} ${className}`
203
+ : styles.visns__ms
204
+ }
101
205
  onChange={(inputValue, action) => {
102
206
  onChange(inputValue, action, settings.id);
103
207
  }}
@@ -106,7 +210,9 @@ function VisnsAsyncSelect({
106
210
  onKeyDown(e);
107
211
  }
108
212
  }}
109
- components={{ SelectList }}
213
+ classNamePrefix="visns-select"
214
+ onCreateOption={isCreatable ? handleCreateOption : undefined}
215
+ components={{ Option: CustomOption }}
110
216
  styles={{
111
217
  menu: (styles) => ({
112
218
  ...styles,
@@ -2382,12 +2382,32 @@ const DataGrid = forwardRef(
2382
2382
  data[column.id] &&
2383
2383
  data[column.id] !== ''
2384
2384
  ) {
2385
+ let content = String(
2386
+ data[column.id]
2387
+ );
2388
+
2389
+ // If removeFormat is true, strip HTML tags
2390
+ if (column.removeFormat) {
2391
+ content = content.replace(
2392
+ /<\/?[^>]+(>|$)/g,
2393
+ ''
2394
+ ); // Simple regex to strip HTML tags
2395
+ }
2396
+
2397
+ // If truncate has a value, limit content to specified number of characters
2398
+ if (column.truncate) {
2399
+ content =
2400
+ content.length >
2401
+ column.truncate
2402
+ ? content.substring(
2403
+ 0,
2404
+ column.truncate
2405
+ ) + '...'
2406
+ : content;
2407
+ }
2408
+
2385
2409
  return (
2386
- <span>
2387
- {parse(
2388
- String(data[column.id])
2389
- )}
2390
- </span>
2410
+ <span>{parse(content)}</span>
2391
2411
  );
2392
2412
  } else {
2393
2413
  return null;
@@ -253,65 +253,70 @@ function Field({
253
253
  }, [settings.options, settings, counter]);
254
254
 
255
255
  const renderLabel = () => {
256
- if (
257
- [
258
- 'address',
259
- 'async-dropdown-ajax',
260
- 'checkbox',
261
- 'colour',
262
- 'copytoclipboard',
263
- 'date',
264
- 'datetime',
265
- 'dropdown',
266
- 'dropdown-ajax',
267
- 'dynamicdata',
268
- 'file',
269
- 'gallery',
270
- 'image',
271
- 'multi-dropdown',
272
- 'multi-dropdown-ajax',
273
- 'plaindate',
274
- 'plaindatetime',
275
- 'plainrelation',
276
- 'plainrelationarray',
277
- 'radio',
278
- 'radio-ajax',
279
- 'richeditor',
280
- 'signature',
281
- 'table_radio',
282
- 'table_text',
283
- 'time',
284
- ].includes(settings.type)
285
- ) {
286
- return (
287
- <span className={`${styles.fi__span} ${styles.prefocus}`}>
288
- {settings.label} {settings.required ? '*' : ''}
289
- </span>
290
- );
291
- } else if (['plaintext'].includes(settings.type)) {
292
- if (settings.description) {
293
- return (
294
- <span>
295
- {settings.description
296
- ? parse(settings.description)
297
- : ''}
298
- </span>
299
- );
300
- } else {
256
+ if (settings.label && settings.label !== '') {
257
+ if (
258
+ [
259
+ 'address',
260
+ 'async-dropdown-ajax',
261
+ 'checkbox',
262
+ 'colour',
263
+ 'copytoclipboard',
264
+ 'date',
265
+ 'datetime',
266
+ 'dropdown',
267
+ 'dropdown-ajax',
268
+ 'dynamicdata',
269
+ 'file',
270
+ 'gallery',
271
+ 'image',
272
+ 'multi-dropdown',
273
+ 'multi-dropdown-ajax',
274
+ 'plaindate',
275
+ 'plaindatetime',
276
+ 'plainrelation',
277
+ 'plainrelationarray',
278
+ 'radio',
279
+ 'radio-ajax',
280
+ 'signature',
281
+ 'table_radio',
282
+ 'table_text',
283
+ 'time',
284
+ ].includes(settings.type)
285
+ ) {
301
286
  return (
302
287
  <span className={`${styles.fi__span} ${styles.prefocus}`}>
303
288
  {settings.label} {settings.required ? '*' : ''}
304
289
  </span>
305
290
  );
291
+ } else if (['plaintext'].includes(settings.type)) {
292
+ if (settings.description) {
293
+ return (
294
+ <span>
295
+ {settings.description
296
+ ? parse(settings.description)
297
+ : ''}
298
+ </span>
299
+ );
300
+ } else {
301
+ return (
302
+ <span
303
+ className={`${styles.fi__span} ${styles.prefocus}`}
304
+ >
305
+ {settings.label} {settings.required ? '*' : ''}
306
+ </span>
307
+ );
308
+ }
309
+ } else if (['plaintextheading'].includes(settings.type)) {
310
+ return <h2>{settings.label ? settings.label : ''}</h2>;
311
+ } else if (['section'].includes(settings.type)) {
312
+ return (
313
+ <div className={styles.gridtxt__header}>
314
+ <span>{settings.label}</span>
315
+ </div>
316
+ );
317
+ } else if (['richeditor'].includes(settings.type)) {
318
+ return settings.label;
306
319
  }
307
- } else if (['plaintextheading'].includes(settings.type)) {
308
- return <h2>{settings.label ? settings.label : ''}</h2>;
309
- } else if (['section'].includes(settings.type)) {
310
- return (
311
- <div className={styles.gridtxt__header}>
312
- <span>{settings.label}</span>
313
- </div>
314
- );
315
320
  }
316
321
  };
317
322
 
@@ -368,23 +373,26 @@ function Field({
368
373
  );
369
374
  case 'async-dropdown-ajax':
370
375
  return (
371
- <VisnsAsyncSelect
372
- settings={settings}
373
- className={inputClass[settings.id]}
374
- multi={
375
- settings.hasOwnProperty('multi')
376
- ? settings.multi
377
- : false
378
- }
379
- onChange={onChangeSelect}
380
- inputValue={
381
- inputValue === null || inputValue === undefined
382
- ? ''
383
- : inputValue
384
- }
385
- options={options}
386
- dataOptions={dataOptions}
387
- />
376
+ <div className={styles.dropdownContainer}>
377
+ <VisnsAsyncSelect
378
+ settings={settings}
379
+ className={inputClass[settings.id]}
380
+ multi={
381
+ settings.hasOwnProperty('multi')
382
+ ? settings.multi
383
+ : false
384
+ }
385
+ onChange={onChangeSelect}
386
+ inputValue={
387
+ inputValue === null || inputValue === undefined
388
+ ? ''
389
+ : inputValue
390
+ }
391
+ options={options}
392
+ dataOptions={dataOptions}
393
+ isCreatable={settings.isCreatable || false}
394
+ />
395
+ </div>
388
396
  );
389
397
  case 'checkbox':
390
398
  return (
@@ -807,6 +815,13 @@ function Field({
807
815
 
808
816
  return htmlContainer;
809
817
  case 'html5_date':
818
+ // Convert inputValue to 'YYYY-MM-DD' if necessary
819
+ const formattedDate =
820
+ inputValue &&
821
+ moment(inputValue, moment.ISO_8601, true).isValid()
822
+ ? moment(inputValue).format('YYYY-MM-DD')
823
+ : inputValue; // Use as-is if it's already in the correct format or invalid
824
+
810
825
  return (
811
826
  <input
812
827
  type="date"
@@ -814,11 +829,7 @@ function Field({
814
829
  className={inputClass[settings.id]}
815
830
  placeholder=" "
816
831
  onChange={onChange}
817
- value={
818
- inputValue && inputValue !== 'null'
819
- ? inputValue
820
- : ''
821
- }
832
+ value={formattedDate || ''}
822
833
  autoComplete="off"
823
834
  />
824
835
  );
@@ -970,6 +981,7 @@ function Field({
970
981
  ? settings.options
971
982
  : options
972
983
  }
984
+ isCreatable={settings.isCreatable || false}
973
985
  dataOptions={dataOptions}
974
986
  style={style}
975
987
  />
@@ -1172,77 +1184,6 @@ function Field({
1172
1184
  ))}
1173
1185
  </div>
1174
1186
  ) : null;
1175
- case 'richeditor':
1176
- return (
1177
- <Editor
1178
- tinymceScriptSrc="https://d16lktya8ojp5z.cloudfront.net/generic/packages/tinymce_v701/tinymce.min.js"
1179
- licenseKey="gpl"
1180
- onEditorChange={(value, e) => {
1181
- onChangeRicheditor(e, value, settings.id);
1182
- }}
1183
- onInit={(evt, editor) => (editorRef.current = editor)}
1184
- value={inputValue || ''}
1185
- init={{
1186
- branding: false,
1187
- height: settings.height ? settings.height : 500,
1188
- menubar: false,
1189
- plugins: [
1190
- 'advlist',
1191
- 'autolink',
1192
- 'lists',
1193
- 'link',
1194
- 'image',
1195
- 'charmap',
1196
- 'preview',
1197
- 'anchor',
1198
- 'searchreplace',
1199
- 'visualblocks',
1200
- 'code',
1201
- 'fullscreen',
1202
- 'insertdatetime',
1203
- 'media',
1204
- 'table',
1205
- 'code',
1206
- ],
1207
- toolbar:
1208
- 'insertTextDropdown | undo redo | blocks | ' +
1209
- 'bold italic forecolor | alignleft aligncenter ' +
1210
- 'alignright alignjustify | bullist numlist outdent indent | ' +
1211
- 'removeformat | table',
1212
- content_style:
1213
- 'body { font-family:Helvetica,Arial,sans-serif; font-size:14px }',
1214
- setup: (editor) => {
1215
- // Check if insertText exists and is an array of objects
1216
- if (
1217
- settings.insertText &&
1218
- Array.isArray(settings.insertText) &&
1219
- settings.insertText.length > 0
1220
- ) {
1221
- editor.ui.registry.addMenuButton(
1222
- 'insertTextDropdown',
1223
- {
1224
- text: 'Insert Text',
1225
- fetch: (callback) => {
1226
- const items =
1227
- settings.insertText.map(
1228
- (textObj) => ({
1229
- type: 'menuitem',
1230
- text: textObj.text,
1231
- onAction: () =>
1232
- editor.insertContent(
1233
- textObj.content
1234
- ),
1235
- })
1236
- );
1237
- callback(items);
1238
- },
1239
- }
1240
- );
1241
- }
1242
- },
1243
- }}
1244
- />
1245
- );
1246
1187
  case 'signature':
1247
1188
  return (
1248
1189
  <>
@@ -1767,6 +1708,77 @@ function Field({
1767
1708
  shouldCloseOnSelect={true}
1768
1709
  />
1769
1710
  );
1711
+ case 'richeditor':
1712
+ return (
1713
+ <Editor
1714
+ tinymceScriptSrc="https://d16lktya8ojp5z.cloudfront.net/generic/packages/tinymce_v701/tinymce.min.js"
1715
+ licenseKey="gpl"
1716
+ onEditorChange={(value, e) => {
1717
+ onChangeRicheditor(e, value, settings.id);
1718
+ }}
1719
+ onInit={(evt, editor) => (editorRef.current = editor)}
1720
+ value={inputValue || ''}
1721
+ init={{
1722
+ branding: false,
1723
+ height: settings.height ? settings.height : 500,
1724
+ menubar: false,
1725
+ plugins: [
1726
+ 'advlist',
1727
+ 'autolink',
1728
+ 'lists',
1729
+ 'link',
1730
+ 'image',
1731
+ 'charmap',
1732
+ 'preview',
1733
+ 'anchor',
1734
+ 'searchreplace',
1735
+ 'visualblocks',
1736
+ 'code',
1737
+ 'fullscreen',
1738
+ 'insertdatetime',
1739
+ 'media',
1740
+ 'table',
1741
+ 'code',
1742
+ ],
1743
+ toolbar:
1744
+ 'insertTextDropdown | undo redo | blocks | ' +
1745
+ 'bold italic forecolor | alignleft aligncenter ' +
1746
+ 'alignright alignjustify | bullist numlist outdent indent | ' +
1747
+ 'removeformat | table',
1748
+ content_style:
1749
+ 'body { font-family:Helvetica,Arial,sans-serif; font-size:14px }',
1750
+ setup: (editor) => {
1751
+ // Check if insertText exists and is an array of objects
1752
+ if (
1753
+ settings.insertText &&
1754
+ Array.isArray(settings.insertText) &&
1755
+ settings.insertText.length > 0
1756
+ ) {
1757
+ editor.ui.registry.addMenuButton(
1758
+ 'insertTextDropdown',
1759
+ {
1760
+ text: 'Insert Text',
1761
+ fetch: (callback) => {
1762
+ const items =
1763
+ settings.insertText.map(
1764
+ (textObj) => ({
1765
+ type: 'menuitem',
1766
+ text: textObj.text,
1767
+ onAction: () =>
1768
+ editor.insertContent(
1769
+ textObj.content
1770
+ ),
1771
+ })
1772
+ );
1773
+ callback(items);
1774
+ },
1775
+ }
1776
+ );
1777
+ }
1778
+ },
1779
+ }}
1780
+ />
1781
+ );
1770
1782
  case 'time':
1771
1783
  return (
1772
1784
  <DatePicker
@@ -1868,7 +1880,11 @@ function Field({
1868
1880
 
1869
1881
  return (
1870
1882
  <div className={formItemClass} style={formItemStyle}>
1871
- <label className={styles.fi__label}>
1883
+ <label
1884
+ className={
1885
+ settings.type === 'richeditor' ? '' : styles.fi__label
1886
+ }
1887
+ >
1872
1888
  {renderLabel()}
1873
1889
  {renderInput()}
1874
1890
  {renderFieldLabel()}
@@ -215,9 +215,9 @@ function Form({
215
215
  const handleSelectOption = () => {
216
216
  updateFormData({ [id]: inputValue });
217
217
 
218
- // Update any matching form data properties
219
- Object.keys(inputValue).forEach((key) => {
220
- if (formData.hasOwnProperty(key)) {
218
+ // Update any matching form data properties except for the second item
219
+ Object.keys(inputValue).forEach((key, index) => {
220
+ if (index !== 1 && formData.hasOwnProperty(key)) {
221
221
  updateFormData({ [key]: inputValue[key] });
222
222
  }
223
223
  });
@@ -287,6 +287,7 @@ function Form({
287
287
  // Switch cases to handle different actions
288
288
  switch (action.action) {
289
289
  case 'select-option':
290
+ case 'create-option': // New case to handle create-option
290
291
  handleSelectOption();
291
292
  break;
292
293
  case 'clear':