@visns-studio/visns-components 5.0.1-8.1 → 5.0.2

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 (27) hide show
  1. package/package.json +1 -3
  2. package/src/components/crm/AsyncSelect.jsx +15 -121
  3. package/src/components/crm/DataGrid.jsx +5 -25
  4. package/src/components/crm/Field.jsx +148 -174
  5. package/src/components/crm/Form.jsx +28 -41
  6. package/src/components/crm/MultiSelect.jsx +29 -96
  7. package/src/components/crm/Navigation.jsx +40 -2
  8. package/src/components/crm/QrCode.jsx +8 -12
  9. package/src/components/crm/auth/Login.jsx +1 -9
  10. package/src/components/crm/auth/styles/Login.module.scss +3 -4
  11. package/src/components/crm/auth/styles/Profile.module.scss +14 -56
  12. package/src/components/crm/generic/GenericAuth.jsx +2 -5
  13. package/src/components/crm/generic/GenericDetail.jsx +18 -509
  14. package/src/components/crm/generic/styles/AuditLog.module.scss +0 -4
  15. package/src/components/crm/generic/styles/GenericDetail.module.scss +121 -261
  16. package/src/components/crm/generic/styles/GenericDynamic.module.scss +67 -35
  17. package/src/components/crm/generic/styles/GenericFormBuilder.module.scss +43 -4
  18. package/src/components/crm/generic/styles/GenericIndex.module.scss +1 -1
  19. package/src/components/crm/styles/Autocomplete.module.scss +20 -12
  20. package/src/components/crm/styles/DataGrid.module.scss +3 -2
  21. package/src/components/crm/styles/Field.module.scss +13 -19
  22. package/src/components/crm/styles/Form.module.scss +5 -5
  23. package/src/components/crm/styles/Navigation.module.scss +61 -11
  24. package/src/components/crm/styles/QrCode.module.scss +0 -4
  25. package/src/components/styles/global.css +5 -95
  26. package/src/components/crm/generic/GenericEditableTable.jsx +0 -407
  27. package/src/components/crm/generic/styles/GenericEditableTable.module.scss +0 -178
@@ -1,407 +0,0 @@
1
- import React, { useEffect, useMemo, useState } from 'react';
2
- import debounce from 'lodash.debounce';
3
- import { toast } from 'react-toastify';
4
- import Popup from 'reactjs-popup';
5
- import { confirmAlert } from 'react-confirm-alert';
6
- import { TrashCan, ArrowUp, ArrowDown } from 'akar-icons';
7
-
8
- import CustomFetch from '../Fetch';
9
- import Form from '../Form';
10
- import MultiSelect from '../MultiSelect';
11
-
12
- import 'react-confirm-alert/src/react-confirm-alert.css';
13
- import styles from './styles/GenericEditableTable.module.scss';
14
-
15
- const GenericEditableTable = ({
16
- schedulingConfig,
17
- data,
18
- dataId,
19
- modalOpen,
20
- preloadedOptions,
21
- }) => {
22
- const { columns, rows, form } = schedulingConfig;
23
-
24
- const [localData, setLocalData] = useState([]);
25
- const [groupedCategories, setGroupedCategories] = useState([]);
26
-
27
- useEffect(() => {
28
- const enrichedData = data[rows.key]?.map((entry, index) => ({
29
- ...entry,
30
- sort_order: entry.sort_order ?? index + 1,
31
- }));
32
- setLocalData(enrichedData || []);
33
- }, [data, rows.key]);
34
-
35
- const debouncedUpdate = useMemo(
36
- () =>
37
- debounce(async (updatedData) => {
38
- try {
39
- const res = await CustomFetch(
40
- `${form.url}/${dataId}`,
41
- 'PUT',
42
- updatedData
43
- );
44
- if (!res.error) {
45
- toast.success('Template updated successfully');
46
- } else {
47
- toast.error(res.error);
48
- }
49
- } catch (error) {
50
- console.error('Error updating field:', error);
51
- }
52
- }, 1000),
53
- []
54
- );
55
-
56
- const groupCategoriesByType = (data) => {
57
- const grouped = {};
58
-
59
- data.forEach((category, categoryKey) => {
60
- const categoryType =
61
- category[rows.categoryKey.id]?.[rows.categoryKey.label] || null;
62
-
63
- const categoryId = category[rows.categoryKey.id]?.id || null;
64
-
65
- const groupKey = categoryId || 'no_category';
66
-
67
- if (!grouped[groupKey]) {
68
- grouped[groupKey] = {
69
- id: categoryId || 'no_category',
70
- category: categoryType || '',
71
- entries: [],
72
- };
73
- }
74
- grouped[groupKey].entries.push({
75
- ...category,
76
- keyCounter: categoryKey,
77
- });
78
- });
79
-
80
- Object.values(grouped).forEach((group) => {
81
- group.entries.sort((a, b) => a.sort_order - b.sort_order);
82
- });
83
-
84
- // Ensure 'no_category' group is at the top
85
- const groupedArray = Object.values(grouped);
86
- return groupedArray.sort((a, b) =>
87
- a.id === 'no_category' ? -1 : b.id === 'no_category' ? 1 : 0
88
- );
89
- };
90
-
91
- const handleFieldChange = (value, fieldId, keyCounter) => {
92
- setLocalData((prev) => {
93
- const updatedDetail = [...prev];
94
- updatedDetail[keyCounter] = {
95
- ...updatedDetail[keyCounter],
96
- [fieldId]: value,
97
- };
98
-
99
- debouncedUpdate({ [rows.key]: updatedDetail });
100
-
101
- return updatedDetail;
102
- });
103
- };
104
-
105
- const handleSortChange = (groupId, entryId, direction) => {
106
- setLocalData((prev) => {
107
- const updatedDetail = [...prev];
108
- const group = groupCategoriesByType(updatedDetail).find(
109
- (group) => group.id === groupId
110
- );
111
-
112
- if (group) {
113
- const index = group.entries.findIndex(
114
- (entry) => entry.id === entryId
115
- );
116
-
117
- if (
118
- (direction === 'up' && index > 0) ||
119
- (direction === 'down' && index < group.entries.length - 1)
120
- ) {
121
- const swapIndex =
122
- direction === 'up' ? index - 1 : index + 1;
123
- [
124
- group.entries[index].sort_order,
125
- group.entries[swapIndex].sort_order,
126
- ] = [
127
- group.entries[swapIndex].sort_order,
128
- group.entries[index].sort_order,
129
- ];
130
-
131
- const reordered = updatedDetail.map((item) => {
132
- const entry = group.entries.find(
133
- (e) => e.id === item.id
134
- );
135
- return entry
136
- ? { ...item, sort_order: entry.sort_order }
137
- : item;
138
- });
139
-
140
- debouncedUpdate({ [rows.key]: reordered });
141
- return reordered;
142
- }
143
- }
144
-
145
- return updatedDetail;
146
- });
147
- };
148
-
149
- const handleDeleteRow = (entry) => {
150
- confirmAlert({
151
- title: 'Confirm to Delete',
152
- message: 'Are you sure you want to delete this entry?',
153
- buttons: [
154
- {
155
- label: 'Yes',
156
- onClick: async () => {
157
- setLocalData((prev) => {
158
- const updatedDetail = prev.filter(
159
- (item) => item.id !== entry.id
160
- );
161
- debouncedUpdate({ [rows.key]: updatedDetail });
162
- return updatedDetail;
163
- });
164
- },
165
- },
166
- {
167
- label: 'No',
168
- },
169
- ],
170
- });
171
- };
172
-
173
- const renderScheduledTableField = (entry, column, keyCounter) => {
174
- if (!entry || !column) return null;
175
-
176
- switch (column.type) {
177
- case 'currency':
178
- case 'number':
179
- return (
180
- <input
181
- type="text"
182
- value={entry[column.id] || ''}
183
- onChange={(e) =>
184
- handleFieldChange(
185
- e.target.value,
186
- column.id,
187
- keyCounter
188
- )
189
- }
190
- style={{ textAlign: 'right' }}
191
- />
192
- );
193
- case 'date':
194
- return (
195
- <input
196
- type="date"
197
- value={entry[column.id] || ''}
198
- onChange={(e) =>
199
- handleFieldChange(
200
- e.target.value,
201
- column.id,
202
- keyCounter
203
- )
204
- }
205
- style={{ textAlign: 'right' }}
206
- />
207
- );
208
- case 'dropdown':
209
- return (
210
- <select
211
- value={entry[column.id] || ''}
212
- onChange={(e) =>
213
- handleFieldChange(
214
- e.target.value,
215
- column.id,
216
- keyCounter
217
- )
218
- }
219
- >
220
- {column.options.map((option) => (
221
- <option key={option.id} value={option.id}>
222
- {option.label}
223
- </option>
224
- ))}
225
- </select>
226
- );
227
- case 'dropdown-ajax':
228
- const options = preloadedOptions[column.id] || [];
229
- return (
230
- <select
231
- value={entry[column.id] || ''}
232
- onChange={(e) =>
233
- handleFieldChange(
234
- e.target.value,
235
- column.id,
236
- keyCounter
237
- )
238
- }
239
- >
240
- <option>Select an Option</option>
241
- {options.map((option) => (
242
- <option key={option.id} value={option.id}>
243
- {option.label}
244
- </option>
245
- ))}
246
- </select>
247
- );
248
- case 'multi-dropdown-ajax':
249
- const multiOptions = preloadedOptions[column.id] || [];
250
- return (
251
- <MultiSelect
252
- settings={{
253
- id: column.id,
254
- }}
255
- className={styles.selectFullWidth}
256
- multi={false}
257
- onChange={(value) =>
258
- handleFieldChange(value, column.id, keyCounter)
259
- }
260
- inputValue={entry[column.id] || ''}
261
- options={multiOptions}
262
- />
263
- );
264
- default:
265
- return (
266
- <input
267
- type="text"
268
- defaultValue={entry[column.id] || ''}
269
- onChange={(e) =>
270
- handleFieldChange(
271
- e.target.value,
272
- column.id,
273
- keyCounter
274
- )
275
- }
276
- />
277
- );
278
- }
279
- };
280
-
281
- useEffect(() => {
282
- setGroupedCategories(groupCategoriesByType(localData));
283
- }, [localData]);
284
-
285
- return (
286
- <div className={styles.gridtxt}>
287
- <div className={styles.gridtxt__header}>
288
- <span>{schedulingConfig.title || 'Estimation Schedule'}</span>
289
- </div>
290
- {groupedCategories.length > 0 ? (
291
- <table className={styles.schedulingTable}>
292
- <thead>
293
- <tr>
294
- {columns.map((column) => (
295
- <th
296
- key={column.id}
297
- style={{ width: column.width || 'auto' }}
298
- >
299
- {column.label}
300
- </th>
301
- ))}
302
- <th style={{ width: '5%' }}></th>
303
- </tr>
304
- </thead>
305
- <tbody>
306
- {groupedCategories.map((group) => (
307
- <React.Fragment key={group.id}>
308
- <tr className={styles.categoryRow}>
309
- <td colSpan={columns.length + 1}>
310
- {group.category}
311
- </td>
312
- </tr>
313
- {group.entries.map((entry, idx) => (
314
- <tr key={entry.id}>
315
- {columns.map((column) => (
316
- <td
317
- key={column.id}
318
- style={{
319
- width:
320
- column.width || 'auto',
321
- textAlign:
322
- column.type ===
323
- 'currency'
324
- ? 'right'
325
- : 'left',
326
- }}
327
- >
328
- {renderScheduledTableField(
329
- entry,
330
- column,
331
- entry.keyCounter
332
- )}
333
- </td>
334
- ))}
335
- <td>
336
- <div className={styles.tdactions}>
337
- {idx > 0 && (
338
- <ArrowUp
339
- size={18}
340
- onClick={() =>
341
- handleSortChange(
342
- group.id,
343
- entry.id,
344
- 'up'
345
- )
346
- }
347
- style={{
348
- cursor: 'pointer',
349
- }}
350
- />
351
- )}
352
- {idx <
353
- group.entries.length -
354
- 1 && (
355
- <ArrowDown
356
- size={18}
357
- onClick={() =>
358
- handleSortChange(
359
- group.id,
360
- entry.id,
361
- 'down'
362
- )
363
- }
364
- style={{
365
- cursor: 'pointer',
366
- }}
367
- />
368
- )}
369
- <TrashCan
370
- size={18}
371
- onClick={() =>
372
- handleDeleteRow(entry)
373
- }
374
- style={{
375
- cursor: 'pointer',
376
- }}
377
- />
378
- </div>
379
- </td>
380
- </tr>
381
- ))}
382
- </React.Fragment>
383
- ))}
384
- </tbody>
385
- </table>
386
- ) : (
387
- <div className={styles.noDataMessage}>
388
- No data available. Please check your filters or try
389
- reloading the data.
390
- </div>
391
- )}
392
-
393
- <div className={styles.polActions}>
394
- <button
395
- className={styles.btn}
396
- onClick={() => {
397
- modalOpen('create', 0);
398
- }}
399
- >
400
- Add
401
- </button>
402
- </div>
403
- </div>
404
- );
405
- };
406
-
407
- export default GenericEditableTable;
@@ -1,178 +0,0 @@
1
- .schedulingTable {
2
- width: 100%;
3
- border-collapse: collapse;
4
- margin: 1em 0;
5
-
6
- th,
7
- td {
8
- padding: 0.75rem;
9
- text-align: left;
10
- border: 1px solid rgba(var(--primary-rgb), 0.1);
11
- }
12
-
13
- th {
14
- background-color: var(--secondary-color);
15
- color: var(--item-color);
16
- font-weight: bold;
17
- }
18
-
19
- td {
20
- background-color: rgba(var(--primary-rgb), 0.05);
21
- }
22
- }
23
-
24
- .categoryRow {
25
- background-color: rgba(var(--primary-rgb), 0.1);
26
- text-align: left;
27
- font-weight: bold;
28
- font-size: 1.2rem;
29
- color: var(--primary-color);
30
- }
31
-
32
- .totalRow {
33
- background-color: rgba(var(--primary-rgb), 0.05);
34
- font-weight: bold;
35
- text-align: right;
36
- }
37
-
38
- .noDataMessage {
39
- text-align: center;
40
- color: var(--secondary-color);
41
- font-size: 1.125em;
42
- padding: 1.5em;
43
- margin: 2em auto;
44
- background: #f9f9f9;
45
- border: 1px solid #ddd;
46
- border-radius: 6px;
47
- max-width: 600px;
48
- box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1);
49
- font-style: italic;
50
- }
51
-
52
- .polActions {
53
- position: fixed;
54
- bottom: 15px;
55
- right: 20px;
56
- width: auto;
57
-
58
- button {
59
- display: block;
60
- padding: 0.35em 1em;
61
- margin: 0 0.15em;
62
- }
63
- }
64
-
65
- .gridtxt {
66
- &__header {
67
- width: 100%;
68
- display: block;
69
- background: rgba(var(--primary-rgb), 0.05);
70
- box-sizing: border-box;
71
- padding: 10px 20px;
72
- border-radius: var(--br);
73
- margin-bottom: 0;
74
- font-weight: 700;
75
- text-align: center;
76
- color: var(--paragraph-color);
77
-
78
- span {
79
- font-weight: 700;
80
- }
81
- }
82
-
83
- > ul {
84
- width: 100%;
85
- display: flex;
86
- justify-content: flex-start;
87
- flex-wrap: wrap;
88
- list-style: none;
89
- padding: 0.85rem;
90
- margin: 0;
91
-
92
- li {
93
- flex: 0 0 calc(50% - 10px);
94
- padding: 0.65rem;
95
- border: 1px solid rgba(var(--primary-rgb), 0.095);
96
- color: var(--paragraph-color);
97
- background: rgba(var(--primary-rgb), 0.015);
98
- margin: 3px;
99
- border-radius: var(--br);
100
- line-height: 1.45;
101
- font-size: 0.85rem;
102
-
103
- &.fw-grid-item {
104
- flex: 0 0 calc(100% - 10px);
105
- }
106
-
107
- &.notecolor {
108
- border: 1px solid var(--secondary-color);
109
- }
110
- }
111
- }
112
- }
113
-
114
- .btn {
115
- width: max-content;
116
- display: inline-block;
117
- position: relative;
118
- padding: 0.65rem 1rem;
119
- cursor: pointer;
120
- font-size: 1rem;
121
- color: var(--tertiary-color);
122
- text-decoration: none;
123
- overflow: hidden;
124
- background: var(--primary-color);
125
- border: 1px solid rgba(var(--primary-color--rgb), 1.1);
126
- border-radius: var(--br);
127
- outline: none;
128
- transition: all 0.2s cubic-bezier(0.85, 0, 0.15, 1) 0s;
129
- font-size: 1.25em;
130
- }
131
-
132
- .btn:hover {
133
- color: var(--primary-color);
134
- background: var(--highlight-color);
135
- border: 1px solid rgba(var(--highlight-rgb), 1.05);
136
- }
137
-
138
- .tdactions {
139
- width: 100%;
140
- display: flex;
141
- flex-wrap: nowrap;
142
- justify-content: flex-start;
143
- align-items: center;
144
- gap: 0.1rem;
145
-
146
- span {
147
- display: flex;
148
- align-items: center;
149
- position: relative;
150
- width: 18px;
151
- height: 18px;
152
-
153
- svg {
154
- width: 18px;
155
- height: 18px;
156
- }
157
- }
158
- }
159
-
160
- .noDataMessage {
161
- text-align: center;
162
- color: var(--primary-color);
163
- font-size: 1rem;
164
- padding: 2rem;
165
- margin: 2rem auto;
166
- background-color: var(--secondary-bg-color, #f8f8f8);
167
- border: 1px solid rgba(var(--primary-rgb), 0.15);
168
- border-radius: 6px;
169
- max-width: 80%;
170
- box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1);
171
- font-weight: 500;
172
- font-family: var(--font-family, Arial, sans-serif);
173
- }
174
-
175
- .noDataMessage:hover {
176
- background-color: rgba(var(--primary-rgb), 0.05);
177
- border-color: rgba(var(--primary-rgb), 0.2);
178
- }