@visns-studio/visns-components 5.9.12 → 5.9.144

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.9.12",
90
+ "version": "5.9.144",
91
91
  "description": "Various packages to assist in the development of our Custom Applications.",
92
92
  "main": "src/index.js",
93
93
  "files": [
@@ -385,7 +385,6 @@ const CellWithTooltip = ({
385
385
  );
386
386
  };
387
387
 
388
-
389
388
  const DataGrid = forwardRef(
390
389
  (
391
390
  {
@@ -622,7 +621,6 @@ const DataGrid = forwardRef(
622
621
  if (ajaxSetting?.groupBy) {
623
622
  groupsInitializedRef.current = false;
624
623
  setCollapsedGroups({}); // Clear existing state when groupBy changes
625
-
626
624
  }
627
625
  }, [ajaxSetting?.groupBy]);
628
626
 
@@ -746,7 +744,7 @@ const DataGrid = forwardRef(
746
744
  setTableData(res.data);
747
745
  }
748
746
  dataRef.current = res.data;
749
-
747
+
750
748
  // Force grid to recalculate row heights after data changes
751
749
  // This helps with multi-line content timing without interfering with core mechanisms
752
750
  setTimeout(() => {
@@ -1500,7 +1498,9 @@ const DataGrid = forwardRef(
1500
1498
  CustomFetch(
1501
1499
  s.url + '/' + d[form.primaryKey],
1502
1500
  s.method,
1503
- {},
1501
+ {
1502
+ ...s.data,
1503
+ },
1504
1504
  function (result) {
1505
1505
  if (result.error === '') {
1506
1506
  handleReload();
@@ -1673,10 +1673,15 @@ const DataGrid = forwardRef(
1673
1673
 
1674
1674
  rowProps.onClick = (event) => {
1675
1675
  // Check if this is a group row - group rows should only handle expand/collapse, not onRowClick
1676
- const isGroupRow = rowProps.isGroupRow ||
1677
- rowProps.data?.__group ||
1678
- event.target.closest('.InovuaReactDataGrid__group-row') ||
1679
- event.target.closest('.InovuaReactDataGrid__group-cell');
1676
+ const isGroupRow =
1677
+ rowProps.isGroupRow ||
1678
+ rowProps.data?.__group ||
1679
+ event.target.closest(
1680
+ '.InovuaReactDataGrid__group-row'
1681
+ ) ||
1682
+ event.target.closest(
1683
+ '.InovuaReactDataGrid__group-cell'
1684
+ );
1680
1685
 
1681
1686
  if (isGroupRow) {
1682
1687
  // For group rows, only allow the original onClick handler (for expand/collapse)
@@ -2274,7 +2279,7 @@ const DataGrid = forwardRef(
2274
2279
  // Apply immediately and with a conservative delay
2275
2280
  applyGroupedStyling();
2276
2281
  const timer1 = setTimeout(applyGroupedStyling, 100);
2277
-
2282
+
2278
2283
  return () => {
2279
2284
  clearTimeout(timer1);
2280
2285
  };
@@ -2296,9 +2301,140 @@ const DataGrid = forwardRef(
2296
2301
  let allow = true;
2297
2302
 
2298
2303
  if (s.active) {
2304
+ // Helper function to get nested values for main active condition (searches for target value)
2305
+ const getNestedValueForMain = (data, path, targetValue) => {
2306
+ if (Array.isArray(path)) {
2307
+ return path.reduce(
2308
+ (currentValue, key) =>
2309
+ currentValue && currentValue[key] !== undefined
2310
+ ? currentValue[key]
2311
+ : undefined,
2312
+ data
2313
+ );
2314
+ } else if (typeof path === 'string' && path.includes('.')) {
2315
+ const keys = path.split('.');
2316
+ let currentValue = data;
2317
+
2318
+ for (let i = 0; i < keys.length; i++) {
2319
+ const key = keys[i];
2320
+
2321
+ if (!currentValue) return undefined;
2322
+
2323
+ // Handle array search pattern like "customers[].pivot.is_primary"
2324
+ if (key.includes('[]')) {
2325
+ const arrayKey = key.replace('[]', '');
2326
+ const remainingPath = keys
2327
+ .slice(i + 1)
2328
+ .join('.');
2329
+
2330
+ if (Array.isArray(currentValue[arrayKey])) {
2331
+ // Search through the array for the target value
2332
+ for (const item of currentValue[arrayKey]) {
2333
+ const result = remainingPath
2334
+ ? getNestedValueForMain(
2335
+ item,
2336
+ remainingPath,
2337
+ targetValue
2338
+ )
2339
+ : item;
2340
+ if (result === targetValue) {
2341
+ return result;
2342
+ }
2343
+ }
2344
+ }
2345
+ return undefined;
2346
+ } else {
2347
+ currentValue = currentValue[key];
2348
+ }
2349
+ }
2350
+
2351
+ return currentValue;
2352
+ } else {
2353
+ // Fallback to simple property access
2354
+ return data[path];
2355
+ }
2356
+ };
2357
+
2358
+ // Helper function to get nested values for additional conditions (returns first valid value)
2359
+ const getNestedValueForAdditional = (data, path) => {
2360
+ if (Array.isArray(path)) {
2361
+ return path.reduce(
2362
+ (currentValue, key) =>
2363
+ currentValue && currentValue[key] !== undefined
2364
+ ? currentValue[key]
2365
+ : undefined,
2366
+ data
2367
+ );
2368
+ } else if (typeof path === 'string' && path.includes('.')) {
2369
+ const keys = path.split('.');
2370
+ let currentValue = data;
2371
+
2372
+ for (let i = 0; i < keys.length; i++) {
2373
+ const key = keys[i];
2374
+
2375
+ if (!currentValue) return undefined;
2376
+
2377
+ // Handle array search pattern like "customers[].id"
2378
+ if (key.includes('[]')) {
2379
+ const arrayKey = key.replace('[]', '');
2380
+ const remainingPath = keys
2381
+ .slice(i + 1)
2382
+ .join('.');
2383
+
2384
+ if (Array.isArray(currentValue[arrayKey])) {
2385
+ // Return the first valid result from the array
2386
+ for (const item of currentValue[arrayKey]) {
2387
+ const result = remainingPath
2388
+ ? getNestedValueForAdditional(
2389
+ item,
2390
+ remainingPath
2391
+ )
2392
+ : item;
2393
+ if (result !== undefined) {
2394
+ return result;
2395
+ }
2396
+ }
2397
+ }
2398
+ return undefined;
2399
+ } else {
2400
+ currentValue = currentValue[key];
2401
+ }
2402
+ }
2403
+
2404
+ return currentValue;
2405
+ } else {
2406
+ // Fallback to simple property access
2407
+ return data[path];
2408
+ }
2409
+ };
2410
+
2411
+ // Get the active value using nested path or simple property access
2412
+ const activeValue = getNestedValueForMain(d, s.active.id, s.active.value);
2413
+ const hasActiveProperty = activeValue !== undefined;
2414
+
2415
+ // Check additional conditions if they exist
2416
+ const checkAdditionalConditions = () => {
2417
+ if (
2418
+ !s.active.additional ||
2419
+ !Array.isArray(s.active.additional)
2420
+ ) {
2421
+ return true; // No additional conditions, so they pass
2422
+ }
2423
+
2424
+ // All additional conditions must be met
2425
+ return s.active.additional.every((condition) => {
2426
+ const conditionValue = getNestedValueForAdditional(d, condition.id);
2427
+ // Type-flexible comparison - convert both to strings for comparison
2428
+ return String(conditionValue) === String(condition.value);
2429
+ });
2430
+ };
2431
+
2432
+ const additionalConditionsMet = checkAdditionalConditions();
2433
+
2299
2434
  if (
2300
- d.hasOwnProperty(s.active.id) &&
2301
- d[s.active.id] === s.active.value &&
2435
+ hasActiveProperty &&
2436
+ String(activeValue) === String(s.active.value) &&
2437
+ additionalConditionsMet &&
2302
2438
  s.active.type !== 'greater' &&
2303
2439
  s.active.type !== 'not null'
2304
2440
  ) {
@@ -2307,14 +2443,16 @@ const DataGrid = forwardRef(
2307
2443
  };
2308
2444
  } else if (
2309
2445
  s.active.type === 'greater' &&
2310
- d[s.active.id] > s.active.value
2446
+ Number(activeValue) > Number(s.active.value) &&
2447
+ additionalConditionsMet
2311
2448
  ) {
2312
2449
  iconStyle = {
2313
2450
  color: s.active.colour,
2314
2451
  };
2315
2452
  } else if (
2316
2453
  s.active.type === 'not null' &&
2317
- d[s.active.id] !== null
2454
+ activeValue !== null &&
2455
+ additionalConditionsMet
2318
2456
  ) {
2319
2457
  iconStyle = {
2320
2458
  color: s.active.colour,
@@ -259,11 +259,13 @@ function Field({
259
259
  if (Array.isArray(s.child)) {
260
260
  s.child.forEach((a) => {
261
261
  if (a) {
262
+ console.info(a);
262
263
  filter = {
263
264
  where: [
264
265
  {
265
266
  id: s.id,
266
267
  value: _inputValue,
268
+ ...(a.whereHas && { whereHas: a.whereHas }),
267
269
  },
268
270
  ],
269
271
  };
@@ -0,0 +1,443 @@
1
+ // Base layout styles
2
+ .authLayout {
3
+ min-height: 100vh;
4
+ display: flex;
5
+ align-items: center;
6
+ justify-content: center;
7
+ background: #f9fafb;
8
+ padding: 1rem;
9
+ }
10
+
11
+ .authContainer {
12
+ width: 100%;
13
+ max-width: 500px;
14
+
15
+ @media (max-width: 768px) {
16
+ max-width: 95%;
17
+ width: 95%;
18
+ }
19
+ }
20
+
21
+ .authWrapper {
22
+ background: white;
23
+ border-radius: 12px;
24
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
25
+ overflow: hidden;
26
+ }
27
+
28
+ .formContainer {
29
+ padding: 2.5rem;
30
+
31
+ @media (max-width: 768px) {
32
+ padding: 2rem;
33
+ }
34
+
35
+ @media (max-width: 480px) {
36
+ padding: 1.5rem;
37
+ }
38
+ }
39
+
40
+ .formContent {
41
+ width: 100%;
42
+ }
43
+
44
+ // Logo styles
45
+ .logo {
46
+ display: block;
47
+ margin: 0 auto 2rem;
48
+ max-width: 150px;
49
+ height: auto;
50
+ }
51
+
52
+ // Typography
53
+ .title {
54
+ text-align: center;
55
+ margin-bottom: 0.5rem;
56
+ color: #111827;
57
+ font-weight: 600;
58
+ font-size: 1.75rem;
59
+ line-height: 1.2;
60
+
61
+ @media (max-width: 768px) {
62
+ font-size: 1.5rem;
63
+ }
64
+ }
65
+
66
+ .subtitle {
67
+ text-align: center;
68
+ margin-bottom: 2rem;
69
+ color: #6b7280;
70
+ font-size: 0.875rem;
71
+ line-height: 1.4;
72
+ }
73
+
74
+ // Form fields wrapper
75
+ .formFields {
76
+ display: flex;
77
+ flex-direction: column;
78
+ gap: 2rem;
79
+
80
+ @media (max-width: 768px) {
81
+ gap: 1.5rem;
82
+ }
83
+ }
84
+
85
+ // Form group (wrapper for each field)
86
+ .formGroup {
87
+ position: relative;
88
+ width: 100%;
89
+ }
90
+
91
+ // Input wrapper and styles
92
+ .inputWrapper {
93
+ position: relative;
94
+ width: 100%;
95
+ }
96
+
97
+ .inputLabel {
98
+ display: block;
99
+ margin-bottom: 0.5rem;
100
+ color: #374151;
101
+ font-size: 0.875rem;
102
+ font-weight: 500;
103
+ }
104
+
105
+ .input {
106
+ width: 100%;
107
+ padding: 1rem 2.5rem 1rem 1rem;
108
+ border: 2px solid #e5e7eb;
109
+ border-radius: 8px;
110
+ font-size: 1.1rem;
111
+ transition: all 0.2s ease-in-out;
112
+ background-color: #ffffff;
113
+ color: #111827;
114
+ min-height: 48px;
115
+
116
+ @media (max-width: 768px) {
117
+ font-size: 16px; /* Prevents zoom on iOS */
118
+ min-height: 52px;
119
+ }
120
+
121
+ &:focus {
122
+ outline: none;
123
+ border-color: var(--primary-color, #4f46e5);
124
+ box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);
125
+ }
126
+
127
+ &::placeholder {
128
+ color: #9ca3af;
129
+ opacity: 1;
130
+ }
131
+
132
+ &:focus::placeholder {
133
+ opacity: 0.5;
134
+ }
135
+ }
136
+
137
+ .inputError {
138
+ border-color: #ef4444;
139
+
140
+ &:focus {
141
+ border-color: #ef4444;
142
+ box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1);
143
+ }
144
+ }
145
+
146
+ .inputDisabled {
147
+ background-color: #f9fafb;
148
+ color: #9ca3af;
149
+ cursor: not-allowed;
150
+ }
151
+
152
+ .labelError {
153
+ color: #ef4444;
154
+ }
155
+
156
+ .inputIcon {
157
+ position: absolute;
158
+ right: 0.75rem;
159
+ top: 50%;
160
+ transform: translateY(-50%);
161
+ color: #9ca3af;
162
+ pointer-events: none;
163
+ z-index: 2;
164
+ }
165
+
166
+ // Error message styles
167
+ .errorMessage {
168
+ margin-top: 0.5rem;
169
+ color: #ef4444;
170
+ font-size: 0.875rem;
171
+ line-height: 1.4;
172
+ }
173
+
174
+ // Button styles
175
+ .button {
176
+ display: inline-flex;
177
+ align-items: center;
178
+ justify-content: center;
179
+ gap: 0.5rem;
180
+ padding: 1rem 1.5rem;
181
+ border: none;
182
+ border-radius: 8px;
183
+ font-size: 1.1rem;
184
+ font-weight: 600;
185
+ cursor: pointer;
186
+ transition: all 0.2s ease-in-out;
187
+ position: relative;
188
+ text-decoration: none;
189
+ line-height: 1.2;
190
+ min-height: 48px;
191
+
192
+ @media (max-width: 768px) {
193
+ min-height: 52px;
194
+ font-size: 1rem;
195
+ }
196
+
197
+ &:focus {
198
+ outline: none;
199
+ box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.1);
200
+ }
201
+
202
+ &:disabled {
203
+ cursor: not-allowed;
204
+ opacity: 0.6;
205
+ }
206
+ }
207
+
208
+ .button--primary {
209
+ background-color: var(--primary-color, #4f46e5);
210
+ color: white;
211
+
212
+ &:hover:not(:disabled) {
213
+ background-color: var(--primary-color-dark, #3730a3);
214
+ }
215
+
216
+ &:focus {
217
+ box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.3);
218
+ }
219
+ }
220
+
221
+ .button--secondary {
222
+ background-color: #6b7280;
223
+ color: white;
224
+
225
+ &:hover:not(:disabled) {
226
+ background-color: #4b5563;
227
+ }
228
+ }
229
+
230
+ .button--outline {
231
+ background-color: transparent;
232
+ color: var(--primary-color, #4f46e5);
233
+ border: 2px solid var(--primary-color, #4f46e5);
234
+
235
+ &:hover:not(:disabled) {
236
+ background-color: var(--primary-color, #4f46e5);
237
+ color: white;
238
+ }
239
+ }
240
+
241
+ .button--danger {
242
+ background-color: #ef4444;
243
+ color: white;
244
+
245
+ &:hover:not(:disabled) {
246
+ background-color: #dc2626;
247
+ }
248
+ }
249
+
250
+ .button--success {
251
+ background-color: #10b981;
252
+ color: white;
253
+
254
+ &:hover:not(:disabled) {
255
+ background-color: #059669;
256
+ }
257
+ }
258
+
259
+ .button--small {
260
+ padding: 0.5rem 1rem;
261
+ font-size: 0.875rem;
262
+ }
263
+
264
+ .button--medium {
265
+ padding: 0.75rem 1.5rem;
266
+ font-size: 1rem;
267
+ }
268
+
269
+ .button--large {
270
+ padding: 1rem 2rem;
271
+ font-size: 1.125rem;
272
+ }
273
+
274
+ .button--fullWidth {
275
+ width: 100%;
276
+ }
277
+
278
+ .button--disabled {
279
+ opacity: 0.6;
280
+ cursor: not-allowed;
281
+ pointer-events: none;
282
+ }
283
+
284
+ .buttonText {
285
+ flex: 1;
286
+ }
287
+
288
+ .buttonIcon {
289
+ display: flex;
290
+ align-items: center;
291
+ justify-content: center;
292
+ }
293
+
294
+ .buttonSpinner {
295
+ width: 1rem;
296
+ height: 1rem;
297
+ border: 2px solid transparent;
298
+ border-top: 2px solid currentColor;
299
+ border-radius: 50%;
300
+ animation: spin 1s linear infinite;
301
+ }
302
+
303
+ @keyframes spin {
304
+ to {
305
+ transform: rotate(360deg);
306
+ }
307
+ }
308
+
309
+ // Checkbox styles
310
+ .checkboxGroup {
311
+ display: flex;
312
+ flex-direction: column;
313
+ gap: 0.5rem;
314
+ }
315
+
316
+ .checkboxWrapper {
317
+ display: flex;
318
+ align-items: center;
319
+ gap: 0.75rem;
320
+ }
321
+
322
+ .checkbox {
323
+ width: 1.25rem;
324
+ height: 1.25rem;
325
+ border: 2px solid #d1d5db;
326
+ border-radius: 4px;
327
+ background-color: white;
328
+ cursor: pointer;
329
+ transition: all 0.2s ease-in-out;
330
+
331
+ &:checked {
332
+ background-color: var(--primary-color, #4f46e5);
333
+ border-color: var(--primary-color, #4f46e5);
334
+ }
335
+
336
+ &:focus {
337
+ outline: none;
338
+ box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);
339
+ }
340
+ }
341
+
342
+ .checkboxError {
343
+ border-color: #ef4444;
344
+ }
345
+
346
+ .checkboxDisabled {
347
+ background-color: #f9fafb;
348
+ cursor: not-allowed;
349
+ opacity: 0.6;
350
+ }
351
+
352
+ .checkboxLabel {
353
+ color: #374151;
354
+ font-size: 0.875rem;
355
+ cursor: pointer;
356
+ user-select: none;
357
+ line-height: 1.4;
358
+ }
359
+
360
+ .checkboxLabelDisabled {
361
+ color: #9ca3af;
362
+ cursor: not-allowed;
363
+ }
364
+
365
+ // Utility classes
366
+ .divider {
367
+ position: relative;
368
+ margin: 1.5rem 0;
369
+ text-align: center;
370
+ color: #6b7280;
371
+ font-size: 0.875rem;
372
+
373
+ &::before {
374
+ content: '';
375
+ position: absolute;
376
+ top: 50%;
377
+ left: 0;
378
+ right: 0;
379
+ height: 1px;
380
+ background-color: #e5e7eb;
381
+ z-index: 0;
382
+ }
383
+
384
+ span {
385
+ background-color: white;
386
+ padding: 0 1rem;
387
+ position: relative;
388
+ z-index: 1;
389
+ }
390
+ }
391
+
392
+ .linkButton {
393
+ background: none;
394
+ border: none;
395
+ color: var(--primary-color, #4f46e5);
396
+ text-decoration: underline;
397
+ cursor: pointer;
398
+ font-size: 0.875rem;
399
+ padding: 0;
400
+
401
+ &:hover {
402
+ color: var(--primary-color-dark, #3730a3);
403
+ }
404
+
405
+ &:focus {
406
+ outline: 2px solid var(--primary-color, #4f46e5);
407
+ outline-offset: 2px;
408
+ }
409
+ }
410
+
411
+ // Responsive design
412
+ @media (max-width: 480px) {
413
+ .authLayout {
414
+ padding: 0.5rem;
415
+ }
416
+
417
+ .authContainer {
418
+ max-width: 100%;
419
+ width: 100%;
420
+ }
421
+
422
+ .formContainer {
423
+ padding: 1.5rem;
424
+ }
425
+
426
+ .title {
427
+ font-size: 1.375rem;
428
+ }
429
+
430
+ .formFields {
431
+ gap: 1.25rem;
432
+ }
433
+
434
+ .input {
435
+ padding: 1rem;
436
+ font-size: 16px;
437
+ }
438
+
439
+ .button {
440
+ padding: 1rem;
441
+ font-size: 1rem;
442
+ }
443
+ }
@@ -2127,6 +2127,41 @@ function GenericDetail({
2127
2127
  config.columns &&
2128
2128
  config.settings
2129
2129
  ) {
2130
+ const processUrlParams = (obj) => {
2131
+ if (Array.isArray(obj)) {
2132
+ return obj.map(processUrlParams);
2133
+ } else if (obj && typeof obj === 'object') {
2134
+ const processed = {};
2135
+ for (const [
2136
+ key,
2137
+ value,
2138
+ ] of Object.entries(obj)) {
2139
+ if (value === 'urlParam') {
2140
+ processed[key] =
2141
+ routeParams[urlParam];
2142
+ } else if (
2143
+ typeof value === 'object' && value !== null
2144
+ ) {
2145
+ processed[key] =
2146
+ processUrlParams(value);
2147
+ } else {
2148
+ processed[key] = value;
2149
+ }
2150
+ }
2151
+ return processed;
2152
+ }
2153
+ return obj;
2154
+ };
2155
+
2156
+ const processedSettings = config.settings
2157
+ ? processUrlParams(config.settings)
2158
+ : config.settings;
2159
+
2160
+ const processedConfig = {
2161
+ ...config,
2162
+ settings: processedSettings,
2163
+ };
2164
+
2130
2165
  return (
2131
2166
  <>
2132
2167
  {activeTabConfig.dropzone &&
@@ -2292,7 +2327,7 @@ function GenericDetail({
2292
2327
  <div className={styles.grid__row}>
2293
2328
  <div className={styles.grid__full}>
2294
2329
  <Table
2295
- {...config}
2330
+ {...processedConfig}
2296
2331
  ref={gridRef}
2297
2332
  gridHeight={
2298
2333
  windowHeight * 0.65