@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.
@@ -1,54 +1,109 @@
1
1
  import React, { useEffect, useState } from 'react';
2
+ import CustomFetch from './Fetch';
2
3
  import SelectList from './SelectList';
3
- import Select, { createFilter } from 'react-select';
4
+ import Select, { createFilter, components } from 'react-select';
5
+ import CreatableSelect from 'react-select/creatable';
4
6
  import styles from './styles/AsyncSelect.module.scss';
5
7
 
8
+ // Custom Option component to add a second line of text
9
+ const CustomOption = (props) => (
10
+ <components.Option {...props}>
11
+ <div>
12
+ {props.data.label}
13
+ {props.data.description && props.data.description !== '' && (
14
+ <div style={{ fontSize: '0.85em', color: '#888' }}>
15
+ {props.data.description}
16
+ </div>
17
+ )}
18
+ </div>
19
+ </components.Option>
20
+ );
21
+
6
22
  function MultiSelect({
7
23
  className,
8
- inputValue,
24
+ inputValue = [],
9
25
  multi,
10
26
  onChange,
11
- options,
27
+ options = [],
12
28
  placeholder,
13
29
  settings,
14
30
  style,
31
+ isCreatable = false,
32
+ creatableConfig = {},
15
33
  }) {
16
34
  const [selectOptions, setSelectOptions] = useState([]);
35
+ const [selectValue, setSelectValue] = useState([]);
17
36
 
18
37
  useEffect(() => {
19
- let _options = [];
38
+ const _options = Array.isArray(options)
39
+ ? options.map((a) => ({
40
+ value: a.id,
41
+ label: a.label || a.name || a.description || 'Unknown',
42
+ description: a.description || '', // Include description field
43
+ ...a,
44
+ }))
45
+ : [];
46
+ setSelectOptions(_options);
47
+ }, [options]);
20
48
 
21
- if (options.length > 0) {
22
- options.forEach((a) => {
23
- let _optionItem = {
24
- value: a.id,
25
- label: a.label,
26
- };
49
+ useEffect(() => {
50
+ const normalizeInputValue = (value) => {
51
+ // Ensure value is an array, wrapping single items if necessary
52
+ return Array.isArray(value) ? value : value ? [value] : [];
53
+ };
54
+
55
+ const _values = normalizeInputValue(inputValue).map((a) => ({
56
+ value: a.id,
57
+ label: a.label || a.name || a.description || 'Unknown',
58
+ description: a.description || '', // Include description field
59
+ ...a,
60
+ }));
27
61
 
28
- Object.keys(a).forEach((b) => {
29
- if (b !== 'id') {
30
- _optionItem = {
31
- ..._optionItem,
32
- [b]: a[b],
33
- };
34
- }
35
- });
62
+ setSelectValue(_values);
63
+ }, [inputValue]);
36
64
 
37
- _options.push(_optionItem);
38
- });
65
+ const handleCreate = async (inputValue) => {
66
+ if (creatableConfig.url && creatableConfig.method) {
67
+ const res = await CustomFetch(
68
+ creatableConfig.url,
69
+ creatableConfig.method,
70
+ { label: inputValue }
71
+ );
39
72
 
40
- setSelectOptions(_options);
73
+ if (res && res.data) {
74
+ const newOption = {
75
+ value: res.data.id,
76
+ label: res.data.label || inputValue,
77
+ description: res.data.description || 'Newly created item', // Default description for new items
78
+ ...res.data,
79
+ };
80
+
81
+ // Update options and trigger onChange with the new option
82
+ setSelectOptions((prevOptions) => [...prevOptions, newOption]);
83
+ setSelectValue((prevValues) =>
84
+ multi ? [...prevValues, newOption] : [newOption]
85
+ );
86
+
87
+ // Trigger onChange with the new option
88
+ onChange(
89
+ multi ? [...selectValue, newOption] : newOption,
90
+ { action: 'create-option' },
91
+ settings.id
92
+ );
93
+ }
41
94
  }
42
- }, [options]);
95
+ };
96
+
97
+ const SelectComponent = isCreatable ? CreatableSelect : Select;
43
98
 
44
99
  return (
45
- <Select
46
- closeMenuOnSelect={multi ? false : true}
100
+ <SelectComponent
101
+ closeMenuOnSelect={!multi}
47
102
  isClearable
48
103
  isSearchable
49
104
  isMulti={multi}
50
105
  filterOption={createFilter({ ignoreAccents: false })}
51
- components={{ SelectList }}
106
+ components={{ Option: CustomOption }} // Use the custom Option component
52
107
  options={selectOptions}
53
108
  onChange={(inputValue, action) => {
54
109
  onChange(inputValue, action, settings.id);
@@ -69,7 +124,8 @@ function MultiSelect({
69
124
  : '52px',
70
125
  }),
71
126
  }}
72
- value={inputValue}
127
+ value={selectValue}
128
+ onCreateOption={isCreatable ? handleCreate : undefined} // Only add if creatable
73
129
  placeholder={placeholder ? placeholder : 'Select...'}
74
130
  />
75
131
  );
@@ -12,7 +12,7 @@
12
12
  .fi__span {
13
13
  position: absolute;
14
14
  transition: all 200ms;
15
- opacity: 0.8;
15
+ opacity: 1;
16
16
  left: 0;
17
17
  transform-origin: top left;
18
18
  cursor: text;
@@ -164,10 +164,11 @@
164
164
  input:not(:placeholder-shown) + .fi__span,
165
165
  textarea:not(:placeholder-shown) + .fi__span,
166
166
  select:not(:placeholder-shown) + .fi__span {
167
- transform: translateY(-110%) translateX(10px) scale(0.75);
167
+ transform: translateY(-135%) translateX(10px) scale(0.9);
168
168
  opacity: 1;
169
169
  padding: 0;
170
170
  background: var(--bg-color);
171
- font-weight: 300;
171
+ font-weight: 400;
172
172
  transition: all 0.5s cubic-bezier(0.5, 0.5, 0, 1);
173
+ color: rgba(var(--paragraph-rgb), 0.65) !important;
173
174
  }
@@ -3,11 +3,11 @@
3
3
  input:not(:placeholder-shown) + .fi__span,
4
4
  textarea:not(:placeholder-shown) + .fi__span,
5
5
  select:not(:placeholder-shown) + .fi__span {
6
- transform: translateY(-40%) translateX(10px) scale(0.75);
6
+ transform: translateY(-40%) translateX(10px) scale(0.9);
7
7
  opacity: 1;
8
- padding: 0 4px;
8
+ padding: 0 0.25rem;
9
9
  background: var(--tertiary-color);
10
- font-weight: 300;
10
+ font-weight: 400;
11
11
  border-radius: var(--br);
12
12
  }
13
13
 
@@ -319,7 +319,7 @@ select:not(:placeholder-shown) + .fi__span {
319
319
  a {
320
320
  text-decoration: none;
321
321
  color: var(--secondary-color);
322
- font-weight: 300;
322
+ font-weight: 400;
323
323
  }
324
324
 
325
325
  svg {
@@ -10,6 +10,7 @@ import Dropzone from 'react-dropzone';
10
10
  import { confirmAlert } from 'react-confirm-alert';
11
11
  import truncate from 'truncate';
12
12
  import { CopyToClipboard } from 'react-copy-to-clipboard';
13
+ import { saveAs } from 'file-saver';
13
14
  import { toast } from 'react-toastify';
14
15
  import {
15
16
  CircleCheck,
@@ -75,6 +76,8 @@ function GenericDetail({
75
76
  if (activeTab) {
76
77
  const activeTabConfig = tabs.find((t) => t.id === activeTab.id);
77
78
 
79
+ console.info(tabs);
80
+
78
81
  if (type === 'view') {
79
82
  setActiveStage(stage.id);
80
83
  } else {
@@ -95,6 +98,32 @@ function GenericDetail({
95
98
 
96
99
  if (res.data.error === '') {
97
100
  handleReload();
101
+ toast.success('Stage updated successfully');
102
+ } else {
103
+ toast.error(res.data.error);
104
+ }
105
+ } else if (
106
+ stages &&
107
+ stages.url &&
108
+ stages.url !== '' &&
109
+ stages.key &&
110
+ stages.key !== '' &&
111
+ stages.urlParam &&
112
+ stages.urlParam !== ''
113
+ ) {
114
+ const url = `${stages.url}/${routeParams[urlParam]}`;
115
+
116
+ const res = await CustomFetch(
117
+ url,
118
+ stages.method || 'POST',
119
+ {
120
+ [stages.key]: stage.id,
121
+ }
122
+ );
123
+
124
+ if (res.data.error === '') {
125
+ handleReload();
126
+ toast.success('Stage updated successfully');
98
127
  } else {
99
128
  toast.error(res.data.error);
100
129
  }
@@ -436,10 +465,32 @@ function GenericDetail({
436
465
  if (activeTabConfig && activeTabConfig.type) {
437
466
  switch (activeTabConfig.type) {
438
467
  case 'dropzone':
439
- const downloadFile = (id) => {
440
- window.open(
441
- `/ajax/files/download/${id}/${activeTabConfig.folder}`
442
- );
468
+ const downloadFile = (file) => {
469
+ const { id, file_url, file_name } = file;
470
+
471
+ // If file_url exists, use it for the download, else use the id to download
472
+ const urlToDownload =
473
+ file_url || `/ajax/files/download/${id}`;
474
+
475
+ // Use fetch to retrieve the file as a blob
476
+ fetch(urlToDownload, {
477
+ method: 'GET',
478
+ headers: {
479
+ 'Content-Type':
480
+ 'application/octet-stream', // Specify content type as binary
481
+ },
482
+ })
483
+ .then((response) => response.blob()) // Convert response to Blob
484
+ .then((blob) => {
485
+ // Use FileSaver to download the file with the specified name
486
+ saveAs(blob, file_name);
487
+ })
488
+ .catch((error) => {
489
+ console.error(
490
+ 'Error downloading the file:',
491
+ error
492
+ );
493
+ });
443
494
  };
444
495
 
445
496
  const deleteFile = (e, id, name) => {
@@ -521,9 +572,13 @@ function GenericDetail({
521
572
  key: response.key,
522
573
  bucket: response.bucket,
523
574
  filename: file.name,
575
+ file_name: file.name,
524
576
  filesize: file.size,
577
+ file_size: file.size,
525
578
  extension:
526
579
  response.extension,
580
+ file_extension:
581
+ response.extension,
527
582
  file_relationship:
528
583
  activeTabConfig.file_relationship,
529
584
  };
@@ -533,9 +588,30 @@ function GenericDetail({
533
588
  'PUT',
534
589
  requestBody,
535
590
  (res) => {
536
- setFiles(
537
- res.data.files
538
- );
591
+ if (
592
+ activeTabConfig.file_relationship &&
593
+ res.data[
594
+ activeTabConfig
595
+ .file_relationship
596
+ ]
597
+ ) {
598
+ setFiles(
599
+ res.data[
600
+ activeTabConfig
601
+ .file_relationship
602
+ ]
603
+ );
604
+ } else {
605
+ if (
606
+ res?.data
607
+ ?.files
608
+ ) {
609
+ setFiles(
610
+ res.data
611
+ .files
612
+ );
613
+ }
614
+ }
539
615
  }
540
616
  );
541
617
  });
@@ -635,7 +711,7 @@ function GenericDetail({
635
711
  <li
636
712
  onClick={() => {
637
713
  downloadFile(
638
- a.id
714
+ a
639
715
  );
640
716
  }}
641
717
  key={'tf-' + b}
@@ -1377,8 +1453,17 @@ function GenericDetail({
1377
1453
 
1378
1454
  tabs.forEach((tab) => {
1379
1455
  if (tab.type && tab.type === 'dropzone') {
1380
- if (res.files) {
1381
- setFiles(res.files);
1456
+ if (
1457
+ tab.file_relationship &&
1458
+ tab.file_relationship !== ''
1459
+ ) {
1460
+ if (res[tab.file_relationship]) {
1461
+ setFiles(res[tab.file_relationship]);
1462
+ }
1463
+ } else {
1464
+ if (res.files) {
1465
+ setFiles(res.files);
1466
+ }
1382
1467
  }
1383
1468
  }
1384
1469
  });
@@ -1454,10 +1539,6 @@ function GenericDetail({
1454
1539
  <div className={styles.oppstatus}>
1455
1540
  <ul className={styles.opppath}>
1456
1541
  {stages.data.map((stage) => {
1457
- if (!stage.id) {
1458
- return null; // Skip rendering this stage
1459
- }
1460
-
1461
1542
  const isStageComplete =
1462
1543
  data[stages.key] >= stage.id;
1463
1544