@visns-studio/visns-components 5.6.8 → 5.6.10

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.
@@ -7,6 +7,7 @@ import { saveAs } from 'file-saver';
7
7
  import _ from 'lodash';
8
8
  import parse from 'html-react-parser';
9
9
  import moment from 'moment';
10
+ import { CloudDownload } from 'akar-icons';
10
11
 
11
12
  import CustomFetch from '../Fetch';
12
13
  import Download from '../Download';
@@ -15,6 +16,7 @@ import Table from '../DataGrid';
15
16
  import TableFilter from '../TableFilter';
16
17
 
17
18
  import styles from './styles/GenericIndex.module.scss';
19
+ import reportStyles from './styles/ReportForm.module.scss';
18
20
 
19
21
  function useWindowHeight() {
20
22
  const [windowHeight, setWindowHeight] = useState(window.innerHeight);
@@ -33,7 +35,7 @@ function useConfig(setting, tabs, filters, setRowsSelected, tableSetting) {
33
35
  const [subnav, setSubnav] = useState(filters || []);
34
36
 
35
37
  useEffect(() => {
36
- setConfig({
38
+ const newConfig = {
37
39
  export: setting.export || {},
38
40
  ajaxSetting: setting.ajaxSetting,
39
41
  form: setting.form,
@@ -43,28 +45,142 @@ function useConfig(setting, tabs, filters, setRowsSelected, tableSetting) {
43
45
  style: setting.style || {},
44
46
  tableSetting,
45
47
  type: setting.type ? setting.type : 'table',
46
- });
48
+ };
49
+
50
+ setConfig(newConfig);
47
51
  setSubnav(filters);
48
52
  }, [setting, filters, setRowsSelected, tableSetting]);
49
53
 
50
54
  useEffect(() => {
51
55
  if (tabs?.length > 0 && subnav) {
52
- const activeTab = subnav.find((s) => s.show);
53
- const activeTabConfig = tabs.find((t) => t.id === activeTab?.id);
54
-
55
- if (activeTabConfig) {
56
- setConfig((currentConfig) => ({
57
- ...currentConfig,
58
- id: activeTabConfig.id,
59
- ajaxSetting: activeTabConfig.ajaxSetting,
60
- form: activeTabConfig.form,
61
- columns: activeTabConfig.columns,
62
- settings: activeTabConfig.settings,
63
- type: activeTabConfig.type ? activeTabConfig.type : 'table',
64
- }));
56
+ // Find active item (direct or child)
57
+ const findActiveItem = (items) => {
58
+ // First check direct items that are not parents
59
+ const directActive = items.find(
60
+ (item) => item.show === true && !item.isParent
61
+ );
62
+ if (directActive) {
63
+ return { item: directActive, isChild: false };
64
+ }
65
+
66
+ // Then check children of parent items
67
+ for (const item of items) {
68
+ if (
69
+ item.isParent &&
70
+ item.children &&
71
+ item.children.length > 0
72
+ ) {
73
+ const activeChild = item.children.find(
74
+ (child) => child.show === true
75
+ );
76
+ if (activeChild) {
77
+ return {
78
+ item: activeChild,
79
+ isChild: true,
80
+ parent: item,
81
+ };
82
+ }
83
+ }
84
+ }
85
+
86
+ // Finally, check for active parent items
87
+ const activeParent = items.find(
88
+ (item) => item.show === true && item.isParent
89
+ );
90
+ if (activeParent) {
91
+ // If parent is active but no child is active, activate the first child
92
+ if (
93
+ activeParent.children &&
94
+ activeParent.children.length > 0
95
+ ) {
96
+ const firstChild = activeParent.children[0];
97
+ return {
98
+ item: firstChild,
99
+ isChild: true,
100
+ parent: activeParent,
101
+ };
102
+ }
103
+
104
+ return { item: activeParent, isChild: false };
105
+ }
106
+
107
+ return null;
108
+ };
109
+
110
+ const activeItemResult = findActiveItem(subnav);
111
+
112
+ if (activeItemResult) {
113
+ const activeItem = activeItemResult.item;
114
+
115
+ // Find the tab config for this item
116
+ const activeTabConfig = tabs.find(
117
+ (t) => t.id === activeItem.id
118
+ );
119
+
120
+ if (activeTabConfig) {
121
+ // We found a matching tab config, use it
122
+ const updatedConfig = {
123
+ ...config,
124
+ id: activeTabConfig.id,
125
+ ajaxSetting: activeTabConfig.ajaxSetting,
126
+ form: activeTabConfig.form,
127
+ columns: activeTabConfig.columns,
128
+ settings: activeTabConfig.settings,
129
+ // Preserve the style from the original config or use the tab config's style
130
+ style:
131
+ config.style ||
132
+ activeTabConfig.style ||
133
+ setting.style ||
134
+ {},
135
+ type: activeTabConfig.type
136
+ ? activeTabConfig.type
137
+ : 'table',
138
+ };
139
+
140
+ setConfig(updatedConfig);
141
+ } else if (activeItemResult.isChild) {
142
+ // If this is a child item but we couldn't find a tab config,
143
+ // try to use the base config but update the filter
144
+
145
+ // Create a new config based on the base settings
146
+ const baseConfig = {
147
+ export: setting.export || {},
148
+ ajaxSetting: setting.ajaxSetting
149
+ ? { ...setting.ajaxSetting }
150
+ : undefined,
151
+ form: setting.form,
152
+ columns: setting.columns,
153
+ setRowsSelected,
154
+ settings: setting.settings,
155
+ // Make sure we have a style property
156
+ style: config.style || setting.style || {},
157
+ tableSetting,
158
+ type: setting.type ? setting.type : 'table',
159
+ };
160
+
161
+ // Add the filter for the active child
162
+ if (
163
+ baseConfig.ajaxSetting &&
164
+ baseConfig.ajaxSetting.where
165
+ ) {
166
+ // Remove any existing filter with this ID
167
+ baseConfig.ajaxSetting.where =
168
+ baseConfig.ajaxSetting.where.filter(
169
+ (w) => w.id !== activeItem.id
170
+ );
171
+
172
+ // Add the new filter
173
+ baseConfig.ajaxSetting.where.push({
174
+ id: activeItem.id,
175
+ value: activeItem.value || '',
176
+ });
177
+ }
178
+
179
+ setConfig(baseConfig);
180
+ }
65
181
  }
66
182
  }
67
- }, [subnav, tabs]);
183
+ }, [subnav, tabs, config, setting, setRowsSelected, tableSetting]);
68
184
 
69
185
  return { config, setConfig, subnav, setSubnav };
70
186
  }
@@ -85,7 +201,7 @@ const ReportForm = ({ config, userProfile }) => {
85
201
  setFormData((prevData) => ({ ...prevData, [id]: date }));
86
202
  };
87
203
 
88
- const handleChangeSelect = (inputValue, action, id) => {
204
+ const handleChangeSelect = (inputValue, _, id) => {
89
205
  setFormData((prevData) => ({ ...prevData, [id]: inputValue }));
90
206
  };
91
207
 
@@ -106,6 +222,8 @@ const ReportForm = ({ config, userProfile }) => {
106
222
  const errorMessage = `Please fill in a value for the following field(s): ${fieldNames}.`;
107
223
  toast.error(errorMessage);
108
224
  } else {
225
+ toast.info('Preparing your export...');
226
+
109
227
  const res = await Download(
110
228
  config.form.url,
111
229
  config.form.method,
@@ -119,6 +237,7 @@ const ReportForm = ({ config, userProfile }) => {
119
237
  if (res.data && res.data instanceof Blob) {
120
238
  const fileName = `${config.form.filename}`;
121
239
  saveAs(res.data, fileName);
240
+ toast.success('Export completed successfully');
122
241
  } else {
123
242
  // Handle the case where the response is not a Blob
124
243
  toast.error('Invalid file format received.');
@@ -127,6 +246,7 @@ const ReportForm = ({ config, userProfile }) => {
127
246
  }
128
247
  } catch (err) {
129
248
  console.error(err);
249
+ toast.error('An error occurred during export');
130
250
  }
131
251
  };
132
252
 
@@ -138,36 +258,63 @@ const ReportForm = ({ config, userProfile }) => {
138
258
  setFormData(tmpForm);
139
259
  }, [config.form?.filters]);
140
260
 
261
+ // Determine if the form has a title
262
+ const formTitle = config.form?.title || 'Export Report';
263
+ const formDescription =
264
+ config.form?.description || 'Select your export criteria below.';
265
+
141
266
  return (
142
- <div className="formcontainer">
143
- <form className="modalForm">
267
+ <div className={reportStyles.reportFormContainer}>
268
+ <form className={reportStyles.reportForm}>
269
+ <h2 className={reportStyles.formTitle}>{formTitle}</h2>
270
+ <p className={reportStyles.formDescription}>
271
+ {formDescription}
272
+ </p>
273
+
144
274
  {config.form?.filters.map((item, key) => (
145
- <Field
146
- api={userProfile.settings.api}
147
- autocompleteCallback={() => {}}
148
- childDropdownCallback={() => {}}
149
- fetchData={() => {}}
150
- formData={formData}
151
- inputClass={''}
152
- inputValue={formData[item.id]}
153
- key={`filter-fields-${key}`}
154
- mapbox={{}}
155
- onChange={handleChange}
156
- onChangeCheckbox={() => {}}
157
- onChangeDate={handleChangeDate}
158
- onChangeNumberFormat={() => {}}
159
- onChangeRicheditor={() => {}}
160
- onChangeSelect={handleChangeSelect}
161
- onChangeToggle={() => {}}
162
- settings={item}
163
- setFormData={setFormData}
164
- style={userProfile.settings.style}
165
- uploadProgress={0}
166
- userProfile={userProfile}
167
- />
275
+ <div
276
+ key={`filter-container-${key}`}
277
+ className={`${reportStyles.formField} ${
278
+ item.fullWidth ? reportStyles.fullWidthField : ''
279
+ }`}
280
+ >
281
+ <Field
282
+ api={userProfile.settings.api}
283
+ autocompleteCallback={() => {}}
284
+ childDropdownCallback={() => {}}
285
+ fetchData={() => {}}
286
+ formData={formData}
287
+ inputClass={''}
288
+ inputValue={formData[item.id]}
289
+ key={`filter-fields-${key}`}
290
+ mapbox={{}}
291
+ onChange={handleChange}
292
+ onChangeCheckbox={() => {}}
293
+ onChangeDate={handleChangeDate}
294
+ onChangeNumberFormat={() => {}}
295
+ onChangeRicheditor={() => {}}
296
+ onChangeSelect={handleChangeSelect}
297
+ onChangeToggle={() => {}}
298
+ settings={item}
299
+ setFormData={setFormData}
300
+ style={userProfile.settings.style}
301
+ uploadProgress={0}
302
+ userProfile={userProfile}
303
+ />
304
+ </div>
168
305
  ))}
169
- <div className="formItem fwItem lastItem">
170
- <button className="btn modalsave" onClick={handleExport}>
306
+
307
+ <div className={reportStyles.formActions}>
308
+ <button
309
+ className={reportStyles.exportButton}
310
+ onClick={handleExport}
311
+ type="button"
312
+ >
313
+ <CloudDownload
314
+ strokeWidth={2}
315
+ size={18}
316
+ style={{ marginRight: '8px' }}
317
+ />{' '}
171
318
  Export
172
319
  </button>
173
320
  </div>
@@ -184,7 +331,7 @@ function GenericIndex({
184
331
  userProfile,
185
332
  }) {
186
333
  const gridRef = useRef(null);
187
- const params = useParams();
334
+ useParams(); // Using router params if needed in the future
188
335
  const [rowsSelected, setRowsSelected] = useState({});
189
336
  const [tableData, setTableData] = useState([]);
190
337
  const [tableInfo, setTableInfo] = useState({});
@@ -208,12 +355,18 @@ function GenericIndex({
208
355
 
209
356
  const handleCustomAction = async () => {
210
357
  try {
211
- const { data, method, message, url } = setting?.functions
212
- ?.customAction
358
+ // Get the custom action configuration
359
+ const customActionConfig = setting?.functions?.customAction
213
360
  ? setting.functions.customAction
214
361
  : config.form.functions.customAction;
362
+
363
+ if (customActionConfig) {
364
+ // Implementation will be added when needed
365
+ console.log('Custom action triggered', customActionConfig);
366
+ }
215
367
  } catch (err) {
216
368
  console.error(err);
369
+ toast.error('An error occurred while performing the custom action');
217
370
  }
218
371
  };
219
372
 
@@ -340,55 +493,43 @@ function GenericIndex({
340
493
 
341
494
  const renderTable = useCallback(() => {
342
495
  const isConfigValid = (config) => {
496
+ // Check if config exists and has the required properties
497
+ if (!config) return false;
498
+
343
499
  return (
344
500
  config.ajaxSetting &&
345
501
  config.form &&
346
502
  config.columns &&
347
- config.settings
503
+ config.settings !== undefined // Allow empty settings array
348
504
  );
349
505
  };
350
506
 
351
- const renderTableComponent = () => (
352
- <Table
353
- {...config}
354
- ref={gridRef}
355
- hoverColor={config.style.hoverColor}
356
- gridHeight={windowHeight * 0.65}
357
- setConfig={setConfig}
358
- setTableData={setTableData}
359
- setTotal={setTotal}
360
- style={userProfile?.settings?.style || {}}
361
- userProfile={userProfile}
362
- />
363
- );
364
-
365
- if (subnav) {
366
- // Find the active subnav item with show: true
367
- const activeSubnav = subnav.find((item) => item.show === true);
368
-
369
- // Check if the config.id matches the active subnav id
370
- if (
371
- activeSubnav &&
372
- activeSubnav.id === config.id &&
373
- isConfigValid(config)
374
- ) {
375
- return renderTableComponent();
376
- }
507
+ const renderTableComponent = () => {
508
+ // Ensure config has a style property
509
+ const configWithStyle = {
510
+ ...config,
511
+ style: config.style || userProfile?.settings?.style || {},
512
+ };
377
513
 
378
- // Check for active alternative subnav
379
- const activeAltSubNav = subnav.find(
380
- (item) =>
381
- item.class === 'subactive' || item.class === 'activetab'
514
+ return (
515
+ <Table
516
+ {...configWithStyle}
517
+ ref={gridRef}
518
+ hoverColor={configWithStyle.style?.hoverColor}
519
+ gridHeight={windowHeight * 0.65}
520
+ setConfig={setConfig}
521
+ setTableData={setTableData}
522
+ setTotal={setTotal}
523
+ style={userProfile?.settings?.style || {}}
524
+ userProfile={userProfile}
525
+ />
382
526
  );
527
+ };
383
528
 
384
- const activeTabExist =
385
- config.ajaxSetting?.where?.some(
386
- (w) =>
387
- w.id === activeAltSubNav?.id &&
388
- parseInt(w.value) === parseInt(activeAltSubNav?.value)
389
- ) || false;
390
-
391
- if (isConfigValid(config) && activeTabExist) {
529
+ if (subnav) {
530
+ // Simply check if the config is valid - we've already set up the config
531
+ // in the useConfig hook based on the active filter
532
+ if (isConfigValid(config)) {
392
533
  return renderTableComponent();
393
534
  }
394
535
  } else if (isConfigValid(config)) {
@@ -409,6 +550,20 @@ function GenericIndex({
409
550
  }
410
551
  }, [config, renderTable]);
411
552
 
553
+ // Custom handler for filter changes
554
+ const handleFilterChange = useCallback(
555
+ (newFilters, newSettings) => {
556
+ // Update the filters
557
+ setSubnav(newFilters);
558
+
559
+ // Update the settings
560
+ if (newSettings) {
561
+ setConfig(newSettings);
562
+ }
563
+ },
564
+ [setSubnav, setConfig]
565
+ );
566
+
412
567
  const renderContent = useCallback(() => {
413
568
  if (subnav?.length > 0) {
414
569
  return (
@@ -422,6 +577,7 @@ function GenericIndex({
422
577
  setFilters={setSubnav}
423
578
  setSettings={setConfig}
424
579
  type={layout}
580
+ onFilterChange={handleFilterChange}
425
581
  />
426
582
  </div>
427
583
  <div className={styles.tabcontent__main}>
@@ -443,6 +599,7 @@ function GenericIndex({
443
599
  setFilters={setSubnav}
444
600
  setSettings={setConfig}
445
601
  type={layout}
602
+ onFilterChange={handleFilterChange}
446
603
  />
447
604
  </div>
448
605
  <div className={styles.grid__subcontent}>
@@ -459,7 +616,7 @@ function GenericIndex({
459
616
  </div>
460
617
  );
461
618
  }
462
- }, [layout, subnav, setSubnav, setConfig, renderTable]);
619
+ }, [layout, subnav, setSubnav, setConfig, renderTable, handleFilterChange]);
463
620
 
464
621
  useEffect(() => {
465
622
  if (setting.page?.tableInfo?.hasOwnProperty('url')) {
@@ -22,19 +22,19 @@
22
22
  &__subrow {
23
23
  width: 100%;
24
24
  display: flex;
25
- flex-wrap: wrap;
25
+ flex-wrap: nowrap;
26
26
  height: auto;
27
27
  }
28
28
 
29
29
  &__subnav {
30
- flex: 1;
30
+ flex: 0 0 20%;
31
31
  background: white;
32
32
  border-radius: var(--br);
33
33
  box-sizing: border-box;
34
- padding: 5px;
35
- margin: 8px 0;
34
+ padding: 8px;
35
+ margin: 8px 8px 8px 0;
36
36
  height: auto;
37
- box-shadow: 0 10px 50px rgba(var(--primary-rgb), 0.05);
37
+ box-shadow: 0 5px 15px rgba(var(--primary-rgb), 0.05);
38
38
 
39
39
  > ul {
40
40
  width: 100%;
@@ -114,7 +114,7 @@
114
114
  }
115
115
 
116
116
  &__subcontent {
117
- flex: 0 1 85%;
117
+ flex: 0 1 80%;
118
118
  background: white;
119
119
  border-radius: var(--br);
120
120
  border: 1px solid var(--border-color);
@@ -138,14 +138,14 @@
138
138
  display: block;
139
139
  background: var(--border-color);
140
140
  box-sizing: border-box;
141
- padding: 10px 20px;
142
- border-top-left-radius: var(--br);
143
- border-top-right-radius: var(--br);
141
+ padding: 10px 15px;
142
+ border-radius: var(--br);
144
143
  margin-bottom: 0;
145
- font-weight: 700;
144
+ font-weight: 600;
146
145
  text-align: center;
147
146
  color: var(--paragraph-color);
148
147
  position: relative;
148
+ font-size: 0.95rem;
149
149
 
150
150
  span {
151
151
  font-weight: 700;
@@ -257,7 +257,7 @@ input[type='file']:hover {
257
257
  }
258
258
 
259
259
  .formBuilderItem {
260
- width: 25%;
260
+ width: 50%;
261
261
  padding: 0.35em;
262
262
  position: relative;
263
263
  box-sizing: border-box;
@@ -318,7 +318,7 @@ input[type='file']:hover {
318
318
  }
319
319
 
320
320
  .qtrBuilderItem {
321
- flex-basis: 25%;
321
+ flex-basis: 50%;
322
322
  }
323
323
 
324
324
  .halfBuilderItem {
@@ -871,45 +871,73 @@ select:not(:placeholder-shown) + .fi__span {
871
871
  .sortableListContainer {
872
872
  max-height: 800px; /* Optional for scrollable content */
873
873
  overflow-y: auto;
874
+ padding: 0.75rem;
875
+ border-radius: var(--br);
876
+ background-color: rgba(var(--primary-rgb), 0.02);
874
877
  }
875
878
 
876
879
  .dragitem {
877
880
  list-style: none;
878
881
  display: flex;
879
- justify-content: center;
882
+ justify-content: flex-start;
880
883
  align-items: center;
881
884
  flex-wrap: wrap;
882
- border-radius: 5px;
885
+ border-radius: 6px;
883
886
  border: 1px solid rgba(var(--primary-rgb), 0.2);
884
- box-shadow: 0px 0px 30px rgba(var(--primary-rgb), 0.1);
885
- padding: 1.25rem 1.25rem 1.25rem 2rem;
887
+ box-shadow: 0px 0px 15px rgba(var(--primary-rgb), 0.15);
888
+ padding: 0.8rem 5rem 0.8rem 2rem;
886
889
  box-sizing: border-box;
887
890
  cursor: grab;
888
891
  list-style: none;
889
892
  color: var(--paragraph-color);
893
+ font-size: 0.9rem;
894
+ font-weight: 500;
890
895
 
891
- svg {
896
+ > svg:first-child {
892
897
  width: 100%;
893
898
  max-width: 18px;
894
899
  position: absolute;
895
- left: 5px;
896
- top: 5px;
900
+ left: 6px;
901
+ top: 50%;
902
+ transform: translateY(-50%);
903
+ color: var(--primary-color);
897
904
  }
898
905
 
899
- .edit {
900
- width: 100%;
901
- max-width: 18px;
906
+ .actionIcons {
902
907
  position: absolute;
903
- left: 5px;
904
- top: 30px;
908
+ right: 10px;
909
+ top: 50%;
910
+ transform: translateY(-50%);
911
+ display: flex;
912
+ gap: 12px;
913
+ align-items: center;
914
+ z-index: 10;
915
+ }
916
+
917
+ .edit {
918
+ width: 22px;
919
+ height: 22px;
920
+ color: var(--primary-color);
921
+ transition: color 0.2s ease;
922
+ cursor: pointer;
923
+ display: block;
924
+
925
+ &:hover {
926
+ color: var(--secondary-color);
927
+ }
905
928
  }
906
929
 
907
930
  .delete {
908
- width: 100%;
909
- max-width: 18px;
910
- position: absolute;
911
- left: 5px;
912
- top: 60px;
931
+ width: 22px;
932
+ height: 22px;
933
+ color: var(--primary-color);
934
+ transition: color 0.2s ease;
935
+ cursor: pointer;
936
+ display: block;
937
+
938
+ &:hover {
939
+ color: #d32f2f;
940
+ }
913
941
  }
914
942
 
915
943
  .sortimg {
@@ -124,28 +124,50 @@
124
124
  color: var(--paragraph-color);
125
125
  font-size: 0.8rem;
126
126
 
127
- svg {
127
+ > svg:first-child {
128
128
  width: 100%;
129
129
  max-width: 16px;
130
130
  position: absolute;
131
131
  left: 4px;
132
- top: 4px;
132
+ top: 50%;
133
+ transform: translateY(-50%);
133
134
  }
134
135
 
135
- .pencil-edit {
136
- width: 100%;
137
- max-width: 16px;
136
+ .actionIcons {
138
137
  position: absolute;
139
- left: 4px;
140
- top: 24px;
138
+ right: 10px;
139
+ top: 50%;
140
+ transform: translateY(-50%);
141
+ display: flex;
142
+ gap: 12px;
143
+ align-items: center;
144
+ z-index: 10;
145
+ }
146
+
147
+ .pencil-edit {
148
+ width: 22px;
149
+ height: 22px;
150
+ color: var(--primary-color);
151
+ transition: color 0.2s ease;
152
+ cursor: pointer;
153
+ display: block;
154
+
155
+ &:hover {
156
+ color: var(--secondary-color);
157
+ }
141
158
  }
142
159
 
143
160
  .trash-delete {
144
- width: 100%;
145
- max-width: 16px;
146
- position: absolute;
147
- left: 4px;
148
- top: 44px;
161
+ width: 22px;
162
+ height: 22px;
163
+ color: var(--primary-color);
164
+ transition: color 0.2s ease;
165
+ cursor: pointer;
166
+ display: block;
167
+
168
+ &:hover {
169
+ color: #d32f2f;
170
+ }
149
171
  }
150
172
 
151
173
  .sortimg {