@visns-studio/visns-components 1.8.5 → 2.0.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.
Files changed (36) hide show
  1. package/.prettierrc.json +7 -0
  2. package/components/cms/{visns-data-grid.jsx → DataGrid.jsx} +2 -2
  3. package/components/cms/DropZone.jsx +161 -0
  4. package/components/cms/Field.jsx +747 -0
  5. package/components/cms/Form.jsx +475 -0
  6. package/components/cms/sorting/{list.jsx → List.jsx} +3 -3
  7. package/components/crm/{visns-async-select.jsx → AsyncSelect.jsx} +9 -9
  8. package/components/crm/Call.jsx +220 -0
  9. package/components/crm/{visns-data-grid.jsx → DataGrid.jsx} +263 -277
  10. package/components/crm/{visns-field.jsx → Field.jsx} +5 -5
  11. package/components/crm/{visns-form.jsx → Form.jsx} +302 -205
  12. package/components/crm/{visns-multi-select.jsx → MultiSelect.jsx} +1 -1
  13. package/components/crm/{visns-navigation.jsx → Navigation.jsx} +22 -18
  14. package/components/crm/{visns-notification.jsx → Notification.jsx} +1 -1
  15. package/components/crm/TableFilter.jsx +125 -0
  16. package/components/crm/auth/Login.jsx +158 -0
  17. package/components/crm/auth/Profile.jsx +163 -0
  18. package/components/crm/auth/Reset.jsx +87 -0
  19. package/components/crm/auth/Verify.jsx +162 -0
  20. package/components/crm/generic/GenericDetail.jsx +380 -0
  21. package/components/crm/generic/GenericIndex.jsx +112 -0
  22. package/components/crm/generic/NotificationList.jsx +64 -0
  23. package/index.js +37 -19
  24. package/package.json +6 -6
  25. package/components/cms/visns-dropzone.jsx +0 -161
  26. package/components/cms/visns-field.jsx +0 -747
  27. package/components/cms/visns-form.jsx +0 -475
  28. package/components/crm/visns-call.jsx +0 -220
  29. package/components/crm/visns-table-filter.jsx +0 -153
  30. /package/components/cms/sorting/{item.jsx → Item.jsx} +0 -0
  31. /package/components/crm/{visns-autocomplete.jsx → Autocomplete.jsx} +0 -0
  32. /package/components/crm/{visns-download.jsx → Download.jsx} +0 -0
  33. /package/components/crm/{visns-fetch.jsx → Fetch.jsx} +0 -0
  34. /package/components/crm/{visns-loader.jsx → Loader.jsx} +0 -0
  35. /package/components/crm/{visns-select.jsx → Select.jsx} +0 -0
  36. /package/components/crm/{visns-select-list.jsx → SelectList.jsx} +0 -0
@@ -0,0 +1,162 @@
1
+ import React, { useState } from 'react';
2
+ import { useNavigate, useParams } from 'react-router-dom';
3
+ import { toast } from 'react-toastify';
4
+ import parse from 'html-react-parser';
5
+ import validator from 'validator';
6
+ import Reveal from 'react-reveal/Reveal';
7
+
8
+ import CustomFetch from '../Fetch';
9
+
10
+ const Verify = () => {
11
+ const navigate = useNavigate();
12
+ const { code } = useParams();
13
+
14
+ const [formData, setFormData] = useState({
15
+ password: '',
16
+ passwordRepeat: '',
17
+ passwordClass: '',
18
+ passwordRepeatClass: '',
19
+ });
20
+
21
+ const { password, passwordRepeat, passwordClass, passwordRepeatClass } =
22
+ formData;
23
+
24
+ const handleInputChange = (e) => {
25
+ const { name, value } = e.target;
26
+ setFormData((prevFormData) => ({
27
+ ...prevFormData,
28
+ [name]: value,
29
+ passwordClass: '',
30
+ passwordRepeatClass: '',
31
+ }));
32
+ };
33
+
34
+ const validatePassword = () => {
35
+ let _error = '';
36
+
37
+ if (password === '') {
38
+ _error = 'Please type in your new password.';
39
+ setFormData((prevFormData) => ({
40
+ ...prevFormData,
41
+ passwordClass: 'inputError',
42
+ }));
43
+ } else {
44
+ if (password !== passwordRepeat) {
45
+ _error =
46
+ 'The password does not match, please re-type the password.';
47
+ setFormData((prevFormData) => ({
48
+ ...prevFormData,
49
+ passwordClass: 'inputError',
50
+ passwordRepeatClass: 'inputError',
51
+ }));
52
+ } else {
53
+ if (!validator.isStrongPassword(password)) {
54
+ _error =
55
+ 'Your password is too weak it must contain the following:<br />- Must contain at least 8 characters.<br />- Must contain at least one uppercase letter.<br />- Must contain at least one lowercase letter.<br />- Must contain at least one number.<br />- Must contain at least one symbol.<br />';
56
+ }
57
+ }
58
+ }
59
+
60
+ return _error;
61
+ };
62
+
63
+ const handleSubmit = (e) => {
64
+ if (e) {
65
+ e.preventDefault();
66
+ const errorMsg = validatePassword();
67
+
68
+ if (errorMsg === '') {
69
+ CustomFetch(
70
+ '/password/reset',
71
+ 'POST',
72
+ {
73
+ code: code,
74
+ password: password,
75
+ password_confirmation: passwordRepeat,
76
+ },
77
+ function (result) {
78
+ if (result.error === '') {
79
+ toast.success(
80
+ 'You have successfully set a new password, please use your new password to login into the system.'
81
+ );
82
+
83
+ navigate('/login');
84
+ } else {
85
+ toast.error(result.error);
86
+ }
87
+ }
88
+ );
89
+ } else {
90
+ toast.error(<div>{parse(errorMsg)}</div>);
91
+ }
92
+ }
93
+ };
94
+
95
+ return (
96
+ <div className="lcontainer">
97
+ <div className="lwrap">
98
+ <aside className="aside"></aside>
99
+ <div className="logincontainer">
100
+ <form onSubmit={handleSubmit}>
101
+ <Reveal effect="fadeInUp">
102
+ <div className="login">
103
+ <div className="formItem fwItem">
104
+ <h1>Reset your password</h1>
105
+ </div>
106
+ {errorMsg === '' ? (
107
+ <>
108
+ <div className="formItem fwItem">
109
+ <label className="fi__label">
110
+ <input
111
+ type="password"
112
+ name="password"
113
+ value={password}
114
+ onChange={handleInputChange}
115
+ tabIndex="1"
116
+ className={passwordClass}
117
+ />
118
+ <span className="fi__span">
119
+ Password
120
+ </span>
121
+ </label>
122
+ </div>
123
+ <div className="formItem fwItem">
124
+ <label className="fi__label">
125
+ <input
126
+ type="password"
127
+ name="passwordRepeat"
128
+ value={passwordRepeat}
129
+ onChange={handleInputChange}
130
+ tabIndex="2"
131
+ className={
132
+ passwordRepeatClass
133
+ }
134
+ />
135
+ <span className="fi__span">
136
+ Re-type Password
137
+ </span>
138
+ </label>
139
+ </div>
140
+ <div className="formItem fwItem lastItem">
141
+ <button
142
+ className="btn"
143
+ type="submit"
144
+ tabIndex="3"
145
+ >
146
+ Reset
147
+ </button>
148
+ </div>
149
+ </>
150
+ ) : (
151
+ <p>{errorMsg}</p>
152
+ )}
153
+ </div>
154
+ </Reveal>
155
+ </form>
156
+ </div>
157
+ </div>
158
+ </div>
159
+ );
160
+ };
161
+
162
+ export default Verify;
@@ -0,0 +1,380 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { useParams, Link } from 'react-router-dom';
3
+ import moment from 'moment';
4
+ import parse from 'html-react-parser';
5
+ import Popup from 'reactjs-popup';
6
+ import { CircleChevronRight } from 'akar-icons';
7
+
8
+ import CustomFetch from '../Fetch';
9
+ import Form from '../Form';
10
+ import Table from '../DataGrid';
11
+ import TableFilter from '../TableFilter';
12
+
13
+ function GenericDetail({ setting, urlParam, userProfile }) {
14
+ const routeParams = useParams();
15
+ const { filters, page, tabs } = setting;
16
+
17
+ /** General States */
18
+ const [config, setConfig] = useState({});
19
+ const [data, setData] = useState({});
20
+ const [dataReload, setDataReload] = useState(0);
21
+ const [subnav, setSubnav] = useState(filters || []);
22
+ const [total, setTotal] = useState(0);
23
+
24
+ /** General Functions */
25
+ const handleReload = () => {
26
+ const min = 0;
27
+ const max = 999999999;
28
+ const randomNumber = Math.floor(Math.random() * (max - min + 1) + min);
29
+
30
+ setDataReload(randomNumber);
31
+ };
32
+
33
+ const getValueFromData = (data, path, id) => {
34
+ let value =
35
+ path.reduce(
36
+ (result, key) =>
37
+ result
38
+ ? Array.isArray(result[key])
39
+ ? result[key][0]
40
+ : result[key]
41
+ : '',
42
+ data
43
+ ) ?? '';
44
+
45
+ if (value) {
46
+ if (Array.isArray(id)) {
47
+ return id.map((key) => value[key]).join(' ');
48
+ } else {
49
+ return value[id];
50
+ }
51
+ } else {
52
+ return '';
53
+ }
54
+ };
55
+
56
+ const formatDate = (date) => {
57
+ return date && moment(date).format('DD-MM-YYYY') !== '01-01-1970'
58
+ ? moment(date).format('DD-MM-YYYY')
59
+ : '';
60
+ };
61
+
62
+ const renderValue = (item, data) => {
63
+ const { type, id, relation, size } = item;
64
+ const value =
65
+ relation && relation.length > 0
66
+ ? getValueFromData(data, [...relation], id)
67
+ : getValueFromData(data, [], id);
68
+
69
+ const stringValue = String(value ? value : ''); // Convert value to a string
70
+
71
+ switch (type) {
72
+ case 'date':
73
+ return formatDate(value);
74
+ case 'exist':
75
+ return value ? 'Yes' : 'No';
76
+ case 'latest_notes':
77
+ const noteData = data?.[id]?.[0];
78
+ const name = noteData?.user?.name ?? '';
79
+ const description = noteData?.description ?? '';
80
+ const created_at = noteData?.created_at ?? '';
81
+ const formattedDate = formatDate(created_at);
82
+
83
+ if (name !== '' && description !== '' && formattedDate !== '') {
84
+ return parse(
85
+ `<br /><p>${name} - ${formattedDate}</p><p>${description}</p>`
86
+ );
87
+ } else {
88
+ return null;
89
+ }
90
+ default:
91
+ return size === 'full' && stringValue
92
+ ? parse(`<br /><p>${stringValue}</p>`)
93
+ : stringValue;
94
+ }
95
+ };
96
+
97
+ const renderContent = () => {
98
+ if (subnav && subnav.length > 0) {
99
+ const activeTab = subnav.find((s) => s.show === true);
100
+
101
+ if (activeTab) {
102
+ const activeTabConfig = tabs.find((t) => t.id === activeTab.id);
103
+
104
+ if (activeTabConfig && activeTabConfig.type) {
105
+ switch (activeTabConfig.type) {
106
+ case 'form':
107
+ return (
108
+ <div className="gridtxt">
109
+ <div className="gridtxt__header">
110
+ <span>{activeTabConfig.title}</span>
111
+ </div>
112
+ <Form
113
+ columnId={routeParams[urlParam]}
114
+ formSettings={activeTabConfig.form}
115
+ formType="update"
116
+ style={
117
+ userProfile?.settings?.style || {}
118
+ }
119
+ updateForm={setFormData}
120
+ type="inline"
121
+ />
122
+ </div>
123
+ );
124
+ case 'overview':
125
+ return (
126
+ <>
127
+ <div className="gridactions">
128
+ <ul>
129
+ <li>
130
+ <button
131
+ className="btn"
132
+ onClick={() =>
133
+ modalOpen(
134
+ 'update',
135
+ routeParams[
136
+ urlParam
137
+ ]
138
+ )
139
+ }
140
+ >
141
+ Edit
142
+ </button>
143
+ </li>
144
+ </ul>
145
+ </div>
146
+ <div className="gridtxt">
147
+ {activeTabConfig.sections.map(
148
+ (section, sectionIndex) => (
149
+ <React.Fragment
150
+ key={`section-${activeTabConfig.id}-${sectionIndex}`}
151
+ >
152
+ <div className="gridtxt__header">
153
+ <span>
154
+ {section.title}
155
+ </span>
156
+ </div>
157
+ {section.content &&
158
+ section.content.length >
159
+ 0 && (
160
+ <ul className="customer__overview">
161
+ {section.content.map(
162
+ (
163
+ item,
164
+ itemKey
165
+ ) => (
166
+ <li
167
+ key={`li-${item.id}-${itemKey}`}
168
+ className={
169
+ item.size ===
170
+ 'full'
171
+ ? 'fw-grid-item notecolor'
172
+ : null
173
+ }
174
+ >
175
+ <strong>
176
+ {
177
+ item.label
178
+ }
179
+
180
+ :
181
+ </strong>{' '}
182
+ {renderValue(
183
+ item,
184
+ data
185
+ )}
186
+ </li>
187
+ )
188
+ )}
189
+ </ul>
190
+ )}
191
+ </React.Fragment>
192
+ )
193
+ )}
194
+ </div>
195
+ </>
196
+ );
197
+ case 'table':
198
+ if (
199
+ config.ajaxSetting &&
200
+ config.form &&
201
+ config.columns &&
202
+ config.settings
203
+ ) {
204
+ return (
205
+ <Table
206
+ {...config}
207
+ setConfig={setConfig}
208
+ setTotal={setTotal}
209
+ style={
210
+ userProfile?.settings?.style || {}
211
+ }
212
+ />
213
+ );
214
+ } else {
215
+ return null;
216
+ }
217
+ default:
218
+ return null;
219
+ }
220
+ }
221
+ }
222
+ }
223
+ };
224
+
225
+ /** General Hooks */
226
+ useEffect(() => {
227
+ if (tabs && tabs.length > 0) {
228
+ const activeTab = subnav.find((s) => s.show === true);
229
+
230
+ if (activeTab) {
231
+ const activeTabConfig = tabs.find((t) => t.id === activeTab.id);
232
+
233
+ const applyUrlParamsToData = (data) => {
234
+ return data.map((item) => {
235
+ return {
236
+ ...item,
237
+ value:
238
+ item.hasOwnProperty('value') &&
239
+ item.urlParam &&
240
+ routeParams[item.urlParam]
241
+ ? routeParams[item.urlParam]
242
+ : item.value,
243
+ };
244
+ });
245
+ };
246
+
247
+ if (activeTabConfig) {
248
+ switch (activeTabConfig.type) {
249
+ case 'table':
250
+ if (
251
+ activeTabConfig.ajaxSetting &&
252
+ activeTabConfig.ajaxSetting.where &&
253
+ activeTabConfig.ajaxSetting.where.length > 0
254
+ ) {
255
+ const updatedAjaxSetting = {
256
+ ...activeTabConfig.ajaxSetting,
257
+ where: applyUrlParamsToData(
258
+ activeTabConfig.ajaxSetting.where
259
+ ),
260
+ };
261
+ const updatedForm = {
262
+ ...activeTabConfig.form,
263
+ fields: applyUrlParamsToData(
264
+ activeTabConfig.form.fields
265
+ ),
266
+ };
267
+ setConfig({
268
+ ajaxSetting: updatedAjaxSetting,
269
+ form: updatedForm,
270
+ columns: activeTabConfig.columns,
271
+ settings: activeTabConfig.settings,
272
+ });
273
+ }
274
+ break;
275
+ case 'overview':
276
+ setConfig({});
277
+ if (activeTabConfig.form) {
278
+ setFormData(activeTabConfig.form);
279
+ }
280
+ break;
281
+ default:
282
+ setConfig({});
283
+ break;
284
+ }
285
+ }
286
+ }
287
+ }
288
+ }, [subnav, tabs, routeParams]);
289
+
290
+ useEffect(() => {
291
+ if (routeParams[urlParam] && routeParams[urlParam] > 0) {
292
+ CustomFetch(
293
+ `${page.fetchUrl}/${routeParams[urlParam]}`,
294
+ 'GET',
295
+ {},
296
+ (res) => {
297
+ setData(res);
298
+ }
299
+ );
300
+ }
301
+ }, [routeParams, dataReload]);
302
+
303
+ /** Modal States */
304
+ const [formData, setFormData] = useState({});
305
+ const [formType, setFormType] = useState('');
306
+ const [formId, setFormId] = useState(0);
307
+ const [modalShow, setModalShow] = useState(false);
308
+
309
+ /** Modal Functions */
310
+ const modalOpen = (formType, formId) => {
311
+ setModalShow(true);
312
+ setFormType(formType);
313
+ setFormId(formId);
314
+ };
315
+
316
+ const modalClose = () => {
317
+ setModalShow(false);
318
+ };
319
+
320
+ return (
321
+ <>
322
+ <div className="grid">
323
+ <div className="grid__row">
324
+ <div className="grid__full crmtitle">
325
+ <h1>
326
+ <Link
327
+ to={
328
+ page && page.previousUrl
329
+ ? page.previousUrl
330
+ : null
331
+ }
332
+ >
333
+ {page && page.title ? page.title : null}
334
+ </Link>{' '}
335
+ <CircleChevronRight strokeWidth={2} size={18} />{' '}
336
+ {data && data[page.titleKey]
337
+ ? data[page.titleKey]
338
+ : null}
339
+ </h1>
340
+ {total > 0 && (
341
+ <div className="titleInfo">
342
+ <span>
343
+ [<strong>{total}</strong> Total]
344
+ </span>
345
+ </div>
346
+ )}
347
+ </div>
348
+ </div>
349
+ </div>
350
+ <div className="grid">
351
+ <div className="grid__subrow">
352
+ <div className="grid__subnav">
353
+ <TableFilter filters={subnav} setFilters={setSubnav} />
354
+ </div>
355
+ <div className="grid__subcontent">
356
+ {config ? renderContent() : null}
357
+ </div>
358
+ </div>
359
+ </div>
360
+
361
+ <Popup
362
+ open={modalShow}
363
+ onClose={modalClose}
364
+ closeOnDocumentClick={false}
365
+ >
366
+ <Form
367
+ closeModal={modalClose}
368
+ columnId={formId}
369
+ fetchTable={handleReload}
370
+ formSettings={formData}
371
+ formType={formType}
372
+ style={userProfile?.settings?.style || {}}
373
+ updateForm={setFormData}
374
+ />
375
+ </Popup>
376
+ </>
377
+ );
378
+ }
379
+
380
+ export default GenericDetail;
@@ -0,0 +1,112 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { Outlet } from 'react-router-dom';
3
+ import Table from '../DataGrid';
4
+ import TableFilter from '../TableFilter';
5
+
6
+ function GenericIndex({ setting, userProfile }) {
7
+ const { filters, page, tabs, ajaxSetting, form, columns, settings } =
8
+ setting;
9
+
10
+ const [config, setConfig] = useState({});
11
+ const [subnav, setSubnav] = useState(filters || []);
12
+ const [total, setTotal] = useState(0);
13
+ const pageTitle = page?.title;
14
+
15
+ useEffect(() => {
16
+ if (!tabs || tabs.length === 0) {
17
+ setConfig(() => ({
18
+ ajaxSetting,
19
+ form,
20
+ columns,
21
+ settings,
22
+ }));
23
+ }
24
+
25
+ setSubnav(filters);
26
+ }, [setting]);
27
+
28
+ useEffect(() => {
29
+ if (tabs && tabs.length > 0) {
30
+ const activeTab = subnav.find((s) => s.show === true);
31
+
32
+ if (activeTab) {
33
+ const activeTabConfig = tabs.find((t) => t.id === activeTab.id);
34
+
35
+ if (activeTabConfig) {
36
+ setConfig(() => ({
37
+ ajaxSetting: activeTabConfig.ajaxSetting,
38
+ form: activeTabConfig.form,
39
+ columns: activeTabConfig.columns,
40
+ settings: activeTabConfig.settings,
41
+ }));
42
+ }
43
+ }
44
+ }
45
+ }, [subnav, tabs]);
46
+
47
+ return (
48
+ <>
49
+ <div className="grid">
50
+ <div className="grid__row">
51
+ <div className="grid__full crmtitle">
52
+ <h1>{pageTitle}</h1>
53
+ <div className="titleInfo">
54
+ <span>
55
+ [<strong>{total}</strong> Total {pageTitle}]
56
+ </span>
57
+ </div>
58
+ </div>
59
+ </div>
60
+ </div>
61
+
62
+ {config.ajaxSetting &&
63
+ config.form &&
64
+ config.columns &&
65
+ config.settings && (
66
+ <>
67
+ <div className="grid">
68
+ <div className="grid__row">
69
+ {subnav && subnav.length > 0 ? (
70
+ <>
71
+ <div className="grid__subnav">
72
+ <TableFilter
73
+ filters={subnav}
74
+ setFilters={setSubnav}
75
+ setSettings={setConfig}
76
+ />
77
+ </div>
78
+ <div className="grid__subcontent">
79
+ <Table
80
+ {...config}
81
+ setConfig={setConfig}
82
+ setTotal={setTotal}
83
+ style={
84
+ userProfile?.settings
85
+ ?.style || {}
86
+ }
87
+ />
88
+ </div>
89
+ </>
90
+ ) : (
91
+ <div className="grid__full">
92
+ <Table
93
+ {...config}
94
+ setConfig={setConfig}
95
+ setTotal={setTotal}
96
+ style={
97
+ userProfile?.settings?.style ||
98
+ {}
99
+ }
100
+ />
101
+ </div>
102
+ )}
103
+ </div>
104
+ </div>
105
+ <Outlet />
106
+ </>
107
+ )}
108
+ </>
109
+ );
110
+ }
111
+
112
+ export default GenericIndex;