@visns-studio/visns-components 5.2.12 → 5.2.14
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/Field.jsx +0 -3
- package/src/components/crm/generic/GenericDashboard.jsx +25 -21
- package/src/components/crm/generic/GenericDetail.jsx +93 -9
- package/src/components/crm/generic/GenericEditableTable.jsx +96 -17
- package/src/components/crm/generic/styles/GenericDashboard.module.scss +7 -0
package/package.json
CHANGED
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"react-dom": "^17.0.0 || ^18.0.0"
|
|
79
79
|
},
|
|
80
80
|
"name": "@visns-studio/visns-components",
|
|
81
|
-
"version": "5.2.
|
|
81
|
+
"version": "5.2.14",
|
|
82
82
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
83
83
|
"main": "src/index.js",
|
|
84
84
|
"files": [
|
|
@@ -242,7 +242,6 @@ function Field({
|
|
|
242
242
|
}
|
|
243
243
|
});
|
|
244
244
|
} else {
|
|
245
|
-
console.info(s);
|
|
246
245
|
filter = {
|
|
247
246
|
where: [
|
|
248
247
|
{
|
|
@@ -266,8 +265,6 @@ function Field({
|
|
|
266
265
|
});
|
|
267
266
|
}
|
|
268
267
|
|
|
269
|
-
console.info(filter, s.fields);
|
|
270
|
-
|
|
271
268
|
CustomFetch(s.child.url, 'POST', filter, function (result) {
|
|
272
269
|
childDropdownCallback({
|
|
273
270
|
id: s.child.id,
|
|
@@ -19,7 +19,7 @@ import styles from './styles/GenericDashboard.module.scss';
|
|
|
19
19
|
|
|
20
20
|
const toSnakeCase = (str) => str.replace(/[\s-]+/g, '_').toLowerCase();
|
|
21
21
|
|
|
22
|
-
function GenericDashboard({ setting, userProfile }) {
|
|
22
|
+
function GenericDashboard({ setting, userProfile, dynamicDashboard = false }) {
|
|
23
23
|
const swapy = useRef(null);
|
|
24
24
|
const container = useRef(null);
|
|
25
25
|
const navigate = useNavigate();
|
|
@@ -893,7 +893,9 @@ function GenericDashboard({ setting, userProfile }) {
|
|
|
893
893
|
</table>
|
|
894
894
|
</div>
|
|
895
895
|
) : (
|
|
896
|
-
<div
|
|
896
|
+
<div className={styles.noData}>
|
|
897
|
+
No data available
|
|
898
|
+
</div>
|
|
897
899
|
)}
|
|
898
900
|
</div>
|
|
899
901
|
);
|
|
@@ -1038,25 +1040,27 @@ function GenericDashboard({ setting, userProfile }) {
|
|
|
1038
1040
|
|
|
1039
1041
|
return (
|
|
1040
1042
|
<>
|
|
1041
|
-
|
|
1042
|
-
{
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1043
|
+
{dynamicDashboard && (
|
|
1044
|
+
<div className={styles.editButtonContainer}>
|
|
1045
|
+
{editMode && (
|
|
1046
|
+
<>
|
|
1047
|
+
<button
|
|
1048
|
+
className={styles.btn}
|
|
1049
|
+
style={{ marginRight: '10px' }}
|
|
1050
|
+
onClick={() => setShowAddWidgetModal(true)}
|
|
1051
|
+
>
|
|
1052
|
+
Add Widget
|
|
1053
|
+
</button>
|
|
1054
|
+
</>
|
|
1055
|
+
)}
|
|
1056
|
+
<button
|
|
1057
|
+
className={styles.btn}
|
|
1058
|
+
onClick={() => setEditMode((prev) => !prev)}
|
|
1059
|
+
>
|
|
1060
|
+
{editMode ? 'Done' : 'Edit Dashboard'}
|
|
1061
|
+
</button>
|
|
1062
|
+
</div>
|
|
1063
|
+
)}
|
|
1060
1064
|
<div className={styles.grid} ref={container}>
|
|
1061
1065
|
{renderWidgets()}
|
|
1062
1066
|
<Popup open={modalShow} onClose={closeModal}>
|
|
@@ -3,6 +3,7 @@ import '../../styles/global.css';
|
|
|
3
3
|
import React, { useEffect, useRef, useState } from 'react';
|
|
4
4
|
import { Link, useParams } from 'react-router-dom';
|
|
5
5
|
import { Calendar, momentLocalizer } from 'react-big-calendar';
|
|
6
|
+
import withDragAndDrop from 'react-big-calendar/lib/addons/dragAndDrop';
|
|
6
7
|
import moment from 'moment';
|
|
7
8
|
import parse from 'html-react-parser';
|
|
8
9
|
import Popup from 'reactjs-popup';
|
|
@@ -25,6 +26,7 @@ import {
|
|
|
25
26
|
|
|
26
27
|
import 'react-confirm-alert/src/react-confirm-alert.css';
|
|
27
28
|
import 'react-big-calendar/lib/css/react-big-calendar.css';
|
|
29
|
+
import 'react-big-calendar/lib/addons/dragAndDrop/styles.css';
|
|
28
30
|
|
|
29
31
|
import Breadcrumb from '../Breadcrumb';
|
|
30
32
|
import CustomFetch from '../Fetch';
|
|
@@ -39,6 +41,7 @@ import TableFilter from '../TableFilter';
|
|
|
39
41
|
import styles from './styles/GenericDetail.module.scss';
|
|
40
42
|
|
|
41
43
|
const localizer = momentLocalizer(moment);
|
|
44
|
+
const DnDCalendar = withDragAndDrop(Calendar);
|
|
42
45
|
|
|
43
46
|
function GenericDetail({
|
|
44
47
|
extraUrlParam,
|
|
@@ -884,11 +887,11 @@ function GenericDetail({
|
|
|
884
887
|
|
|
885
888
|
if (activeTabConfig && activeTabConfig.type) {
|
|
886
889
|
switch (activeTabConfig.type) {
|
|
887
|
-
case 'calendar':
|
|
890
|
+
case 'calendar': {
|
|
891
|
+
// Prepare events from your data, converting moment objects to Date objects
|
|
888
892
|
const events =
|
|
889
893
|
data?.[activeTabConfig?.dataKey]
|
|
890
894
|
?.map((event) => {
|
|
891
|
-
// Safely access keys using optional chaining and defaults
|
|
892
895
|
const keys =
|
|
893
896
|
activeTabConfig?.config?.keys || {};
|
|
894
897
|
const eventStart = event?.[keys.start]
|
|
@@ -898,23 +901,100 @@ function GenericDetail({
|
|
|
898
901
|
? moment(event[keys.end])
|
|
899
902
|
: null;
|
|
900
903
|
|
|
901
|
-
// Only include valid events
|
|
902
904
|
if (eventStart && eventEnd) {
|
|
903
905
|
return {
|
|
904
906
|
title:
|
|
905
907
|
event?.[keys.title] ||
|
|
906
|
-
'Untitled Event',
|
|
907
|
-
start: eventStart,
|
|
908
|
-
end: eventEnd,
|
|
908
|
+
'Untitled Event',
|
|
909
|
+
start: eventStart.toDate(),
|
|
910
|
+
end: eventEnd.toDate(),
|
|
911
|
+
entryId: event?.[keys.id],
|
|
909
912
|
};
|
|
910
913
|
}
|
|
911
|
-
|
|
912
|
-
return null; // Filter out invalid events
|
|
914
|
+
return null;
|
|
913
915
|
})
|
|
914
916
|
?.filter(Boolean) || [];
|
|
915
917
|
|
|
918
|
+
// Callback when an event is dragged and dropped to a new slot
|
|
919
|
+
const onEventDrop = async ({
|
|
920
|
+
event,
|
|
921
|
+
start,
|
|
922
|
+
end,
|
|
923
|
+
allDay,
|
|
924
|
+
}) => {
|
|
925
|
+
try {
|
|
926
|
+
await CustomFetch(
|
|
927
|
+
activeTabConfig.config.urls.update,
|
|
928
|
+
'PUT',
|
|
929
|
+
{
|
|
930
|
+
dataId: routeParams[
|
|
931
|
+
activeTabConfig.urlParam
|
|
932
|
+
],
|
|
933
|
+
key: activeTabConfig.dataKey,
|
|
934
|
+
id: event.entryId,
|
|
935
|
+
[activeTabConfig.config.keys.start]:
|
|
936
|
+
start,
|
|
937
|
+
[activeTabConfig.config.keys.end]:
|
|
938
|
+
end,
|
|
939
|
+
}
|
|
940
|
+
);
|
|
941
|
+
|
|
942
|
+
// Optionally, handle the response if needed
|
|
943
|
+
toast.success(
|
|
944
|
+
activeTabConfig.config.messages
|
|
945
|
+
.dragAndDrop ||
|
|
946
|
+
'Event updated via drag and drop!'
|
|
947
|
+
);
|
|
948
|
+
handleReload();
|
|
949
|
+
} catch (error) {
|
|
950
|
+
console.error(
|
|
951
|
+
'Error updating event:',
|
|
952
|
+
error
|
|
953
|
+
);
|
|
954
|
+
toast.error('Error updating event.');
|
|
955
|
+
}
|
|
956
|
+
};
|
|
957
|
+
|
|
958
|
+
// Callback when an event is resized
|
|
959
|
+
const onEventResize = async ({
|
|
960
|
+
event,
|
|
961
|
+
start,
|
|
962
|
+
end,
|
|
963
|
+
}) => {
|
|
964
|
+
try {
|
|
965
|
+
await CustomFetch(
|
|
966
|
+
activeTabConfig.config.urls.update,
|
|
967
|
+
'PUT',
|
|
968
|
+
{
|
|
969
|
+
dataId: routeParams[
|
|
970
|
+
activeTabConfig.urlParam
|
|
971
|
+
],
|
|
972
|
+
key: activeTabConfig.dataKey,
|
|
973
|
+
id: event.entryId,
|
|
974
|
+
[activeTabConfig.config.keys.start]:
|
|
975
|
+
start,
|
|
976
|
+
[activeTabConfig.config.keys.end]:
|
|
977
|
+
end,
|
|
978
|
+
}
|
|
979
|
+
);
|
|
980
|
+
|
|
981
|
+
// Optionally, update your event in the backend here using an API call.
|
|
982
|
+
toast.success(
|
|
983
|
+
activeTabConfig.config.messages
|
|
984
|
+
.resize || 'Event resized!'
|
|
985
|
+
);
|
|
986
|
+
handleReload();
|
|
987
|
+
} catch (error) {
|
|
988
|
+
console.error(
|
|
989
|
+
'Error resizing event:',
|
|
990
|
+
error
|
|
991
|
+
);
|
|
992
|
+
toast.error('Error resizing event.');
|
|
993
|
+
}
|
|
994
|
+
};
|
|
995
|
+
|
|
916
996
|
return (
|
|
917
|
-
<
|
|
997
|
+
<DnDCalendar
|
|
918
998
|
localizer={localizer}
|
|
919
999
|
events={events}
|
|
920
1000
|
startAccessor="start"
|
|
@@ -931,8 +1011,12 @@ function GenericDetail({
|
|
|
931
1011
|
'day',
|
|
932
1012
|
]
|
|
933
1013
|
}
|
|
1014
|
+
onEventDrop={onEventDrop}
|
|
1015
|
+
resizable
|
|
1016
|
+
onEventResize={onEventResize}
|
|
934
1017
|
/>
|
|
935
1018
|
);
|
|
1019
|
+
}
|
|
936
1020
|
case 'dropzone':
|
|
937
1021
|
const downloadFile = (file) => {
|
|
938
1022
|
const { id, file_url, file_name } = file;
|
|
@@ -233,8 +233,6 @@ const GenericEditableTable = ({
|
|
|
233
233
|
const groupKey = categoryId || 'no_category';
|
|
234
234
|
// Use the category's sort_order if provided; otherwise, default to a high number (e.g. 9999)
|
|
235
235
|
|
|
236
|
-
console.info(categoryInfo);
|
|
237
|
-
|
|
238
236
|
const catSortOrder =
|
|
239
237
|
categoryInfo && typeof categoryInfo.sort_order !== 'undefined'
|
|
240
238
|
? categoryInfo.sort_order
|
|
@@ -270,8 +268,6 @@ const GenericEditableTable = ({
|
|
|
270
268
|
return a.catSortOrder - b.catSortOrder;
|
|
271
269
|
});
|
|
272
270
|
|
|
273
|
-
console.info(groupedArray);
|
|
274
|
-
|
|
275
271
|
return groupedArray;
|
|
276
272
|
};
|
|
277
273
|
|
|
@@ -310,7 +306,7 @@ const GenericEditableTable = ({
|
|
|
310
306
|
.catch(() => toast.error('Failed to copy to clipboard'));
|
|
311
307
|
};
|
|
312
308
|
|
|
313
|
-
//
|
|
309
|
+
// Updated handleFieldChange to support "add" functionality
|
|
314
310
|
const handleFieldChange = (value, fieldId, keyCounter) => {
|
|
315
311
|
setLocalData((prev) => {
|
|
316
312
|
const updatedDetail = [...prev];
|
|
@@ -319,6 +315,34 @@ const GenericEditableTable = ({
|
|
|
319
315
|
[fieldId]: value,
|
|
320
316
|
};
|
|
321
317
|
|
|
318
|
+
// Loop through columns to see if any has an "add" property.
|
|
319
|
+
columns.forEach((col) => {
|
|
320
|
+
if (
|
|
321
|
+
col.add &&
|
|
322
|
+
(fieldId === col.id || fieldId === col.add.from)
|
|
323
|
+
) {
|
|
324
|
+
const fromVal = updatedDetail[keyCounter][col.add.from];
|
|
325
|
+
const daysVal = parseFloat(
|
|
326
|
+
updatedDetail[keyCounter][col.id]
|
|
327
|
+
);
|
|
328
|
+
if (fromVal && !isNaN(daysVal)) {
|
|
329
|
+
const newDate = new Date(fromVal);
|
|
330
|
+
if (!isNaN(newDate)) {
|
|
331
|
+
newDate.setDate(newDate.getDate() + daysVal);
|
|
332
|
+
// Format the date as YYYY-MM-DD
|
|
333
|
+
const yyyy = newDate.getFullYear();
|
|
334
|
+
let mm = newDate.getMonth() + 1;
|
|
335
|
+
let dd = newDate.getDate();
|
|
336
|
+
if (mm < 10) mm = '0' + mm;
|
|
337
|
+
if (dd < 10) dd = '0' + dd;
|
|
338
|
+
updatedDetail[keyCounter][
|
|
339
|
+
col.add.to
|
|
340
|
+
] = `${yyyy}-${mm}-${dd}`;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
|
|
322
346
|
debouncedUpdate({ [rows.key]: updatedDetail });
|
|
323
347
|
return updatedDetail;
|
|
324
348
|
});
|
|
@@ -445,6 +469,7 @@ const GenericEditableTable = ({
|
|
|
445
469
|
)
|
|
446
470
|
}
|
|
447
471
|
style={{ textAlign: 'right' }}
|
|
472
|
+
readOnly={column.readOnly || false}
|
|
448
473
|
/>
|
|
449
474
|
);
|
|
450
475
|
case 'date':
|
|
@@ -460,6 +485,7 @@ const GenericEditableTable = ({
|
|
|
460
485
|
)
|
|
461
486
|
}
|
|
462
487
|
style={{ textAlign: 'right' }}
|
|
488
|
+
readOnly={column.readOnly || false}
|
|
463
489
|
/>
|
|
464
490
|
);
|
|
465
491
|
case 'dropdown':
|
|
@@ -473,6 +499,7 @@ const GenericEditableTable = ({
|
|
|
473
499
|
keyCounter
|
|
474
500
|
)
|
|
475
501
|
}
|
|
502
|
+
readOnly={column.readOnly || false}
|
|
476
503
|
>
|
|
477
504
|
{column.options.map((option) => (
|
|
478
505
|
<option key={option.id} value={option.id}>
|
|
@@ -493,6 +520,7 @@ const GenericEditableTable = ({
|
|
|
493
520
|
keyCounter
|
|
494
521
|
)
|
|
495
522
|
}
|
|
523
|
+
readOnly={column.readOnly || false}
|
|
496
524
|
>
|
|
497
525
|
<option>Select an Option</option>
|
|
498
526
|
{options.map((option) => (
|
|
@@ -537,6 +565,7 @@ const GenericEditableTable = ({
|
|
|
537
565
|
keyCounter
|
|
538
566
|
)
|
|
539
567
|
}
|
|
568
|
+
readOnly={column.readOnly || false}
|
|
540
569
|
/>
|
|
541
570
|
);
|
|
542
571
|
}
|
|
@@ -544,21 +573,71 @@ const GenericEditableTable = ({
|
|
|
544
573
|
|
|
545
574
|
// --- New row functions for inline creation ---
|
|
546
575
|
|
|
547
|
-
//
|
|
576
|
+
// Updated handleNewFieldChange to support "add" functionality in new rows
|
|
548
577
|
const handleNewFieldChange = (value, fieldId, groupId = null) => {
|
|
549
578
|
if (hasCategory) {
|
|
550
|
-
setNewEntries((prev) =>
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
579
|
+
setNewEntries((prev) => {
|
|
580
|
+
const updated = {
|
|
581
|
+
...prev,
|
|
582
|
+
[groupId]: {
|
|
583
|
+
...prev[groupId],
|
|
584
|
+
[fieldId]: value,
|
|
585
|
+
},
|
|
586
|
+
};
|
|
587
|
+
columns.forEach((col) => {
|
|
588
|
+
if (
|
|
589
|
+
col.add &&
|
|
590
|
+
(fieldId === col.id || fieldId === col.add.from)
|
|
591
|
+
) {
|
|
592
|
+
const fromVal = updated[groupId][col.add.from];
|
|
593
|
+
const daysVal = parseFloat(updated[groupId][col.id]);
|
|
594
|
+
if (fromVal && !isNaN(daysVal)) {
|
|
595
|
+
const newDate = new Date(fromVal);
|
|
596
|
+
if (!isNaN(newDate)) {
|
|
597
|
+
newDate.setDate(newDate.getDate() + daysVal);
|
|
598
|
+
const yyyy = newDate.getFullYear();
|
|
599
|
+
let mm = newDate.getMonth() + 1;
|
|
600
|
+
let dd = newDate.getDate();
|
|
601
|
+
if (mm < 10) mm = '0' + mm;
|
|
602
|
+
if (dd < 10) dd = '0' + dd;
|
|
603
|
+
updated[groupId][
|
|
604
|
+
col.add.to
|
|
605
|
+
] = `${yyyy}-${mm}-${dd}`;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
});
|
|
610
|
+
return updated;
|
|
611
|
+
});
|
|
557
612
|
} else {
|
|
558
|
-
setNewEntry((prev) =>
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
613
|
+
setNewEntry((prev) => {
|
|
614
|
+
const updated = {
|
|
615
|
+
...prev,
|
|
616
|
+
[fieldId]: value,
|
|
617
|
+
};
|
|
618
|
+
columns.forEach((col) => {
|
|
619
|
+
if (
|
|
620
|
+
col.add &&
|
|
621
|
+
(fieldId === col.id || fieldId === col.add.from)
|
|
622
|
+
) {
|
|
623
|
+
const fromVal = updated[col.add.from];
|
|
624
|
+
const daysVal = parseFloat(updated[col.id]);
|
|
625
|
+
if (fromVal && !isNaN(daysVal)) {
|
|
626
|
+
const newDate = new Date(fromVal);
|
|
627
|
+
if (!isNaN(newDate)) {
|
|
628
|
+
newDate.setDate(newDate.getDate() + daysVal);
|
|
629
|
+
const yyyy = newDate.getFullYear();
|
|
630
|
+
let mm = newDate.getMonth() + 1;
|
|
631
|
+
let dd = newDate.getDate();
|
|
632
|
+
if (mm < 10) mm = '0' + mm;
|
|
633
|
+
if (dd < 10) dd = '0' + dd;
|
|
634
|
+
updated[col.add.to] = `${yyyy}-${mm}-${dd}`;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
});
|
|
639
|
+
return updated;
|
|
640
|
+
});
|
|
562
641
|
}
|
|
563
642
|
};
|
|
564
643
|
|