@visns-studio/visns-components 5.8.1 → 5.8.3
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 +12 -12
- package/src/components/cms/DropZone.jsx +110 -74
- package/src/components/cms/Gallery.jsx +147 -0
- package/src/components/cms/sorting/styles/Item.module.scss +5 -5
- package/src/components/cms/styles/DropZone.module.css +347 -0
- package/src/components/cms/styles/Gallery.module.css +197 -0
- package/src/components/crm/TableFilter.jsx +180 -21
- package/src/components/crm/generic/GenericIndex.jsx +184 -32
- package/src/components/crm/generic/GenericReport.jsx +1 -1
- package/src/components/crm/generic/styles/{SweetAlertCustom.css → SweetAlert.module.css} +22 -22
- package/src/components/utils/ConfirmDialog.js +1 -1
package/package.json
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
"@fontsource/barlow": "^5.2.5",
|
|
5
5
|
"@inovua/reactdatagrid-community": "^5.10.2",
|
|
6
6
|
"@inovua/reactdatagrid-enterprise": "^5.10.2",
|
|
7
|
-
"@nivo/bar": "^0.
|
|
8
|
-
"@nivo/core": "^0.
|
|
9
|
-
"@nivo/line": "^0.
|
|
10
|
-
"@nivo/pie": "^0.
|
|
7
|
+
"@nivo/bar": "^0.95.0",
|
|
8
|
+
"@nivo/core": "^0.95.0",
|
|
9
|
+
"@nivo/line": "^0.95.0",
|
|
10
|
+
"@nivo/pie": "^0.95.0",
|
|
11
11
|
"@tinymce/tinymce-react": "^6.1.0",
|
|
12
12
|
"@visns-studio/visns-datagrid-community": "^1.0.14",
|
|
13
13
|
"@visns-studio/visns-datagrid-enterprise": "^1.0.14",
|
|
@@ -18,14 +18,14 @@
|
|
|
18
18
|
"awesome-debounce-promise": "^2.1.0",
|
|
19
19
|
"browser-image-compression": "^2.0.2",
|
|
20
20
|
"dayjs": "^1.11.13",
|
|
21
|
-
"fabric": "^6.6.
|
|
21
|
+
"fabric": "^6.6.5",
|
|
22
22
|
"file-saver": "^2.0.5",
|
|
23
|
-
"framer-motion": "^12.
|
|
24
|
-
"html-react-parser": "^5.2.
|
|
23
|
+
"framer-motion": "^12.11.0",
|
|
24
|
+
"html-react-parser": "^5.2.5",
|
|
25
25
|
"lodash": "^4.17.21",
|
|
26
26
|
"lodash.debounce": "^4.0.8",
|
|
27
27
|
"moment": "^2.30.1",
|
|
28
|
-
"motion": "^12.
|
|
28
|
+
"motion": "^12.11.0",
|
|
29
29
|
"numeral": "^2.0.6",
|
|
30
30
|
"pluralize": "^8.0.0",
|
|
31
31
|
"qrcode.react": "^4.2.0",
|
|
@@ -60,12 +60,12 @@
|
|
|
60
60
|
"validator": "^13.15.0",
|
|
61
61
|
"vite": "^6.3.5",
|
|
62
62
|
"yarn": "^1.22.22",
|
|
63
|
-
"yet-another-react-lightbox": "^3.23.
|
|
63
|
+
"yet-another-react-lightbox": "^3.23.1"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
66
|
"@babel/core": "^7.27.1",
|
|
67
67
|
"@babel/plugin-transform-runtime": "^7.27.1",
|
|
68
|
-
"@babel/preset-env": "^7.27.
|
|
68
|
+
"@babel/preset-env": "^7.27.2",
|
|
69
69
|
"@babel/preset-react": "^7.27.1",
|
|
70
70
|
"babel-loader": "^10.0.0",
|
|
71
71
|
"copy-webpack-plugin": "^13.0.0",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"mini-css-extract-plugin": "^2.9.2",
|
|
75
75
|
"react": "^18.3.1",
|
|
76
76
|
"react-dom": "^18.3.1",
|
|
77
|
-
"sass": "^1.
|
|
77
|
+
"sass": "^1.88.0",
|
|
78
78
|
"sass-loader": "^16.0.5"
|
|
79
79
|
},
|
|
80
80
|
"peerDependencies": {
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
"react-dom": "^17.0.0 || ^18.0.0"
|
|
83
83
|
},
|
|
84
84
|
"name": "@visns-studio/visns-components",
|
|
85
|
-
"version": "5.8.
|
|
85
|
+
"version": "5.8.3",
|
|
86
86
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
87
87
|
"main": "src/index.js",
|
|
88
88
|
"files": [
|
|
@@ -9,7 +9,8 @@ import imageCompression from 'browser-image-compression';
|
|
|
9
9
|
|
|
10
10
|
import { confirmDialog } from '../utils/ConfirmDialog';
|
|
11
11
|
import CustomFetch from '../crm/Fetch';
|
|
12
|
-
import
|
|
12
|
+
import Gallery from './Gallery';
|
|
13
|
+
import styles from './styles/DropZone.module.css';
|
|
13
14
|
|
|
14
15
|
function VisnsDropZone({ fetchData, files, settings }) {
|
|
15
16
|
const [data, setData] = useState(files);
|
|
@@ -210,10 +211,92 @@ function VisnsDropZone({ fetchData, files, settings }) {
|
|
|
210
211
|
|
|
211
212
|
return (
|
|
212
213
|
<>
|
|
213
|
-
{
|
|
214
|
-
<div className=
|
|
215
|
-
|
|
216
|
-
|
|
214
|
+
<div className={styles.dropzoneWrapper}>
|
|
215
|
+
<div className={styles.progress}>
|
|
216
|
+
<motion.div
|
|
217
|
+
className={styles.progressBar}
|
|
218
|
+
initial={{ width: '0%' }}
|
|
219
|
+
animate={{
|
|
220
|
+
width: loadingProgress + '%',
|
|
221
|
+
}}
|
|
222
|
+
transition={{
|
|
223
|
+
duration: 1,
|
|
224
|
+
ease: [0.165, 0.84, 0.44, 1.0],
|
|
225
|
+
}}
|
|
226
|
+
/>
|
|
227
|
+
</div>
|
|
228
|
+
<section className={styles.dropzoneContainer}>
|
|
229
|
+
<div {...getRootProps()}>
|
|
230
|
+
<input {...getInputProps()} />
|
|
231
|
+
{isDragActive ? (
|
|
232
|
+
<p>Drop the files here ...</p>
|
|
233
|
+
) : (
|
|
234
|
+
<p>
|
|
235
|
+
Drag 'n' drop some files here, or click to
|
|
236
|
+
select files
|
|
237
|
+
</p>
|
|
238
|
+
)}
|
|
239
|
+
</div>
|
|
240
|
+
{settings.type !== 'gallery' ? (
|
|
241
|
+
<ul className={styles.dropzoneFiles}>
|
|
242
|
+
{data && data.length > 0
|
|
243
|
+
? data.map((a, b) => (
|
|
244
|
+
<li
|
|
245
|
+
onClick={() => {
|
|
246
|
+
handleDownload(a.id);
|
|
247
|
+
}}
|
|
248
|
+
key={'tf-' + b}
|
|
249
|
+
className={styles.dropzoneFileItem}
|
|
250
|
+
>
|
|
251
|
+
<div
|
|
252
|
+
className={
|
|
253
|
+
styles.dropzoneFileContent
|
|
254
|
+
}
|
|
255
|
+
>
|
|
256
|
+
<File
|
|
257
|
+
strokeWidth={2}
|
|
258
|
+
size={20}
|
|
259
|
+
className={
|
|
260
|
+
styles.dropzoneFileIcon
|
|
261
|
+
}
|
|
262
|
+
/>
|
|
263
|
+
<span
|
|
264
|
+
className={
|
|
265
|
+
styles.dropzoneFileName
|
|
266
|
+
}
|
|
267
|
+
>
|
|
268
|
+
{a.file_name}
|
|
269
|
+
</span>
|
|
270
|
+
</div>
|
|
271
|
+
<button
|
|
272
|
+
onClick={(e) => {
|
|
273
|
+
e.preventDefault();
|
|
274
|
+
e.stopPropagation();
|
|
275
|
+
handleDelete(
|
|
276
|
+
a.id,
|
|
277
|
+
a.file_name
|
|
278
|
+
);
|
|
279
|
+
}}
|
|
280
|
+
className={
|
|
281
|
+
styles.dropzoneDeleteBtn
|
|
282
|
+
}
|
|
283
|
+
aria-label="Delete file"
|
|
284
|
+
title="Delete file"
|
|
285
|
+
>
|
|
286
|
+
<TrashCan
|
|
287
|
+
strokeWidth={2}
|
|
288
|
+
size={18}
|
|
289
|
+
/>
|
|
290
|
+
</button>
|
|
291
|
+
</li>
|
|
292
|
+
))
|
|
293
|
+
: null}
|
|
294
|
+
</ul>
|
|
295
|
+
) : null}
|
|
296
|
+
</section>
|
|
297
|
+
{settings.type === 'gallery' ? (
|
|
298
|
+
<div className={styles.imageContainer}>
|
|
299
|
+
<Gallery
|
|
217
300
|
items={data}
|
|
218
301
|
onSortEnd={onSortEnd}
|
|
219
302
|
axis="xy"
|
|
@@ -222,115 +305,68 @@ function VisnsDropZone({ fetchData, files, settings }) {
|
|
|
222
305
|
handleDelete={handleDelete}
|
|
223
306
|
handleEdit={handleEdit}
|
|
224
307
|
useDragHandle
|
|
225
|
-
|
|
308
|
+
distance={1}
|
|
226
309
|
/>
|
|
227
|
-
|
|
228
|
-
</div>
|
|
229
|
-
) : null}
|
|
230
|
-
<div className="progress">
|
|
231
|
-
<motion.div
|
|
232
|
-
className="progress__bar"
|
|
233
|
-
initial={{ width: '0%' }}
|
|
234
|
-
animate={{
|
|
235
|
-
width: loadingProgress + '%',
|
|
236
|
-
}}
|
|
237
|
-
transition={{
|
|
238
|
-
duration: 1,
|
|
239
|
-
ease: [0.165, 0.84, 0.44, 1.0],
|
|
240
|
-
}}
|
|
241
|
-
/>
|
|
242
|
-
</div>
|
|
243
|
-
<section className="dropzone__container">
|
|
244
|
-
<div {...getRootProps()}>
|
|
245
|
-
<input {...getInputProps()} />
|
|
246
|
-
{isDragActive ? (
|
|
247
|
-
<p>Drop the files here ...</p>
|
|
248
|
-
) : (
|
|
249
|
-
<p>
|
|
250
|
-
Drag 'n' drop some files here, or click to select
|
|
251
|
-
files
|
|
252
|
-
</p>
|
|
253
|
-
)}
|
|
254
|
-
</div>
|
|
255
|
-
{settings.type !== 'gallery' ? (
|
|
256
|
-
<ul className="dropzone__files">
|
|
257
|
-
{data && data.length > 0
|
|
258
|
-
? data.map((a, b) => (
|
|
259
|
-
<li
|
|
260
|
-
onClick={() => {
|
|
261
|
-
handleDownload(a.id);
|
|
262
|
-
}}
|
|
263
|
-
key={'tf-' + b}
|
|
264
|
-
>
|
|
265
|
-
<File strokeWidth={2} size={18} />
|
|
266
|
-
{a.file_name}
|
|
267
|
-
<button
|
|
268
|
-
onClick={(e) => {
|
|
269
|
-
e.preventDefault();
|
|
270
|
-
e.stopPropagation();
|
|
271
|
-
|
|
272
|
-
handleDelete(a.id, a.file_name);
|
|
273
|
-
}}
|
|
274
|
-
>
|
|
275
|
-
<TrashCan strokeWidth={2} size={18} />
|
|
276
|
-
</button>
|
|
277
|
-
</li>
|
|
278
|
-
))
|
|
279
|
-
: null}
|
|
280
|
-
</ul>
|
|
310
|
+
</div>
|
|
281
311
|
) : null}
|
|
282
|
-
</
|
|
312
|
+
</div>
|
|
283
313
|
<Popup
|
|
284
314
|
open={modalShow}
|
|
285
315
|
onClose={modalClose}
|
|
286
316
|
closeOnDocumentClick={false}
|
|
287
317
|
>
|
|
288
|
-
<div className=
|
|
289
|
-
<div className=
|
|
290
|
-
<div className=
|
|
318
|
+
<div className={`${styles.modalwrap} top--modal`}>
|
|
319
|
+
<div className={styles.modal}>
|
|
320
|
+
<div className={styles.modalHeader}>
|
|
291
321
|
<h1>Update File Metadata</h1>
|
|
292
322
|
<button
|
|
293
|
-
className=
|
|
323
|
+
className={styles.modalClose}
|
|
294
324
|
onClick={modalClose}
|
|
295
325
|
>
|
|
296
326
|
<CircleX strokeWidth={1} size={24} />
|
|
297
327
|
</button>
|
|
298
328
|
</div>
|
|
299
|
-
<div className=
|
|
329
|
+
<div className={styles.modalContent}>
|
|
300
330
|
<div className="formcontainer">
|
|
301
331
|
<form
|
|
302
332
|
className="modalForm"
|
|
303
333
|
onSubmit={handleSubmit}
|
|
304
334
|
>
|
|
305
|
-
<div
|
|
306
|
-
|
|
335
|
+
<div
|
|
336
|
+
className={`${styles.formItem} fwItem`}
|
|
337
|
+
>
|
|
338
|
+
<label className={styles.fiLabel}>
|
|
307
339
|
<input
|
|
308
340
|
type="text"
|
|
309
341
|
name="title"
|
|
310
342
|
onChange={handleChange}
|
|
311
343
|
value={modalData.title}
|
|
312
344
|
/>
|
|
313
|
-
<span className=
|
|
345
|
+
<span className={styles.fiSpan}>
|
|
314
346
|
Title
|
|
315
347
|
</span>
|
|
316
348
|
</label>
|
|
317
349
|
</div>
|
|
318
|
-
<div
|
|
319
|
-
|
|
350
|
+
<div
|
|
351
|
+
className={`${styles.formItem} fwItem`}
|
|
352
|
+
>
|
|
353
|
+
<label className={styles.fiLabel}>
|
|
320
354
|
<textarea
|
|
321
355
|
name="description"
|
|
322
356
|
rows="5"
|
|
323
357
|
onChange={handleChange}
|
|
324
358
|
value={modalData.description}
|
|
325
359
|
></textarea>
|
|
326
|
-
<span className=
|
|
360
|
+
<span className={styles.fiSpan}>
|
|
327
361
|
Description
|
|
328
362
|
</span>
|
|
329
363
|
</label>
|
|
330
364
|
</div>
|
|
331
|
-
<div
|
|
365
|
+
<div
|
|
366
|
+
className={`${styles.formItem} fwItem lastItem`}
|
|
367
|
+
>
|
|
332
368
|
<button
|
|
333
|
-
className=
|
|
369
|
+
className={`btn ${styles.btnModalsave}`}
|
|
334
370
|
type="submit"
|
|
335
371
|
>
|
|
336
372
|
Save
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
SortableContainer,
|
|
4
|
+
SortableElement,
|
|
5
|
+
sortableHandle,
|
|
6
|
+
} from 'react-sortable-hoc';
|
|
7
|
+
import { ChevronVertical, Pencil, TrashCan } from 'akar-icons';
|
|
8
|
+
import styles from './styles/Gallery.module.css';
|
|
9
|
+
|
|
10
|
+
// Drag handle component
|
|
11
|
+
const DragHandle = sortableHandle(() => (
|
|
12
|
+
<div className={styles.galleryDragHandle}>
|
|
13
|
+
<ChevronVertical strokeWidth={1.5} size={16} />
|
|
14
|
+
</div>
|
|
15
|
+
));
|
|
16
|
+
|
|
17
|
+
// Utility function to truncate text
|
|
18
|
+
const truncateText = (text, maxLength) => {
|
|
19
|
+
if (text && text.length > maxLength) {
|
|
20
|
+
return `${text.substring(0, maxLength)}...`;
|
|
21
|
+
}
|
|
22
|
+
return text;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Sortable gallery item
|
|
26
|
+
const SortableGalleryItem = SortableElement(
|
|
27
|
+
({ value, handleEdit, handleDelete }) => {
|
|
28
|
+
const imageUrl = value.image_url || value.file_url;
|
|
29
|
+
const title =
|
|
30
|
+
value.file_title || value.label || value.file_name || 'Untitled';
|
|
31
|
+
const description = value.file_description || '';
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div className={styles.galleryItem}>
|
|
35
|
+
<DragHandle />
|
|
36
|
+
|
|
37
|
+
<div className={styles.galleryImage}>
|
|
38
|
+
{imageUrl ? (
|
|
39
|
+
<img src={imageUrl} alt={title} />
|
|
40
|
+
) : (
|
|
41
|
+
<img
|
|
42
|
+
src="data:image/png;base64,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"
|
|
43
|
+
alt="Placeholder"
|
|
44
|
+
/>
|
|
45
|
+
)}
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<div className={styles.galleryInfo}>
|
|
49
|
+
<h3 className={styles.galleryTitle}>
|
|
50
|
+
{truncateText(title, 30)}
|
|
51
|
+
</h3>
|
|
52
|
+
{description && (
|
|
53
|
+
<p className={styles.galleryDescription}>
|
|
54
|
+
{truncateText(description, 100)}
|
|
55
|
+
</p>
|
|
56
|
+
)}
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<div className={styles.galleryControls}>
|
|
60
|
+
{handleEdit && (
|
|
61
|
+
<button
|
|
62
|
+
className={`${styles.galleryControl} ${styles.edit}`}
|
|
63
|
+
onClick={(e) => {
|
|
64
|
+
e.preventDefault();
|
|
65
|
+
e.stopPropagation();
|
|
66
|
+
handleEdit(value);
|
|
67
|
+
}}
|
|
68
|
+
title="Edit"
|
|
69
|
+
>
|
|
70
|
+
<Pencil strokeWidth={1.5} size={16} />
|
|
71
|
+
</button>
|
|
72
|
+
)}
|
|
73
|
+
|
|
74
|
+
{handleDelete && (
|
|
75
|
+
<button
|
|
76
|
+
className={`${styles.galleryControl} ${styles.delete}`}
|
|
77
|
+
onClick={(e) => {
|
|
78
|
+
e.preventDefault();
|
|
79
|
+
e.stopPropagation();
|
|
80
|
+
handleDelete(
|
|
81
|
+
value.id,
|
|
82
|
+
value.file_name || value.label
|
|
83
|
+
);
|
|
84
|
+
}}
|
|
85
|
+
title="Delete"
|
|
86
|
+
>
|
|
87
|
+
<TrashCan strokeWidth={1.5} size={16} />
|
|
88
|
+
</button>
|
|
89
|
+
)}
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
// Sortable gallery container
|
|
97
|
+
const Gallery = SortableContainer(({ items, handleEdit, handleDelete }) => {
|
|
98
|
+
if (!items || items.length === 0) {
|
|
99
|
+
return (
|
|
100
|
+
<div className={styles.galleryEmpty}>
|
|
101
|
+
<div className={styles.galleryEmptyIcon}>
|
|
102
|
+
<svg
|
|
103
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
104
|
+
width="48"
|
|
105
|
+
height="48"
|
|
106
|
+
viewBox="0 0 24 24"
|
|
107
|
+
fill="none"
|
|
108
|
+
stroke="currentColor"
|
|
109
|
+
strokeWidth="1"
|
|
110
|
+
strokeLinecap="round"
|
|
111
|
+
strokeLinejoin="round"
|
|
112
|
+
>
|
|
113
|
+
<rect
|
|
114
|
+
x="3"
|
|
115
|
+
y="3"
|
|
116
|
+
width="18"
|
|
117
|
+
height="18"
|
|
118
|
+
rx="2"
|
|
119
|
+
ry="2"
|
|
120
|
+
></rect>
|
|
121
|
+
<circle cx="8.5" cy="8.5" r="1.5"></circle>
|
|
122
|
+
<polyline points="21 15 16 10 5 21"></polyline>
|
|
123
|
+
</svg>
|
|
124
|
+
</div>
|
|
125
|
+
<p className={styles.galleryEmptyText}>
|
|
126
|
+
No images to display. Drag and drop files to upload.
|
|
127
|
+
</p>
|
|
128
|
+
</div>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return (
|
|
133
|
+
<div className={styles.galleryGrid}>
|
|
134
|
+
{items.map((value, index) => (
|
|
135
|
+
<SortableGalleryItem
|
|
136
|
+
key={`item-${index}`}
|
|
137
|
+
index={index}
|
|
138
|
+
value={value}
|
|
139
|
+
handleEdit={handleEdit}
|
|
140
|
+
handleDelete={handleDelete}
|
|
141
|
+
/>
|
|
142
|
+
))}
|
|
143
|
+
</div>
|
|
144
|
+
);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
export default Gallery;
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
|
|
12
12
|
:global {
|
|
13
13
|
.delete {
|
|
14
|
-
width:
|
|
15
|
-
height:
|
|
16
|
-
color:
|
|
14
|
+
width: 32px;
|
|
15
|
+
height: 32px;
|
|
16
|
+
color: #fff;
|
|
17
17
|
transition: color 0.2s ease;
|
|
18
18
|
cursor: pointer;
|
|
19
19
|
display: block;
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
.pencil-edit {
|
|
27
|
-
width:
|
|
28
|
-
height:
|
|
27
|
+
width: 32px;
|
|
28
|
+
height: 32px;
|
|
29
29
|
color: var(--primary-color);
|
|
30
30
|
transition: color 0.2s ease;
|
|
31
31
|
cursor: pointer;
|