@visns-studio/visns-components 5.26.5 → 6.0.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
@@ -5,8 +5,6 @@
5
5
  "@dnd-kit/utilities": "^3.2.2",
6
6
  "@emotion/is-prop-valid": "^1.4.0",
7
7
  "@fontsource/barlow": "^5.2.8",
8
- "@inovua/reactdatagrid-community": "^5.10.2",
9
- "@inovua/reactdatagrid-enterprise": "^5.10.2",
10
8
  "@mapbox/mapbox-gl-geocoder": "^5.1.2",
11
9
  "@nivo/bar": "^0.87.0",
12
10
  "@nivo/core": "^0.87.0",
@@ -17,8 +15,8 @@
17
15
  "@tinymce/miniature": "^6.0.0",
18
16
  "@tinymce/tinymce-react": "^6.3.0",
19
17
  "@uiw/react-color": "^2.10.1",
20
- "@visns-studio/visns-datagrid-community": "1.1.0",
21
- "@visns-studio/visns-datagrid-enterprise": "1.1.0",
18
+ "@visns-studio/visns-datagrid-community": "1.2.0",
19
+ "@visns-studio/visns-datagrid-enterprise": "1.2.0",
22
20
  "@vitejs/plugin-react": "^4.7.0",
23
21
  "add": "^2.0.6",
24
22
  "array-move": "^4.0.0",
@@ -50,7 +48,6 @@
50
48
  "react-password-strength-bar": "^0.4.1",
51
49
  "react-promise-tracker": "^2.1.1",
52
50
  "react-quill": "^2.0.0",
53
- "react-reveal": "^1.2.2",
54
51
  "react-router-dom": "^6.30.3",
55
52
  "react-select": "^5.10.2",
56
53
  "react-signature-pad-wrapper": "^4.3.2",
@@ -94,7 +91,7 @@
94
91
  "react-dom": "^17.0.0 || ^18.0.0"
95
92
  },
96
93
  "name": "@visns-studio/visns-components",
97
- "version": "5.26.5",
94
+ "version": "6.0.0",
98
95
  "description": "Various packages to assist in the development of our Custom Applications.",
99
96
  "main": "src/index.js",
100
97
  "files": [
@@ -115,5 +112,9 @@
115
112
  "url": "https://github.com/visnsstudio/visns-components/issues"
116
113
  },
117
114
  "homepage": "https://github.com/visnsstudio/visns-components#readme",
118
- "packageManager": "yarn@4.9.2"
115
+ "packageManager": "yarn@4.9.2",
116
+ "resolutions": {
117
+ "@visns-studio/visns-datagrid-community": "portal:../visns-datagrid/lib/community-edition",
118
+ "@visns-studio/visns-datagrid-enterprise": "portal:../visns-datagrid/lib/enterprise-edition"
119
+ }
119
120
  }
@@ -1,3 +1,4 @@
1
+ import debugLog from './utils/debugLog';
1
2
  import './styles/global.css';
2
3
  import './styles/global-datagrid.css';
3
4
 
@@ -301,21 +302,39 @@ const DataGrid = forwardRef(
301
302
  const [showImportModal, setShowImportModal] = useState(false);
302
303
 
303
304
  /** Modal Functions */
305
+ // Mirrors modalShow for use inside the dataSource closure (state
306
+ // would be stale there); used by the requireFirstEntry flow.
307
+ const modalShowRef = useRef(false);
308
+
304
309
  const modalOpen = (formType, formId) => {
310
+ modalShowRef.current = true;
305
311
  setModalShow(true);
306
312
  setFormType(formType);
307
313
  setFormId(formId);
308
314
  };
309
315
 
310
316
  const modalClose = () => {
317
+ modalShowRef.current = false;
311
318
  setModalShow(false);
312
319
  setBulkEditMode(false);
313
320
  setBulkEditGroupData(null);
321
+
322
+ // Opt-in (form.create.requireFirstEntry): this grid must not be
323
+ // left empty. Closing the form while no rows exist triggers a
324
+ // reload, whose empty result reopens the create form — the only
325
+ // way out is saving at least one entry (or leaving the page).
326
+ if (
327
+ form?.create?.requireFirstEntry &&
328
+ Array.isArray(dataRef.current) &&
329
+ dataRef.current.length === 0
330
+ ) {
331
+ handleReload();
332
+ }
314
333
  };
315
334
 
316
335
  /** Import Modal Functions */
317
336
  const handleBulkUploadClick = () => {
318
- console.log('[DataGrid] Opening import modal with:', {
337
+ debugLog('[DataGrid] Opening import modal with:', {
319
338
  paramValue,
320
339
  pageData,
321
340
  routeParams,
@@ -555,6 +574,35 @@ const DataGrid = forwardRef(
555
574
  }
556
575
  dataRef.current = res.data;
557
576
 
577
+ // Opt-in (form.create.requireFirstEntry): when a clean
578
+ // load (no search/filter narrowing) finds no rows, open
579
+ // the create form automatically so the user is walked
580
+ // straight into entering the first record. Combined with
581
+ // modalClose's reload-when-empty, dismissing the form
582
+ // just brings it back until one entry is saved.
583
+ if (
584
+ form?.create?.requireFirstEntry &&
585
+ Array.isArray(res.data) &&
586
+ res.data.length === 0 &&
587
+ !modalShowRef.current &&
588
+ !dSearch &&
589
+ !(
590
+ Array.isArray(params.filterValue) &&
591
+ params.filterValue.some(
592
+ (f) =>
593
+ f?.value !== null &&
594
+ f?.value !== undefined &&
595
+ f?.value !== ''
596
+ )
597
+ )
598
+ ) {
599
+ toast.warning(
600
+ form.create.requireFirstEntryMessage ||
601
+ 'At least one entry is required — please add the first record.'
602
+ );
603
+ modalOpen('create', null);
604
+ }
605
+
558
606
  // Opt-in grouped display (ajaxSetting.treeGroupBy {key,
559
607
  // column, parentFields, countLabel}): consecutive rows
560
608
  // sharing a non-empty `key` value get a synthetic summary
@@ -687,7 +735,7 @@ const DataGrid = forwardRef(
687
735
 
688
736
  // Get default limit from userProfile preferences, tableSetting, or default to 25
689
737
  const getDefaultLimit = () => {
690
- console.log('[DataGrid] getDefaultLimit called:', {
738
+ debugLog('[DataGrid] getDefaultLimit called:', {
691
739
  userProfile_rows_per_page: userProfile?.rows_per_page,
692
740
  userProfile_settings_rows_per_page: userProfile?.settings?.rows_per_page,
693
741
  tableSetting_pageSize: tableSetting?.pageSize,
@@ -701,7 +749,7 @@ const DataGrid = forwardRef(
701
749
 
702
750
  const [limit, setLimit] = useState(() => {
703
751
  const initialLimit = getDefaultLimit();
704
- console.log('[DataGrid] Initial limit set to:', initialLimit);
752
+ debugLog('[DataGrid] Initial limit set to:', initialLimit);
705
753
  return initialLimit;
706
754
  });
707
755
  const [search, setSearch] = useState('');
@@ -713,7 +761,7 @@ const DataGrid = forwardRef(
713
761
 
714
762
  // Effect to update limit when userProfile becomes available (for async loaded profiles)
715
763
  useEffect(() => {
716
- console.log('[DataGrid] useEffect triggered:', {
764
+ debugLog('[DataGrid] useEffect triggered:', {
717
765
  initialLimitApplied: initialLimitApplied.current,
718
766
  userProfile_rows_per_page: userProfile?.rows_per_page,
719
767
  userProfile_settings_rows_per_page: userProfile?.settings?.rows_per_page,
@@ -722,9 +770,9 @@ const DataGrid = forwardRef(
722
770
  // Only apply the user's preference once, when userProfile first becomes available
723
771
  if (!initialLimitApplied.current) {
724
772
  const userLimit = userProfile?.rows_per_page || userProfile?.settings?.rows_per_page;
725
- console.log('[DataGrid] Checking userLimit:', userLimit);
773
+ debugLog('[DataGrid] Checking userLimit:', userLimit);
726
774
  if (userLimit && userLimit > 0) {
727
- console.log('[DataGrid] Setting limit to userLimit:', userLimit);
775
+ debugLog('[DataGrid] Setting limit to userLimit:', userLimit);
728
776
  setLimit(userLimit);
729
777
  initialLimitApplied.current = true;
730
778
  }
@@ -1699,17 +1747,17 @@ const DataGrid = forwardRef(
1699
1747
 
1700
1748
  switch (iconConfig.id) {
1701
1749
  case 'clock':
1702
- console.log(
1750
+ debugLog(
1703
1751
  `Starting timer action for group: ${groupValue}`
1704
1752
  );
1705
1753
  break;
1706
1754
  case 'alarm':
1707
- console.log(
1755
+ debugLog(
1708
1756
  `Setting alarm action for group: ${groupValue}`
1709
1757
  );
1710
1758
  break;
1711
1759
  case 'check':
1712
- console.log(
1760
+ debugLog(
1713
1761
  `Mark complete action for group: ${groupValue}`
1714
1762
  );
1715
1763
  break;
@@ -1734,18 +1782,18 @@ const DataGrid = forwardRef(
1734
1782
  // Open modal for bulk editing
1735
1783
  modalOpen('update', null);
1736
1784
  } else {
1737
- console.log(
1785
+ debugLog(
1738
1786
  `Bulk edit action for group: ${groupValue}`
1739
1787
  );
1740
1788
  }
1741
1789
  break;
1742
1790
  case 'envelope':
1743
- console.log(
1791
+ debugLog(
1744
1792
  `Send notification action for group: ${groupValue}`
1745
1793
  );
1746
1794
  break;
1747
1795
  default:
1748
- console.log(
1796
+ debugLog(
1749
1797
  `Custom action ${iconConfig.id} for group: ${groupValue}`
1750
1798
  );
1751
1799
  break;
@@ -1917,7 +1965,7 @@ const DataGrid = forwardRef(
1917
1965
  */
1918
1966
  const onRowClick = useCallback(
1919
1967
  (rowProps, event) => {
1920
- console.log('🔍 DataGrid onRowClick triggered:', {
1968
+ debugLog('🔍 DataGrid onRowClick triggered:', {
1921
1969
  rowData: rowProps.data,
1922
1970
  event: event,
1923
1971
  target: event.target,
@@ -1928,7 +1976,7 @@ const DataGrid = forwardRef(
1928
1976
  '.InovuaReactDataGrid__cell'
1929
1977
  );
1930
1978
 
1931
- console.log('🔍 Cell element found:', cellElement);
1979
+ debugLog('🔍 Cell element found:', cellElement);
1932
1980
 
1933
1981
  // Add null check to prevent errors when cellElement is null
1934
1982
  if (!cellElement) {
@@ -1942,7 +1990,7 @@ const DataGrid = forwardRef(
1942
1990
  cellElement.parentNode.children
1943
1991
  ).indexOf(cellElement);
1944
1992
 
1945
- console.log('🔍 Column analysis:', {
1993
+ debugLog('🔍 Column analysis:', {
1946
1994
  columnIndex: columnIndex,
1947
1995
  totalColumns: rowProps.columns.length,
1948
1996
  isLastColumn: columnIndex === rowProps.columns.length - 1,
@@ -1956,11 +2004,11 @@ const DataGrid = forwardRef(
1956
2004
  rowProps.columns.length > 1 &&
1957
2005
  columnIndex === rowProps.columns.length - 1
1958
2006
  ) {
1959
- console.log('🔍 Last column clicked - no action');
2007
+ debugLog('🔍 Last column clicked - no action');
1960
2008
  // Handle last column click
1961
2009
  } else {
1962
2010
  const column = rowProps.columns[columnIndex];
1963
- console.log('🔍 Column clicked:', {
2011
+ debugLog('🔍 Column clicked:', {
1964
2012
  columnId: column?.id,
1965
2013
  columnType: column?.type,
1966
2014
  hasLink: !!column?.link,
@@ -2041,7 +2089,7 @@ const DataGrid = forwardRef(
2041
2089
  'width=1440,height=1024,menubar=1,resizable=1,status=1,titlebar=1,toolbar=1'
2042
2090
  );
2043
2091
  } else {
2044
- console.log('🔍 Checking for update setting:', {
2092
+ debugLog('🔍 Checking for update setting:', {
2045
2093
  settings: settingsRef.current,
2046
2094
  form: formRef.current,
2047
2095
  primaryKey: formRef.current?.primaryKey,
@@ -2054,13 +2102,13 @@ const DataGrid = forwardRef(
2054
2102
  (setting) => setting.id === 'update'
2055
2103
  );
2056
2104
 
2057
- console.log(
2105
+ debugLog(
2058
2106
  '🔍 Update setting found:',
2059
2107
  updateSetting
2060
2108
  );
2061
2109
 
2062
2110
  if (updateSetting) {
2063
- console.log('✅ Opening modal with:', {
2111
+ debugLog('✅ Opening modal with:', {
2064
2112
  type: 'update',
2065
2113
  id: rowProps.data[
2066
2114
  formRef.current.primaryKey
@@ -2091,7 +2139,7 @@ const DataGrid = forwardRef(
2091
2139
  const { onClick } = rowProps;
2092
2140
 
2093
2141
  rowProps.onClick = (event) => {
2094
- console.log('🔍 Row onClick triggered:', {
2142
+ debugLog('🔍 Row onClick triggered:', {
2095
2143
  rowId: rowProps.data?.id,
2096
2144
  rowData: rowProps.data,
2097
2145
  event: event,
@@ -2110,10 +2158,10 @@ const DataGrid = forwardRef(
2110
2158
  '.InovuaReactDataGrid__group-cell'
2111
2159
  );
2112
2160
 
2113
- console.log('🔍 Group row check:', isGroupRow);
2161
+ debugLog('🔍 Group row check:', isGroupRow);
2114
2162
 
2115
2163
  if (isGroupRow) {
2116
- console.log(
2164
+ debugLog(
2117
2165
  '🔍 Group row detected - calling original onClick only'
2118
2166
  );
2119
2167
  // For group rows, only allow the original onClick handler (for expand/collapse)
@@ -2128,7 +2176,7 @@ const DataGrid = forwardRef(
2128
2176
  '.InovuaReactDataGrid__cell'
2129
2177
  );
2130
2178
 
2131
- console.log('🔍 Cell element in row handler:', cellElement);
2179
+ debugLog('🔍 Cell element in row handler:', cellElement);
2132
2180
 
2133
2181
  // Add null check to prevent errors when cellElement is null
2134
2182
  if (!cellElement) {
@@ -2144,14 +2192,14 @@ const DataGrid = forwardRef(
2144
2192
 
2145
2193
  const column = rowProps.columns[columnIndex];
2146
2194
 
2147
- console.log('🔍 Row handler column analysis:', {
2195
+ debugLog('🔍 Row handler column analysis:', {
2148
2196
  columnIndex: columnIndex,
2149
2197
  columnId: column?.id,
2150
2198
  isCheckboxColumn: column?.id === '__checkbox-column',
2151
2199
  });
2152
2200
 
2153
2201
  if (column.id === '__checkbox-column') {
2154
- console.log(
2202
+ debugLog(
2155
2203
  '🔍 Checkbox column clicked - handling checkbox logic'
2156
2204
  );
2157
2205
  // For checkbox column clicks, we need to handle them specially
@@ -2199,14 +2247,14 @@ const DataGrid = forwardRef(
2199
2247
  onClick(event);
2200
2248
  }
2201
2249
  } else {
2202
- console.log(
2250
+ debugLog(
2203
2251
  '🔍 Non-checkbox column - calling onRowClick'
2204
2252
  );
2205
2253
  // For non-checkbox columns, handle normal row clicks
2206
2254
  // But do NOT trigger checkbox selection
2207
2255
  onRowClick(rowProps, event);
2208
2256
 
2209
- console.log(
2257
+ debugLog(
2210
2258
  '🔍 Calling original onClick handler:',
2211
2259
  !!onClick
2212
2260
  );
@@ -2787,12 +2835,12 @@ const DataGrid = forwardRef(
2787
2835
  // Check function-based condition if it exists
2788
2836
  if (allow && s.condition && typeof s.condition === 'function') {
2789
2837
  try {
2790
- console.log('🔍 DataGrid: Evaluating condition for setting:', s.id, 'with data:', d);
2838
+ debugLog('🔍 DataGrid: Evaluating condition for setting:', s.id, 'with data:', d);
2791
2839
  const conditionResult = s.condition(d);
2792
- console.log('🔍 DataGrid: Condition result:', conditionResult, 'for setting:', s.id);
2840
+ debugLog('🔍 DataGrid: Condition result:', conditionResult, 'for setting:', s.id);
2793
2841
  if (!conditionResult) {
2794
2842
  allow = false;
2795
- console.log('🔍 DataGrid: Setting blocked by condition:', s.id);
2843
+ debugLog('🔍 DataGrid: Setting blocked by condition:', s.id);
2796
2844
  }
2797
2845
  } catch (error) {
2798
2846
  console.warn('🔍 DataGrid: Condition function error for setting:', s.id, error);
@@ -2852,7 +2900,7 @@ const DataGrid = forwardRef(
2852
2900
  return null;
2853
2901
  }
2854
2902
  } else {
2855
- console.log('🔍 Setting NOT rendered (allow=false):', {
2903
+ debugLog('🔍 Setting NOT rendered (allow=false):', {
2856
2904
  settingId: s.id,
2857
2905
  reason: 'filtered out by permissions or active conditions',
2858
2906
  });
@@ -3420,7 +3468,7 @@ const DataGrid = forwardRef(
3420
3468
  shouldAutoEnableWordWrap &&
3421
3469
  column.wordWrap !== true
3422
3470
  ) {
3423
- console.log(
3471
+ debugLog(
3424
3472
  `DataGrid: Auto-enabled wordWrap for column "${columnId}" (${column.label})`
3425
3473
  );
3426
3474
  }
@@ -3445,7 +3493,7 @@ const DataGrid = forwardRef(
3445
3493
 
3446
3494
  // Debug logging for wordWrap cell rendering
3447
3495
  if (column.wordWrap === true) {
3448
- console.log(
3496
+ debugLog(
3449
3497
  'DataGrid DEBUG - Rendering WordWrap Cell:',
3450
3498
  {
3451
3499
  columnId,
@@ -3827,7 +3875,7 @@ const DataGrid = forwardRef(
3827
3875
 
3828
3876
  if (userPreferredLimit && userPreferredLimit > 0) {
3829
3877
  // User has a preference, don't override it
3830
- console.log('[DataGrid] Respecting user preference, not auto-calculating limit:', userPreferredLimit);
3878
+ debugLog('[DataGrid] Respecting user preference, not auto-calculating limit:', userPreferredLimit);
3831
3879
  } else if (ajaxSetting?.take >= 35) {
3832
3880
  setLimit(ajaxSetting.take);
3833
3881
  } else {
@@ -4033,7 +4081,7 @@ const DataGrid = forwardRef(
4033
4081
  } else {
4034
4082
  newState[groupValue] = true;
4035
4083
  }
4036
- console.log(
4084
+ debugLog(
4037
4085
  'Updated collapsed groups:',
4038
4086
  newState
4039
4087
  );
@@ -4152,7 +4200,7 @@ const DataGrid = forwardRef(
4152
4200
  if (
4153
4201
  groupClickTimeoutRef.current
4154
4202
  ) {
4155
- console.log(
4203
+ debugLog(
4156
4204
  '🖱️ Ignoring rapid click'
4157
4205
  );
4158
4206
  return;
@@ -4167,7 +4215,7 @@ const DataGrid = forwardRef(
4167
4215
  'input[type="checkbox"]'
4168
4216
  )
4169
4217
  ) {
4170
- console.log(
4218
+ debugLog(
4171
4219
  '🖱️ Ignoring click on expand button or checkbox'
4172
4220
  );
4173
4221
  return;
@@ -2416,6 +2416,38 @@ function Form({
2416
2416
  toast.error('Failed to fetch data:', error);
2417
2417
  }
2418
2418
  } else {
2419
+ // Bulk edit opens without a record fetch (columnId is null), so
2420
+ // seed each field with the value the group rows already share —
2421
+ // otherwise saved values (e.g. an assigned user) render blank and
2422
+ // required fields force a re-pick on every open.
2423
+ if (bulkEditMode && bulkEditGroupData?.groupRows?.length) {
2424
+ const modalFields =
2425
+ bulkEditGroupData.iconConfig?.formModal?.fields || [];
2426
+
2427
+ formSettings.fields.forEach((item) => {
2428
+ const { id, type, parent } = item;
2429
+
2430
+ if (parent) return;
2431
+ if (modalFields.length && !modalFields.includes(id)) return;
2432
+
2433
+ const values = bulkEditGroupData.groupRows.map(
2434
+ (row) => row?.[id]
2435
+ );
2436
+ const first = values[0];
2437
+ const isShared =
2438
+ first !== undefined &&
2439
+ first !== null &&
2440
+ first !== '' &&
2441
+ values.every((value) => value === first);
2442
+
2443
+ if (isShared) {
2444
+ _formData[id] = ['date', 'datetime'].includes(type)
2445
+ ? moment(first).toDate()
2446
+ : first;
2447
+ }
2448
+ });
2449
+ }
2450
+
2419
2451
  setFormData(_formData);
2420
2452
  }
2421
2453
  };
@@ -11,6 +11,7 @@ import {
11
11
  UserSearch,
12
12
  ClipboardCheck,
13
13
  LogOut,
14
+ Settings,
14
15
  Shield,
15
16
  Image,
16
17
  Phone,
@@ -238,6 +239,7 @@ function Navigation({
238
239
  phone: Phone,
239
240
  shield: Shield,
240
241
  signOut: LogOut,
242
+ settings: Settings,
241
243
  search: Search,
242
244
  userSearch: UserSearch,
243
245
  clipboardCheck: ClipboardCheck,
@@ -259,7 +261,6 @@ function Navigation({
259
261
  return (
260
262
  <li>
261
263
  <button
262
- title="Scan Business Card"
263
264
  data-tooltip-id="system-tooltip"
264
265
  data-tooltip-content="Scan Business Card"
265
266
  onClick={() => setShowBusinessCardOcr(true)}
@@ -284,7 +285,6 @@ function Navigation({
284
285
  return (
285
286
  <li>
286
287
  <button
287
- title={n.label}
288
288
  data-tooltip-id="system-tooltip"
289
289
  data-tooltip-content={n.label}
290
290
  onClick={handleLogout}
@@ -305,7 +305,6 @@ function Navigation({
305
305
  data-tooltip-content={n.label}
306
306
  className={`${activeClass}`}
307
307
  target={n.target ? n.target : '_self'}
308
- title={n.label}
309
308
  >
310
309
  {IconComponent && (
311
310
  <IconComponent size={19} strokeWidth={2} />
@@ -373,7 +373,19 @@ function TableFilter({
373
373
  )
374
374
  )
375
375
  ) {
376
- window.open(url);
376
+ // Items with target "_self" navigate in the same tab; the
377
+ // default remains a new window for backward compatibility.
378
+ const clickedItem =
379
+ filters.find((f) => f.id === id) ||
380
+ filters
381
+ .flatMap((f) => f.children || [])
382
+ .find((c) => c.id === id);
383
+
384
+ if (clickedItem?.target === '_self') {
385
+ window.location.assign(url);
386
+ } else {
387
+ window.open(url);
388
+ }
377
389
  }
378
390
 
379
391
  // Calculate the updated settings
@@ -2,7 +2,7 @@ import '../styles/global.css';
2
2
 
3
3
  import React, { useState } from 'react';
4
4
  import { Link, useLocation, useNavigate } from 'react-router-dom';
5
- import Reveal from 'react-reveal/Reveal';
5
+ import Reveal from '../utils/Reveal';
6
6
  import { toast } from 'react-toastify';
7
7
  import { User, Lock } from 'lucide-react';
8
8
 
@@ -1,7 +1,7 @@
1
1
  import '../styles/global.css';
2
2
 
3
3
  import React, { useState } from 'react';
4
- import Reveal from 'react-reveal/Reveal';
4
+ import Reveal from '../utils/Reveal';
5
5
  import { toast } from 'react-toastify';
6
6
  import parse from 'html-react-parser';
7
7
  import { User } from 'lucide-react';
@@ -2,7 +2,7 @@ import '../styles/global.css';
2
2
 
3
3
  import React, { useState, useEffect } from 'react';
4
4
  import { useNavigate, useLocation } from 'react-router-dom';
5
- import Reveal from 'react-reveal/Reveal';
5
+ import Reveal from '../utils/Reveal';
6
6
  import { toast } from 'react-toastify';
7
7
  import { Lock } from 'lucide-react';
8
8
 
@@ -5,7 +5,7 @@ import { useNavigate, useParams } from 'react-router-dom';
5
5
  import { toast } from 'react-toastify';
6
6
  import parse from 'html-react-parser';
7
7
  import validator from 'validator';
8
- import Reveal from 'react-reveal/Reveal';
8
+ import Reveal from '../utils/Reveal';
9
9
 
10
10
  import CustomFetch from '../Fetch';
11
11
 
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { useParams, useNavigate, useLocation } from 'react-router-dom';
3
- import Reveal from 'react-reveal/Reveal';
3
+ import Reveal from '../utils/Reveal';
4
4
  import { ToastContainer } from 'react-toastify';
5
5
  import Form from '../Form';
6
6
 
@@ -1,6 +1,6 @@
1
1
  import React, { useEffect, useState } from 'react';
2
2
  import { Outlet } from 'react-router-dom';
3
- import Reveal from 'react-reveal/Reveal';
3
+ import Reveal from '../utils/Reveal';
4
4
  import DataGrid from '../DataGrid';
5
5
 
6
6
  function CmsIndex({ setting, userProfile }) {
@@ -1,6 +1,6 @@
1
1
  import React, { useState, useEffect } from 'react';
2
2
  import { useLocation } from 'react-router-dom';
3
- import Reveal from 'react-reveal/Reveal';
3
+ import Reveal from '../utils/Reveal';
4
4
  import { arrayMoveImmutable } from 'array-move';
5
5
  import { toast } from 'react-toastify';
6
6
  import CustomFetch from '../Fetch';
@@ -2994,6 +2994,14 @@ function GenericDetail({
2994
2994
  {activeTabConfig.export && (
2995
2995
  <button
2996
2996
  className={styles.btn}
2997
+ style={{
2998
+ display: 'inline-flex',
2999
+ alignItems: 'center',
3000
+ justifyContent: 'center',
3001
+ gap: '7px',
3002
+ minWidth: '92px',
3003
+ height: '42px',
3004
+ }}
2997
3005
  onClick={() =>
2998
3006
  handleExport(
2999
3007
  activeTabConfig.export
@@ -3008,6 +3016,14 @@ function GenericDetail({
3008
3016
  {activeTabConfig.customAction && (
3009
3017
  <button
3010
3018
  className={styles.btn}
3019
+ style={{
3020
+ display: 'inline-flex',
3021
+ alignItems: 'center',
3022
+ justifyContent: 'center',
3023
+ gap: '7px',
3024
+ minWidth: '92px',
3025
+ height: '42px',
3026
+ }}
3011
3027
  onClick={() =>
3012
3028
  handleCustomAction(
3013
3029
  activeTabConfig.customAction
@@ -3025,6 +3041,14 @@ function GenericDetail({
3025
3041
  <button
3026
3042
  key={index}
3027
3043
  className={styles.btn}
3044
+ style={{
3045
+ display: 'inline-flex',
3046
+ alignItems: 'center',
3047
+ justifyContent: 'center',
3048
+ gap: '7px',
3049
+ minWidth: '92px',
3050
+ height: '42px',
3051
+ }}
3028
3052
  onClick={() =>
3029
3053
  handleCustomAction(customAction)
3030
3054
  }
@@ -211,7 +211,6 @@
211
211
  border-radius: var(--br);
212
212
  outline: none;
213
213
  transition: all 0.2s cubic-bezier(0.85, 0, 0.15, 1) 0s;
214
- font-size: 1.25em;
215
214
  }
216
215
 
217
216
  .btn:hover:not(:disabled) {
@@ -0,0 +1,19 @@
1
+ import React from 'react';
2
+
3
+ /**
4
+ * Drop-in replacement for react-reveal's <Reveal effect="...">, which is
5
+ * unmaintained and depends on legacy React lifecycles/context removed in
6
+ * React 19. The effect class (e.g. "fadeInUp") is expected to define its
7
+ * own @keyframes/duration in the consuming app's CSS; its animation runs
8
+ * when the element mounts.
9
+ */
10
+ const Reveal = ({ effect = '', className = '', children, ...rest }) => (
11
+ <div
12
+ className={[effect, className].filter(Boolean).join(' ')}
13
+ {...rest}
14
+ >
15
+ {children}
16
+ </div>
17
+ );
18
+
19
+ export default Reveal;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Opt-in debug logger. Silent by default so component internals don't spam
3
+ * the console; enable in a browser with:
4
+ * localStorage.setItem('visns-debug', '1')
5
+ * and disable with localStorage.removeItem('visns-debug').
6
+ */
7
+ const debugLog = (...args) => {
8
+ try {
9
+ if (
10
+ typeof window !== 'undefined' &&
11
+ window.localStorage?.getItem('visns-debug')
12
+ ) {
13
+ console.log(...args);
14
+ }
15
+ } catch (e) {
16
+ // Storage unavailable (private mode / SSR) — stay silent
17
+ }
18
+ };
19
+
20
+ export default debugLog;