@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.
- package/package.json +1 -1
- package/src/components/crm/TableFilter.jsx +287 -102
- package/src/components/crm/auth/Login.jsx +51 -39
- package/src/components/crm/auth/Reset.jsx +11 -5
- package/src/components/crm/auth/TwoFactorAuth.jsx +7 -9
- package/src/components/crm/auth/Verify.jsx +11 -5
- package/src/components/crm/auth/styles/Login.module.scss +110 -39
- package/src/components/crm/auth/styles/Reset.module.scss +51 -46
- package/src/components/crm/auth/styles/TwoFactorAuth.module.scss +154 -25
- package/src/components/crm/auth/styles/Verify.module.scss +51 -46
- package/src/components/crm/examples/FilterExample.jsx +207 -0
- package/src/components/crm/examples/README.md +113 -0
- package/src/components/crm/generic/GenericIndex.jsx +244 -87
- package/src/components/crm/generic/styles/ReportForm.module.scss +150 -0
- package/src/components/crm/styles/TableFilter.module.scss +121 -23
|
@@ -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
|
-
|
|
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
|
-
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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,
|
|
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=
|
|
143
|
-
<form className=
|
|
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
|
-
<
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
170
|
-
|
|
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
|
-
|
|
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
|
-
|
|
212
|
-
|
|
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
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
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
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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
|
+
}
|