@visns-studio/visns-components 5.6.8 → 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/TwoFactorAuth.jsx +7 -9
- package/src/components/crm/auth/styles/TwoFactorAuth.module.scss +154 -25
- 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
|
@@ -1,47 +1,176 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
.formItem {
|
|
2
|
+
width: 100%;
|
|
3
|
+
position: relative;
|
|
4
|
+
box-sizing: border-box;
|
|
5
|
+
margin-bottom: 1rem;
|
|
3
6
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
.fi__label {
|
|
8
|
+
position: relative;
|
|
9
|
+
display: flex;
|
|
10
|
+
align-items: center;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.fi__span {
|
|
14
|
+
position: absolute;
|
|
15
|
+
transition: all 200ms;
|
|
16
|
+
opacity: 0.7;
|
|
17
|
+
left: 0;
|
|
18
|
+
transform-origin: top left;
|
|
19
|
+
cursor: text;
|
|
20
|
+
padding: 0 1rem;
|
|
21
|
+
font-size: 0.9rem;
|
|
22
|
+
color: #6b7280;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
input[name='verificationCode'] {
|
|
26
|
+
letter-spacing: 0.25rem;
|
|
27
|
+
font-size: 1.2rem;
|
|
28
|
+
text-align: center;
|
|
29
|
+
}
|
|
11
30
|
}
|
|
12
31
|
|
|
13
|
-
.
|
|
14
|
-
|
|
15
|
-
|
|
32
|
+
.fwItem,
|
|
33
|
+
.fwBuilderItem {
|
|
34
|
+
grid-column: 1 / -1;
|
|
35
|
+
width: 100%;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.lastItem .btn {
|
|
39
|
+
width: 100%;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.loginlogo {
|
|
43
|
+
display: block;
|
|
44
|
+
width: 100%;
|
|
45
|
+
max-width: 150px;
|
|
46
|
+
height: auto;
|
|
47
|
+
margin: 0 auto;
|
|
48
|
+
padding-bottom: 1.5rem;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.logincontainer {
|
|
52
|
+
width: 100%;
|
|
53
|
+
min-height: 100vh;
|
|
54
|
+
display: grid;
|
|
55
|
+
place-items: center;
|
|
16
56
|
padding: 1rem;
|
|
57
|
+
background-color: #f9fafb;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.login {
|
|
61
|
+
width: 28rem;
|
|
62
|
+
padding: 2rem;
|
|
17
63
|
margin: 0;
|
|
18
64
|
display: grid;
|
|
19
65
|
grid-template-columns: 1fr;
|
|
20
|
-
gap:
|
|
66
|
+
gap: 1rem;
|
|
67
|
+
background-color: white;
|
|
68
|
+
border-radius: 0.75rem;
|
|
69
|
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1),
|
|
70
|
+
0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
|
71
|
+
|
|
72
|
+
> .formItem {
|
|
73
|
+
padding: 0;
|
|
74
|
+
|
|
75
|
+
small {
|
|
76
|
+
position: absolute;
|
|
77
|
+
width: 18px;
|
|
78
|
+
height: 18px;
|
|
79
|
+
right: 14px;
|
|
80
|
+
top: 14px;
|
|
81
|
+
opacity: 0.4;
|
|
82
|
+
color: #6b7280;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
21
85
|
|
|
22
86
|
h2 {
|
|
23
87
|
text-align: center;
|
|
24
|
-
margin: 0;
|
|
25
|
-
color:
|
|
88
|
+
margin-bottom: 0.5rem;
|
|
89
|
+
color: #111827;
|
|
90
|
+
font-weight: 600;
|
|
91
|
+
font-size: 1.5rem;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.instructions {
|
|
96
|
+
text-align: center;
|
|
97
|
+
margin: 0 0 1.5rem;
|
|
98
|
+
color: var(--paragraph-color, #6b7280);
|
|
99
|
+
font-size: 0.95rem;
|
|
100
|
+
line-height: 1.4;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.forgotten span {
|
|
104
|
+
display: block;
|
|
105
|
+
text-align: center;
|
|
106
|
+
color: #6b7280;
|
|
107
|
+
padding: 1rem 0;
|
|
108
|
+
font-size: 0.875rem;
|
|
109
|
+
|
|
110
|
+
a {
|
|
111
|
+
color: var(--primary-color, #4f46e5);
|
|
112
|
+
text-decoration: none;
|
|
26
113
|
font-weight: 500;
|
|
114
|
+
|
|
115
|
+
&:hover {
|
|
116
|
+
text-decoration: underline;
|
|
117
|
+
}
|
|
27
118
|
}
|
|
28
119
|
}
|
|
29
120
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
121
|
+
.lcontainer {
|
|
122
|
+
min-height: 100vh;
|
|
123
|
+
display: flex;
|
|
124
|
+
align-items: center;
|
|
125
|
+
justify-content: center;
|
|
126
|
+
background-color: #f9fafb;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.lwrap {
|
|
130
|
+
display: grid;
|
|
131
|
+
grid-template-columns: 1fr;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.btn {
|
|
135
|
+
width: max-content;
|
|
136
|
+
display: inline-block;
|
|
34
137
|
position: relative;
|
|
35
|
-
|
|
36
|
-
|
|
138
|
+
padding: 0.75rem 1rem;
|
|
139
|
+
cursor: pointer;
|
|
140
|
+
font-size: 0.875rem;
|
|
141
|
+
font-weight: 500;
|
|
142
|
+
color: white;
|
|
143
|
+
text-decoration: none;
|
|
144
|
+
overflow: hidden;
|
|
145
|
+
background: var(--primary-color, #4f46e5);
|
|
146
|
+
border: 1px solid var(--primary-color, #4f46e5);
|
|
147
|
+
border-radius: 0.5rem;
|
|
148
|
+
outline: none;
|
|
149
|
+
transition: all 0.2s ease;
|
|
150
|
+
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
|
37
151
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
152
|
+
&:hover {
|
|
153
|
+
background: #4338ca;
|
|
154
|
+
border-color: #4338ca;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
&:focus {
|
|
158
|
+
box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.25);
|
|
42
159
|
}
|
|
43
160
|
}
|
|
44
161
|
|
|
162
|
+
input:not(:placeholder-shown) + .fi__span,
|
|
163
|
+
textarea:not(:placeholder-shown) + .fi__span,
|
|
164
|
+
select:not(:placeholder-shown) + .fi__span {
|
|
165
|
+
transform: translateY(-135%) translateX(10px) scale(0.85);
|
|
166
|
+
opacity: 1;
|
|
167
|
+
padding: 0;
|
|
168
|
+
background: white;
|
|
169
|
+
font-weight: 500;
|
|
170
|
+
transition: all 0.3s ease;
|
|
171
|
+
color: var(--primary-color, #4f46e5) !important;
|
|
172
|
+
}
|
|
173
|
+
|
|
45
174
|
.rememberMeIndicator {
|
|
46
175
|
display: flex;
|
|
47
176
|
align-items: center;
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import TableFilter from '../TableFilter';
|
|
3
|
+
|
|
4
|
+
const FilterExample = () => {
|
|
5
|
+
// Initial filter structure with parent-child relationships
|
|
6
|
+
const initialFilters = [
|
|
7
|
+
{
|
|
8
|
+
id: "personnelLicensesAndRecords",
|
|
9
|
+
label: "Personnel Licenses and Records",
|
|
10
|
+
isParent: true,
|
|
11
|
+
show: false,
|
|
12
|
+
class: "",
|
|
13
|
+
children: [
|
|
14
|
+
{
|
|
15
|
+
id: "completedSiteInduction",
|
|
16
|
+
show: false,
|
|
17
|
+
label: "Completed Site Induction",
|
|
18
|
+
class: ""
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: "unapprovedWhiteCard",
|
|
22
|
+
show: false,
|
|
23
|
+
label: "Unapproved White Card",
|
|
24
|
+
class: ""
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: "unapprovedLicense",
|
|
28
|
+
show: false,
|
|
29
|
+
label: "Unapproved License",
|
|
30
|
+
class: ""
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: "outstandingLicense",
|
|
34
|
+
show: false,
|
|
35
|
+
label: "Outstanding License",
|
|
36
|
+
class: ""
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: "unapprovedCovid",
|
|
40
|
+
show: false,
|
|
41
|
+
label: "Unapproved COVID",
|
|
42
|
+
class: ""
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: "insurance",
|
|
48
|
+
label: "Insurance",
|
|
49
|
+
isParent: true,
|
|
50
|
+
show: false,
|
|
51
|
+
class: "",
|
|
52
|
+
children: [
|
|
53
|
+
{
|
|
54
|
+
id: "allInsurance",
|
|
55
|
+
show: false,
|
|
56
|
+
label: "All Insurance",
|
|
57
|
+
class: ""
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: "unapprovedInsurance",
|
|
61
|
+
show: false,
|
|
62
|
+
label: "Unapproved Insurance",
|
|
63
|
+
class: ""
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: "expiredInsurance",
|
|
67
|
+
show: false,
|
|
68
|
+
label: "Expired Insurance",
|
|
69
|
+
class: ""
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: "outstandingInsurance",
|
|
73
|
+
show: false,
|
|
74
|
+
label: "Outstanding Insurance",
|
|
75
|
+
class: ""
|
|
76
|
+
}
|
|
77
|
+
]
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: "workingHours",
|
|
81
|
+
label: "Working Hours",
|
|
82
|
+
isParent: true,
|
|
83
|
+
show: false,
|
|
84
|
+
class: "",
|
|
85
|
+
children: [
|
|
86
|
+
{
|
|
87
|
+
id: "projectTotalHours",
|
|
88
|
+
show: false,
|
|
89
|
+
label: "Project Total Hours",
|
|
90
|
+
class: ""
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: "contractorTotalHours",
|
|
94
|
+
show: false,
|
|
95
|
+
label: "Contractor Total Hours",
|
|
96
|
+
class: ""
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
id: "sitePersonnelWorkHours",
|
|
100
|
+
show: false,
|
|
101
|
+
label: "Site Personnel Work Hours",
|
|
102
|
+
class: ""
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
id: "sitePersonnelRecords",
|
|
106
|
+
show: false,
|
|
107
|
+
label: "Site Personnel Records",
|
|
108
|
+
class: ""
|
|
109
|
+
}
|
|
110
|
+
]
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
id: "prequalifications",
|
|
114
|
+
label: "Prequalifications",
|
|
115
|
+
isParent: true,
|
|
116
|
+
show: false,
|
|
117
|
+
class: "",
|
|
118
|
+
children: [
|
|
119
|
+
{
|
|
120
|
+
id: "prequalificationsUnapproved",
|
|
121
|
+
show: false,
|
|
122
|
+
label: "Unapproved Prequalifications",
|
|
123
|
+
class: ""
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
id: "prequalificationsExpiring",
|
|
127
|
+
show: false,
|
|
128
|
+
label: "Expiring Prequalifications",
|
|
129
|
+
class: ""
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
id: "prequalifiedContractors",
|
|
133
|
+
show: false,
|
|
134
|
+
label: "Prequalified Contractors",
|
|
135
|
+
class: ""
|
|
136
|
+
}
|
|
137
|
+
]
|
|
138
|
+
}
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
const [filters, setFilters] = useState(initialFilters);
|
|
142
|
+
const [settings, setSettings] = useState({
|
|
143
|
+
ajaxSetting: {
|
|
144
|
+
where: []
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Find the active filter to display content
|
|
149
|
+
const getActiveFilter = () => {
|
|
150
|
+
let activeFilter = null;
|
|
151
|
+
|
|
152
|
+
// Check each parent filter
|
|
153
|
+
filters.forEach(parent => {
|
|
154
|
+
if (parent.children) {
|
|
155
|
+
// Check if any child is active
|
|
156
|
+
parent.children.forEach(child => {
|
|
157
|
+
if (child.show) {
|
|
158
|
+
activeFilter = {
|
|
159
|
+
parent: parent.label,
|
|
160
|
+
child: child.label,
|
|
161
|
+
id: child.id
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
return activeFilter;
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const activeFilter = getActiveFilter();
|
|
172
|
+
|
|
173
|
+
return (
|
|
174
|
+
<div style={{ maxWidth: '800px', margin: '0 auto', padding: '20px' }}>
|
|
175
|
+
<h1 style={{ marginBottom: '20px' }}>Reports</h1>
|
|
176
|
+
|
|
177
|
+
<div style={{ display: 'flex', gap: '20px' }}>
|
|
178
|
+
<div style={{ width: '300px' }}>
|
|
179
|
+
<TableFilter
|
|
180
|
+
filters={filters}
|
|
181
|
+
setFilters={setFilters}
|
|
182
|
+
setSettings={setSettings}
|
|
183
|
+
/>
|
|
184
|
+
</div>
|
|
185
|
+
|
|
186
|
+
<div style={{ flex: 1, padding: '20px', backgroundColor: '#f9f9f9', borderRadius: '8px', minHeight: '300px' }}>
|
|
187
|
+
{activeFilter ? (
|
|
188
|
+
<div>
|
|
189
|
+
<h2>{activeFilter.child}</h2>
|
|
190
|
+
<p>Category: {activeFilter.parent}</p>
|
|
191
|
+
<p>Filter ID: {activeFilter.id}</p>
|
|
192
|
+
<div style={{ marginTop: '20px', padding: '15px', backgroundColor: '#fff', borderRadius: '4px' }}>
|
|
193
|
+
<p>This is the content area that would display data based on the selected filter.</p>
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
196
|
+
) : (
|
|
197
|
+
<div style={{ textAlign: 'center', color: '#666' }}>
|
|
198
|
+
<p>Please select a filter option to view content</p>
|
|
199
|
+
</div>
|
|
200
|
+
)}
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
);
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
export default FilterExample;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# TableFilter Component with Parent-Child Structure
|
|
2
|
+
|
|
3
|
+
This document explains how to use the updated TableFilter component with parent-child relationships.
|
|
4
|
+
|
|
5
|
+
## Structure of Filter Data
|
|
6
|
+
|
|
7
|
+
The TableFilter component now supports a hierarchical structure with parent categories and child filters. Here's how to structure your filter data:
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
const filters = [
|
|
11
|
+
{
|
|
12
|
+
id: "parentCategoryId",
|
|
13
|
+
label: "Parent Category Name",
|
|
14
|
+
isParent: true, // Important: mark as parent
|
|
15
|
+
show: false,
|
|
16
|
+
class: "",
|
|
17
|
+
children: [
|
|
18
|
+
{
|
|
19
|
+
id: "childFilter1",
|
|
20
|
+
show: false,
|
|
21
|
+
label: "Child Filter 1",
|
|
22
|
+
class: ""
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: "childFilter2",
|
|
26
|
+
show: false,
|
|
27
|
+
label: "Child Filter 2",
|
|
28
|
+
class: ""
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
// More parent categories...
|
|
33
|
+
];
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Key Features
|
|
37
|
+
|
|
38
|
+
1. **Collapsible Parent Categories**: Click on a parent category to show/hide its children
|
|
39
|
+
2. **Visual Indicators**: Arrow indicators show whether a parent category is expanded
|
|
40
|
+
3. **Styling**: Distinct styling for parent categories and child filters
|
|
41
|
+
4. **Active States**: Visual indication of active filters
|
|
42
|
+
5. **Mobile Responsive**: Adapts to smaller screens with a toggle menu
|
|
43
|
+
|
|
44
|
+
## Usage Example
|
|
45
|
+
|
|
46
|
+
```jsx
|
|
47
|
+
import React, { useState } from 'react';
|
|
48
|
+
import TableFilter from '../TableFilter';
|
|
49
|
+
|
|
50
|
+
function MyComponent() {
|
|
51
|
+
const [filters, setFilters] = useState([
|
|
52
|
+
// Your filter structure here
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
const [settings, setSettings] = useState({
|
|
56
|
+
ajaxSetting: {
|
|
57
|
+
where: []
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<TableFilter
|
|
63
|
+
filters={filters}
|
|
64
|
+
setFilters={setFilters}
|
|
65
|
+
setSettings={setSettings}
|
|
66
|
+
/>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## How It Works
|
|
72
|
+
|
|
73
|
+
1. **Parent Categories**: When you click on a parent category, it toggles the visibility of its children
|
|
74
|
+
2. **Child Filters**: When you click on a child filter:
|
|
75
|
+
- It becomes active (highlighted)
|
|
76
|
+
- The `show` property is set to `true`
|
|
77
|
+
- The `settings` object is updated with the filter's ID and value
|
|
78
|
+
- The content area should update based on the selected filter
|
|
79
|
+
|
|
80
|
+
## Handling Filter Selection
|
|
81
|
+
|
|
82
|
+
To display content based on the selected filter, check which filter has `show: true`:
|
|
83
|
+
|
|
84
|
+
```javascript
|
|
85
|
+
// Find the active filter
|
|
86
|
+
const getActiveFilter = () => {
|
|
87
|
+
let activeFilter = null;
|
|
88
|
+
|
|
89
|
+
filters.forEach(parent => {
|
|
90
|
+
if (parent.children) {
|
|
91
|
+
parent.children.forEach(child => {
|
|
92
|
+
if (child.show) {
|
|
93
|
+
activeFilter = {
|
|
94
|
+
parent: parent.label,
|
|
95
|
+
child: child.label,
|
|
96
|
+
id: child.id
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
return activeFilter;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// Use the active filter to display content
|
|
107
|
+
const activeFilter = getActiveFilter();
|
|
108
|
+
if (activeFilter) {
|
|
109
|
+
// Display content for the active filter
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
See the `FilterExample.jsx` file for a complete working example.
|