@visns-studio/visns-components 5.6.14 → 5.7.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.
package/package.json CHANGED
@@ -2,8 +2,6 @@
2
2
  "dependencies": {
3
3
  "@emotion/is-prop-valid": "^1.3.1",
4
4
  "@fontsource/barlow": "^5.2.5",
5
- "@inovua/reactdatagrid-community": "^5.10.2",
6
- "@inovua/reactdatagrid-enterprise": "^5.10.2",
7
5
  "@nivo/bar": "^0.88.0",
8
6
  "@nivo/core": "^0.88.0",
9
7
  "@nivo/line": "^0.88.0",
@@ -16,7 +14,6 @@
16
14
  "akar-icons": "^1.9.31",
17
15
  "array-move": "^4.0.0",
18
16
  "awesome-debounce-promise": "^2.1.0",
19
- "axios": "^1.9.0",
20
17
  "browser-image-compression": "^2.0.2",
21
18
  "dayjs": "^1.11.13",
22
19
  "fabric": "^6.6.4",
@@ -33,7 +30,6 @@
33
30
  "quill-image-uploader": "^1.3.0",
34
31
  "react-big-calendar": "^1.18.0",
35
32
  "react-color": "^2.19.3",
36
- "react-confirm-alert": "^3.0.6",
37
33
  "react-copy-to-clipboard": "^5.1.0",
38
34
  "react-datepicker": "^8.3.0",
39
35
  "react-dropzone": "^14.3.8",
@@ -84,7 +80,7 @@
84
80
  "react-dom": "^17.0.0 || ^18.0.0"
85
81
  },
86
82
  "name": "@visns-studio/visns-components",
87
- "version": "5.6.14",
83
+ "version": "5.7.1",
88
84
  "description": "Various packages to assist in the development of our Custom Applications.",
89
85
  "main": "src/index.js",
90
86
  "files": [
@@ -1,6 +1,5 @@
1
1
  import React, { useCallback, useEffect, useState } from 'react';
2
2
  import { useNavigate } from 'react-router-dom';
3
- import axios from 'axios';
4
3
  import debounce from 'lodash.debounce';
5
4
  import moment from 'moment';
6
5
  import numeral from 'numeral';
@@ -10,9 +9,10 @@ import NumberFilter from '@visns-studio/visns-datagrid-enterprise/NumberFilter';
10
9
  import SelectFilter from '@visns-studio/visns-datagrid-enterprise/SelectFilter';
11
10
  import DateFilter from '@visns-studio/visns-datagrid-enterprise/DateFilter';
12
11
  import ReactDataGrid from '@visns-studio/visns-datagrid-enterprise';
13
- import { confirmAlert } from 'react-confirm-alert';
14
12
  import { toast } from 'react-toastify';
15
13
  import Toggle from 'react-toggle';
14
+ import fetchUtil from '../../utils/fetchUtil';
15
+ import { confirmDialog } from '../utils/ConfirmDialog';
16
16
  import {
17
17
  Backspace,
18
18
  Check,
@@ -32,7 +32,6 @@ import {
32
32
  } from 'akar-icons';
33
33
 
34
34
  import '@visns-studio/visns-datagrid-enterprise/index.css';
35
- import 'react-confirm-alert/src/react-confirm-alert.css';
36
35
  import 'react-toggle/style.css';
37
36
 
38
37
  import CustomFetch from '../crm/Fetch';
@@ -82,7 +81,7 @@ const loadData = async (
82
81
  }
83
82
 
84
83
  try {
85
- const response = await axios.post(url, params);
84
+ const response = await fetchUtil.post(url, params);
86
85
  const { data, total } = response.data;
87
86
  return { data, count: total };
88
87
  } catch (error) {
@@ -171,11 +170,12 @@ const DataGrid = ({
171
170
  modalOpen('update', d.id);
172
171
  break;
173
172
  case 'delete':
174
- confirmAlert({
173
+ confirmDialog({
175
174
  title: 'Delete Item',
176
175
  message: `Are you sure you want to delete ${
177
176
  d.name ? d.name : d.label ? d.label : 'this item'
178
177
  }?`,
178
+ data: d, // Pass the row data
179
179
  buttons: [
180
180
  {
181
181
  label: 'Yes',
@@ -213,9 +213,10 @@ const DataGrid = ({
213
213
  });
214
214
  break;
215
215
  default:
216
- confirmAlert({
216
+ confirmDialog({
217
217
  title: s.title,
218
218
  message: '',
219
+ data: d, // Pass the row data
219
220
  buttons: [
220
221
  {
221
222
  label: 'Yes',
@@ -1208,7 +1209,69 @@ const DataGrid = ({
1208
1209
  onClick={(e) => {
1209
1210
  e.preventDefault();
1210
1211
  e.stopPropagation();
1211
- handleSettingClick(setting, data);
1212
+
1213
+ // Actions that should have confirmation dialogs
1214
+ const actionsRequiringConfirmation = [
1215
+ 'complete',
1216
+ 'primary',
1217
+ 'ssa',
1218
+ ];
1219
+
1220
+ // If this action requires confirmation and is not delete (which has its own confirmation)
1221
+ if (
1222
+ actionsRequiringConfirmation.includes(
1223
+ setting.id
1224
+ ) &&
1225
+ setting.id !== 'delete'
1226
+ ) {
1227
+ // Get tooltip content for the message
1228
+ const tooltipContent =
1229
+ setting.active &&
1230
+ data.hasOwnProperty(
1231
+ setting.active.id
1232
+ ) &&
1233
+ data[setting.active.id] ===
1234
+ setting.active.value
1235
+ ? setting.active.title
1236
+ : setting.title;
1237
+
1238
+ // Create a message based on the tooltip
1239
+ let message = `Are you sure you want to ${tooltipContent.toLowerCase()}?`;
1240
+
1241
+ // Add item identifier if available
1242
+ const itemName =
1243
+ data.name ||
1244
+ data.label ||
1245
+ data.title ||
1246
+ data.id;
1247
+ if (itemName) {
1248
+ message += ` Item: "${itemName}"`;
1249
+ }
1250
+
1251
+ // Show confirmation dialog
1252
+ confirmDialog({
1253
+ title: tooltipContent,
1254
+ message: message,
1255
+ data: data,
1256
+ buttons: [
1257
+ {
1258
+ label: 'Yes',
1259
+ onClick: () =>
1260
+ handleSettingClick(
1261
+ setting,
1262
+ data
1263
+ ),
1264
+ },
1265
+ {
1266
+ label: 'No',
1267
+ onClick: () => {},
1268
+ },
1269
+ ],
1270
+ });
1271
+ } else {
1272
+ // For actions that don't need confirmation or already have it (delete)
1273
+ handleSettingClick(setting, data);
1274
+ }
1212
1275
  }}
1213
1276
  >
1214
1277
  {renderSetting(setting, data)}
@@ -1,15 +1,13 @@
1
1
  import React, { useEffect, useState, useCallback } from 'react';
2
2
  import { motion } from 'framer-motion';
3
3
  import { useDropzone } from 'react-dropzone';
4
- import { confirmAlert } from 'react-confirm-alert';
5
4
  import { CircleX, File, TrashCan } from 'akar-icons';
6
5
  import { toast } from 'react-toastify';
7
6
  import { arrayMoveImmutable } from 'array-move';
8
7
  import Popup from 'reactjs-popup';
9
8
  import imageCompression from 'browser-image-compression';
10
9
 
11
- import 'react-confirm-alert/src/react-confirm-alert.css';
12
-
10
+ import { confirmDialog } from '../utils/ConfirmDialog';
13
11
  import CustomFetch from '../crm/Fetch';
14
12
  import SortableList from './sorting/List';
15
13
 
@@ -122,9 +120,10 @@ function VisnsDropZone({ fetchData, files, settings }) {
122
120
 
123
121
  const handleDelete = (id, name) => {
124
122
  if (id && id > 0) {
125
- confirmAlert({
123
+ confirmDialog({
126
124
  title: 'Confirm to Delete File',
127
125
  message: 'Are you sure you want to delete ' + name,
126
+ data: { name: name }, // Pass the file name as data
128
127
  buttons: [
129
128
  {
130
129
  label: 'Yes',
@@ -5,11 +5,9 @@ import slugify from 'react-slugify';
5
5
  import moment from 'moment';
6
6
  import Reveal from 'react-reveal/Reveal';
7
7
  import parse from 'html-react-parser';
8
- import { confirmAlert } from 'react-confirm-alert';
9
8
  import { toast } from 'react-toastify';
10
9
  import { Copy, TrashBin } from 'akar-icons';
11
-
12
- import 'react-confirm-alert/src/react-confirm-alert.css';
10
+ import { confirmDialog } from '../utils/ConfirmDialog';
13
11
 
14
12
  function Form(props) {
15
13
  const { dataId, navigate, form } = props;
@@ -245,9 +243,10 @@ function Form(props) {
245
243
 
246
244
  const handleDelete = (e) => {
247
245
  if (e) {
248
- confirmAlert({
246
+ confirmDialog({
249
247
  title: 'Delete this entry',
250
248
  message: 'Are you sure you want to delete it?',
249
+ data: data, // Pass the data object for context
251
250
  buttons: [
252
251
  {
253
252
  label: 'Yes',
@@ -4,8 +4,6 @@ import Reveal from 'react-reveal/Reveal';
4
4
  import { ToastContainer } from 'react-toastify';
5
5
  import CmsForm from '../Form';
6
6
 
7
- import 'react-confirm-alert/src/react-confirm-alert.css';
8
-
9
7
  function CmsDetail({ setting }) {
10
8
  const { dataId } = useParams();
11
9
  const navigate = useNavigate();
@@ -1,10 +1,16 @@
1
- import React, { useState, useEffect } from 'react';
2
- import axios from 'axios';
1
+ import React, { useState, useEffect, useCallback, useMemo, memo } from 'react';
3
2
  import AwesomeDebouncePromise from 'awesome-debounce-promise';
4
3
  import { ToggleOffFill, ToggleOnFill } from 'akar-icons';
5
4
  import styles from './styles/Autocomplete.module.scss';
5
+ import fetchUtil from '../../utils/fetchUtil';
6
6
 
7
- const VisnsAutocomplete = (props) => {
7
+ // Create the debounced function outside the component to prevent recreation
8
+ const createDebouncedSearch = (fn) =>
9
+ AwesomeDebouncePromise(fn, 500, {
10
+ key: (id) => id,
11
+ });
12
+
13
+ const VisnsAutocomplete = memo((props) => {
8
14
  const {
9
15
  api,
10
16
  country,
@@ -15,160 +21,188 @@ const VisnsAutocomplete = (props) => {
15
21
  setFormData,
16
22
  style,
17
23
  } = props;
24
+
18
25
  const [search, setSearch] = useState('');
19
26
  const [results, setResults] = useState([]);
20
27
  const [isLoading, setIsLoading] = useState(false);
21
28
  const [overwrite, setOverwrite] = useState(false);
22
29
 
23
- const performSearch = (id, value) => {
24
- if (id > 0) {
25
- if (value === '') {
26
- setResults([]);
27
- setIsLoading(false);
28
- } else {
29
- let url = `https://api.mapbox.com/geocoding/v5/mapbox.places/${value}.json?types=address,poi&access_token=${api}`;
30
+ // Memoize URL building logic to avoid recalculations
31
+ const buildSearchUrl = useCallback(
32
+ (value) => {
33
+ if (!value || !api) return null;
30
34
 
31
- if (country) {
32
- url += `&country=${country}`;
33
- }
35
+ let url = `https://api.mapbox.com/geocoding/v5/mapbox.places/${value}.json?types=address,poi&access_token=${api}`;
34
36
 
35
- if (mapbox) {
36
- if (mapbox.proximity) {
37
- url += `&proximity=${mapbox.proximity}`;
38
- }
37
+ if (country) {
38
+ url += `&country=${country}`;
39
+ }
39
40
 
40
- if (mapbox.bbox) {
41
- url += `&bbox=${mapbox.bbox}`;
42
- }
41
+ if (mapbox) {
42
+ if (mapbox.proximity) {
43
+ url += `&proximity=${mapbox.proximity}`;
43
44
  }
44
45
 
45
- axios
46
- .get(url)
47
- .then((response) => {
48
- setResults(response.data.features);
49
- setIsLoading(false);
50
- })
51
- .catch(function (error) {
52
- console.log(error);
53
- });
46
+ if (mapbox.bbox) {
47
+ url += `&bbox=${mapbox.bbox}`;
48
+ }
54
49
  }
55
- }
56
- };
57
50
 
58
- const handleSearchChange = async (e) => {
59
- if (e) {
60
- setSearch(e.target.value);
51
+ return url;
52
+ },
53
+ [api, country, mapbox]
54
+ );
55
+
56
+ const performSearch = useCallback(
57
+ (id, value) => {
58
+ if (id <= 0 || !value) {
59
+ setResults([]);
60
+ setIsLoading(false);
61
+ return;
62
+ }
63
+
64
+ const url = buildSearchUrl(value);
65
+ if (!url) return;
66
+
67
+ fetchUtil
68
+ .get(url)
69
+ .then((response) => {
70
+ setResults(response.data.features || []);
71
+ setIsLoading(false);
72
+ })
73
+ .catch((error) => {
74
+ console.log(error);
75
+ setIsLoading(false);
76
+ });
77
+ },
78
+ [buildSearchUrl]
79
+ );
80
+
81
+ // Create the debounced search function using our memoized performSearch
82
+ const performSearchDebounced = useMemo(
83
+ () => createDebouncedSearch(performSearch),
84
+ [performSearch]
85
+ );
86
+
87
+ const handleSearchChange = useCallback(
88
+ async (e) => {
89
+ if (!e) return;
90
+
91
+ const newValue = e.target.value;
92
+ setSearch(newValue);
61
93
  setFormData((prevState) => ({
62
94
  ...prevState,
63
- [field]: e.target.value,
95
+ [field]: newValue,
64
96
  }));
65
97
 
66
- if (overwrite === false) {
98
+ if (!overwrite) {
67
99
  setIsLoading(true);
100
+ await performSearchDebounced(1, newValue);
68
101
  }
102
+ },
103
+ [field, overwrite, performSearchDebounced, setFormData]
104
+ );
69
105
 
70
- if (overwrite === false) {
71
- await performSearchDebounced(1, e.target.value);
72
- }
73
- }
74
- };
75
-
76
- const performSearchDebounced = AwesomeDebouncePromise(performSearch, 500, {
77
- key: (id, value) => id,
78
- });
106
+ const handleItemClicked = useCallback(
107
+ (place) => {
108
+ const address = place.address || '';
109
+ const text = place.text || '';
110
+ const search = address && text ? `${address} ${text}` : text;
79
111
 
80
- const handleItemClicked = (place) => {
81
- const address =
82
- place.address !== undefined && place.address !== null
83
- ? place.address
84
- : '';
85
- const text =
86
- place.text !== undefined && place.text !== null ? place.text : '';
87
-
88
- const search = address && text ? `${address} ${text}` : text;
89
- setSearch(search);
90
- setResults([]);
91
- onSelect(place, field);
92
- };
93
-
94
- const handleOverwrite = () => {
95
- if (overwrite === false) {
96
- setOverwrite(true);
112
+ setSearch(search);
97
113
  setResults([]);
98
- } else {
99
- setOverwrite(false);
100
- }
101
- };
114
+ onSelect(place, field);
115
+ },
116
+ [field, onSelect]
117
+ );
102
118
 
103
- useEffect(() => {
104
- if (overwrite === false) {
105
- if (search !== '') {
106
- performSearch();
119
+ const handleOverwrite = useCallback(() => {
120
+ setOverwrite((prevState) => {
121
+ if (!prevState) {
122
+ setResults([]);
107
123
  }
124
+ return !prevState;
125
+ });
126
+ }, []);
127
+
128
+ // Effect to handle overwrite toggle
129
+ useEffect(() => {
130
+ if (!overwrite && search) {
131
+ setIsLoading(true);
132
+ performSearch(1, search);
108
133
  }
109
- }, [overwrite]);
134
+ }, [overwrite, search, performSearch]);
110
135
 
136
+ // Effect to sync with inputValue prop
111
137
  useEffect(() => {
112
- setSearch(inputValue);
138
+ if (inputValue !== undefined) {
139
+ setSearch(inputValue);
140
+ }
113
141
  }, [inputValue]);
114
142
 
143
+ // Memoize the input style to prevent recalculation
144
+ const inputStyle = useMemo(
145
+ () => ({
146
+ height: style?.multi_select_height || '52px',
147
+ }),
148
+ [style]
149
+ );
150
+
115
151
  return (
116
- <>
117
- <div className={styles.AutocompletePlace}>
118
- <input
119
- className={styles['AutocompletePlace-input']}
120
- style={{
121
- height:
122
- style && style.multi_select_height
123
- ? style.multi_select_height
124
- : '52px',
125
- }}
126
- type="text"
127
- value={search || ''}
128
- onChange={handleSearchChange}
129
- placeholder="Type an address"
130
- autoComplete="off"
152
+ <div className={styles.AutocompletePlace}>
153
+ <input
154
+ className={styles['AutocompletePlace-input']}
155
+ style={inputStyle}
156
+ type="text"
157
+ value={search || ''}
158
+ onChange={handleSearchChange}
159
+ placeholder="Type an address"
160
+ autoComplete="off"
161
+ />
162
+
163
+ {overwrite ? (
164
+ <ToggleOnFill
165
+ data-tooltip-id="system-tooltip"
166
+ data-tooltip-content="Enable Autocomplete"
167
+ strokeWidth={2}
168
+ size={24}
169
+ onClick={handleOverwrite}
170
+ className={styles.toggleActive}
131
171
  />
132
- {overwrite === false ? (
133
- <ToggleOffFill
134
- data-tooltip-id="system-tooltip"
135
- data-tooltip-content="Disable Autocomplete"
136
- strokeWidth={2}
137
- size={24}
138
- onClick={handleOverwrite}
139
- />
140
- ) : (
141
- <ToggleOnFill
142
- data-tooltip-id="system-tooltip"
143
- data-tooltip-content="Enable Autocomplete"
144
- strokeWidth={2}
145
- size={24}
146
- onClick={handleOverwrite}
147
- className={styles.toggleActive}
148
- />
149
- )}
150
- {results.length > 0 ? (
151
- <ul className="AutocompletePlace-results">
152
- {results.map((place) => (
153
- <li
154
- key={place.id}
155
- className={styles['AutocompletePlace-items']}
156
- onClick={() => handleItemClicked(place)}
157
- >
158
- {place.place_name}
159
- </li>
160
- ))}
161
-
162
- {isLoading && (
163
- <li className={styles['AutocompletePlace-items']}>
164
- Loading...
165
- </li>
166
- )}
167
- </ul>
168
- ) : null}
169
- </div>
170
- </>
172
+ ) : (
173
+ <ToggleOffFill
174
+ data-tooltip-id="system-tooltip"
175
+ data-tooltip-content="Disable Autocomplete"
176
+ strokeWidth={2}
177
+ size={24}
178
+ onClick={handleOverwrite}
179
+ />
180
+ )}
181
+
182
+ {results.length > 0 && (
183
+ <ul className="AutocompletePlace-results">
184
+ {results.map((place) => (
185
+ <li
186
+ key={place.id}
187
+ className={styles['AutocompletePlace-items']}
188
+ onClick={() => handleItemClicked(place)}
189
+ >
190
+ {place.place_name}
191
+ </li>
192
+ ))}
193
+
194
+ {isLoading && (
195
+ <li className={styles['AutocompletePlace-items']}>
196
+ Loading...
197
+ </li>
198
+ )}
199
+ </ul>
200
+ )}
201
+ </div>
171
202
  );
172
- };
203
+ });
204
+
205
+ // Add display name for debugging
206
+ VisnsAutocomplete.displayName = 'VisnsAutocomplete';
173
207
 
174
208
  export default VisnsAutocomplete;