@visns-studio/visns-components 5.6.1 → 5.6.3
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
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.3",
|
|
88
88
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
89
89
|
"main": "src/index.js",
|
|
90
90
|
"files": [
|
|
@@ -9,6 +9,7 @@ function TableFilter({
|
|
|
9
9
|
type,
|
|
10
10
|
}) {
|
|
11
11
|
const [visibleChildren, setVisibleChildren] = useState(null);
|
|
12
|
+
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
|
12
13
|
const isFirstRender = useRef(true);
|
|
13
14
|
|
|
14
15
|
useEffect(() => {
|
|
@@ -116,6 +117,9 @@ function TableFilter({
|
|
|
116
117
|
if (collapsible && filtertype === 'parent') {
|
|
117
118
|
setVisibleChildren(id);
|
|
118
119
|
}
|
|
120
|
+
|
|
121
|
+
// Close mobile menu after selection on small screens
|
|
122
|
+
setMobileMenuOpen(false);
|
|
119
123
|
};
|
|
120
124
|
|
|
121
125
|
const renderContent = (d) => {
|
|
@@ -184,14 +188,40 @@ function TableFilter({
|
|
|
184
188
|
}
|
|
185
189
|
};
|
|
186
190
|
|
|
191
|
+
const toggleMobileMenu = () => {
|
|
192
|
+
setMobileMenuOpen(!mobileMenuOpen);
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
// Find active filter for mobile display
|
|
196
|
+
const activeFilter = filters.find((filter) => filter.show === true);
|
|
197
|
+
const activeLabel = activeFilter ? activeFilter.label : 'Select Option';
|
|
198
|
+
|
|
187
199
|
return (
|
|
188
|
-
<
|
|
189
|
-
{
|
|
190
|
-
<
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
200
|
+
<div className={styles.tableFilterContainer}>
|
|
201
|
+
<div className={styles.mobileToggle} onClick={toggleMobileMenu}>
|
|
202
|
+
<span className={styles.activeFilterLabel}>{activeLabel}</span>
|
|
203
|
+
<span
|
|
204
|
+
className={`${styles.mobileMenuIcon} ${
|
|
205
|
+
mobileMenuOpen ? styles.open : ''
|
|
206
|
+
}`}
|
|
207
|
+
>
|
|
208
|
+
<span></span>
|
|
209
|
+
<span></span>
|
|
210
|
+
<span></span>
|
|
211
|
+
</span>
|
|
212
|
+
</div>
|
|
213
|
+
<ul
|
|
214
|
+
className={`${styles.tableFilter} ${
|
|
215
|
+
mobileMenuOpen ? styles.mobileOpen : ''
|
|
216
|
+
}`}
|
|
217
|
+
>
|
|
218
|
+
{filters.map((item, key) => (
|
|
219
|
+
<React.Fragment key={'table-filter-' + key}>
|
|
220
|
+
{renderContent(item)}
|
|
221
|
+
</React.Fragment>
|
|
222
|
+
))}
|
|
223
|
+
</ul>
|
|
224
|
+
</div>
|
|
195
225
|
);
|
|
196
226
|
}
|
|
197
227
|
|
|
@@ -474,11 +474,17 @@ const GenericReport = ({ setting = {} }) => {
|
|
|
474
474
|
const [gridColumns, setGridColumns] = useState([]);
|
|
475
475
|
const [isLoading, setIsLoading] = useState(false);
|
|
476
476
|
|
|
477
|
+
// State for JSON field keys
|
|
478
|
+
const [jsonFieldKeys, setJsonFieldKeys] = useState({});
|
|
479
|
+
const [loadingJsonKeys, setLoadingJsonKeys] = useState({});
|
|
480
|
+
const [showJsonKeySelector, setShowJsonKeySelector] = useState({});
|
|
481
|
+
|
|
477
482
|
// Extract additional settings with defaults
|
|
478
483
|
const {
|
|
479
484
|
reportsUrl = '/ajax/reportBuilder/reports',
|
|
480
485
|
executeUrl = '/ajax/reportBuilder/execute',
|
|
481
486
|
suggestedJoinsUrl = '/ajax/reportBuilder/getSuggestedJoins',
|
|
487
|
+
jsonFieldKeysUrl = '/ajax/reportBuilder/getJsonFieldKeys',
|
|
482
488
|
} = setting;
|
|
483
489
|
|
|
484
490
|
// Fetch all database tables and saved reports on component mount
|
|
@@ -1051,6 +1057,118 @@ const GenericReport = ({ setting = {} }) => {
|
|
|
1051
1057
|
setShowFilteringSection(true);
|
|
1052
1058
|
};
|
|
1053
1059
|
|
|
1060
|
+
// Fetch JSON field keys for a specific table and column
|
|
1061
|
+
const fetchJsonFieldKeys = async (tableName, columnName) => {
|
|
1062
|
+
const cacheKey = `${tableName}.${columnName}`;
|
|
1063
|
+
|
|
1064
|
+
// Check if we're already loading this data
|
|
1065
|
+
if (loadingJsonKeys[cacheKey]) {
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
// Check if we already have this data cached
|
|
1070
|
+
if (jsonFieldKeys[cacheKey]) {
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
// Set loading state
|
|
1075
|
+
setLoadingJsonKeys((prev) => ({
|
|
1076
|
+
...prev,
|
|
1077
|
+
[cacheKey]: true,
|
|
1078
|
+
}));
|
|
1079
|
+
|
|
1080
|
+
try {
|
|
1081
|
+
const res = await CustomFetch(jsonFieldKeysUrl, 'POST', {
|
|
1082
|
+
table: tableName,
|
|
1083
|
+
column: columnName,
|
|
1084
|
+
limit: 100, // Analyze up to 100 records
|
|
1085
|
+
});
|
|
1086
|
+
|
|
1087
|
+
if (res.data?.success && res.data?.data?.keys) {
|
|
1088
|
+
// Store the keys in state
|
|
1089
|
+
setJsonFieldKeys((prev) => ({
|
|
1090
|
+
...prev,
|
|
1091
|
+
[cacheKey]: res.data.data.keys,
|
|
1092
|
+
}));
|
|
1093
|
+
} else {
|
|
1094
|
+
console.warn(
|
|
1095
|
+
`No JSON keys found for ${tableName}.${columnName} or invalid response format`
|
|
1096
|
+
);
|
|
1097
|
+
// Set empty array for this field
|
|
1098
|
+
setJsonFieldKeys((prev) => ({
|
|
1099
|
+
...prev,
|
|
1100
|
+
[cacheKey]: [],
|
|
1101
|
+
}));
|
|
1102
|
+
}
|
|
1103
|
+
} catch (error) {
|
|
1104
|
+
console.error(
|
|
1105
|
+
`Error fetching JSON field keys for ${tableName}.${columnName}:`,
|
|
1106
|
+
error
|
|
1107
|
+
);
|
|
1108
|
+
toast.error(
|
|
1109
|
+
`Failed to load JSON field keys for ${formatName(
|
|
1110
|
+
tableName
|
|
1111
|
+
)}.${formatName(columnName)}`
|
|
1112
|
+
);
|
|
1113
|
+
// Set empty array for this field on error
|
|
1114
|
+
setJsonFieldKeys((prev) => ({
|
|
1115
|
+
...prev,
|
|
1116
|
+
[cacheKey]: [],
|
|
1117
|
+
}));
|
|
1118
|
+
} finally {
|
|
1119
|
+
// Update loading state
|
|
1120
|
+
setLoadingJsonKeys((prev) => ({
|
|
1121
|
+
...prev,
|
|
1122
|
+
[cacheKey]: false,
|
|
1123
|
+
}));
|
|
1124
|
+
}
|
|
1125
|
+
};
|
|
1126
|
+
|
|
1127
|
+
// Check if a column is a JSON type
|
|
1128
|
+
const isJsonColumn = (tableName, columnName) => {
|
|
1129
|
+
// First check the main table
|
|
1130
|
+
if (tableName === selectedTable) {
|
|
1131
|
+
const column = tableColumns.find((col) => col.name === columnName);
|
|
1132
|
+
return (
|
|
1133
|
+
column && column.type && column.type.toLowerCase() === 'json'
|
|
1134
|
+
);
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
// Then check joined tables
|
|
1138
|
+
for (const join of joins) {
|
|
1139
|
+
if (join.targetTable === tableName && join.availableColumns) {
|
|
1140
|
+
const column = join.availableColumns.find(
|
|
1141
|
+
(col) => col.name === columnName
|
|
1142
|
+
);
|
|
1143
|
+
return (
|
|
1144
|
+
column &&
|
|
1145
|
+
column.type &&
|
|
1146
|
+
column.type.toLowerCase() === 'json'
|
|
1147
|
+
);
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
return false;
|
|
1152
|
+
};
|
|
1153
|
+
|
|
1154
|
+
// Toggle JSON key selector visibility
|
|
1155
|
+
const toggleJsonKeySelector = (groupIndex, filterIndex) => {
|
|
1156
|
+
const key = `${groupIndex}-${filterIndex}`;
|
|
1157
|
+
setShowJsonKeySelector((prev) => ({
|
|
1158
|
+
...prev,
|
|
1159
|
+
[key]: !prev[key],
|
|
1160
|
+
}));
|
|
1161
|
+
};
|
|
1162
|
+
|
|
1163
|
+
// Handle JSON key selection
|
|
1164
|
+
const handleJsonKeySelect = (groupIndex, filterIndex, key) => {
|
|
1165
|
+
// Update the filter value with the selected JSON key
|
|
1166
|
+
handleFilterCriterionChange(groupIndex, filterIndex, 'jsonKey', key);
|
|
1167
|
+
|
|
1168
|
+
// Hide the selector
|
|
1169
|
+
toggleJsonKeySelector(groupIndex, filterIndex);
|
|
1170
|
+
};
|
|
1171
|
+
|
|
1054
1172
|
// Update a filter criterion
|
|
1055
1173
|
const handleFilterCriterionChange = (
|
|
1056
1174
|
groupIndex,
|
|
@@ -1067,6 +1185,34 @@ const GenericReport = ({ setting = {} }) => {
|
|
|
1067
1185
|
updatedFilterCriteria.groups[groupIndex].filters[filterIndex][field] =
|
|
1068
1186
|
value;
|
|
1069
1187
|
|
|
1188
|
+
// If we're changing the table or column, check if it's a JSON field
|
|
1189
|
+
if (field === 'table' || field === 'column') {
|
|
1190
|
+
const filter =
|
|
1191
|
+
updatedFilterCriteria.groups[groupIndex].filters[filterIndex];
|
|
1192
|
+
const tableName = field === 'table' ? value : filter.table;
|
|
1193
|
+
const columnName = field === 'column' ? value : filter.column;
|
|
1194
|
+
|
|
1195
|
+
// If both table and column are set, check if it's a JSON field
|
|
1196
|
+
if (tableName && columnName) {
|
|
1197
|
+
const isJson = isJsonColumn(tableName, columnName);
|
|
1198
|
+
|
|
1199
|
+
// If it's a JSON field, fetch the keys
|
|
1200
|
+
if (isJson) {
|
|
1201
|
+
fetchJsonFieldKeys(tableName, columnName);
|
|
1202
|
+
|
|
1203
|
+
// Add a jsonKey field if it doesn't exist
|
|
1204
|
+
if (!filter.jsonKey) {
|
|
1205
|
+
filter.jsonKey = '';
|
|
1206
|
+
}
|
|
1207
|
+
} else {
|
|
1208
|
+
// If it's not a JSON field, remove the jsonKey field if it exists
|
|
1209
|
+
if (filter.jsonKey) {
|
|
1210
|
+
delete filter.jsonKey;
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1070
1216
|
setFilterCriteria(updatedFilterCriteria);
|
|
1071
1217
|
};
|
|
1072
1218
|
|
|
@@ -3615,31 +3761,217 @@ const GenericReport = ({ setting = {} }) => {
|
|
|
3615
3761
|
styles.joinField
|
|
3616
3762
|
}
|
|
3617
3763
|
>
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3764
|
+
{/* Check if this is a JSON field */}
|
|
3765
|
+
{criterion.table &&
|
|
3766
|
+
criterion.column &&
|
|
3767
|
+
isJsonColumn(
|
|
3768
|
+
criterion.table,
|
|
3769
|
+
criterion.column
|
|
3770
|
+
) ? (
|
|
3771
|
+
<>
|
|
3772
|
+
<label>
|
|
3773
|
+
JSON
|
|
3774
|
+
Path:
|
|
3775
|
+
</label>
|
|
3776
|
+
<div
|
|
3777
|
+
className={
|
|
3778
|
+
styles.jsonKeyInputContainer
|
|
3779
|
+
}
|
|
3780
|
+
>
|
|
3781
|
+
<input
|
|
3782
|
+
type="text"
|
|
3783
|
+
className={
|
|
3784
|
+
styles.formControl
|
|
3785
|
+
}
|
|
3786
|
+
value={
|
|
3787
|
+
criterion.jsonKey ||
|
|
3788
|
+
''
|
|
3789
|
+
}
|
|
3790
|
+
onChange={(
|
|
3791
|
+
e
|
|
3792
|
+
) =>
|
|
3793
|
+
handleFilterCriterionChange(
|
|
3794
|
+
groupIndex,
|
|
3795
|
+
filterIndex,
|
|
3796
|
+
'jsonKey',
|
|
3797
|
+
e
|
|
3798
|
+
.target
|
|
3799
|
+
.value
|
|
3800
|
+
)
|
|
3801
|
+
}
|
|
3802
|
+
placeholder="Enter JSON path (e.g., data.name)"
|
|
3803
|
+
/>
|
|
3804
|
+
<button
|
|
3805
|
+
type="button"
|
|
3806
|
+
className={
|
|
3807
|
+
styles.jsonKeyButton
|
|
3808
|
+
}
|
|
3809
|
+
onClick={() =>
|
|
3810
|
+
toggleJsonKeySelector(
|
|
3811
|
+
groupIndex,
|
|
3812
|
+
filterIndex
|
|
3813
|
+
)
|
|
3814
|
+
}
|
|
3815
|
+
title="Select from available JSON keys"
|
|
3816
|
+
>
|
|
3817
|
+
▼
|
|
3818
|
+
</button>
|
|
3819
|
+
</div>
|
|
3820
|
+
|
|
3821
|
+
{/* JSON key selector dropdown */}
|
|
3822
|
+
{showJsonKeySelector[
|
|
3823
|
+
`${groupIndex}-${filterIndex}`
|
|
3824
|
+
] && (
|
|
3825
|
+
<div
|
|
3826
|
+
className={
|
|
3827
|
+
styles.jsonKeyDropdown
|
|
3828
|
+
}
|
|
3829
|
+
>
|
|
3830
|
+
{loadingJsonKeys[
|
|
3831
|
+
`${criterion.table}.${criterion.column}`
|
|
3832
|
+
] ? (
|
|
3833
|
+
<div
|
|
3834
|
+
className={
|
|
3835
|
+
styles.jsonKeyLoading
|
|
3836
|
+
}
|
|
3837
|
+
>
|
|
3838
|
+
Loading
|
|
3839
|
+
available
|
|
3840
|
+
JSON
|
|
3841
|
+
keys...
|
|
3842
|
+
</div>
|
|
3843
|
+
) : jsonFieldKeys[
|
|
3844
|
+
`${criterion.table}.${criterion.column}`
|
|
3845
|
+
]
|
|
3846
|
+
?.length >
|
|
3847
|
+
0 ? (
|
|
3848
|
+
<div
|
|
3849
|
+
className={
|
|
3850
|
+
styles.jsonKeyList
|
|
3851
|
+
}
|
|
3852
|
+
>
|
|
3853
|
+
{jsonFieldKeys[
|
|
3854
|
+
`${criterion.table}.${criterion.column}`
|
|
3855
|
+
].map(
|
|
3856
|
+
(
|
|
3857
|
+
keyInfo,
|
|
3858
|
+
idx
|
|
3859
|
+
) => (
|
|
3860
|
+
<div
|
|
3861
|
+
key={
|
|
3862
|
+
idx
|
|
3863
|
+
}
|
|
3864
|
+
className={
|
|
3865
|
+
styles.jsonKeyItem
|
|
3866
|
+
}
|
|
3867
|
+
onClick={() =>
|
|
3868
|
+
handleJsonKeySelect(
|
|
3869
|
+
groupIndex,
|
|
3870
|
+
filterIndex,
|
|
3871
|
+
keyInfo.key
|
|
3872
|
+
)
|
|
3873
|
+
}
|
|
3874
|
+
title={`Used in ${keyInfo.percentage}% of records. Example: ${keyInfo.example}`}
|
|
3875
|
+
>
|
|
3876
|
+
<span
|
|
3877
|
+
className={
|
|
3878
|
+
styles.jsonKeyName
|
|
3879
|
+
}
|
|
3880
|
+
>
|
|
3881
|
+
{
|
|
3882
|
+
keyInfo.key
|
|
3883
|
+
}
|
|
3884
|
+
</span>
|
|
3885
|
+
<span
|
|
3886
|
+
className={
|
|
3887
|
+
styles.jsonKeyFrequency
|
|
3888
|
+
}
|
|
3889
|
+
>
|
|
3890
|
+
{
|
|
3891
|
+
keyInfo.percentage
|
|
3892
|
+
}
|
|
3893
|
+
%
|
|
3894
|
+
</span>
|
|
3895
|
+
</div>
|
|
3896
|
+
)
|
|
3897
|
+
)}
|
|
3898
|
+
</div>
|
|
3899
|
+
) : (
|
|
3900
|
+
<div
|
|
3901
|
+
className={
|
|
3902
|
+
styles.jsonKeyEmpty
|
|
3903
|
+
}
|
|
3904
|
+
>
|
|
3905
|
+
No
|
|
3906
|
+
JSON
|
|
3907
|
+
keys
|
|
3908
|
+
found.
|
|
3909
|
+
Enter
|
|
3910
|
+
a
|
|
3911
|
+
path
|
|
3912
|
+
manually.
|
|
3913
|
+
</div>
|
|
3914
|
+
)}
|
|
3915
|
+
</div>
|
|
3916
|
+
)}
|
|
3917
|
+
|
|
3918
|
+
<label>
|
|
3919
|
+
Value:
|
|
3920
|
+
</label>
|
|
3921
|
+
<input
|
|
3922
|
+
type="text"
|
|
3923
|
+
className={
|
|
3924
|
+
styles.formControl
|
|
3925
|
+
}
|
|
3926
|
+
value={
|
|
3927
|
+
criterion.value ||
|
|
3928
|
+
''
|
|
3929
|
+
}
|
|
3930
|
+
onChange={(
|
|
3931
|
+
e
|
|
3932
|
+
) =>
|
|
3933
|
+
handleFilterCriterionChange(
|
|
3934
|
+
groupIndex,
|
|
3935
|
+
filterIndex,
|
|
3936
|
+
'value',
|
|
3937
|
+
e
|
|
3938
|
+
.target
|
|
3939
|
+
.value
|
|
3940
|
+
)
|
|
3941
|
+
}
|
|
3942
|
+
placeholder="Enter filter value"
|
|
3943
|
+
/>
|
|
3944
|
+
</>
|
|
3945
|
+
) : (
|
|
3946
|
+
<>
|
|
3947
|
+
<label>
|
|
3948
|
+
Value:
|
|
3949
|
+
</label>
|
|
3950
|
+
<input
|
|
3951
|
+
type="text"
|
|
3952
|
+
className={
|
|
3953
|
+
styles.formControl
|
|
3954
|
+
}
|
|
3955
|
+
value={
|
|
3956
|
+
criterion.value ||
|
|
3957
|
+
''
|
|
3958
|
+
}
|
|
3959
|
+
onChange={(
|
|
3960
|
+
e
|
|
3961
|
+
) =>
|
|
3962
|
+
handleFilterCriterionChange(
|
|
3963
|
+
groupIndex,
|
|
3964
|
+
filterIndex,
|
|
3965
|
+
'value',
|
|
3966
|
+
e
|
|
3967
|
+
.target
|
|
3968
|
+
.value
|
|
3969
|
+
)
|
|
3970
|
+
}
|
|
3971
|
+
placeholder="Enter filter value"
|
|
3972
|
+
/>
|
|
3973
|
+
</>
|
|
3974
|
+
)}
|
|
3643
3975
|
</div>
|
|
3644
3976
|
)}
|
|
3645
3977
|
</div>
|
|
@@ -1157,6 +1157,94 @@
|
|
|
1157
1157
|
margin: 16px 0;
|
|
1158
1158
|
}
|
|
1159
1159
|
|
|
1160
|
+
.jsonKeyInputContainer {
|
|
1161
|
+
position: relative;
|
|
1162
|
+
display: flex;
|
|
1163
|
+
align-items: center;
|
|
1164
|
+
width: 100%;
|
|
1165
|
+
|
|
1166
|
+
.jsonKeyButton {
|
|
1167
|
+
position: absolute;
|
|
1168
|
+
right: 0;
|
|
1169
|
+
top: 0;
|
|
1170
|
+
height: 100%;
|
|
1171
|
+
background: none;
|
|
1172
|
+
border: none;
|
|
1173
|
+
border-left: 1px solid var(--border-color, #d1d5db);
|
|
1174
|
+
padding: 0 10px;
|
|
1175
|
+
color: var(--paragraph-color, #4b5563);
|
|
1176
|
+
cursor: pointer;
|
|
1177
|
+
font-size: 0.8em;
|
|
1178
|
+
transition: all 0.2s ease;
|
|
1179
|
+
|
|
1180
|
+
&:hover {
|
|
1181
|
+
background-color: #f3f4f6;
|
|
1182
|
+
color: var(--primary-color, #2563eb);
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
input {
|
|
1187
|
+
padding-right: 30px;
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
.jsonKeyDropdown {
|
|
1192
|
+
position: relative;
|
|
1193
|
+
width: 100%;
|
|
1194
|
+
max-height: 200px;
|
|
1195
|
+
overflow-y: auto;
|
|
1196
|
+
background-color: white;
|
|
1197
|
+
border: 1px solid var(--border-color, #d1d5db);
|
|
1198
|
+
border-radius: 6px;
|
|
1199
|
+
margin-top: 4px;
|
|
1200
|
+
margin-bottom: 12px;
|
|
1201
|
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1),
|
|
1202
|
+
0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
|
1203
|
+
z-index: 100;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
.jsonKeyList {
|
|
1207
|
+
padding: 4px 0;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
.jsonKeyItem {
|
|
1211
|
+
display: flex;
|
|
1212
|
+
justify-content: space-between;
|
|
1213
|
+
align-items: center;
|
|
1214
|
+
padding: 8px 12px;
|
|
1215
|
+
cursor: pointer;
|
|
1216
|
+
transition: all 0.2s ease;
|
|
1217
|
+
font-size: 0.85em;
|
|
1218
|
+
|
|
1219
|
+
&:hover {
|
|
1220
|
+
background-color: #f3f4f6;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
.jsonKeyName {
|
|
1224
|
+
flex: 1;
|
|
1225
|
+
overflow: hidden;
|
|
1226
|
+
text-overflow: ellipsis;
|
|
1227
|
+
white-space: nowrap;
|
|
1228
|
+
color: var(--primary-color, #2563eb);
|
|
1229
|
+
font-weight: 500;
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
.jsonKeyFrequency {
|
|
1233
|
+
color: var(--muted-color, #6b7280);
|
|
1234
|
+
font-size: 0.85em;
|
|
1235
|
+
margin-left: 8px;
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
.jsonKeyLoading,
|
|
1240
|
+
.jsonKeyEmpty {
|
|
1241
|
+
padding: 12px;
|
|
1242
|
+
text-align: center;
|
|
1243
|
+
color: var(--muted-color, #6b7280);
|
|
1244
|
+
font-size: 0.85em;
|
|
1245
|
+
font-style: italic;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1160
1248
|
.jsonPreview {
|
|
1161
1249
|
max-width: 100%;
|
|
1162
1250
|
max-height: 150px;
|
|
@@ -1,8 +1,15 @@
|
|
|
1
|
+
.tableFilterContainer {
|
|
2
|
+
width: 100%;
|
|
3
|
+
position: relative;
|
|
4
|
+
display: block;
|
|
5
|
+
}
|
|
6
|
+
|
|
1
7
|
.tableFilter {
|
|
2
8
|
list-style: none;
|
|
3
9
|
padding: 0;
|
|
4
10
|
margin: 0;
|
|
5
11
|
font-size: 1rem;
|
|
12
|
+
width: 100%;
|
|
6
13
|
}
|
|
7
14
|
|
|
8
15
|
.collapsibleMenu {
|
|
@@ -51,3 +58,81 @@
|
|
|
51
58
|
background: var(--item-color) !important;
|
|
52
59
|
color: var(--paragraph-color) !important;
|
|
53
60
|
}
|
|
61
|
+
|
|
62
|
+
/* Mobile menu toggle button */
|
|
63
|
+
.mobileToggle {
|
|
64
|
+
display: none;
|
|
65
|
+
align-items: center;
|
|
66
|
+
justify-content: space-between;
|
|
67
|
+
padding: 12px 15px;
|
|
68
|
+
background: var(--primary-color, #0b3b2e);
|
|
69
|
+
color: white;
|
|
70
|
+
border-radius: 4px;
|
|
71
|
+
cursor: pointer;
|
|
72
|
+
margin-bottom: 10px;
|
|
73
|
+
border: none;
|
|
74
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.activeFilterLabel {
|
|
78
|
+
font-weight: 600;
|
|
79
|
+
color: white;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.mobileMenuIcon {
|
|
83
|
+
display: flex;
|
|
84
|
+
flex-direction: column;
|
|
85
|
+
justify-content: space-between;
|
|
86
|
+
width: 20px;
|
|
87
|
+
height: 14px;
|
|
88
|
+
position: relative;
|
|
89
|
+
transition: all 0.3s ease;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.mobileMenuIcon span {
|
|
93
|
+
display: block;
|
|
94
|
+
height: 2px;
|
|
95
|
+
width: 100%;
|
|
96
|
+
background-color: white;
|
|
97
|
+
border-radius: 2px;
|
|
98
|
+
transition: all 0.3s ease;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.mobileMenuIcon.open span:nth-child(1) {
|
|
102
|
+
transform: translateY(6px) rotate(45deg);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.mobileMenuIcon.open span:nth-child(2) {
|
|
106
|
+
opacity: 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.mobileMenuIcon.open span:nth-child(3) {
|
|
110
|
+
transform: translateY(-6px) rotate(-45deg);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* Media queries for responsive design */
|
|
114
|
+
@media (max-width: 1024px) {
|
|
115
|
+
.mobileToggle {
|
|
116
|
+
display: flex;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.tableFilter {
|
|
120
|
+
display: none;
|
|
121
|
+
max-height: 0;
|
|
122
|
+
overflow: hidden;
|
|
123
|
+
transition: max-height 0.3s ease;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.tableFilter.mobileOpen {
|
|
127
|
+
display: block;
|
|
128
|
+
max-height: 1000px;
|
|
129
|
+
overflow-y: auto;
|
|
130
|
+
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
131
|
+
border-radius: 4px;
|
|
132
|
+
margin-bottom: 10px;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.link {
|
|
136
|
+
padding: 12px 20px; /* Larger touch targets for mobile */
|
|
137
|
+
}
|
|
138
|
+
}
|