@visns-studio/visns-components 5.6.9 → 5.6.11
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/README.md +476 -12
- package/package.json +1 -1
- package/src/components/cms/sorting/Item.jsx +24 -18
- package/src/components/cms/sorting/List.jsx +3 -1
- package/src/components/cms/sorting/styles/Item.module.scss +38 -0
- package/src/components/cms/sorting/styles/List.module.scss +74 -0
- package/src/components/crm/DataGrid.jsx +22 -0
- package/src/components/crm/generic/GenericFormBuilder.jsx +21 -3
- package/src/components/crm/generic/styles/GenericFormBuilder.module.scss +57 -29
- package/src/components/crm/generic/styles/GenericSort.module.scss +34 -12
- package/src/components/crm/sorting/Item.jsx +22 -18
- package/src/components/crm/sorting/styles/Item.module.scss +31 -10
- package/src/components/crm/sorting/styles/List.module.scss +21 -9
package/README.md
CHANGED
|
@@ -191,12 +191,28 @@ A powerful data table component with sorting, filtering, and pagination.
|
|
|
191
191
|
params: {},
|
|
192
192
|
}}
|
|
193
193
|
columns={[
|
|
194
|
-
{
|
|
195
|
-
{
|
|
194
|
+
{ name: 'id', headerName: 'ID', width: 90 },
|
|
195
|
+
{ name: 'name', headerName: 'Name', width: 150 },
|
|
196
|
+
{
|
|
197
|
+
name: 'department',
|
|
198
|
+
headerName: 'Department',
|
|
199
|
+
width: 180,
|
|
200
|
+
groupSummaryReducer: 'count',
|
|
201
|
+
groupSummaryRenderer: (count) => `${count} employees`,
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
name: 'salary',
|
|
205
|
+
headerName: 'Salary',
|
|
206
|
+
width: 120,
|
|
207
|
+
type: 'number',
|
|
208
|
+
groupSummaryReducer: 'sum',
|
|
209
|
+
groupSummaryRenderer: (sum) => `$${sum.toLocaleString()}`,
|
|
210
|
+
},
|
|
196
211
|
// More columns...
|
|
197
212
|
]}
|
|
198
213
|
form={formConfig}
|
|
199
214
|
gridHeight={500}
|
|
215
|
+
defaultGroupBy={[{ name: 'department' }]}
|
|
200
216
|
settings={{
|
|
201
217
|
pagination: true,
|
|
202
218
|
pageSize: 25,
|
|
@@ -214,16 +230,26 @@ A powerful data table component with sorting, filtering, and pagination.
|
|
|
214
230
|
|
|
215
231
|
#### DataGrid Props
|
|
216
232
|
|
|
217
|
-
| Prop
|
|
218
|
-
|
|
|
219
|
-
| ajaxSetting
|
|
220
|
-
| columns
|
|
221
|
-
|
|
|
222
|
-
|
|
|
223
|
-
|
|
|
224
|
-
|
|
|
225
|
-
|
|
|
226
|
-
|
|
|
233
|
+
| Prop | Description | Type | Default |
|
|
234
|
+
| --------------------- | ------------------------------------------ | -------- | ------- |
|
|
235
|
+
| ajaxSetting | Configuration for data fetching | Object | - |
|
|
236
|
+
| columns | Column definitions | Array | - |
|
|
237
|
+
| defaultExpandedNodes | Initial expanded nodes for TreeGrid | Object | - |
|
|
238
|
+
| defaultGroupBy | Initial grouping configuration | Array | - |
|
|
239
|
+
| expandedNodes | Current expanded nodes for TreeGrid | Object | - |
|
|
240
|
+
| form | Form configuration for editing | Object | - |
|
|
241
|
+
| gridHeight | Height of the grid | Number | 500 |
|
|
242
|
+
| groupBy | Current grouping configuration | Array | - |
|
|
243
|
+
| loadNode | Function to load TreeGrid nodes on demand | Function | - |
|
|
244
|
+
| onExpandedNodesChange | Callback for TreeGrid node expand/collapse | Function | - |
|
|
245
|
+
| onGroupByChange | Callback for grouping changes | Function | - |
|
|
246
|
+
| renderGroupTitle | Custom renderer for group headers | Function | - |
|
|
247
|
+
| settings | Grid settings (pagination, etc.) | Object | - |
|
|
248
|
+
| setFilterData | Callback for filter changes | Function | - |
|
|
249
|
+
| setConfig | Callback for configuration changes | Function | - |
|
|
250
|
+
| style | Custom styles | Object | - |
|
|
251
|
+
| treeColumn | Column name to display TreeGrid hierarchy | String | - |
|
|
252
|
+
| treeNestingSize | Indentation size for TreeGrid levels | Number | 20 |
|
|
227
253
|
|
|
228
254
|
#### Auto-Refresh Functionality
|
|
229
255
|
|
|
@@ -253,6 +279,444 @@ When auto-refresh is configured:
|
|
|
253
279
|
- Setting `autoRefresh: true` uses the default 30-second interval
|
|
254
280
|
- The component silently refreshes data without showing notifications
|
|
255
281
|
|
|
282
|
+
#### Group By Functionality
|
|
283
|
+
|
|
284
|
+
The DataGrid component supports grouping data by one or more columns, allowing you to organize and visualize data in hierarchical structures.
|
|
285
|
+
|
|
286
|
+
##### Using defaultGroupBy
|
|
287
|
+
|
|
288
|
+
The `defaultGroupBy` property allows you to specify initial grouping when the DataGrid loads:
|
|
289
|
+
|
|
290
|
+
```jsx
|
|
291
|
+
<DataGrid
|
|
292
|
+
// Other props...
|
|
293
|
+
defaultGroupBy={[
|
|
294
|
+
{ name: 'category' }, // Group by a single column
|
|
295
|
+
]}
|
|
296
|
+
/>
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
For more complex grouping with multiple levels:
|
|
300
|
+
|
|
301
|
+
```jsx
|
|
302
|
+
<DataGrid
|
|
303
|
+
// Other props...
|
|
304
|
+
defaultGroupBy={[
|
|
305
|
+
{ name: 'department' }, // First level grouping
|
|
306
|
+
{ name: 'role' }, // Second level grouping
|
|
307
|
+
]}
|
|
308
|
+
/>
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
##### Dynamic Grouping
|
|
312
|
+
|
|
313
|
+
You can also control grouping programmatically:
|
|
314
|
+
|
|
315
|
+
```jsx
|
|
316
|
+
const [groupBy, setGroupBy] = useState([{ name: 'category' }]);
|
|
317
|
+
|
|
318
|
+
// Later in your component
|
|
319
|
+
<DataGrid
|
|
320
|
+
// Other props...
|
|
321
|
+
groupBy={groupBy}
|
|
322
|
+
onGroupByChange={setGroupBy}
|
|
323
|
+
/>;
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
##### Server-Side Grouping
|
|
327
|
+
|
|
328
|
+
For server-side grouping, the DataGrid sends the groupBy information to your API endpoint:
|
|
329
|
+
|
|
330
|
+
```jsx
|
|
331
|
+
<DataGrid
|
|
332
|
+
ajaxSetting={{
|
|
333
|
+
url: '/api/data',
|
|
334
|
+
method: 'POST',
|
|
335
|
+
// Other settings...
|
|
336
|
+
}}
|
|
337
|
+
defaultGroupBy={[{ name: 'category' }]}
|
|
338
|
+
// Other props...
|
|
339
|
+
/>
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
Your server will receive a request with groupBy information:
|
|
343
|
+
|
|
344
|
+
```json
|
|
345
|
+
{
|
|
346
|
+
"page": 1,
|
|
347
|
+
"take": 25,
|
|
348
|
+
"groupBy": [{ "name": "category" }]
|
|
349
|
+
// Other parameters...
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
##### Group Summaries
|
|
354
|
+
|
|
355
|
+
You can add summary calculations to grouped data:
|
|
356
|
+
|
|
357
|
+
```jsx
|
|
358
|
+
<DataGrid
|
|
359
|
+
// Other props...
|
|
360
|
+
columns={[
|
|
361
|
+
{
|
|
362
|
+
name: 'amount',
|
|
363
|
+
headerName: 'Amount',
|
|
364
|
+
type: 'number',
|
|
365
|
+
groupSummaryReducer: 'sum', // Options: 'sum', 'avg', 'min', 'max', 'count'
|
|
366
|
+
groupSummaryRenderer: (summary) => `Total: $${summary.toFixed(2)}`,
|
|
367
|
+
},
|
|
368
|
+
// Other columns...
|
|
369
|
+
]}
|
|
370
|
+
defaultGroupBy={[{ name: 'category' }]}
|
|
371
|
+
/>
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
##### Customizing Group Rendering
|
|
375
|
+
|
|
376
|
+
You can customize how groups are displayed:
|
|
377
|
+
|
|
378
|
+
```jsx
|
|
379
|
+
<DataGrid
|
|
380
|
+
// Other props...
|
|
381
|
+
renderGroupTitle={(groupData) => (
|
|
382
|
+
<div style={{ fontWeight: 'bold', color: 'var(--primary-color)' }}>
|
|
383
|
+
{groupData.name}: {groupData.value} ({groupData.count} items)
|
|
384
|
+
</div>
|
|
385
|
+
)}
|
|
386
|
+
defaultGroupBy={[{ name: 'category' }]}
|
|
387
|
+
/>
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
##### Complete Example with Dynamic Grouping
|
|
391
|
+
|
|
392
|
+
Here's a complete example of a component that uses DataGrid with dynamic grouping controls:
|
|
393
|
+
|
|
394
|
+
```jsx
|
|
395
|
+
import React, { useState, useEffect } from 'react';
|
|
396
|
+
import { DataGrid } from 'visns-components';
|
|
397
|
+
|
|
398
|
+
const EmployeeDataGrid = () => {
|
|
399
|
+
// State for grouping
|
|
400
|
+
const [groupBy, setGroupBy] = useState([]);
|
|
401
|
+
|
|
402
|
+
// Sample data for the grid
|
|
403
|
+
const [employees, setEmployees] = useState([]);
|
|
404
|
+
|
|
405
|
+
// Fetch data on component mount
|
|
406
|
+
useEffect(() => {
|
|
407
|
+
const fetchEmployees = async () => {
|
|
408
|
+
try {
|
|
409
|
+
const response = await fetch('/api/employees');
|
|
410
|
+
const data = await response.json();
|
|
411
|
+
setEmployees(data);
|
|
412
|
+
} catch (error) {
|
|
413
|
+
console.error('Error fetching employees:', error);
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
fetchEmployees();
|
|
418
|
+
}, []);
|
|
419
|
+
|
|
420
|
+
// Column definitions
|
|
421
|
+
const columns = [
|
|
422
|
+
{
|
|
423
|
+
name: 'id',
|
|
424
|
+
headerName: 'ID',
|
|
425
|
+
width: 80,
|
|
426
|
+
},
|
|
427
|
+
{
|
|
428
|
+
name: 'name',
|
|
429
|
+
headerName: 'Name',
|
|
430
|
+
width: 180,
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
name: 'department',
|
|
434
|
+
headerName: 'Department',
|
|
435
|
+
width: 150,
|
|
436
|
+
groupSummaryReducer: 'count',
|
|
437
|
+
groupSummaryRenderer: (count) => `${count} employees`,
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
name: 'role',
|
|
441
|
+
headerName: 'Role',
|
|
442
|
+
width: 150,
|
|
443
|
+
},
|
|
444
|
+
{
|
|
445
|
+
name: 'salary',
|
|
446
|
+
headerName: 'Salary',
|
|
447
|
+
width: 120,
|
|
448
|
+
type: 'number',
|
|
449
|
+
groupSummaryReducer: 'sum',
|
|
450
|
+
groupSummaryRenderer: (sum) => `$${sum.toLocaleString()}`,
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
name: 'hireDate',
|
|
454
|
+
headerName: 'Hire Date',
|
|
455
|
+
width: 120,
|
|
456
|
+
type: 'date',
|
|
457
|
+
},
|
|
458
|
+
];
|
|
459
|
+
|
|
460
|
+
// Group by options for the dropdown
|
|
461
|
+
const groupByOptions = [
|
|
462
|
+
{ label: 'No Grouping', value: 'none' },
|
|
463
|
+
{ label: 'Department', value: 'department' },
|
|
464
|
+
{ label: 'Role', value: 'role' },
|
|
465
|
+
{ label: 'Department & Role', value: 'department-role' },
|
|
466
|
+
];
|
|
467
|
+
|
|
468
|
+
// Handle group by change
|
|
469
|
+
const handleGroupByChange = (e) => {
|
|
470
|
+
const value = e.target.value;
|
|
471
|
+
|
|
472
|
+
switch (value) {
|
|
473
|
+
case 'department':
|
|
474
|
+
setGroupBy([{ name: 'department' }]);
|
|
475
|
+
break;
|
|
476
|
+
case 'role':
|
|
477
|
+
setGroupBy([{ name: 'role' }]);
|
|
478
|
+
break;
|
|
479
|
+
case 'department-role':
|
|
480
|
+
setGroupBy([{ name: 'department' }, { name: 'role' }]);
|
|
481
|
+
break;
|
|
482
|
+
default:
|
|
483
|
+
setGroupBy([]);
|
|
484
|
+
break;
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
return (
|
|
489
|
+
<div>
|
|
490
|
+
<div style={{ marginBottom: '1rem' }}>
|
|
491
|
+
<label htmlFor="groupBy">Group By: </label>
|
|
492
|
+
<select
|
|
493
|
+
id="groupBy"
|
|
494
|
+
onChange={handleGroupByChange}
|
|
495
|
+
style={{ padding: '0.5rem', borderRadius: '4px' }}
|
|
496
|
+
>
|
|
497
|
+
{groupByOptions.map((option) => (
|
|
498
|
+
<option key={option.value} value={option.value}>
|
|
499
|
+
{option.label}
|
|
500
|
+
</option>
|
|
501
|
+
))}
|
|
502
|
+
</select>
|
|
503
|
+
</div>
|
|
504
|
+
|
|
505
|
+
<DataGrid
|
|
506
|
+
columns={columns}
|
|
507
|
+
dataSource={employees}
|
|
508
|
+
groupBy={groupBy}
|
|
509
|
+
onGroupByChange={setGroupBy}
|
|
510
|
+
gridHeight={600}
|
|
511
|
+
settings={{
|
|
512
|
+
pagination: true,
|
|
513
|
+
pageSize: 25,
|
|
514
|
+
}}
|
|
515
|
+
renderGroupTitle={(groupData) => (
|
|
516
|
+
<div
|
|
517
|
+
style={{
|
|
518
|
+
fontWeight: 'bold',
|
|
519
|
+
color: 'var(--primary-color)',
|
|
520
|
+
padding: '0.25rem 0',
|
|
521
|
+
}}
|
|
522
|
+
>
|
|
523
|
+
{groupData.name === 'department'
|
|
524
|
+
? 'Department'
|
|
525
|
+
: 'Role'}
|
|
526
|
+
:<span style={{ marginLeft: '0.5rem' }}>
|
|
527
|
+
{groupData.value} ({groupData.count} employees)
|
|
528
|
+
</span>
|
|
529
|
+
</div>
|
|
530
|
+
)}
|
|
531
|
+
/>
|
|
532
|
+
</div>
|
|
533
|
+
);
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
export default EmployeeDataGrid;
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
#### TreeGrid Functionality
|
|
540
|
+
|
|
541
|
+
The DataGrid component also supports hierarchical data display through its TreeGrid functionality. This allows you to represent parent-child relationships in your data with expandable/collapsible nodes.
|
|
542
|
+
|
|
543
|
+
##### Basic TreeGrid Configuration
|
|
544
|
+
|
|
545
|
+
To use the TreeGrid functionality:
|
|
546
|
+
|
|
547
|
+
```jsx
|
|
548
|
+
<DataGrid
|
|
549
|
+
// Other props...
|
|
550
|
+
treeColumn="name" // Specify which column will display the expand/collapse icons
|
|
551
|
+
dataSource={hierarchicalData} // Data with parent-child relationships
|
|
552
|
+
/>
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
Your data should have a structure where parent nodes contain child nodes:
|
|
556
|
+
|
|
557
|
+
```jsx
|
|
558
|
+
const hierarchicalData = [
|
|
559
|
+
{
|
|
560
|
+
id: 1,
|
|
561
|
+
name: 'Parent 1',
|
|
562
|
+
nodes: [
|
|
563
|
+
// Child nodes
|
|
564
|
+
{ id: 11, name: 'Child 1.1' },
|
|
565
|
+
{
|
|
566
|
+
id: 12,
|
|
567
|
+
name: 'Child 1.2',
|
|
568
|
+
nodes: [
|
|
569
|
+
// Nested children
|
|
570
|
+
{ id: 121, name: 'Grandchild 1.2.1' },
|
|
571
|
+
],
|
|
572
|
+
},
|
|
573
|
+
],
|
|
574
|
+
},
|
|
575
|
+
{
|
|
576
|
+
id: 2,
|
|
577
|
+
name: 'Parent 2',
|
|
578
|
+
nodes: [{ id: 21, name: 'Child 2.1' }],
|
|
579
|
+
},
|
|
580
|
+
];
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
##### Controlling Expanded Nodes
|
|
584
|
+
|
|
585
|
+
You can control which nodes are expanded:
|
|
586
|
+
|
|
587
|
+
```jsx
|
|
588
|
+
const [expandedNodes, setExpandedNodes] = useState({
|
|
589
|
+
1: true, // Node with ID 1 is expanded
|
|
590
|
+
'1/12': true, // Node with path 1/12 is expanded
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
<DataGrid
|
|
594
|
+
// Other props...
|
|
595
|
+
treeColumn="name"
|
|
596
|
+
dataSource={hierarchicalData}
|
|
597
|
+
expandedNodes={expandedNodes}
|
|
598
|
+
onExpandedNodesChange={setExpandedNodes}
|
|
599
|
+
/>;
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
##### Asynchronous Node Loading
|
|
603
|
+
|
|
604
|
+
For large datasets, you can load child nodes on demand:
|
|
605
|
+
|
|
606
|
+
```jsx
|
|
607
|
+
<DataGrid
|
|
608
|
+
// Other props...
|
|
609
|
+
treeColumn="name"
|
|
610
|
+
dataSource={asyncHierarchicalData}
|
|
611
|
+
loadNode={({ node }) => {
|
|
612
|
+
// Return a Promise that resolves to the child nodes
|
|
613
|
+
return fetchChildNodes(node.id);
|
|
614
|
+
}}
|
|
615
|
+
/>
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
For asynchronous loading, parent nodes should have `nodes: null` to indicate they have children that need to be loaded.
|
|
619
|
+
|
|
620
|
+
##### Complete TreeGrid Example
|
|
621
|
+
|
|
622
|
+
Here's a complete example of a component that uses DataGrid with TreeGrid functionality:
|
|
623
|
+
|
|
624
|
+
```jsx
|
|
625
|
+
import React, { useState } from 'react';
|
|
626
|
+
import { DataGrid } from 'visns-components';
|
|
627
|
+
|
|
628
|
+
const OrganizationTreeGrid = () => {
|
|
629
|
+
// Sample hierarchical data
|
|
630
|
+
const departments = [
|
|
631
|
+
{
|
|
632
|
+
id: 1,
|
|
633
|
+
name: 'Executive',
|
|
634
|
+
budget: 1500000,
|
|
635
|
+
nodes: [
|
|
636
|
+
{
|
|
637
|
+
id: 11,
|
|
638
|
+
name: 'Finance',
|
|
639
|
+
budget: 850000,
|
|
640
|
+
nodes: [
|
|
641
|
+
{ id: 111, name: 'Accounting', budget: 350000 },
|
|
642
|
+
{ id: 112, name: 'Investment', budget: 500000 },
|
|
643
|
+
],
|
|
644
|
+
},
|
|
645
|
+
{
|
|
646
|
+
id: 12,
|
|
647
|
+
name: 'Operations',
|
|
648
|
+
budget: 650000,
|
|
649
|
+
nodes: [
|
|
650
|
+
{ id: 121, name: 'HR', budget: 200000 },
|
|
651
|
+
{ id: 122, name: 'Facilities', budget: 450000 },
|
|
652
|
+
],
|
|
653
|
+
},
|
|
654
|
+
],
|
|
655
|
+
},
|
|
656
|
+
{
|
|
657
|
+
id: 2,
|
|
658
|
+
name: 'Sales & Marketing',
|
|
659
|
+
budget: 1200000,
|
|
660
|
+
nodes: [
|
|
661
|
+
{ id: 21, name: 'Sales', budget: 700000 },
|
|
662
|
+
{ id: 22, name: 'Marketing', budget: 500000 },
|
|
663
|
+
],
|
|
664
|
+
},
|
|
665
|
+
{
|
|
666
|
+
id: 3,
|
|
667
|
+
name: 'Technology',
|
|
668
|
+
budget: 2000000,
|
|
669
|
+
nodes: [
|
|
670
|
+
{ id: 31, name: 'Development', budget: 1200000 },
|
|
671
|
+
{ id: 32, name: 'IT Support', budget: 800000 },
|
|
672
|
+
],
|
|
673
|
+
},
|
|
674
|
+
];
|
|
675
|
+
|
|
676
|
+
// State for expanded nodes
|
|
677
|
+
const [expandedNodes, setExpandedNodes] = useState({
|
|
678
|
+
1: true, // Executive department is expanded by default
|
|
679
|
+
3: true, // Technology department is expanded by default
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
// Column definitions
|
|
683
|
+
const columns = [
|
|
684
|
+
{
|
|
685
|
+
name: 'name',
|
|
686
|
+
headerName: 'Department',
|
|
687
|
+
width: 250,
|
|
688
|
+
// This column will display the tree expand/collapse icons
|
|
689
|
+
},
|
|
690
|
+
{
|
|
691
|
+
name: 'budget',
|
|
692
|
+
headerName: 'Budget',
|
|
693
|
+
width: 150,
|
|
694
|
+
type: 'number',
|
|
695
|
+
render: ({ value }) => `$${value.toLocaleString()}`,
|
|
696
|
+
},
|
|
697
|
+
];
|
|
698
|
+
|
|
699
|
+
return (
|
|
700
|
+
<div>
|
|
701
|
+
<h2>Organization Structure</h2>
|
|
702
|
+
|
|
703
|
+
<DataGrid
|
|
704
|
+
columns={columns}
|
|
705
|
+
dataSource={departments}
|
|
706
|
+
treeColumn="name"
|
|
707
|
+
expandedNodes={expandedNodes}
|
|
708
|
+
onExpandedNodesChange={setExpandedNodes}
|
|
709
|
+
treeNestingSize={30} // Indentation size for nested levels
|
|
710
|
+
gridHeight={500}
|
|
711
|
+
idProperty="id"
|
|
712
|
+
/>
|
|
713
|
+
</div>
|
|
714
|
+
);
|
|
715
|
+
};
|
|
716
|
+
|
|
717
|
+
export default OrganizationTreeGrid;
|
|
718
|
+
```
|
|
719
|
+
|
|
256
720
|
### Form Components
|
|
257
721
|
|
|
258
722
|
#### Form
|
package/package.json
CHANGED
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"react-dom": "^17.0.0 || ^18.0.0"
|
|
85
85
|
},
|
|
86
86
|
"name": "@visns-studio/visns-components",
|
|
87
|
-
"version": "5.6.
|
|
87
|
+
"version": "5.6.11",
|
|
88
88
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
89
89
|
"main": "src/index.js",
|
|
90
90
|
"files": [
|
|
@@ -2,6 +2,8 @@ import React from 'react';
|
|
|
2
2
|
import { SortableElement, sortableHandle } from 'react-sortable-hoc';
|
|
3
3
|
import { ChevronVertical, Pencil, TrashCan } from 'akar-icons';
|
|
4
4
|
|
|
5
|
+
import styles from './styles/Item.module.scss';
|
|
6
|
+
|
|
5
7
|
const DragHandle = sortableHandle(() => (
|
|
6
8
|
<ChevronVertical className="drag-handle" strokeWidth={1} size={26} />
|
|
7
9
|
));
|
|
@@ -63,9 +65,9 @@ const SortableItem = ({
|
|
|
63
65
|
|
|
64
66
|
const EditButton = () => (
|
|
65
67
|
<Pencil
|
|
66
|
-
className="
|
|
67
|
-
strokeWidth={
|
|
68
|
-
size={
|
|
68
|
+
className="pencil-edit"
|
|
69
|
+
strokeWidth={2}
|
|
70
|
+
size={24}
|
|
69
71
|
onClick={(e) => {
|
|
70
72
|
e.preventDefault();
|
|
71
73
|
e.stopPropagation();
|
|
@@ -91,22 +93,26 @@ const SortableItem = ({
|
|
|
91
93
|
|
|
92
94
|
{renderImage(value)}
|
|
93
95
|
|
|
94
|
-
{
|
|
96
|
+
{(handleEdit || handleDelete) && (
|
|
97
|
+
<div className={styles.actionIcons}>
|
|
98
|
+
{renderEditButton()}
|
|
95
99
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
100
|
+
{handleDelete && (
|
|
101
|
+
<TrashCan
|
|
102
|
+
className="delete"
|
|
103
|
+
strokeWidth={2}
|
|
104
|
+
size={24}
|
|
105
|
+
onClick={(e) => {
|
|
106
|
+
e.preventDefault();
|
|
107
|
+
e.stopPropagation();
|
|
108
|
+
handleDelete(
|
|
109
|
+
value.id || containerIndex,
|
|
110
|
+
value.label || value.file_name
|
|
111
|
+
);
|
|
112
|
+
}}
|
|
113
|
+
/>
|
|
114
|
+
)}
|
|
115
|
+
</div>
|
|
110
116
|
)}
|
|
111
117
|
</li>
|
|
112
118
|
);
|
|
@@ -2,6 +2,8 @@ import React from 'react';
|
|
|
2
2
|
import VisnsSortableItem from './Item';
|
|
3
3
|
import { SortableContainer } from 'react-sortable-hoc';
|
|
4
4
|
|
|
5
|
+
import styles from './styles/List.module.scss';
|
|
6
|
+
|
|
5
7
|
const SortableList = ({
|
|
6
8
|
handleClick,
|
|
7
9
|
handleDelete,
|
|
@@ -10,7 +12,7 @@ const SortableList = ({
|
|
|
10
12
|
showImage,
|
|
11
13
|
}) => {
|
|
12
14
|
return (
|
|
13
|
-
<ul className=
|
|
15
|
+
<ul className={styles.sortlist}>
|
|
14
16
|
{items.map((value, index) => (
|
|
15
17
|
<VisnsSortableItem
|
|
16
18
|
key={`item-${index}`}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
.actionIcons {
|
|
2
|
+
position: absolute;
|
|
3
|
+
right: 10px;
|
|
4
|
+
top: 50%;
|
|
5
|
+
transform: translateY(-50%);
|
|
6
|
+
display: flex;
|
|
7
|
+
gap: 12px;
|
|
8
|
+
align-items: center;
|
|
9
|
+
z-index: 10;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
:global {
|
|
13
|
+
.delete {
|
|
14
|
+
width: 22px;
|
|
15
|
+
height: 22px;
|
|
16
|
+
color: var(--primary-color);
|
|
17
|
+
transition: color 0.2s ease;
|
|
18
|
+
cursor: pointer;
|
|
19
|
+
display: block;
|
|
20
|
+
|
|
21
|
+
&:hover {
|
|
22
|
+
color: #d32f2f;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.pencil-edit {
|
|
27
|
+
width: 22px;
|
|
28
|
+
height: 22px;
|
|
29
|
+
color: var(--primary-color);
|
|
30
|
+
transition: color 0.2s ease;
|
|
31
|
+
cursor: pointer;
|
|
32
|
+
display: block;
|
|
33
|
+
|
|
34
|
+
&:hover {
|
|
35
|
+
color: var(--secondary-color);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
.sortlist {
|
|
2
|
+
width: 100%;
|
|
3
|
+
display: grid;
|
|
4
|
+
grid-gap: 0.4rem;
|
|
5
|
+
grid-template-columns: repeat(1, 1fr);
|
|
6
|
+
box-sizing: border-box;
|
|
7
|
+
padding: 0;
|
|
8
|
+
margin: 0;
|
|
9
|
+
font-size: 0.8rem;
|
|
10
|
+
list-style: none;
|
|
11
|
+
|
|
12
|
+
@media (min-width: 576px) {
|
|
13
|
+
grid-template-columns: repeat(1, 1fr);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@media (min-width: 992px) {
|
|
17
|
+
grid-template-columns: repeat(1, 1fr);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
li {
|
|
21
|
+
display: flex;
|
|
22
|
+
justify-content: flex-start;
|
|
23
|
+
align-items: center;
|
|
24
|
+
flex-wrap: wrap;
|
|
25
|
+
border-radius: 6px;
|
|
26
|
+
border: 1px solid rgba(var(--primary-rgb), 0.2);
|
|
27
|
+
box-shadow: 0px 0px 10px rgba(var(--primary-rgb), 0.1);
|
|
28
|
+
padding: 0.8rem 5rem 0.8rem 2rem;
|
|
29
|
+
margin-bottom: 0.5rem;
|
|
30
|
+
font-size: 0.9rem;
|
|
31
|
+
font-weight: 500;
|
|
32
|
+
transition: all 0.2s ease;
|
|
33
|
+
|
|
34
|
+
box-sizing: border-box;
|
|
35
|
+
cursor: pointer;
|
|
36
|
+
list-style: none;
|
|
37
|
+
position: relative;
|
|
38
|
+
color: var(--paragraph-color);
|
|
39
|
+
user-select: none;
|
|
40
|
+
|
|
41
|
+
&:hover {
|
|
42
|
+
border-color: rgba(var(--primary-rgb), 0.4);
|
|
43
|
+
box-shadow: 0px 0px 12px rgba(var(--primary-rgb), 0.2);
|
|
44
|
+
transform: translateY(-1px);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
> svg:first-child {
|
|
48
|
+
width: 100%;
|
|
49
|
+
max-width: 18px;
|
|
50
|
+
position: absolute;
|
|
51
|
+
left: 6px;
|
|
52
|
+
top: 50%;
|
|
53
|
+
transform: translateY(-50%);
|
|
54
|
+
color: var(--primary-color);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.sortimg {
|
|
59
|
+
width: 40px;
|
|
60
|
+
position: relative;
|
|
61
|
+
height: 40px;
|
|
62
|
+
margin-left: auto;
|
|
63
|
+
border-radius: 4px;
|
|
64
|
+
overflow: hidden;
|
|
65
|
+
|
|
66
|
+
img {
|
|
67
|
+
display: block;
|
|
68
|
+
object-fit: cover;
|
|
69
|
+
height: 100%;
|
|
70
|
+
width: 100%;
|
|
71
|
+
overflow: hidden;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -3206,6 +3206,22 @@ const DataGrid = forwardRef(
|
|
|
3206
3206
|
};
|
|
3207
3207
|
}, []);
|
|
3208
3208
|
|
|
3209
|
+
// Reset state when ajaxSetting changes (e.g., when navigating to a new page)
|
|
3210
|
+
useEffect(() => {
|
|
3211
|
+
// Reset selected state
|
|
3212
|
+
setSelected({});
|
|
3213
|
+
|
|
3214
|
+
// Reset filter value if needed
|
|
3215
|
+
if (setFilterData) {
|
|
3216
|
+
setFilterData([]);
|
|
3217
|
+
}
|
|
3218
|
+
|
|
3219
|
+
// Force reload of data
|
|
3220
|
+
if (gridRef.current && gridRef.current.paginationProps) {
|
|
3221
|
+
gridRef.current.paginationProps.reload();
|
|
3222
|
+
}
|
|
3223
|
+
}, [ajaxSetting?.url]);
|
|
3224
|
+
|
|
3209
3225
|
// Auto-refresh effect
|
|
3210
3226
|
useEffect(() => {
|
|
3211
3227
|
// Clear any existing timer
|
|
@@ -3258,9 +3274,15 @@ const DataGrid = forwardRef(
|
|
|
3258
3274
|
<div className={styles.dataGridContainer}>
|
|
3259
3275
|
{limit && limit > 0 ? (
|
|
3260
3276
|
<ReactDataGrid
|
|
3277
|
+
key={`datagrid-${ajaxSetting?.url || ''}-${
|
|
3278
|
+
ajaxSetting?.groupBy ? 'grouped' : 'ungrouped'
|
|
3279
|
+
}`}
|
|
3261
3280
|
{...tableSetting}
|
|
3262
3281
|
columns={gridColumns}
|
|
3263
3282
|
dataSource={dataSource}
|
|
3283
|
+
{...(ajaxSetting && ajaxSetting.groupBy
|
|
3284
|
+
? { defaultGroupBy: ajaxSetting.groupBy }
|
|
3285
|
+
: {})}
|
|
3264
3286
|
filterValue={filterValue}
|
|
3265
3287
|
limit={limit}
|
|
3266
3288
|
enableColumnAutosize={false}
|
|
@@ -424,7 +424,7 @@ function GenericFormBuilder({ setting, urlParam, userProfile }) {
|
|
|
424
424
|
const sizeClassMap = {
|
|
425
425
|
full: styles.fwBuilderItem,
|
|
426
426
|
half: styles.halfBuilderItem,
|
|
427
|
-
quarter: styles.
|
|
427
|
+
quarter: styles.halfBuilderItem, // Changed to halfBuilderItem to ensure max 2 per row
|
|
428
428
|
};
|
|
429
429
|
|
|
430
430
|
return `${styles.formBuilderItem} ${
|
|
@@ -855,6 +855,12 @@ function GenericFormBuilder({ setting, urlParam, userProfile }) {
|
|
|
855
855
|
<div className={styles.grid}>
|
|
856
856
|
<div className={styles.grid__subrow}>
|
|
857
857
|
<div className={styles.grid__subnav}>
|
|
858
|
+
<div
|
|
859
|
+
className={styles.gridtxt__header}
|
|
860
|
+
style={{ marginBottom: '10px' }}
|
|
861
|
+
>
|
|
862
|
+
<span>Form Fields</span>
|
|
863
|
+
</div>
|
|
858
864
|
{data.detail && data.detail.length > 0 ? (
|
|
859
865
|
<div className={styles.sortableListContainer}>
|
|
860
866
|
<SortableList
|
|
@@ -869,7 +875,19 @@ function GenericFormBuilder({ setting, urlParam, userProfile }) {
|
|
|
869
875
|
useDragHandle
|
|
870
876
|
/>
|
|
871
877
|
</div>
|
|
872
|
-
) :
|
|
878
|
+
) : (
|
|
879
|
+
<div
|
|
880
|
+
style={{
|
|
881
|
+
padding: '10px',
|
|
882
|
+
textAlign: 'center',
|
|
883
|
+
color: 'rgba(var(--paragraph-color-rgb), 0.7)',
|
|
884
|
+
fontSize: '0.9rem',
|
|
885
|
+
}}
|
|
886
|
+
>
|
|
887
|
+
No fields added yet. Click "Add Field" to get
|
|
888
|
+
started.
|
|
889
|
+
</div>
|
|
890
|
+
)}
|
|
873
891
|
</div>
|
|
874
892
|
<div className={styles.grid__subcontent}>
|
|
875
893
|
<div className={styles.formSplit}>
|
|
@@ -1165,7 +1183,7 @@ function GenericFormBuilder({ setting, urlParam, userProfile }) {
|
|
|
1165
1183
|
'description'
|
|
1166
1184
|
);
|
|
1167
1185
|
}}
|
|
1168
|
-
onInit={(
|
|
1186
|
+
onInit={(_, editor) =>
|
|
1169
1187
|
(editorRef.current = editor)
|
|
1170
1188
|
}
|
|
1171
1189
|
value={
|
|
@@ -22,19 +22,19 @@
|
|
|
22
22
|
&__subrow {
|
|
23
23
|
width: 100%;
|
|
24
24
|
display: flex;
|
|
25
|
-
flex-wrap:
|
|
25
|
+
flex-wrap: nowrap;
|
|
26
26
|
height: auto;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
&__subnav {
|
|
30
|
-
flex:
|
|
30
|
+
flex: 0 0 20%;
|
|
31
31
|
background: white;
|
|
32
32
|
border-radius: var(--br);
|
|
33
33
|
box-sizing: border-box;
|
|
34
|
-
padding:
|
|
35
|
-
margin: 8px 0;
|
|
34
|
+
padding: 8px;
|
|
35
|
+
margin: 8px 8px 8px 0;
|
|
36
36
|
height: auto;
|
|
37
|
-
box-shadow: 0
|
|
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
|
|
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
|
|
142
|
-
border-
|
|
143
|
-
border-top-right-radius: var(--br);
|
|
141
|
+
padding: 10px 15px;
|
|
142
|
+
border-radius: var(--br);
|
|
144
143
|
margin-bottom: 0;
|
|
145
|
-
font-weight:
|
|
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:
|
|
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:
|
|
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:
|
|
882
|
+
justify-content: flex-start;
|
|
880
883
|
align-items: center;
|
|
881
884
|
flex-wrap: wrap;
|
|
882
|
-
border-radius:
|
|
885
|
+
border-radius: 6px;
|
|
883
886
|
border: 1px solid rgba(var(--primary-rgb), 0.2);
|
|
884
|
-
box-shadow: 0px 0px
|
|
885
|
-
padding:
|
|
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:
|
|
896
|
-
top:
|
|
900
|
+
left: 6px;
|
|
901
|
+
top: 50%;
|
|
902
|
+
transform: translateY(-50%);
|
|
903
|
+
color: var(--primary-color);
|
|
897
904
|
}
|
|
898
905
|
|
|
899
|
-
.
|
|
900
|
-
width: 100%;
|
|
901
|
-
max-width: 18px;
|
|
906
|
+
.actionIcons {
|
|
902
907
|
position: absolute;
|
|
903
|
-
|
|
904
|
-
top:
|
|
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:
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
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:
|
|
132
|
+
top: 50%;
|
|
133
|
+
transform: translateY(-50%);
|
|
133
134
|
}
|
|
134
135
|
|
|
135
|
-
.
|
|
136
|
-
width: 100%;
|
|
137
|
-
max-width: 16px;
|
|
136
|
+
.actionIcons {
|
|
138
137
|
position: absolute;
|
|
139
|
-
|
|
140
|
-
top:
|
|
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:
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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 {
|
|
@@ -65,9 +65,9 @@ const SortableItem = ({
|
|
|
65
65
|
|
|
66
66
|
const EditButton = () => (
|
|
67
67
|
<Pencil
|
|
68
|
-
className={styles
|
|
69
|
-
strokeWidth={
|
|
70
|
-
size={
|
|
68
|
+
className={styles.edit}
|
|
69
|
+
strokeWidth={2}
|
|
70
|
+
size={24}
|
|
71
71
|
onClick={(e) => {
|
|
72
72
|
e.preventDefault();
|
|
73
73
|
e.stopPropagation();
|
|
@@ -93,22 +93,26 @@ const SortableItem = ({
|
|
|
93
93
|
|
|
94
94
|
{renderImage(value)}
|
|
95
95
|
|
|
96
|
-
{
|
|
96
|
+
{(handleEdit || handleDelete) && (
|
|
97
|
+
<div className={styles.actionIcons}>
|
|
98
|
+
{renderEditButton()}
|
|
97
99
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
100
|
+
{handleDelete && (
|
|
101
|
+
<TrashCan
|
|
102
|
+
className={styles.delete}
|
|
103
|
+
strokeWidth={2}
|
|
104
|
+
size={24}
|
|
105
|
+
onClick={(e) => {
|
|
106
|
+
e.preventDefault();
|
|
107
|
+
e.stopPropagation();
|
|
108
|
+
handleDelete(
|
|
109
|
+
value.id || containerIndex,
|
|
110
|
+
value.label || value.file_name
|
|
111
|
+
);
|
|
112
|
+
}}
|
|
113
|
+
/>
|
|
114
|
+
)}
|
|
115
|
+
</div>
|
|
112
116
|
)}
|
|
113
117
|
</li>
|
|
114
118
|
);
|
|
@@ -1,15 +1,36 @@
|
|
|
1
|
-
.
|
|
2
|
-
width: 100%;
|
|
3
|
-
max-width: 16px;
|
|
1
|
+
.actionIcons {
|
|
4
2
|
position: absolute;
|
|
5
|
-
|
|
6
|
-
top:
|
|
3
|
+
right: 10px;
|
|
4
|
+
top: 50%;
|
|
5
|
+
transform: translateY(-50%);
|
|
6
|
+
display: flex;
|
|
7
|
+
gap: 12px;
|
|
8
|
+
align-items: center;
|
|
9
|
+
z-index: 10;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.edit {
|
|
13
|
+
width: 22px;
|
|
14
|
+
height: 22px;
|
|
15
|
+
color: var(--primary-color);
|
|
16
|
+
transition: color 0.2s ease;
|
|
17
|
+
cursor: pointer;
|
|
18
|
+
display: block;
|
|
19
|
+
|
|
20
|
+
&:hover {
|
|
21
|
+
color: var(--secondary-color);
|
|
22
|
+
}
|
|
7
23
|
}
|
|
8
24
|
|
|
9
25
|
.delete {
|
|
10
|
-
width:
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
26
|
+
width: 22px;
|
|
27
|
+
height: 22px;
|
|
28
|
+
color: var(--primary-color);
|
|
29
|
+
transition: color 0.2s ease;
|
|
30
|
+
cursor: pointer;
|
|
31
|
+
display: block;
|
|
32
|
+
|
|
33
|
+
&:hover {
|
|
34
|
+
color: #d32f2f;
|
|
35
|
+
}
|
|
15
36
|
}
|
|
@@ -10,22 +10,26 @@
|
|
|
10
10
|
list-style: none;
|
|
11
11
|
|
|
12
12
|
@media (min-width: 576px) {
|
|
13
|
-
grid-template-columns: repeat(
|
|
13
|
+
grid-template-columns: repeat(1, 1fr);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
@media (min-width: 992px) {
|
|
17
|
-
grid-template-columns: repeat(
|
|
17
|
+
grid-template-columns: repeat(1, 1fr);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
li {
|
|
21
21
|
display: flex;
|
|
22
|
-
justify-content:
|
|
22
|
+
justify-content: flex-start;
|
|
23
23
|
align-items: center;
|
|
24
24
|
flex-wrap: wrap;
|
|
25
|
-
border-radius:
|
|
25
|
+
border-radius: 6px;
|
|
26
26
|
border: 1px solid rgba(var(--primary-rgb), 0.2);
|
|
27
27
|
box-shadow: 0px 0px 10px rgba(var(--primary-rgb), 0.1);
|
|
28
|
-
padding: 0.
|
|
28
|
+
padding: 0.8rem 5rem 0.8rem 2rem;
|
|
29
|
+
margin-bottom: 0.5rem;
|
|
30
|
+
font-size: 0.9rem;
|
|
31
|
+
font-weight: 500;
|
|
32
|
+
transition: all 0.2s ease;
|
|
29
33
|
|
|
30
34
|
box-sizing: border-box;
|
|
31
35
|
cursor: pointer;
|
|
@@ -34,12 +38,20 @@
|
|
|
34
38
|
color: var(--paragraph-color);
|
|
35
39
|
user-select: none;
|
|
36
40
|
|
|
37
|
-
|
|
41
|
+
&:hover {
|
|
42
|
+
border-color: rgba(var(--primary-rgb), 0.4);
|
|
43
|
+
box-shadow: 0px 0px 12px rgba(var(--primary-rgb), 0.2);
|
|
44
|
+
transform: translateY(-1px);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
> svg:first-child {
|
|
38
48
|
width: 100%;
|
|
39
|
-
max-width:
|
|
49
|
+
max-width: 18px;
|
|
40
50
|
position: absolute;
|
|
41
|
-
left:
|
|
42
|
-
top:
|
|
51
|
+
left: 6px;
|
|
52
|
+
top: 50%;
|
|
53
|
+
transform: translateY(-50%);
|
|
54
|
+
color: var(--primary-color);
|
|
43
55
|
}
|
|
44
56
|
}
|
|
45
57
|
|