@visns-studio/visns-components 5.0.28 → 5.1.0
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/DataGrid.jsx +4 -1
- package/src/components/crm/Navigation.jsx +52 -20
- package/src/components/crm/QuickAction.jsx +117 -394
- package/src/components/crm/generic/GenericDashboard.jsx +4 -11
- package/src/components/crm/generic/GenericIndex.jsx +35 -1
- package/src/components/crm/generic/GenericMain.jsx +4 -0
- package/src/components/crm/styles/Form.module.scss +1 -1
- package/src/components/crm/styles/Navigation.module.scss +5 -3
- package/src/components/crm/styles/QuickAction.module.scss +126 -0
package/package.json
CHANGED
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
"react-dom": "^17.0.0 || ^18.0.0"
|
|
78
78
|
},
|
|
79
79
|
"name": "@visns-studio/visns-components",
|
|
80
|
-
"version": "5.0
|
|
80
|
+
"version": "5.1.0",
|
|
81
81
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
82
82
|
"main": "src/index.js",
|
|
83
83
|
"files": [
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
Bug,
|
|
5
5
|
Calendar,
|
|
6
6
|
Draft,
|
|
7
|
+
PeopleGroup,
|
|
7
8
|
Person,
|
|
8
9
|
Search,
|
|
9
10
|
SignOut,
|
|
@@ -207,11 +208,18 @@ function Navigation({
|
|
|
207
208
|
bug: Bug,
|
|
208
209
|
calendar: Calendar,
|
|
209
210
|
draft: Draft,
|
|
211
|
+
peopleGroup: PeopleGroup,
|
|
210
212
|
person: Person,
|
|
211
213
|
shield: Shield,
|
|
212
214
|
signOut: SignOut,
|
|
213
215
|
};
|
|
214
216
|
|
|
217
|
+
// Check if the setting's URL matches the current page
|
|
218
|
+
const isActive = n.url && n.url === `/${currentPage}`;
|
|
219
|
+
|
|
220
|
+
// Add active class if the setting is active
|
|
221
|
+
const activeClass = isActive ? styles.active : '';
|
|
222
|
+
|
|
215
223
|
if (n.id === 'notifications') {
|
|
216
224
|
return (
|
|
217
225
|
<li>
|
|
@@ -237,6 +245,7 @@ function Navigation({
|
|
|
237
245
|
data-tooltip-id="system-tooltip"
|
|
238
246
|
data-tooltip-content={n.label}
|
|
239
247
|
onClick={handleLogout}
|
|
248
|
+
className={activeClass} // Add active class to the logout button
|
|
240
249
|
>
|
|
241
250
|
<IconComponent size={19} strokeWidth={2} />
|
|
242
251
|
</button>
|
|
@@ -246,12 +255,14 @@ function Navigation({
|
|
|
246
255
|
const IconComponent = iconComponents[n.icon];
|
|
247
256
|
|
|
248
257
|
return (
|
|
249
|
-
<li>
|
|
258
|
+
<li className={activeClass}>
|
|
259
|
+
{' '}
|
|
260
|
+
{/* Apply active class to the list item */}
|
|
250
261
|
<Link
|
|
251
262
|
to={n.url}
|
|
252
263
|
data-tooltip-id="system-tooltip"
|
|
253
264
|
data-tooltip-content={n.label}
|
|
254
|
-
className=
|
|
265
|
+
className={`tooltip ${activeClass}`} // Apply active class to the link
|
|
255
266
|
target={n.target ? n.target : '_self'}
|
|
256
267
|
>
|
|
257
268
|
<IconComponent size={19} strokeWidth={2} />
|
|
@@ -387,30 +398,51 @@ function Navigation({
|
|
|
387
398
|
useEffect(() => {
|
|
388
399
|
setNavData((prevNavData) => {
|
|
389
400
|
const updateNavItems = (nav) => {
|
|
390
|
-
const cleanUrl = (url) =>
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
: url
|
|
394
|
-
|
|
395
|
-
|
|
401
|
+
const cleanUrl = (url) => {
|
|
402
|
+
if (url) {
|
|
403
|
+
// Ensure the URL starts with a slash. If it doesn't, prepend it.
|
|
404
|
+
return url.startsWith('/') ? url : `/${url}`;
|
|
405
|
+
}
|
|
406
|
+
return '';
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
// Ensure both URLs have the leading slash for consistent comparison
|
|
396
410
|
const currentUrl = cleanUrl(currentPage);
|
|
397
|
-
const navUrl = nav.url
|
|
398
|
-
? cleanUrl(nav.url)
|
|
399
|
-
? cleanUrl(nav.url)
|
|
400
|
-
: 'dashboard'
|
|
401
|
-
: '';
|
|
411
|
+
const navUrl = nav.url ? cleanUrl(nav.url) : 'dashboard';
|
|
402
412
|
|
|
413
|
+
// Split current URL into segments
|
|
414
|
+
const currentUrlSegments = currentUrl
|
|
415
|
+
.split('/')
|
|
416
|
+
.filter(Boolean);
|
|
417
|
+
|
|
418
|
+
// Handle the case where navUrl is just '/'
|
|
403
419
|
if (
|
|
404
|
-
navUrl ===
|
|
405
|
-
(
|
|
406
|
-
nav.children.some(
|
|
407
|
-
(child) => child.url === `/${currentPage}`
|
|
408
|
-
))
|
|
420
|
+
navUrl === '/' &&
|
|
421
|
+
(currentUrl === '/dashboard' || currentUrl === '/')
|
|
409
422
|
) {
|
|
410
|
-
return { ...nav, class: 'nav-item active' };
|
|
423
|
+
return { ...nav, class: 'nav-item active' }; // Mark as active for dashboard if navUrl is '/'
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// If current URL has 2 or fewer segments, check if navUrl is contained in currentUrl
|
|
427
|
+
if (currentUrlSegments.length <= 2) {
|
|
428
|
+
if (currentUrl.includes(navUrl) && nav.id !== 'dashboard') {
|
|
429
|
+
return { ...nav, class: 'nav-item active' }; // Mark as active if contained
|
|
430
|
+
}
|
|
411
431
|
} else {
|
|
412
|
-
|
|
432
|
+
// If more than 2 segments, use exact match logic
|
|
433
|
+
if (
|
|
434
|
+
navUrl === currentUrl ||
|
|
435
|
+
(nav.children &&
|
|
436
|
+
nav.children.some(
|
|
437
|
+
(child) =>
|
|
438
|
+
cleanUrl(child.url) === `/${currentPage}`
|
|
439
|
+
))
|
|
440
|
+
) {
|
|
441
|
+
return { ...nav, class: 'nav-item active' }; // Mark as active
|
|
442
|
+
}
|
|
413
443
|
}
|
|
444
|
+
|
|
445
|
+
return { ...nav, class: 'nav-item' }; // Default class
|
|
414
446
|
};
|
|
415
447
|
|
|
416
448
|
return {
|
|
@@ -6,292 +6,119 @@ import { Search } from 'akar-icons';
|
|
|
6
6
|
import CustomFetch from './Fetch';
|
|
7
7
|
import Form from './Form';
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
import styles from './styles/QuickAction.module.scss';
|
|
10
|
+
|
|
11
|
+
const QuickAction = ({ childRef, setting }) => {
|
|
10
12
|
const navigate = useNavigate();
|
|
11
13
|
const location = useLocation();
|
|
12
14
|
const searchBarRef = useRef(null);
|
|
13
15
|
|
|
14
|
-
/**
|
|
15
|
-
const [
|
|
16
|
-
const [
|
|
17
|
-
const [
|
|
18
|
-
const [
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
title: 'Create a new Contact entry',
|
|
23
|
-
},
|
|
24
|
-
update: {
|
|
25
|
-
title: 'Update an Existing Contact entry',
|
|
26
|
-
},
|
|
27
|
-
fields: [
|
|
28
|
-
{
|
|
29
|
-
id: 'id',
|
|
30
|
-
label: 'ID',
|
|
31
|
-
type: 'hidden',
|
|
32
|
-
required: false,
|
|
33
|
-
size: 'half',
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
id: 'title',
|
|
37
|
-
label: 'Title',
|
|
38
|
-
type: 'text',
|
|
39
|
-
required: false,
|
|
40
|
-
size: 'full',
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
id: 'firstname',
|
|
44
|
-
label: 'Firstname',
|
|
45
|
-
type: 'text',
|
|
46
|
-
required: true,
|
|
47
|
-
size: 'half',
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
id: 'surname',
|
|
51
|
-
label: 'Surname',
|
|
52
|
-
type: 'text',
|
|
53
|
-
required: true,
|
|
54
|
-
size: 'half',
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
id: 'address1',
|
|
58
|
-
label: 'Address',
|
|
59
|
-
type: 'json',
|
|
60
|
-
parent: 'address',
|
|
61
|
-
required: false,
|
|
62
|
-
size: 'half',
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
id: 'suburb',
|
|
66
|
-
label: 'Suburb',
|
|
67
|
-
type: 'json',
|
|
68
|
-
parent: 'address',
|
|
69
|
-
required: false,
|
|
70
|
-
size: 'half',
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
id: 'postcode',
|
|
74
|
-
label: 'Postcode',
|
|
75
|
-
type: 'json',
|
|
76
|
-
parent: 'address',
|
|
77
|
-
required: false,
|
|
78
|
-
size: 'half',
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
id: 'state',
|
|
82
|
-
label: 'State',
|
|
83
|
-
type: 'json',
|
|
84
|
-
parent: 'address',
|
|
85
|
-
required: false,
|
|
86
|
-
size: 'half',
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
id: 'phone',
|
|
90
|
-
label: 'Phone',
|
|
91
|
-
type: 'phone',
|
|
92
|
-
required: false,
|
|
93
|
-
size: 'half',
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
id: 'mobile',
|
|
97
|
-
label: 'mobile',
|
|
98
|
-
type: 'mobile',
|
|
99
|
-
required: false,
|
|
100
|
-
size: 'half',
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
id: 'fax',
|
|
104
|
-
label: 'Fax',
|
|
105
|
-
type: 'phone',
|
|
106
|
-
required: false,
|
|
107
|
-
size: 'half',
|
|
108
|
-
},
|
|
109
|
-
{
|
|
110
|
-
id: 'email',
|
|
111
|
-
label: 'Email',
|
|
112
|
-
type: 'text',
|
|
113
|
-
required: false,
|
|
114
|
-
size: 'half',
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
id: 'client_id',
|
|
118
|
-
label: 'User',
|
|
119
|
-
type: 'multi-dropdown-ajax',
|
|
120
|
-
url: '/ajax/clients/dropdown',
|
|
121
|
-
relation: ['client'],
|
|
122
|
-
relationName: 'name',
|
|
123
|
-
required: false,
|
|
124
|
-
size: 'half',
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
id: 'position',
|
|
128
|
-
label: 'Position',
|
|
129
|
-
type: 'text',
|
|
130
|
-
required: false,
|
|
131
|
-
size: 'half',
|
|
132
|
-
},
|
|
133
|
-
{
|
|
134
|
-
id: 'contact_types',
|
|
135
|
-
label: 'Role Type (Select Applicable)',
|
|
136
|
-
type: 'multi-dropdown-ajax',
|
|
137
|
-
url: '/ajax/contactTypes/dropdown',
|
|
138
|
-
required: false,
|
|
139
|
-
multi: true,
|
|
140
|
-
size: 'half',
|
|
141
|
-
},
|
|
142
|
-
{
|
|
143
|
-
id: 'contacts_industries',
|
|
144
|
-
label: 'Sector (Select Applicable)',
|
|
145
|
-
type: 'multi-dropdown-ajax',
|
|
146
|
-
url: '/ajax/industries/dropdown',
|
|
147
|
-
required: true,
|
|
148
|
-
multi: true,
|
|
149
|
-
size: 'half',
|
|
150
|
-
},
|
|
151
|
-
{
|
|
152
|
-
id: 'stakeholder_type_id',
|
|
153
|
-
label: 'Stakeholder Type',
|
|
154
|
-
type: 'dropdown-ajax',
|
|
155
|
-
url: '/ajax/stakeholderTypes/dropdown',
|
|
156
|
-
where: [],
|
|
157
|
-
child: {
|
|
158
|
-
id: 'stakeholder_category_id',
|
|
159
|
-
url: '/ajax/stakeholderCategories/dropdown',
|
|
160
|
-
},
|
|
161
|
-
relation: ['stakeholderTypes'],
|
|
162
|
-
relation_from: 'id',
|
|
163
|
-
required: false,
|
|
164
|
-
size: 'half',
|
|
165
|
-
},
|
|
166
|
-
{
|
|
167
|
-
id: 'stakeholder_category_id',
|
|
168
|
-
label: 'Stakeholder Category',
|
|
169
|
-
type: 'dropdown',
|
|
170
|
-
options: [],
|
|
171
|
-
required: false,
|
|
172
|
-
size: 'half',
|
|
173
|
-
},
|
|
174
|
-
{
|
|
175
|
-
id: 'bio',
|
|
176
|
-
label: 'Bio',
|
|
177
|
-
type: 'richeditor',
|
|
178
|
-
required: false,
|
|
179
|
-
size: 'full',
|
|
180
|
-
},
|
|
181
|
-
],
|
|
182
|
-
});
|
|
16
|
+
/** State Variables */
|
|
17
|
+
const [search, setSearch] = useState('');
|
|
18
|
+
const [searchResults, setSearchResults] = useState([]);
|
|
19
|
+
const [showSearchResults, setShowSearchResults] = useState(false);
|
|
20
|
+
const [modalShow, setModalShow] = useState(false);
|
|
21
|
+
const [formType, setFormType] = useState('create');
|
|
22
|
+
const [formId, setFormId] = useState(0);
|
|
23
|
+
const [form, setForm] = useState({});
|
|
183
24
|
|
|
184
|
-
|
|
185
|
-
|
|
25
|
+
/** Helper Functions */
|
|
26
|
+
const fetchSearchResults = async () => {
|
|
27
|
+
try {
|
|
28
|
+
const res = await CustomFetch(setting.search.url, 'POST', {
|
|
29
|
+
search,
|
|
30
|
+
});
|
|
31
|
+
setSearchResults(res.data);
|
|
32
|
+
setSearch('');
|
|
33
|
+
} catch (e) {
|
|
34
|
+
console.error(e);
|
|
35
|
+
}
|
|
186
36
|
};
|
|
187
37
|
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
38
|
+
const handleSearch = (e) => {
|
|
39
|
+
const value = e.target.value;
|
|
40
|
+
setSearch(value);
|
|
41
|
+
if (e.key === 'Enter') fetchSearchResults();
|
|
192
42
|
};
|
|
193
43
|
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
if (
|
|
199
|
-
|
|
44
|
+
const handleSelectResult = (type, result) => {
|
|
45
|
+
const url = setting.search.types.find((t) => t.title === type)?.url;
|
|
46
|
+
if (url) {
|
|
47
|
+
const key = setting.search.types.find((t) => t.title === type)?.key;
|
|
48
|
+
if (key && result[key]) {
|
|
49
|
+
navigate(`${url}/${result[key]}`);
|
|
50
|
+
} else {
|
|
51
|
+
navigate(url);
|
|
200
52
|
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
setContactForm(() => ({
|
|
204
|
-
..._form,
|
|
205
|
-
}));
|
|
53
|
+
}
|
|
54
|
+
setSearchResults([]);
|
|
206
55
|
};
|
|
207
56
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
create: {
|
|
216
|
-
title: 'Create a new User entry',
|
|
217
|
-
},
|
|
218
|
-
update: {
|
|
219
|
-
title: 'Update an Existing User entry',
|
|
220
|
-
},
|
|
221
|
-
fields: [
|
|
222
|
-
{
|
|
223
|
-
id: 'id',
|
|
224
|
-
label: 'ID',
|
|
225
|
-
type: 'hidden',
|
|
226
|
-
required: false,
|
|
227
|
-
size: 'half',
|
|
228
|
-
},
|
|
229
|
-
{
|
|
230
|
-
id: 'name',
|
|
231
|
-
label: 'Name',
|
|
232
|
-
type: 'text',
|
|
233
|
-
required: true,
|
|
234
|
-
size: 'half',
|
|
235
|
-
},
|
|
236
|
-
{
|
|
237
|
-
id: 'abn',
|
|
238
|
-
label: 'ABN',
|
|
239
|
-
type: 'text',
|
|
240
|
-
required: false,
|
|
241
|
-
size: 'half',
|
|
242
|
-
},
|
|
243
|
-
{
|
|
244
|
-
id: 'notes',
|
|
245
|
-
label: 'Notes',
|
|
246
|
-
type: 'textarea',
|
|
247
|
-
required: false,
|
|
248
|
-
size: 'full',
|
|
249
|
-
},
|
|
250
|
-
],
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
const clientModalClose = (e) => {
|
|
254
|
-
setClientModalShow(false);
|
|
57
|
+
const handleButtonClick = (buttonSetting) => {
|
|
58
|
+
// Check if the form object is identical to the current form
|
|
59
|
+
if (JSON.stringify(buttonSetting.form) === JSON.stringify(form)) {
|
|
60
|
+
modalOpen('create', 0); // Call modalOpen if they are the same
|
|
61
|
+
} else {
|
|
62
|
+
setForm(buttonSetting.form); // Otherwise, update the form state
|
|
63
|
+
}
|
|
255
64
|
};
|
|
256
65
|
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
66
|
+
const modalClose = () => setModalShow(false);
|
|
67
|
+
const modalOpen = (ft, fi) => {
|
|
68
|
+
setModalShow(true);
|
|
69
|
+
setFormType(ft);
|
|
70
|
+
setFormId(fi);
|
|
261
71
|
};
|
|
262
72
|
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
}
|
|
73
|
+
const childDropdownCallback = (data) => {
|
|
74
|
+
setForm((prevForm) => {
|
|
75
|
+
const updatedFields = prevForm.fields.map((field) =>
|
|
76
|
+
field.id === data.id ? { ...field, options: data.data } : field
|
|
77
|
+
);
|
|
78
|
+
return { ...prevForm, fields: updatedFields };
|
|
270
79
|
});
|
|
271
|
-
|
|
272
|
-
setClientForm(() => ({
|
|
273
|
-
..._form,
|
|
274
|
-
}));
|
|
275
80
|
};
|
|
276
81
|
|
|
277
|
-
/**
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
82
|
+
/** Effect Hooks */
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
if (form?.fields?.length) {
|
|
85
|
+
modalOpen('create', 0);
|
|
86
|
+
}
|
|
87
|
+
}, [form]);
|
|
88
|
+
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
const hasSearchResults = setting.search.types.some(
|
|
91
|
+
(type) => searchResults[type.id]?.length > 0
|
|
92
|
+
);
|
|
93
|
+
setShowSearchResults(hasSearchResults);
|
|
94
|
+
}, [searchResults]);
|
|
281
95
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
const handleClickOutside = (event) => {
|
|
98
|
+
if (
|
|
99
|
+
searchBarRef.current &&
|
|
100
|
+
!searchBarRef.current.contains(event.target)
|
|
101
|
+
) {
|
|
102
|
+
setShowSearchResults(false);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
106
|
+
return () =>
|
|
107
|
+
document.removeEventListener('mousedown', handleClickOutside);
|
|
108
|
+
}, []);
|
|
285
109
|
|
|
110
|
+
/** Component Structure */
|
|
111
|
+
const ResultSection = ({ title, data }) => {
|
|
112
|
+
if (!data?.length) return null;
|
|
286
113
|
return (
|
|
287
114
|
<>
|
|
288
|
-
<div className=
|
|
115
|
+
<div className={styles.resultItem}>
|
|
289
116
|
<h3>{title}</h3>
|
|
290
117
|
</div>
|
|
291
118
|
{data.map((result) => (
|
|
292
119
|
<div
|
|
293
120
|
key={result.id}
|
|
294
|
-
className=
|
|
121
|
+
className={styles.resultItem}
|
|
295
122
|
onClick={() => handleSelectResult(title, result)}
|
|
296
123
|
>
|
|
297
124
|
<p>
|
|
@@ -305,110 +132,26 @@ const QuickAction = ({ childRef, navConfig }) => {
|
|
|
305
132
|
);
|
|
306
133
|
};
|
|
307
134
|
|
|
308
|
-
const handleSearch = (e) => {
|
|
309
|
-
setSearchResults([]);
|
|
310
|
-
setSearch(e.target.value);
|
|
311
|
-
|
|
312
|
-
if (e.key === 'Enter') {
|
|
313
|
-
// Check if the Enter key was pressed
|
|
314
|
-
fetchSearchResults();
|
|
315
|
-
}
|
|
316
|
-
};
|
|
317
|
-
|
|
318
|
-
const handleSelectResult = (type, d) => {
|
|
319
|
-
switch (type) {
|
|
320
|
-
case 'Contacts':
|
|
321
|
-
navigate(`/contacts`);
|
|
322
|
-
break;
|
|
323
|
-
case 'Enquiries':
|
|
324
|
-
navigate(`/leads/${d.id}`);
|
|
325
|
-
break;
|
|
326
|
-
case 'Users':
|
|
327
|
-
navigate(`/clients/${d.id}`);
|
|
328
|
-
break;
|
|
329
|
-
}
|
|
330
|
-
setSearchResults([]);
|
|
331
|
-
};
|
|
332
|
-
|
|
333
|
-
const fetchSearchResults = async () => {
|
|
334
|
-
try {
|
|
335
|
-
const res = await CustomFetch('/ajax/dashboard/search', 'POST', {
|
|
336
|
-
search,
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
setSearchResults(res.data);
|
|
340
|
-
setSearch('');
|
|
341
|
-
} catch (e) {
|
|
342
|
-
console.log(e);
|
|
343
|
-
}
|
|
344
|
-
};
|
|
345
|
-
|
|
346
|
-
useEffect(() => {
|
|
347
|
-
let ssr = false;
|
|
348
|
-
|
|
349
|
-
if (searchResults?.contacts?.length > 0) {
|
|
350
|
-
ssr = true;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
if (searchResults?.enquiries?.length > 0) {
|
|
354
|
-
ssr = true;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
if (searchResults?.users?.length > 0) {
|
|
358
|
-
ssr = true;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
setShowSearchResults(ssr);
|
|
362
|
-
}, [searchResults]);
|
|
363
|
-
|
|
364
|
-
useEffect(() => {
|
|
365
|
-
const handleClickOutside = (event) => {
|
|
366
|
-
if (
|
|
367
|
-
searchBarRef.current &&
|
|
368
|
-
!searchBarRef.current.contains(event.target)
|
|
369
|
-
) {
|
|
370
|
-
setShowSearchResults(false);
|
|
371
|
-
}
|
|
372
|
-
};
|
|
373
|
-
|
|
374
|
-
document.addEventListener('mousedown', handleClickOutside);
|
|
375
|
-
return () => {
|
|
376
|
-
document.removeEventListener('mousedown', handleClickOutside);
|
|
377
|
-
};
|
|
378
|
-
}, []);
|
|
379
|
-
|
|
380
135
|
return (
|
|
381
136
|
<>
|
|
382
|
-
<div className=
|
|
137
|
+
<div className={styles.quickActions}>
|
|
383
138
|
<span>Quick Actions</span>
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
<button
|
|
399
|
-
onClick={() => {
|
|
400
|
-
if (location.pathname === '/leads/create') {
|
|
401
|
-
console.info('aa');
|
|
402
|
-
childRef.current.contactOpen();
|
|
403
|
-
} else {
|
|
404
|
-
contactModalOpen('create', 0);
|
|
405
|
-
}
|
|
406
|
-
}}
|
|
407
|
-
>
|
|
408
|
-
New Contact
|
|
409
|
-
</button>
|
|
139
|
+
{setting.buttons.map((b) => (
|
|
140
|
+
<button
|
|
141
|
+
key={`button-${b.id}`}
|
|
142
|
+
onClick={() => {
|
|
143
|
+
if (b.url) {
|
|
144
|
+
navigate(b.url);
|
|
145
|
+
} else {
|
|
146
|
+
handleButtonClick(b);
|
|
147
|
+
}
|
|
148
|
+
}}
|
|
149
|
+
>
|
|
150
|
+
{b.label}
|
|
151
|
+
</button>
|
|
152
|
+
))}
|
|
410
153
|
|
|
411
|
-
<div className=
|
|
154
|
+
<div className={styles.searchBar} ref={searchBarRef}>
|
|
412
155
|
<input
|
|
413
156
|
type="text"
|
|
414
157
|
placeholder="Search..."
|
|
@@ -416,56 +159,36 @@ const QuickAction = ({ childRef, navConfig }) => {
|
|
|
416
159
|
onChange={handleSearch}
|
|
417
160
|
onKeyDown={handleSearch}
|
|
418
161
|
/>
|
|
419
|
-
|
|
420
|
-
<div className="iconContainer">
|
|
162
|
+
<div className={styles.iconContainer}>
|
|
421
163
|
<Search
|
|
422
164
|
strokeWidth={2}
|
|
423
165
|
size={18}
|
|
424
|
-
className=
|
|
166
|
+
className={styles.searchIcon}
|
|
425
167
|
/>
|
|
426
168
|
</div>
|
|
427
169
|
|
|
428
170
|
{showSearchResults && (
|
|
429
|
-
<div className=
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
data={searchResults.enquiries}
|
|
438
|
-
handleSelectResult={handleSelectResult}
|
|
439
|
-
/>
|
|
440
|
-
<ResultSection
|
|
441
|
-
title="Users"
|
|
442
|
-
data={searchResults.users}
|
|
443
|
-
handleSelectResult={handleSelectResult}
|
|
444
|
-
/>
|
|
171
|
+
<div className={styles.searchResults}>
|
|
172
|
+
{setting.search.types.map((t) => (
|
|
173
|
+
<ResultSection
|
|
174
|
+
key={t.id}
|
|
175
|
+
title={t.title}
|
|
176
|
+
data={searchResults[t.id]}
|
|
177
|
+
/>
|
|
178
|
+
))}
|
|
445
179
|
</div>
|
|
446
180
|
)}
|
|
447
181
|
</div>
|
|
448
182
|
</div>
|
|
449
183
|
|
|
450
|
-
<Popup open={
|
|
451
|
-
<Form
|
|
452
|
-
closeModal={contactModalClose}
|
|
453
|
-
fetchTable={() => {}}
|
|
454
|
-
formSettings={contactForm}
|
|
455
|
-
formType={contactFormType}
|
|
456
|
-
columnId={contactFormId}
|
|
457
|
-
childDropdownCallback={contactChildDropdownCallback}
|
|
458
|
-
/>
|
|
459
|
-
</Popup>
|
|
460
|
-
|
|
461
|
-
<Popup open={clientModalShow} onClose={clientModalClose}>
|
|
184
|
+
<Popup open={modalShow} onClose={modalClose}>
|
|
462
185
|
<Form
|
|
463
|
-
closeModal={
|
|
186
|
+
closeModal={modalClose}
|
|
464
187
|
fetchTable={() => {}}
|
|
465
|
-
formSettings={
|
|
466
|
-
formType={
|
|
467
|
-
columnId={
|
|
468
|
-
childDropdownCallback={
|
|
188
|
+
formSettings={form}
|
|
189
|
+
formType={formType}
|
|
190
|
+
columnId={formId}
|
|
191
|
+
childDropdownCallback={childDropdownCallback}
|
|
469
192
|
/>
|
|
470
193
|
</Popup>
|
|
471
194
|
</>
|
|
@@ -70,9 +70,9 @@ function GenericDashboard({ setting }) {
|
|
|
70
70
|
}
|
|
71
71
|
};
|
|
72
72
|
|
|
73
|
-
useEffect(() => {
|
|
74
|
-
|
|
75
|
-
}, [setting]);
|
|
73
|
+
// useEffect(() => {
|
|
74
|
+
// fetchData(filters); // Fetch initial data
|
|
75
|
+
// }, [setting]);
|
|
76
76
|
|
|
77
77
|
const openModal = (widgetId) => {
|
|
78
78
|
setModalContent(widgetId);
|
|
@@ -165,6 +165,7 @@ function GenericDashboard({ setting }) {
|
|
|
165
165
|
useEffect(() => {
|
|
166
166
|
// Fetch data whenever filters are updated
|
|
167
167
|
if (Object.keys(filters).length > 0) {
|
|
168
|
+
console.info(filters);
|
|
168
169
|
fetchData(filters);
|
|
169
170
|
}
|
|
170
171
|
}, [filters]);
|
|
@@ -244,14 +245,6 @@ function GenericDashboard({ setting }) {
|
|
|
244
245
|
}
|
|
245
246
|
})}
|
|
246
247
|
<div className={styles['filter-actions']}>
|
|
247
|
-
{/* <button
|
|
248
|
-
className={`${styles.btn} ${styles['btn-primary']}`}
|
|
249
|
-
onClick={() => {
|
|
250
|
-
fetchData(filters); // Trigger fetchData with the current filters
|
|
251
|
-
}}
|
|
252
|
-
>
|
|
253
|
-
Apply Filters
|
|
254
|
-
</button> */}
|
|
255
248
|
<button
|
|
256
249
|
className={`${styles.btn} ${styles['btn-secondary']}`}
|
|
257
250
|
onClick={() => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import '../../styles/global.css';
|
|
2
2
|
|
|
3
3
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
4
|
-
import { Outlet } from 'react-router-dom';
|
|
4
|
+
import { useParams, Outlet } from 'react-router-dom';
|
|
5
5
|
import { toast } from 'react-toastify';
|
|
6
6
|
import { saveAs } from 'file-saver';
|
|
7
7
|
import _ from 'lodash';
|
|
@@ -182,7 +182,9 @@ function GenericIndex({
|
|
|
182
182
|
userProfile,
|
|
183
183
|
}) {
|
|
184
184
|
const gridRef = useRef(null);
|
|
185
|
+
const params = useParams();
|
|
185
186
|
const [rowsSelected, setRowsSelected] = useState({});
|
|
187
|
+
const [tableInfo, setTableInfo] = useState({});
|
|
186
188
|
const [total, setTotal] = useState(0);
|
|
187
189
|
const windowHeight = useWindowHeight();
|
|
188
190
|
const { config, setConfig, subnav, setSubnav } = useConfig(
|
|
@@ -370,6 +372,28 @@ function GenericIndex({
|
|
|
370
372
|
}
|
|
371
373
|
}, [layout, subnav, setSubnav, setConfig, renderTable]);
|
|
372
374
|
|
|
375
|
+
useEffect(() => {
|
|
376
|
+
if (setting.page?.tableInfo?.hasOwnProperty('url')) {
|
|
377
|
+
const fetchTableInfo = async () => {
|
|
378
|
+
try {
|
|
379
|
+
const res = await CustomFetch(
|
|
380
|
+
setting.page.tableInfo.url,
|
|
381
|
+
'POST',
|
|
382
|
+
{}
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
if (res.data) {
|
|
386
|
+
setTableInfo(res.data);
|
|
387
|
+
}
|
|
388
|
+
} catch (error) {
|
|
389
|
+
console.error(error);
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
fetchTableInfo();
|
|
394
|
+
}
|
|
395
|
+
}, [setting]);
|
|
396
|
+
|
|
373
397
|
useEffect(() => {
|
|
374
398
|
if (setActiveNav) {
|
|
375
399
|
setActiveNav();
|
|
@@ -389,6 +413,16 @@ function GenericIndex({
|
|
|
389
413
|
>
|
|
390
414
|
<h1>{setting.page?.title}</h1>
|
|
391
415
|
<div className={styles.titleInfo}>
|
|
416
|
+
{setting.page?.tableInfo?.hasOwnProperty(
|
|
417
|
+
'heading'
|
|
418
|
+
) &&
|
|
419
|
+
setting.page.tableInfo.heading.length > 0 &&
|
|
420
|
+
setting.page.tableInfo.heading.map((h) => (
|
|
421
|
+
<span>
|
|
422
|
+
[<strong>{tableInfo[h.id]}</strong>{' '}
|
|
423
|
+
{h.label}]{' '}
|
|
424
|
+
</span>
|
|
425
|
+
))}
|
|
392
426
|
<span>
|
|
393
427
|
[<strong>{total}</strong> Total{' '}
|
|
394
428
|
{setting.page?.title}]
|
|
@@ -6,6 +6,7 @@ import { Routes, Route, Navigate } from 'react-router-dom';
|
|
|
6
6
|
import Call from '../Call';
|
|
7
7
|
import Loader from '../Loader';
|
|
8
8
|
import Navigation from '../Navigation';
|
|
9
|
+
import QuickAction from '../QuickAction';
|
|
9
10
|
|
|
10
11
|
import styles from './styles/GenericMain.module.scss';
|
|
11
12
|
|
|
@@ -85,6 +86,9 @@ const GenericMain = ({
|
|
|
85
86
|
userProfile={userProfile}
|
|
86
87
|
/>
|
|
87
88
|
</header>
|
|
89
|
+
{navigation.hasOwnProperty('quickActions') && (
|
|
90
|
+
<QuickAction setting={navigation.quickActions} />
|
|
91
|
+
)}
|
|
88
92
|
<main className={styles.content}>
|
|
89
93
|
<div className={styles['content-main']} id="app">
|
|
90
94
|
<div className={styles.padding}>
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
|
|
62
62
|
a,
|
|
63
63
|
span {
|
|
64
|
-
font-size:
|
|
64
|
+
font-size: 0.9rem;
|
|
65
65
|
display: flex;
|
|
66
66
|
flex-wrap: nowrap;
|
|
67
67
|
align-items: center;
|
|
@@ -260,7 +260,8 @@
|
|
|
260
260
|
display: flex;
|
|
261
261
|
align-items: center;
|
|
262
262
|
|
|
263
|
-
&:hover
|
|
263
|
+
&:hover,
|
|
264
|
+
&.active {
|
|
264
265
|
background: var(--primary-color);
|
|
265
266
|
|
|
266
267
|
svg {
|
|
@@ -283,7 +284,8 @@
|
|
|
283
284
|
outline: none;
|
|
284
285
|
appearance: none;
|
|
285
286
|
|
|
286
|
-
&:hover
|
|
287
|
+
&:hover,
|
|
288
|
+
&.active {
|
|
287
289
|
background: var(--primary-color);
|
|
288
290
|
|
|
289
291
|
svg {
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
.quickActions {
|
|
2
|
+
width: 100%;
|
|
3
|
+
background: rgba(var(--bg-rgb), 0.95);
|
|
4
|
+
padding: var(--padding);
|
|
5
|
+
border-top: solid 4px rgba(var(--bg-rgb), 1.05);
|
|
6
|
+
border-bottom: solid 1px rgba(var(--primary-rgb), 0.25);
|
|
7
|
+
color: var(--paragraph-color);
|
|
8
|
+
display: flex;
|
|
9
|
+
flex-wrap: nowrap;
|
|
10
|
+
align-items: center;
|
|
11
|
+
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
|
|
12
|
+
|
|
13
|
+
> span {
|
|
14
|
+
width: max-content;
|
|
15
|
+
display: block;
|
|
16
|
+
font-weight: var(--title-weight);
|
|
17
|
+
padding: 0 1rem 0 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
> button {
|
|
21
|
+
appearance: none;
|
|
22
|
+
border: 1px solid rgba(var(--primary-rgb), 0.25);
|
|
23
|
+
border-radius: var(--br);
|
|
24
|
+
background: transparent;
|
|
25
|
+
width: max-content;
|
|
26
|
+
color: var(--paragraph-color);
|
|
27
|
+
padding: 0.35rem 0.65rem;
|
|
28
|
+
margin: 0 0.25rem;
|
|
29
|
+
transition: all 0.25s cubic-bezier(0.25, 0.8, 0.25, 1);
|
|
30
|
+
|
|
31
|
+
&:hover {
|
|
32
|
+
background: var(--tertiary-color);
|
|
33
|
+
transform: translateY(-2px);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.searchBar {
|
|
38
|
+
display: flex;
|
|
39
|
+
align-items: center;
|
|
40
|
+
background: transparent;
|
|
41
|
+
margin: 0 0.25rem;
|
|
42
|
+
flex-grow: 1;
|
|
43
|
+
position: relative;
|
|
44
|
+
|
|
45
|
+
input[type='text'] {
|
|
46
|
+
flex-grow: 1;
|
|
47
|
+
border: none;
|
|
48
|
+
outline: none;
|
|
49
|
+
padding: 4px 0.65rem;
|
|
50
|
+
padding-right: 30px;
|
|
51
|
+
border-radius: var(--br);
|
|
52
|
+
height: 32px;
|
|
53
|
+
font-size: 14px;
|
|
54
|
+
width: 100%;
|
|
55
|
+
box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.05);
|
|
56
|
+
|
|
57
|
+
&:focus {
|
|
58
|
+
box-shadow: 0 0 8px rgba(var(--primary-rgb), 0.5);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.iconContainer {
|
|
63
|
+
position: absolute;
|
|
64
|
+
right: 10px;
|
|
65
|
+
display: flex;
|
|
66
|
+
align-items: center;
|
|
67
|
+
justify-content: center;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.searchIcon {
|
|
71
|
+
width: 20px;
|
|
72
|
+
height: 20px;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.searchResults {
|
|
77
|
+
position: absolute;
|
|
78
|
+
top: 100%;
|
|
79
|
+
width: 100%;
|
|
80
|
+
background: var(--third-color);
|
|
81
|
+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
|
|
82
|
+
z-index: 100;
|
|
83
|
+
max-height: 400px;
|
|
84
|
+
overflow-y: auto;
|
|
85
|
+
border-radius: var(--br);
|
|
86
|
+
margin-top: 5px;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.resultItem {
|
|
90
|
+
padding: 0.5rem 1rem;
|
|
91
|
+
cursor: pointer;
|
|
92
|
+
transition: background-color 0.3s ease, transform 0.2s ease;
|
|
93
|
+
|
|
94
|
+
&:hover {
|
|
95
|
+
background-color: #f2f2f2;
|
|
96
|
+
transform: translateX(5px);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
&:last-child {
|
|
100
|
+
border-bottom: none;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
h3 {
|
|
104
|
+
margin: 0;
|
|
105
|
+
font-weight: var(--title-weight);
|
|
106
|
+
color: var(--header-color);
|
|
107
|
+
font-size: 14px;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
p {
|
|
111
|
+
margin: 0.25rem 0 0;
|
|
112
|
+
color: var(--paragraph-color);
|
|
113
|
+
font-size: 13px;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
&.selected {
|
|
117
|
+
background-color: rgba(var(--primary-rgb), 0.1);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.resultItemDivider {
|
|
122
|
+
height: 1px;
|
|
123
|
+
background-color: var(--border-color);
|
|
124
|
+
margin: 0.25rem 0;
|
|
125
|
+
}
|
|
126
|
+
}
|