@visns-studio/visns-components 5.12.7 → 5.12.8
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/CategorizedDropZone.jsx +286 -0
- package/src/components/generic/GenericDetail.jsx +25 -0
- package/src/components/generic/StagePopupModal.jsx +14 -1
- package/src/components/styles/CategorizedDropZone.module.scss +217 -0
- package/src/components/styles/DropZone.module.css +1 -1
- package/src/index.js +2 -0
package/package.json
CHANGED
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"react-dom": "^17.0.0 || ^18.0.0"
|
|
88
88
|
},
|
|
89
89
|
"name": "@visns-studio/visns-components",
|
|
90
|
-
"version": "5.12.
|
|
90
|
+
"version": "5.12.8",
|
|
91
91
|
"description": "Various packages to assist in the development of our Custom Applications.",
|
|
92
92
|
"main": "src/index.js",
|
|
93
93
|
"files": [
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import DropZone from './DropZone';
|
|
3
|
+
import CustomFetch from './Fetch';
|
|
4
|
+
import styles from './styles/CategorizedDropZone.module.scss';
|
|
5
|
+
|
|
6
|
+
const CategorizedDropZone = ({
|
|
7
|
+
filetype = 'image',
|
|
8
|
+
folder = 'uploads',
|
|
9
|
+
dataKey = 'files',
|
|
10
|
+
file_relationship = 'files',
|
|
11
|
+
url = '/ajax/files',
|
|
12
|
+
method = 'POST',
|
|
13
|
+
deleteUrl = '/ajax/files/delete',
|
|
14
|
+
categories = [],
|
|
15
|
+
categoriesUrl = null,
|
|
16
|
+
categoriesWhere = [],
|
|
17
|
+
entityData = {},
|
|
18
|
+
routeParams = {},
|
|
19
|
+
fetchData = null
|
|
20
|
+
}) => {
|
|
21
|
+
const [activeCategory, setActiveCategory] = useState(null);
|
|
22
|
+
const [categorizedFiles, setCategorizedFiles] = useState({});
|
|
23
|
+
const [dynamicCategories, setDynamicCategories] = useState([]);
|
|
24
|
+
const [loadingCategories, setLoadingCategories] = useState(false);
|
|
25
|
+
|
|
26
|
+
// Get final categories (dynamic or static)
|
|
27
|
+
const finalCategories = dynamicCategories.length > 0 ? dynamicCategories : categories;
|
|
28
|
+
|
|
29
|
+
// Fetch categories from API if categoriesUrl is provided
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (categoriesUrl) {
|
|
32
|
+
setLoadingCategories(true);
|
|
33
|
+
|
|
34
|
+
const requestData = {};
|
|
35
|
+
if (categoriesWhere && categoriesWhere.length > 0) {
|
|
36
|
+
requestData.where = categoriesWhere;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
CustomFetch(categoriesUrl, 'POST', requestData)
|
|
40
|
+
.then(response => {
|
|
41
|
+
if (response.data && !response.data.error) {
|
|
42
|
+
const categoriesData = Array.isArray(response.data.data) ? response.data.data :
|
|
43
|
+
Array.isArray(response.data) ? response.data : [];
|
|
44
|
+
|
|
45
|
+
// Transform API data to match expected format
|
|
46
|
+
const transformedCategories = categoriesData.map(cat => ({
|
|
47
|
+
id: cat.slug || cat.id,
|
|
48
|
+
label: cat.name || cat.label,
|
|
49
|
+
description: cat.description || ''
|
|
50
|
+
}));
|
|
51
|
+
|
|
52
|
+
setDynamicCategories(transformedCategories);
|
|
53
|
+
|
|
54
|
+
// Set first category as active if none selected
|
|
55
|
+
if (!activeCategory && transformedCategories.length > 0) {
|
|
56
|
+
setActiveCategory(transformedCategories[0].id);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
.catch(error => {
|
|
61
|
+
console.error('Failed to load categories:', error);
|
|
62
|
+
})
|
|
63
|
+
.finally(() => {
|
|
64
|
+
setLoadingCategories(false);
|
|
65
|
+
});
|
|
66
|
+
} else if (categories.length > 0 && !activeCategory) {
|
|
67
|
+
// Set first static category as active
|
|
68
|
+
setActiveCategory(categories[0].id);
|
|
69
|
+
}
|
|
70
|
+
}, [categoriesUrl, categoriesWhere, categories, activeCategory]);
|
|
71
|
+
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
console.log('CategorizedDropZone: Initializing files', {
|
|
74
|
+
finalCategories,
|
|
75
|
+
entityData: entityData[dataKey],
|
|
76
|
+
dataKey
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Initialize categorized files structure
|
|
80
|
+
const initialCategorizedFiles = {};
|
|
81
|
+
finalCategories.forEach(category => {
|
|
82
|
+
initialCategorizedFiles[category.id] = [];
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// If there are existing files, categorize them based on file_description
|
|
86
|
+
if (entityData[dataKey] && Array.isArray(entityData[dataKey])) {
|
|
87
|
+
console.log('CategorizedDropZone: Processing files', entityData[dataKey]);
|
|
88
|
+
|
|
89
|
+
entityData[dataKey].forEach(file => {
|
|
90
|
+
console.log('CategorizedDropZone: Processing file', {
|
|
91
|
+
file_name: file.file_name,
|
|
92
|
+
file_description: file.file_description,
|
|
93
|
+
id: file.id
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const category = getCategoryFromDescription(file.file_description);
|
|
97
|
+
console.log('CategorizedDropZone: Category extracted', category);
|
|
98
|
+
|
|
99
|
+
if (category && initialCategorizedFiles[category]) {
|
|
100
|
+
initialCategorizedFiles[category].push(file);
|
|
101
|
+
console.log(`CategorizedDropZone: Added file ${file.file_name} to category ${category}`);
|
|
102
|
+
} else {
|
|
103
|
+
console.log(`CategorizedDropZone: No category match for file ${file.file_name}`, {
|
|
104
|
+
extractedCategory: category,
|
|
105
|
+
availableCategories: Object.keys(initialCategorizedFiles)
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
console.log('CategorizedDropZone: Final categorized files', initialCategorizedFiles);
|
|
112
|
+
setCategorizedFiles(initialCategorizedFiles);
|
|
113
|
+
}, [entityData, dataKey, finalCategories]);
|
|
114
|
+
|
|
115
|
+
const getCategoryFromDescription = (description) => {
|
|
116
|
+
if (!description) return null;
|
|
117
|
+
|
|
118
|
+
// Check if the description contains a category identifier (handles both slugs and numeric IDs)
|
|
119
|
+
const categoryMatch = description.match(/^category:([^-\s]+)/i);
|
|
120
|
+
if (categoryMatch) {
|
|
121
|
+
const extractedId = categoryMatch[1];
|
|
122
|
+
|
|
123
|
+
// First, try to find by exact ID match
|
|
124
|
+
const exactMatch = finalCategories.find(cat => cat.id === extractedId);
|
|
125
|
+
if (exactMatch) return extractedId;
|
|
126
|
+
|
|
127
|
+
// If no exact match, try to find by numeric ID if extracted ID is numeric
|
|
128
|
+
if (/^\d+$/.test(extractedId)) {
|
|
129
|
+
const numericMatch = finalCategories.find(cat => cat.id == extractedId);
|
|
130
|
+
if (numericMatch) return numericMatch.id;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Log for debugging
|
|
134
|
+
console.log('Category extraction debug:', {
|
|
135
|
+
description,
|
|
136
|
+
extractedId,
|
|
137
|
+
availableCategories: finalCategories.map(c => ({ id: c.id, label: c.label }))
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Fallback: check if description matches any category label
|
|
142
|
+
const matchedCategory = finalCategories.find(cat =>
|
|
143
|
+
description.toLowerCase().includes(cat.label.toLowerCase())
|
|
144
|
+
);
|
|
145
|
+
return matchedCategory?.id || null;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const handleCategoryChange = (categoryId) => {
|
|
149
|
+
setActiveCategory(categoryId);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const getCurrentCategoryFiles = () => {
|
|
153
|
+
return categorizedFiles[activeCategory] || [];
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const handleFileUpload = (files) => {
|
|
157
|
+
// Add category prefix to file descriptions
|
|
158
|
+
const activeCategoryData = finalCategories.find(cat => cat.id === activeCategory);
|
|
159
|
+
const categoryPrefix = `category:${activeCategory}`;
|
|
160
|
+
|
|
161
|
+
// Update the files with category information
|
|
162
|
+
const updatedFiles = files.map(file => ({
|
|
163
|
+
...file,
|
|
164
|
+
file_description: file.file_description ?
|
|
165
|
+
`${categoryPrefix} - ${file.file_description}` :
|
|
166
|
+
`${categoryPrefix} - ${activeCategoryData?.description || activeCategoryData?.label}`
|
|
167
|
+
}));
|
|
168
|
+
|
|
169
|
+
// Update the categorized files state
|
|
170
|
+
setCategorizedFiles(prev => ({
|
|
171
|
+
...prev,
|
|
172
|
+
[activeCategory]: [...(prev[activeCategory] || []), ...updatedFiles]
|
|
173
|
+
}));
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const handleFileDelete = (fileId) => {
|
|
177
|
+
// Remove file from the appropriate category
|
|
178
|
+
setCategorizedFiles(prev => {
|
|
179
|
+
const updated = { ...prev };
|
|
180
|
+
Object.keys(updated).forEach(categoryId => {
|
|
181
|
+
updated[categoryId] = updated[categoryId].filter(file => file.id !== fileId);
|
|
182
|
+
});
|
|
183
|
+
return updated;
|
|
184
|
+
});
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const handleFileUpdate = (updatedFile) => {
|
|
188
|
+
// Update file in the appropriate category
|
|
189
|
+
const newCategory = getCategoryFromDescription(updatedFile.file_description);
|
|
190
|
+
|
|
191
|
+
setCategorizedFiles(prev => {
|
|
192
|
+
const updated = { ...prev };
|
|
193
|
+
|
|
194
|
+
// Remove from old category
|
|
195
|
+
Object.keys(updated).forEach(categoryId => {
|
|
196
|
+
updated[categoryId] = updated[categoryId].filter(file => file.id !== updatedFile.id);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Add to new category
|
|
200
|
+
if (newCategory && updated[newCategory]) {
|
|
201
|
+
updated[newCategory].push(updatedFile);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return updated;
|
|
205
|
+
});
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
// Prepare entity data for the current category
|
|
209
|
+
const currentCategoryEntityData = {
|
|
210
|
+
...entityData,
|
|
211
|
+
[dataKey]: getCurrentCategoryFiles()
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
if (loadingCategories) {
|
|
215
|
+
return (
|
|
216
|
+
<div className={styles.categorizedDropZone}>
|
|
217
|
+
<div className={styles.loadingState}>
|
|
218
|
+
Loading categories...
|
|
219
|
+
</div>
|
|
220
|
+
</div>
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (finalCategories.length === 0) {
|
|
225
|
+
return (
|
|
226
|
+
<div className={styles.categorizedDropZone}>
|
|
227
|
+
<div className={styles.emptyState}>
|
|
228
|
+
No categories available. Please configure site photo categories in settings.
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return (
|
|
235
|
+
<div className={styles.categorizedDropZone}>
|
|
236
|
+
<div className={styles.categoryTabs}>
|
|
237
|
+
{finalCategories.map(category => (
|
|
238
|
+
<button
|
|
239
|
+
key={category.id}
|
|
240
|
+
className={`${styles.categoryTab} ${activeCategory === category.id ? styles.active : ''}`}
|
|
241
|
+
onClick={() => handleCategoryChange(category.id)}
|
|
242
|
+
>
|
|
243
|
+
{category.label}
|
|
244
|
+
{categorizedFiles[category.id] && categorizedFiles[category.id].length > 0 && (
|
|
245
|
+
<span className={styles.fileCount}>
|
|
246
|
+
({categorizedFiles[category.id].length})
|
|
247
|
+
</span>
|
|
248
|
+
)}
|
|
249
|
+
</button>
|
|
250
|
+
))}
|
|
251
|
+
</div>
|
|
252
|
+
|
|
253
|
+
<div className={styles.categoryContent}>
|
|
254
|
+
{activeCategory && (
|
|
255
|
+
<div className={styles.categoryPanel}>
|
|
256
|
+
{finalCategories.find(cat => cat.id === activeCategory)?.description && (
|
|
257
|
+
<p className={styles.categoryDescription}>
|
|
258
|
+
{finalCategories.find(cat => cat.id === activeCategory)?.description}
|
|
259
|
+
</p>
|
|
260
|
+
)}
|
|
261
|
+
|
|
262
|
+
<DropZone
|
|
263
|
+
fetchData={fetchData || (() => {
|
|
264
|
+
// Default fetch function - refresh current category files
|
|
265
|
+
console.log('Files updated for category:', activeCategory);
|
|
266
|
+
})}
|
|
267
|
+
files={getCurrentCategoryFiles()}
|
|
268
|
+
settings={{
|
|
269
|
+
url: url,
|
|
270
|
+
method: method,
|
|
271
|
+
type: 'gallery',
|
|
272
|
+
data: {
|
|
273
|
+
file_relationship: file_relationship,
|
|
274
|
+
file_description: `category:${activeCategory} - ${finalCategories.find(cat => cat.id === activeCategory)?.label || activeCategory}`,
|
|
275
|
+
},
|
|
276
|
+
model: entityData?.constructor?.name || 'Design'
|
|
277
|
+
}}
|
|
278
|
+
/>
|
|
279
|
+
</div>
|
|
280
|
+
)}
|
|
281
|
+
</div>
|
|
282
|
+
</div>
|
|
283
|
+
);
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
export default CategorizedDropZone;
|
|
@@ -80,6 +80,7 @@ import QrCode from '../QrCode';
|
|
|
80
80
|
import StagePopupModal from './StagePopupModal';
|
|
81
81
|
import Table from '../DataGrid';
|
|
82
82
|
import TableFilter from '../TableFilter';
|
|
83
|
+
import CategorizedDropZone from '../CategorizedDropZone';
|
|
83
84
|
|
|
84
85
|
// Proposal components
|
|
85
86
|
import ProposalTemplateSectionManager from '../proposal/ProposalTemplateSectionManager';
|
|
@@ -2748,6 +2749,30 @@ function GenericDetail({
|
|
|
2748
2749
|
} else {
|
|
2749
2750
|
return null;
|
|
2750
2751
|
}
|
|
2752
|
+
case 'categorized_dropzone':
|
|
2753
|
+
// Replace URL parameters like {dataId} with actual values
|
|
2754
|
+
const replaceUrlParams = (url) => {
|
|
2755
|
+
if (!url) return url;
|
|
2756
|
+
return url.replace(/{dataId}/g, routeParams[urlParam] || '');
|
|
2757
|
+
};
|
|
2758
|
+
|
|
2759
|
+
return (
|
|
2760
|
+
<CategorizedDropZone
|
|
2761
|
+
filetype={activeTabConfig.filetype}
|
|
2762
|
+
folder={activeTabConfig.folder}
|
|
2763
|
+
dataKey={activeTabConfig.key}
|
|
2764
|
+
file_relationship={activeTabConfig.file_relationship}
|
|
2765
|
+
url={replaceUrlParams(activeTabConfig.url)}
|
|
2766
|
+
method={activeTabConfig.method}
|
|
2767
|
+
deleteUrl={replaceUrlParams(activeTabConfig.deleteUrl)}
|
|
2768
|
+
categories={activeTabConfig.categories || []}
|
|
2769
|
+
categoriesUrl={activeTabConfig.categoriesUrl}
|
|
2770
|
+
categoriesWhere={activeTabConfig.categoriesWhere}
|
|
2771
|
+
entityData={data}
|
|
2772
|
+
routeParams={routeParams}
|
|
2773
|
+
fetchData={handleReload}
|
|
2774
|
+
/>
|
|
2775
|
+
);
|
|
2751
2776
|
case 'scheduling':
|
|
2752
2777
|
return (
|
|
2753
2778
|
<GenericEditableTable
|
|
@@ -56,7 +56,20 @@ const StagePopupModal = ({
|
|
|
56
56
|
url = url.replace(`{${key}}`, routeParams[key]);
|
|
57
57
|
});
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
// Prepare request data with where and fields parameters
|
|
60
|
+
const requestData = {};
|
|
61
|
+
|
|
62
|
+
// Add where parameter if available
|
|
63
|
+
if (field.where) {
|
|
64
|
+
requestData.where = field.where;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Add fields parameter if available
|
|
68
|
+
if (field.fields) {
|
|
69
|
+
requestData.fields = field.fields;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const response = await CustomFetch(url, 'POST', requestData);
|
|
60
73
|
|
|
61
74
|
if (response.data && !response.data.error) {
|
|
62
75
|
// Handle both nested (response.data.data) and direct (response.data) array formats
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
// CategorizedDropZone.module.scss
|
|
2
|
+
.categorizedDropZone {
|
|
3
|
+
width: 100%;
|
|
4
|
+
margin-bottom: 20px;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.categoryTabs {
|
|
8
|
+
display: flex;
|
|
9
|
+
flex-wrap: wrap;
|
|
10
|
+
gap: 4px;
|
|
11
|
+
margin-bottom: 24px;
|
|
12
|
+
padding: 6px;
|
|
13
|
+
background-color: #f8f9fa;
|
|
14
|
+
border-radius: 8px;
|
|
15
|
+
border: 1px solid #e9ecef;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.categoryTab {
|
|
19
|
+
display: flex;
|
|
20
|
+
align-items: center;
|
|
21
|
+
gap: 8px;
|
|
22
|
+
padding: 10px 16px;
|
|
23
|
+
background-color: #ffffff;
|
|
24
|
+
border: 1px solid #dee2e6;
|
|
25
|
+
border-radius: 6px;
|
|
26
|
+
cursor: pointer;
|
|
27
|
+
font-size: 14px;
|
|
28
|
+
font-weight: 500;
|
|
29
|
+
color: #6c757d;
|
|
30
|
+
transition: all 0.2s ease;
|
|
31
|
+
white-space: nowrap;
|
|
32
|
+
outline: none;
|
|
33
|
+
|
|
34
|
+
&:hover {
|
|
35
|
+
color: #495057;
|
|
36
|
+
background-color: #f8f9fa;
|
|
37
|
+
border-color: #adb5bd;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
&.active {
|
|
41
|
+
color: #ffffff;
|
|
42
|
+
background-color: #3498db;
|
|
43
|
+
border-color: #3498db;
|
|
44
|
+
|
|
45
|
+
.fileCount {
|
|
46
|
+
background-color: rgba(255, 255, 255, 0.2);
|
|
47
|
+
color: white;
|
|
48
|
+
border-color: rgba(255, 255, 255, 0.3);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
&:focus {
|
|
53
|
+
outline: 2px solid #3498db;
|
|
54
|
+
outline-offset: 2px;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.fileCount {
|
|
59
|
+
background-color: #e9ecef;
|
|
60
|
+
color: #495057;
|
|
61
|
+
padding: 3px 7px;
|
|
62
|
+
border-radius: 10px;
|
|
63
|
+
font-size: 11px;
|
|
64
|
+
font-weight: 600;
|
|
65
|
+
min-width: 20px;
|
|
66
|
+
text-align: center;
|
|
67
|
+
border: 1px solid #ced4da;
|
|
68
|
+
transition: all 0.2s ease;
|
|
69
|
+
line-height: 1;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.categoryContent {
|
|
73
|
+
min-height: 300px;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.categoryPanel {
|
|
77
|
+
width: 100%;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.categoryTitle {
|
|
81
|
+
font-size: 20px;
|
|
82
|
+
font-weight: 600;
|
|
83
|
+
color: #2c3e50;
|
|
84
|
+
margin: 0 20px 8px 20px;
|
|
85
|
+
padding-bottom: 8px;
|
|
86
|
+
border-bottom: 2px solid #3498db;
|
|
87
|
+
display: block;
|
|
88
|
+
width: calc(100% - 40px);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.categoryDescription {
|
|
92
|
+
font-size: 14px;
|
|
93
|
+
color: #6c757d;
|
|
94
|
+
margin: 0 0 24px 0;
|
|
95
|
+
padding: 0 20px;
|
|
96
|
+
font-style: italic;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.loadingState,
|
|
100
|
+
.emptyState {
|
|
101
|
+
text-align: center;
|
|
102
|
+
padding: 40px 20px;
|
|
103
|
+
color: #666;
|
|
104
|
+
font-size: 16px;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.loadingState {
|
|
108
|
+
background-color: #f8f9fa;
|
|
109
|
+
border-radius: 8px;
|
|
110
|
+
border: 1px dashed #ddd;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.emptyState {
|
|
114
|
+
background-color: #fff3cd;
|
|
115
|
+
border: 1px solid #ffeaa7;
|
|
116
|
+
border-radius: 8px;
|
|
117
|
+
color: #856404;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Responsive design
|
|
121
|
+
@media (max-width: 768px) {
|
|
122
|
+
.categoryTabs {
|
|
123
|
+
gap: 3px;
|
|
124
|
+
padding: 4px;
|
|
125
|
+
margin-bottom: 20px;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.categoryTab {
|
|
129
|
+
flex: 1;
|
|
130
|
+
justify-content: center;
|
|
131
|
+
padding: 8px 12px;
|
|
132
|
+
font-size: 13px;
|
|
133
|
+
min-width: 0; // Allow tabs to shrink
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.fileCount {
|
|
137
|
+
font-size: 10px;
|
|
138
|
+
padding: 2px 5px;
|
|
139
|
+
min-width: 16px;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.categoryTitle {
|
|
143
|
+
font-size: 18px;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.categoryDescription {
|
|
147
|
+
font-size: 13px;
|
|
148
|
+
margin-bottom: 20px;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Very small screens
|
|
153
|
+
@media (max-width: 480px) {
|
|
154
|
+
.categoryTabs {
|
|
155
|
+
flex-direction: column;
|
|
156
|
+
gap: 2px;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.categoryTab {
|
|
160
|
+
width: 100%;
|
|
161
|
+
padding: 12px 16px;
|
|
162
|
+
font-size: 14px;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Dark theme support
|
|
167
|
+
@media (prefers-color-scheme: dark) {
|
|
168
|
+
.categoryTabs {
|
|
169
|
+
background-color: #343a40;
|
|
170
|
+
border-color: #495057;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.categoryTab {
|
|
174
|
+
background-color: #495057;
|
|
175
|
+
border-color: #6c757d;
|
|
176
|
+
color: #adb5bd;
|
|
177
|
+
|
|
178
|
+
&:hover {
|
|
179
|
+
background-color: #6c757d;
|
|
180
|
+
border-color: #adb5bd;
|
|
181
|
+
color: #f8f9fa;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
&.active {
|
|
185
|
+
background-color: #3498db;
|
|
186
|
+
border-color: #3498db;
|
|
187
|
+
color: #ffffff;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.fileCount {
|
|
192
|
+
background-color: #6c757d;
|
|
193
|
+
color: #f8f9fa;
|
|
194
|
+
border-color: #adb5bd;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.categoryTitle {
|
|
198
|
+
color: #f8f9fa;
|
|
199
|
+
border-bottom-color: #3498db;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.categoryDescription {
|
|
203
|
+
color: #adb5bd;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.loadingState {
|
|
207
|
+
background-color: #343a40;
|
|
208
|
+
border-color: #495057;
|
|
209
|
+
color: #adb5bd;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.emptyState {
|
|
213
|
+
background-color: #856404;
|
|
214
|
+
border-color: #ffc107;
|
|
215
|
+
color: #fff3cd;
|
|
216
|
+
}
|
|
217
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** CMS Components */
|
|
2
2
|
import DataGrid from './components/DataGrid';
|
|
3
3
|
import DropZone from './components/DropZone';
|
|
4
|
+
import CategorizedDropZone from './components/CategorizedDropZone';
|
|
4
5
|
import SortableList from './components/sorting/List';
|
|
5
6
|
|
|
6
7
|
/** CRM Components */
|
|
@@ -100,6 +101,7 @@ export {
|
|
|
100
101
|
DatePickerPortal,
|
|
101
102
|
Download,
|
|
102
103
|
DropZone,
|
|
104
|
+
CategorizedDropZone,
|
|
103
105
|
Field,
|
|
104
106
|
Form,
|
|
105
107
|
GalleryModal,
|