@visns-studio/visns-components 5.6.7 → 5.6.9

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')) {
@@ -0,0 +1,150 @@
1
+ /* Report Form Styles */
2
+
3
+ .reportFormContainer {
4
+ width: 100%;
5
+ background: var(--item-color, white);
6
+ border-radius: var(--br, 8px);
7
+ padding: 24px;
8
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
9
+ }
10
+
11
+ .reportForm {
12
+ width: 100%;
13
+ display: grid;
14
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
15
+ gap: 20px;
16
+ }
17
+
18
+ .formTitle {
19
+ grid-column: 1 / -1;
20
+ font-size: 1.25rem;
21
+ font-weight: 600;
22
+ color: var(--heading-color, #1f2937);
23
+ margin-bottom: 16px;
24
+ padding-bottom: 8px;
25
+ border-bottom: 1px solid var(--border-color, #e5e7eb);
26
+ }
27
+
28
+ .formDescription {
29
+ grid-column: 1 / -1;
30
+ font-size: 0.95rem;
31
+ color: var(--muted-color, #6b7280);
32
+ margin-bottom: 20px;
33
+ }
34
+
35
+ .formField {
36
+ width: 100%;
37
+ position: relative;
38
+ margin-bottom: 8px;
39
+
40
+ /* Style improvements for form fields */
41
+ :global(.fi__label) {
42
+ margin-bottom: 6px;
43
+ font-weight: 500;
44
+ color: var(--heading-color, #374151);
45
+ }
46
+
47
+ :global(.react-select__control) {
48
+ border-color: var(--border-color, #d1d5db);
49
+ box-shadow: none;
50
+
51
+ &:hover {
52
+ border-color: var(--primary-color, #2563eb);
53
+ }
54
+
55
+ &:focus-within {
56
+ border-color: var(--primary-color, #2563eb);
57
+ box-shadow: 0 0 0 1px var(--primary-color, #2563eb);
58
+ }
59
+ }
60
+
61
+ input,
62
+ select,
63
+ textarea {
64
+ border-radius: var(--br, 6px);
65
+ border: 1px solid var(--border-color, #d1d5db);
66
+ padding: 10px 12px;
67
+ width: 100%;
68
+ font-size: 0.95rem;
69
+
70
+ &:focus {
71
+ outline: none;
72
+ border-color: var(--primary-color, #2563eb);
73
+ box-shadow: 0 0 0 3px rgba(var(--primary-rgb, 37, 99, 235), 0.1);
74
+ }
75
+ }
76
+ }
77
+
78
+ .fullWidthField {
79
+ grid-column: 1 / -1;
80
+ }
81
+
82
+ .formActions {
83
+ grid-column: 1 / -1;
84
+ display: flex;
85
+ justify-content: flex-end;
86
+ margin-top: 16px;
87
+ padding-top: 16px;
88
+ border-top: 1px solid var(--border-color, #e5e7eb);
89
+ }
90
+
91
+ .exportButton {
92
+ display: inline-flex;
93
+ align-items: center;
94
+ justify-content: center;
95
+ padding: 12px 28px;
96
+ background-color: var(--primary-color, #2563eb);
97
+ color: white;
98
+ border: none;
99
+ border-radius: var(--br, 6px);
100
+ font-size: 1rem;
101
+ font-weight: 500;
102
+ cursor: pointer;
103
+ transition: all 0.2s ease;
104
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
105
+ min-width: 140px;
106
+
107
+ svg {
108
+ transition: transform 0.2s ease;
109
+ vertical-align: middle;
110
+ position: relative;
111
+ top: -1px;
112
+ }
113
+
114
+ &:hover {
115
+ background-color: var(
116
+ --primary-hover-color,
117
+ #1d4ed8
118
+ ); /* Darker shade of primary */
119
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
120
+
121
+ svg {
122
+ transform: translateY(-1px);
123
+ }
124
+ }
125
+
126
+ &:focus {
127
+ outline: none;
128
+ box-shadow: 0 0 0 3px rgba(var(--primary-rgb, 37, 99, 235), 0.3);
129
+ }
130
+
131
+ &:active {
132
+ transform: translateY(1px);
133
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
134
+ }
135
+ }
136
+
137
+ /* Responsive adjustments */
138
+ @media (max-width: 768px) {
139
+ .reportForm {
140
+ grid-template-columns: 1fr;
141
+ }
142
+
143
+ .reportFormContainer {
144
+ padding: 16px;
145
+ }
146
+
147
+ .formActions {
148
+ justify-content: center;
149
+ }
150
+ }