@visns-studio/visns-components 5.12.15 → 5.13.0

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
@@ -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.15",
90
+ "version": "5.13.0",
91
91
  "description": "Various packages to assist in the development of our Custom Applications.",
92
92
  "main": "src/index.js",
93
93
  "files": [
@@ -16,48 +16,78 @@ const CategorizedDropZone = ({
16
16
  categoriesWhere = [],
17
17
  entityData = {},
18
18
  routeParams = {},
19
- fetchData = null
19
+ fetchData = null,
20
20
  }) => {
21
+ useEffect(() => {
22
+ // Initialize component
23
+ }, []);
24
+
25
+ // Helper function to get nested object value using dot notation
26
+ const getNestedValue = (obj, path) => {
27
+ return path.split('.').reduce((current, key) => {
28
+ return current && current[key] !== undefined ? current[key] : null;
29
+ }, obj);
30
+ };
31
+
32
+ // Helper function to set nested object value using dot notation
33
+ const setNestedValue = (obj, path, value) => {
34
+ const keys = path.split('.');
35
+ const lastKey = keys.pop();
36
+ const target = keys.reduce((current, key) => {
37
+ if (!current[key]) current[key] = {};
38
+ return current[key];
39
+ }, obj);
40
+ target[lastKey] = value;
41
+ };
21
42
  const [activeCategory, setActiveCategory] = useState(null);
22
43
  const [categorizedFiles, setCategorizedFiles] = useState({});
23
44
  const [dynamicCategories, setDynamicCategories] = useState([]);
24
45
  const [loadingCategories, setLoadingCategories] = useState(false);
25
46
 
26
47
  // Get final categories (dynamic or static)
27
- const finalCategories = dynamicCategories.length > 0 ? dynamicCategories : categories;
48
+ const finalCategories =
49
+ dynamicCategories.length > 0 ? dynamicCategories : categories;
28
50
 
29
51
  // Fetch categories from API if categoriesUrl is provided
30
52
  useEffect(() => {
31
53
  if (categoriesUrl) {
32
54
  setLoadingCategories(true);
33
-
55
+
34
56
  const requestData = {};
35
57
  if (categoriesWhere && categoriesWhere.length > 0) {
36
58
  requestData.where = categoriesWhere;
37
59
  }
38
60
 
39
61
  CustomFetch(categoriesUrl, 'POST', requestData)
40
- .then(response => {
62
+ .then((response) => {
41
63
  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
-
64
+ const categoriesData = Array.isArray(response.data.data)
65
+ ? response.data.data
66
+ : Array.isArray(response.data)
67
+ ? response.data
68
+ : [];
69
+
45
70
  // 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
-
71
+ const transformedCategories = categoriesData.map(
72
+ (cat) => ({
73
+ id: cat.slug || cat.id,
74
+ label: cat.name || cat.label,
75
+ description: cat.description || '',
76
+ })
77
+ );
78
+
52
79
  setDynamicCategories(transformedCategories);
53
-
80
+
54
81
  // Set first category as active if none selected
55
- if (!activeCategory && transformedCategories.length > 0) {
82
+ if (
83
+ !activeCategory &&
84
+ transformedCategories.length > 0
85
+ ) {
56
86
  setActiveCategory(transformedCategories[0].id);
57
87
  }
58
88
  }
59
89
  })
60
- .catch(error => {
90
+ .catch((error) => {
61
91
  console.error('Failed to load categories:', error);
62
92
  })
63
93
  .finally(() => {
@@ -70,76 +100,108 @@ const CategorizedDropZone = ({
70
100
  }, [categoriesUrl, categoriesWhere, categories, activeCategory]);
71
101
 
72
102
  useEffect(() => {
103
+ const currentFiles = getNestedValue(entityData, dataKey);
73
104
  console.log('CategorizedDropZone: Initializing files', {
74
105
  finalCategories,
75
- entityData: entityData[dataKey],
76
- dataKey
106
+ entityData: currentFiles,
107
+ dataKey,
77
108
  });
78
-
109
+
79
110
  // Initialize categorized files structure
80
111
  const initialCategorizedFiles = {};
81
- finalCategories.forEach(category => {
112
+ finalCategories.forEach((category) => {
82
113
  initialCategorizedFiles[category.id] = [];
83
114
  });
84
-
115
+
85
116
  // 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 => {
117
+ if (currentFiles && Array.isArray(currentFiles)) {
118
+ console.log('CategorizedDropZone: Processing files', currentFiles);
119
+
120
+ currentFiles.forEach((file) => {
90
121
  console.log('CategorizedDropZone: Processing file', {
91
122
  file_name: file.file_name,
92
123
  file_description: file.file_description,
93
- id: file.id
124
+ id: file.id,
94
125
  });
95
-
96
- const category = getCategoryFromDescription(file.file_description);
97
- console.log('CategorizedDropZone: Category extracted', category);
98
-
126
+
127
+ const category = getCategoryFromDescription(
128
+ file.file_description
129
+ );
130
+ console.log(
131
+ 'CategorizedDropZone: Category extracted',
132
+ category
133
+ );
134
+
99
135
  if (category && initialCategorizedFiles[category]) {
100
136
  initialCategorizedFiles[category].push(file);
101
- console.log(`CategorizedDropZone: Added file ${file.file_name} to category ${category}`);
137
+ console.log(
138
+ `CategorizedDropZone: Added file ${file.file_name} to category ${category}`
139
+ );
102
140
  } else {
103
- console.log(`CategorizedDropZone: No category match for file ${file.file_name}`, {
104
- extractedCategory: category,
105
- availableCategories: Object.keys(initialCategorizedFiles)
106
- });
141
+ // For uncategorized files, add them to the first available category as fallback
142
+ if (finalCategories.length > 0) {
143
+ const firstCategory = finalCategories[0].id;
144
+ initialCategorizedFiles[firstCategory].push(file);
145
+ console.log(
146
+ `CategorizedDropZone: Added uncategorized file ${file.file_name} to first category ${firstCategory}`
147
+ );
148
+ } else {
149
+ console.log(
150
+ `CategorizedDropZone: No category match for file ${file.file_name}`,
151
+ {
152
+ extractedCategory: category,
153
+ availableCategories: Object.keys(
154
+ initialCategorizedFiles
155
+ ),
156
+ }
157
+ );
158
+ }
107
159
  }
108
160
  });
109
161
  }
110
-
111
- console.log('CategorizedDropZone: Final categorized files', initialCategorizedFiles);
162
+
163
+ console.log(
164
+ 'CategorizedDropZone: Final categorized files',
165
+ initialCategorizedFiles
166
+ );
112
167
  setCategorizedFiles(initialCategorizedFiles);
113
168
  }, [entityData, dataKey, finalCategories]);
114
169
 
115
170
  const getCategoryFromDescription = (description) => {
116
171
  if (!description) return null;
117
-
172
+
118
173
  // Check if the description contains a category identifier (handles both slugs and numeric IDs)
119
174
  const categoryMatch = description.match(/^category:([^-\s]+)/i);
120
175
  if (categoryMatch) {
121
176
  const extractedId = categoryMatch[1];
122
-
177
+
123
178
  // First, try to find by exact ID match
124
- const exactMatch = finalCategories.find(cat => cat.id === extractedId);
179
+ const exactMatch = finalCategories.find(
180
+ (cat) => cat.id === extractedId
181
+ );
125
182
  if (exactMatch) return extractedId;
126
-
183
+
127
184
  // If no exact match, try to find by numeric ID if extracted ID is numeric
128
185
  if (/^\d+$/.test(extractedId)) {
129
- const numericMatch = finalCategories.find(cat => cat.id == extractedId);
186
+ const numericMatch = finalCategories.find(
187
+ (cat) => cat.id == extractedId
188
+ );
130
189
  if (numericMatch) return numericMatch.id;
131
190
  }
132
-
191
+
133
192
  // Log for debugging
134
193
  console.log('Category extraction debug:', {
135
194
  description,
136
195
  extractedId,
137
- availableCategories: finalCategories.map(c => ({ id: c.id, label: c.label }))
196
+ availableCategories: finalCategories.map((c) => ({
197
+ id: c.id,
198
+ label: c.label,
199
+ })),
138
200
  });
139
201
  }
140
-
202
+
141
203
  // Fallback: check if description matches any category label
142
- const matchedCategory = finalCategories.find(cat =>
204
+ const matchedCategory = finalCategories.find((cat) =>
143
205
  description.toLowerCase().includes(cat.label.toLowerCase())
144
206
  );
145
207
  return matchedCategory?.id || null;
@@ -155,30 +217,40 @@ const CategorizedDropZone = ({
155
217
 
156
218
  const handleFileUpload = (files) => {
157
219
  // Add category prefix to file descriptions
158
- const activeCategoryData = finalCategories.find(cat => cat.id === activeCategory);
220
+ const activeCategoryData = finalCategories.find(
221
+ (cat) => cat.id === activeCategory
222
+ );
159
223
  const categoryPrefix = `category:${activeCategory}`;
160
-
224
+
161
225
  // Update the files with category information
162
- const updatedFiles = files.map(file => ({
226
+ const updatedFiles = files.map((file) => ({
163
227
  ...file,
164
- file_description: file.file_description ?
165
- `${categoryPrefix} - ${file.file_description}` :
166
- `${categoryPrefix} - ${activeCategoryData?.description || activeCategoryData?.label}`
228
+ file_description: file.file_description
229
+ ? `${categoryPrefix} - ${file.file_description}`
230
+ : `${categoryPrefix} - ${
231
+ activeCategoryData?.description ||
232
+ activeCategoryData?.label
233
+ }`,
167
234
  }));
168
-
235
+
169
236
  // Update the categorized files state
170
- setCategorizedFiles(prev => ({
237
+ setCategorizedFiles((prev) => ({
171
238
  ...prev,
172
- [activeCategory]: [...(prev[activeCategory] || []), ...updatedFiles]
239
+ [activeCategory]: [
240
+ ...(prev[activeCategory] || []),
241
+ ...updatedFiles,
242
+ ],
173
243
  }));
174
244
  };
175
245
 
176
246
  const handleFileDelete = (fileId) => {
177
247
  // Remove file from the appropriate category
178
- setCategorizedFiles(prev => {
248
+ setCategorizedFiles((prev) => {
179
249
  const updated = { ...prev };
180
- Object.keys(updated).forEach(categoryId => {
181
- updated[categoryId] = updated[categoryId].filter(file => file.id !== fileId);
250
+ Object.keys(updated).forEach((categoryId) => {
251
+ updated[categoryId] = updated[categoryId].filter(
252
+ (file) => file.id !== fileId
253
+ );
182
254
  });
183
255
  return updated;
184
256
  });
@@ -186,57 +258,65 @@ const CategorizedDropZone = ({
186
258
 
187
259
  const handleFileUpdate = (updatedFile) => {
188
260
  // Update file in the appropriate category
189
- const newCategory = getCategoryFromDescription(updatedFile.file_description);
190
-
191
- setCategorizedFiles(prev => {
261
+ const newCategory = getCategoryFromDescription(
262
+ updatedFile.file_description
263
+ );
264
+
265
+ setCategorizedFiles((prev) => {
192
266
  const updated = { ...prev };
193
-
267
+
194
268
  // Remove from old category
195
- Object.keys(updated).forEach(categoryId => {
196
- updated[categoryId] = updated[categoryId].filter(file => file.id !== updatedFile.id);
269
+ Object.keys(updated).forEach((categoryId) => {
270
+ updated[categoryId] = updated[categoryId].filter(
271
+ (file) => file.id !== updatedFile.id
272
+ );
197
273
  });
198
-
274
+
199
275
  // Add to new category
200
276
  if (newCategory && updated[newCategory]) {
201
277
  updated[newCategory].push(updatedFile);
202
278
  }
203
-
279
+
204
280
  return updated;
205
281
  });
206
282
  };
207
283
 
208
284
  // Prepare entity data for the current category
209
- const currentCategoryEntityData = {
210
- ...entityData,
211
- [dataKey]: getCurrentCategoryFiles()
212
- };
285
+ const currentCategoryEntityData = JSON.parse(JSON.stringify(entityData));
286
+ setNestedValue(
287
+ currentCategoryEntityData,
288
+ dataKey,
289
+ getCurrentCategoryFiles()
290
+ );
213
291
 
214
292
  if (loadingCategories) {
215
293
  return (
216
294
  <div className={styles.categorizedDropZone}>
217
- <div className={styles.loadingState}>
218
- Loading categories...
219
- </div>
295
+ <div className={styles.loadingState}>Loading categories...</div>
220
296
  </div>
221
297
  );
222
298
  }
223
299
 
224
300
  if (finalCategories.length === 0) {
225
301
  // No categories available - show a simple dropzone without categorization
302
+ const currentFiles = getNestedValue(entityData, dataKey) || [];
226
303
  console.log('CategorizedDropZone: No categories, showing all files', {
227
304
  dataKey,
228
- files: entityData[dataKey] || [],
229
- entityData
305
+ files: currentFiles,
306
+ entityData,
230
307
  });
231
-
308
+
232
309
  return (
233
310
  <div className={styles.categorizedDropZone}>
234
311
  <div className={styles.noCategoryContent}>
235
312
  <DropZone
236
- fetchData={fetchData || (() => {
237
- console.log('Files updated (no categories)');
238
- })}
239
- files={entityData[dataKey] || []}
313
+ fetchData={
314
+ fetchData ||
315
+ (() => {
316
+ console.log('Files updated (no categories)');
317
+ })
318
+ }
319
+ files={currentFiles}
240
320
  settings={{
241
321
  url: url,
242
322
  method: method,
@@ -248,7 +328,7 @@ const CategorizedDropZone = ({
248
328
  model: entityData?.constructor?.name || 'Design',
249
329
  folder: folder,
250
330
  filetype: filetype,
251
- deleteUrl: deleteUrl
331
+ deleteUrl: deleteUrl,
252
332
  }}
253
333
  />
254
334
  </div>
@@ -259,36 +339,51 @@ const CategorizedDropZone = ({
259
339
  return (
260
340
  <div className={styles.categorizedDropZone}>
261
341
  <div className={styles.categoryTabs}>
262
- {finalCategories.map(category => (
342
+ {finalCategories.map((category) => (
263
343
  <button
264
344
  key={category.id}
265
- className={`${styles.categoryTab} ${activeCategory === category.id ? styles.active : ''}`}
345
+ className={`${styles.categoryTab} ${
346
+ activeCategory === category.id ? styles.active : ''
347
+ }`}
266
348
  onClick={() => handleCategoryChange(category.id)}
267
349
  >
268
350
  {category.label}
269
- {categorizedFiles[category.id] && categorizedFiles[category.id].length > 0 && (
270
- <span className={styles.fileCount}>
271
- ({categorizedFiles[category.id].length})
272
- </span>
273
- )}
351
+ {categorizedFiles[category.id] &&
352
+ categorizedFiles[category.id].length > 0 && (
353
+ <span className={styles.fileCount}>
354
+ ({categorizedFiles[category.id].length})
355
+ </span>
356
+ )}
274
357
  </button>
275
358
  ))}
276
359
  </div>
277
-
360
+
278
361
  <div className={styles.categoryContent}>
279
362
  {activeCategory && (
280
363
  <div className={styles.categoryPanel}>
281
- {finalCategories.find(cat => cat.id === activeCategory)?.description && (
364
+ {finalCategories.find(
365
+ (cat) => cat.id === activeCategory
366
+ )?.description && (
282
367
  <p className={styles.categoryDescription}>
283
- {finalCategories.find(cat => cat.id === activeCategory)?.description}
368
+ {
369
+ finalCategories.find(
370
+ (cat) => cat.id === activeCategory
371
+ )?.description
372
+ }
284
373
  </p>
285
374
  )}
286
-
375
+
287
376
  <DropZone
288
- fetchData={fetchData || (() => {
289
- // Default fetch function - refresh current category files
290
- console.log('Files updated for category:', activeCategory);
291
- })}
377
+ fetchData={
378
+ fetchData ||
379
+ (() => {
380
+ // Default fetch function - refresh current category files
381
+ console.log(
382
+ 'Files updated for category:',
383
+ activeCategory
384
+ );
385
+ })
386
+ }
292
387
  files={getCurrentCategoryFiles()}
293
388
  settings={{
294
389
  url: url,
@@ -296,9 +391,14 @@ const CategorizedDropZone = ({
296
391
  type: 'gallery',
297
392
  data: {
298
393
  file_relationship: file_relationship,
299
- file_description: `category:${activeCategory} - ${finalCategories.find(cat => cat.id === activeCategory)?.label || activeCategory}`,
394
+ file_description: `category:${activeCategory} - ${
395
+ finalCategories.find(
396
+ (cat) => cat.id === activeCategory
397
+ )?.label || activeCategory
398
+ }`,
300
399
  },
301
- model: entityData?.constructor?.name || 'Design'
400
+ model:
401
+ entityData?.constructor?.name || 'Design',
302
402
  }}
303
403
  />
304
404
  </div>
@@ -308,4 +408,4 @@ const CategorizedDropZone = ({
308
408
  );
309
409
  };
310
410
 
311
- export default CategorizedDropZone;
411
+ export default CategorizedDropZone;
@@ -354,8 +354,14 @@ export const renderDropdownColumn = ({
354
354
  return (
355
355
  <select
356
356
  name={column.id}
357
- value={data[column.id]?.id || ''}
358
- style={{ padding: '7px' }}
357
+ value={data[column.id]?.id || data[column.id] || ''}
358
+ style={{
359
+ padding: '7px',
360
+ width: '100%',
361
+ border: 'none',
362
+ backgroundColor: 'transparent',
363
+ outline: 'none',
364
+ }}
359
365
  onChange={(e) => {
360
366
  e.preventDefault();
361
367
 
@@ -384,10 +390,55 @@ export const renderDropdownColumn = ({
384
390
  {option.label}
385
391
  </option>
386
392
  ))
393
+ : column.options && column.options.length > 0
394
+ ? column.options.map((option, index) => (
395
+ <option value={option.id} key={index}>
396
+ {option.label}
397
+ </option>
398
+ ))
387
399
  : null}
388
400
  </select>
389
401
  );
390
402
  },
403
+ cellProps: ({ data }) => {
404
+ // Find the selected option to get its color
405
+ const selectedValue = data[column.id]?.id || data[column.id] || '';
406
+ let selectedOption = null;
407
+
408
+ // Debug logging
409
+ console.log('Dropdown cellProps debug:', {
410
+ columnId: column.id,
411
+ selectedValue,
412
+ dataValue: data[column.id],
413
+ dropdownData: dropdownData[column.id],
414
+ columnOptions: column.options
415
+ });
416
+
417
+ if (selectedValue && dropdownData[column.id]) {
418
+ selectedOption = dropdownData[column.id].find(option => option.id === selectedValue);
419
+ console.log('Found option in dropdownData:', selectedOption);
420
+ }
421
+
422
+ // If no option found in dropdownData, check if column has options array
423
+ if (!selectedOption && selectedValue && column.options) {
424
+ selectedOption = column.options.find(option => option.id === selectedValue);
425
+ console.log('Found option in column.options:', selectedOption);
426
+ }
427
+
428
+ // Return cell props with background color
429
+ if (selectedOption?.colour) {
430
+ console.log('Applying background color:', selectedOption.colour);
431
+ return {
432
+ style: {
433
+ backgroundColor: selectedOption.colour,
434
+ color: '#000',
435
+ }
436
+ };
437
+ }
438
+
439
+ console.log('No color found, returning empty props');
440
+ return {};
441
+ },
391
442
  };
392
443
  };
393
444