@visns-studio/visns-components 5.0.4 → 5.0.5
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 +1 -1
- package/src/components/crm/AsyncSelect.jsx +121 -15
- package/src/components/crm/DataGrid.jsx +25 -5
- package/src/components/crm/Field.jsx +163 -147
- package/src/components/crm/Form.jsx +4 -3
- package/src/components/crm/MultiSelect.jsx +82 -26
- package/src/components/crm/auth/styles/Login.module.scss +4 -3
- package/src/components/crm/auth/styles/Profile.module.scss +4 -4
- package/src/components/crm/generic/GenericDetail.jsx +95 -14
- package/src/components/crm/generic/styles/GenericDetail.module.scss +132 -118
- package/src/components/crm/generic/styles/GenericIndex.module.scss +1 -1
- package/src/components/crm/styles/DataGrid.module.scss +2 -3
- package/src/components/crm/styles/Field.module.scss +13 -11
- package/src/components/crm/styles/Form.module.scss +1 -1
- package/src/components/crm/styles/Navigation.module.scss +12 -10
- package/src/components/styles/global.css +40 -6
|
@@ -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
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
_optionItem = {
|
|
31
|
-
..._optionItem,
|
|
32
|
-
[b]: a[b],
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
});
|
|
62
|
+
setSelectValue(_values);
|
|
63
|
+
}, [inputValue]);
|
|
36
64
|
|
|
37
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const SelectComponent = isCreatable ? CreatableSelect : Select;
|
|
43
98
|
|
|
44
99
|
return (
|
|
45
|
-
<
|
|
46
|
-
closeMenuOnSelect={multi
|
|
100
|
+
<SelectComponent
|
|
101
|
+
closeMenuOnSelect={!multi}
|
|
47
102
|
isClearable
|
|
48
103
|
isSearchable
|
|
49
104
|
isMulti={multi}
|
|
50
105
|
filterOption={createFilter({ ignoreAccents: false })}
|
|
51
|
-
components={{
|
|
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={
|
|
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:
|
|
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(-
|
|
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:
|
|
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.
|
|
6
|
+
transform: translateY(-40%) translateX(10px) scale(0.9);
|
|
7
7
|
opacity: 1;
|
|
8
|
-
padding: 0
|
|
8
|
+
padding: 0 0.25rem;
|
|
9
9
|
background: var(--tertiary-color);
|
|
10
|
-
font-weight:
|
|
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:
|
|
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 = (
|
|
440
|
-
|
|
441
|
-
|
|
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
|
-
|
|
537
|
-
|
|
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
|
|
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 (
|
|
1381
|
-
|
|
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
|
|