@bit.rhplus/ui.grid-layout 0.0.4 → 0.0.6

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.
@@ -6,6 +6,8 @@
6
6
  import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
7
7
  import { useGridLayoutApi } from './useGridLayoutApi';
8
8
  import { debounce } from 'lodash';
9
+ // Import tooltip komponent pro eliminaci circular dependency
10
+ // import * as Tooltip from '../grid/tooltips';
9
11
  /**
10
12
  * Hook pro správu grid layout s automatickým ukládáním
11
13
  * @param {Object} config - Konfigurace grid layout
@@ -15,7 +17,7 @@ import { debounce } from 'lodash';
15
17
  * @param {string} [config.filterName] - Název filtru (volitelné)
16
18
  * @param {boolean} [config.enabled=true] - Zapnout/vypnout layout management
17
19
  * @param {boolean} [config.autoSave=true] - Automatické ukládání při změnách
18
- * @param {number} [config.autoSaveDelay=2000] - Zpoždění auto-save v ms
20
+ * @param {number} [config.autoSaveDelay=500] - Zpoždění auto-save v ms
19
21
  * @param {Array} config.columnDefs - AG-Grid column definitions
20
22
  * @param {string} [config.accessToken] - Přístupový token
21
23
  * @param {boolean} [config.waitForSavedFields=false] - Skrýt columnDefs dokud nejsou načtena savedFields
@@ -24,7 +26,7 @@ import { debounce } from 'lodash';
24
26
  * @param {Function} [config.onError] - Callback při chybě
25
27
  * @returns {Object} Grid layout management interface
26
28
  */
27
- export const useGridLayout = ({ userKey, applicationName, gridName, filterName, enabled = true, autoSave = true, autoSaveDelay = 2000, columnDefs = [], accessToken, waitForSavedFields = false, // Nový prop pro odložené zobrazení columnDefs
29
+ export const useGridLayout = ({ userKey, applicationName, gridName, filterName, enabled = true, autoSave = true, autoSaveDelay = 500, columnDefs = [], accessToken, waitForSavedFields = false, // Nový prop pro odložené zobrazení columnDefs
28
30
  onLayoutLoaded, onLayoutSaved, onError, }) => {
29
31
  // Validace columnDefs - musí být array
30
32
  if (columnDefs !== undefined &&
@@ -36,16 +38,19 @@ onLayoutLoaded, onLayoutSaved, onError, }) => {
36
38
  // Refs pro AG-Grid API
37
39
  const gridApiRef = useRef(null);
38
40
  const columnApiRef = useRef(null);
41
+ // Ref mapa pro ukládání aktuálních šířek sloupců (fieldId -> šířka)
42
+ const columnWidthRefsMap = useRef(new Map());
39
43
  // State
40
44
  const [isInitialized, setIsInitialized] = useState(false);
41
45
  const [isGridReady, setIsGridReady] = useState(false);
42
46
  const [isLoading, setIsLoading] = useState(false);
43
47
  const [isSaving, setIsSaving] = useState(false);
48
+ const [isApplyingLayout, setIsApplyingLayout] = useState(false);
44
49
  const [error, setError] = useState(null);
45
50
  const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
46
51
  const [isColumnEditorOpen, setIsColumnEditorOpen] = useState(false);
47
52
  const [lastKnownColumnState, setLastKnownColumnState] = useState(null);
48
- const [columnDefsVersion, setColumnDefsVersion] = useState(0); // Pro tracking změn v stableColumnDefsRef
53
+ const [columnWidthsVersion, setColumnWidthsVersion] = useState(0); // Pro trigger preTransformedColumnDefs
49
54
  // Grid Layout API hook
50
55
  const gridLayoutApi = useGridLayoutApi({
51
56
  userKey,
@@ -72,110 +77,6 @@ onLayoutLoaded, onLayoutSaved, onError, }) => {
72
77
  Array.isArray(columnDefs) &&
73
78
  columnDefs.length > 0,
74
79
  });
75
- // // Bezpečné aktualizování lastKnownColumnState při otevření editoru
76
- // useEffect(() => {
77
- // if (isColumnEditorOpen && columnApiRef.current && typeof columnApiRef.current.getColumnState === 'function') {
78
- // try {
79
- // const currentState = columnApiRef.current.getColumnState();
80
- // if (Array.isArray(currentState) && currentState.length > 0) {
81
- // // Aktualizujeme lastKnownColumnState po otevření editoru
82
- // const validColumnDefs = Array.isArray(columnDefs) ? columnDefs : [];
83
- // const formattedState = currentState.map((columnStateItem, index) => {
84
- // const colDef = validColumnDefs.find(cd => cd.field === columnStateItem.colId) || {};
85
- // const savedField = savedFields?.records?.find(sf => sf.fieldName === columnStateItem.colId);
86
- // return {
87
- // id: columnStateItem.colId,
88
- // field: columnStateItem.colId,
89
- // headerName: savedField?.headerName || colDef.headerName || columnStateItem.colId,
90
- // originalHeaderName: colDef.headerName || columnStateItem.colId,
91
- // width: columnStateItem.width || colDef.width || 100,
92
- // originalWidth: colDef.width || 100,
93
- // visible: !columnStateItem.hide,
94
- // order: index
95
- // };
96
- // });
97
- // setLastKnownColumnState(formattedState);
98
- // console.log('[GridLayout] Updated lastKnownColumnState after opening editor');
99
- // }
100
- // } catch (error) {
101
- // console.error('[GridLayout] Error updating column state after opening editor:', error);
102
- // }
103
- // }
104
- // }, [isColumnEditorOpen, columnDefs, savedFields, columnApiRef]);
105
- // MutationObserver pro sledování změn v DOM a okamžitou aktualizaci headerName
106
- // useEffect(() => {
107
- // if (!savedFields?.records || !isInitialized || !enabled) return;
108
- // // Reference na observer pro cleanup
109
- // let observer = null;
110
- // try {
111
- // console.log('[GridLayout] Setting up MutationObserver for header changes');
112
- // // Funkce pro aktualizaci headerName
113
- // const updateHeaderNames = () => {
114
- // try {
115
- // // Vytvoříme mapu fieldName -> headerName z API dat
116
- // const headerNameMap = new Map();
117
- // savedFields.records.forEach(field => {
118
- // if (field.fieldName && field.headerName) {
119
- // headerNameMap.set(field.fieldName, field.headerName);
120
- // }
121
- // });
122
- // // Najdeme všechny hlavičky sloupců v DOM
123
- // const headerCells = document.querySelectorAll('.ag-header-cell');
124
- // // Aktualizujeme texty hlaviček
125
- // headerCells.forEach(headerCell => {
126
- // try {
127
- // // Získáme ID sloupce z DOM atributů
128
- // const colId = headerCell.getAttribute('col-id');
129
- // if (colId && headerNameMap.has(colId)) {
130
- // // Najdeme element s textem hlavičky
131
- // const headerTextEl = headerCell.querySelector('.ag-header-cell-text');
132
- // if (headerTextEl) {
133
- // const newHeaderName = headerNameMap.get(colId);
134
- // const currentText = headerTextEl.textContent;
135
- // if (currentText !== newHeaderName) {
136
- // console.log(`[GridLayout] MutationObserver update: Column '${colId}' header from '${currentText}' to '${newHeaderName}'`);
137
- // headerTextEl.textContent = newHeaderName;
138
- // }
139
- // }
140
- // }
141
- // } catch (cellError) {
142
- // // Tiché selhání - nechceme, aby MutationObserver padal
143
- // }
144
- // });
145
- // } catch (error) {
146
- // // Tiché selhání - nechceme, aby MutationObserver padal
147
- // }
148
- // };
149
- // // Najdeme element hlavičky
150
- // const headerElement = document.querySelector('.ag-header');
151
- // if (headerElement) {
152
- // // Vytvoříme observer, který bude sledovat změny v hlavičce
153
- // observer = new MutationObserver((mutations) => {
154
- // // Detekovali jsme změnu v DOM hlavičky, aktualizujeme headerName
155
- // updateHeaderNames();
156
- // });
157
- // // Začneme sledovat změny v hlavičce
158
- // observer.observe(headerElement, {
159
- // childList: true, // sledujeme přidání/odebírání elementů
160
- // subtree: true, // sledujeme změny i v potomcích
161
- // characterData: true, // sledujeme změny textu
162
- // attributeFilter: ['col-id', 'class'] // sledujeme změny těchto atributů
163
- // });
164
- // console.log('[GridLayout] MutationObserver set up successfully');
165
- // } else {
166
- // console.log('[GridLayout] Header element not found for MutationObserver');
167
- // }
168
- // } catch (error) {
169
- // console.error('[GridLayout] Error setting up MutationObserver:', error);
170
- // }
171
- // // Cleanup - odpojit observer při unmount
172
- // return () => {
173
- // if (observer) {
174
- // observer.disconnect();
175
- // console.log('[GridLayout] MutationObserver disconnected');
176
- // }
177
- // };
178
- // }, [savedFields, isInitialized, enabled]);
179
80
  /**
180
81
  * Error handler
181
82
  */
@@ -193,40 +94,37 @@ onLayoutLoaded, onLayoutSaved, onError, }) => {
193
94
  const stableHandleErrorRef = useRef(handleError);
194
95
  // Reference pro ukládání stavu sloupců bez state update v render cyklu
195
96
  const stableCurrentColumnsRef = useRef(null);
196
- // Aktualizujeme ref hodnoty
97
+ // Jednoduchá aktualizace ref hodnot - BEZ aktualizace columnDefs
197
98
  useEffect(() => {
198
- // Pro columnDefs zachováváme aktualizované šířky pokud existují
199
- if (stableColumnDefsRef.current && Array.isArray(stableColumnDefsRef.current) && Array.isArray(columnDefs)) {
200
- // Vytvoříme mapu existujících šířek
201
- const existingWidthsMap = new Map();
202
- stableColumnDefsRef.current.forEach(colDef => {
203
- const fieldId = colDef.field || colDef.colId;
204
- if (fieldId && colDef.width) {
205
- existingWidthsMap.set(fieldId, colDef.width);
206
- }
207
- });
208
- // Aktualizujeme columnDefs s existujícími šířkami
209
- const mergedColumnDefs = columnDefs.map(colDef => {
210
- const fieldId = colDef.field || colDef.colId;
211
- if (fieldId && existingWidthsMap.has(fieldId)) {
212
- return {
213
- ...colDef,
214
- width: existingWidthsMap.get(fieldId)
215
- };
216
- }
217
- return colDef;
218
- });
219
- stableColumnDefsRef.current = mergedColumnDefs;
220
- setColumnDefsVersion(prev => prev + 1); // Trigger useMemo přepočet
221
- }
222
- else {
223
- // První nastavení nebo pokud nejsou dostupné předchozí data
224
- stableColumnDefsRef.current = columnDefs;
99
+ // Při změně columnDefs vyčistíme columnWidthRefsMap a přeneseme existující šířky
100
+ if (columnDefs !== stableColumnDefsRef.current) {
101
+ const newWidthMap = new Map();
102
+ // Zachováme šířky pro sloupce, které stále existují
103
+ if (Array.isArray(columnDefs)) {
104
+ columnDefs.forEach(colDef => {
105
+ const fieldId = colDef.field || colDef.colId;
106
+ if (fieldId) {
107
+ // Zkusíme najít existující šířku v ref map
108
+ const existingWidth = columnWidthRefsMap.current.get(fieldId);
109
+ if (existingWidth !== undefined) {
110
+ newWidthMap.set(fieldId, existingWidth);
111
+ }
112
+ else if (colDef.width) {
113
+ // Použijeme šířku z nového columnDef
114
+ newWidthMap.set(fieldId, colDef.width);
115
+ }
116
+ }
117
+ });
118
+ }
119
+ columnWidthRefsMap.current = newWidthMap;
120
+ console.log('[GridLayout] Cleanup columnWidthRefsMap při změně columnDefs:', Object.fromEntries(newWidthMap));
121
+ setColumnWidthsVersion(prev => prev + 1); // Trigger preTransformedColumnDefs přepočet
225
122
  }
123
+ stableColumnDefsRef.current = columnDefs;
226
124
  stableGridLayoutApiRef.current = gridLayoutApi;
227
125
  stableOnLayoutSavedRef.current = onLayoutSaved;
228
126
  stableHandleErrorRef.current = handleError;
229
- });
127
+ }, [columnDefs, gridLayoutApi, onLayoutSaved, handleError]);
230
128
  // Efekt pro bezpečnou aktualizaci lastKnownColumnState z ref
231
129
  // useEffect(() => {
232
130
  // if (stableCurrentColumnsRef.current) {
@@ -477,11 +375,21 @@ onLayoutLoaded, onLayoutSaved, onError, }) => {
477
375
  }
478
376
  try {
479
377
  setIsLoading(true);
378
+ setIsApplyingLayout(true);
480
379
  // Transformujeme Grid API fields na AG-Grid column state
481
380
  // Použijeme stableColumnDefsRef.current místo columnDefs pro zachování aktuálních šířek
482
381
  const columnDefsToUse = stableColumnDefsRef.current || columnDefs;
483
382
  const columnState = gridLayoutApi.transformFieldsToColumnState(savedFields.records, columnDefsToUse);
484
383
  if (columnState && columnState.length > 0) {
384
+ // Při prvotním načítání inicializujeme ref mapu s API šířkami
385
+ if (!isInitialized) {
386
+ columnState.forEach(colState => {
387
+ if (colState.colId && colState.width) {
388
+ columnWidthRefsMap.current.set(colState.colId, colState.width);
389
+ }
390
+ });
391
+ console.log('[GridLayout] Inicializace columnWidthRefsMap z API:', Object.fromEntries(columnWidthRefsMap.current));
392
+ }
485
393
  // Pokud je waitForSavedFields true, columnDefs jsou už pre-transformované,
486
394
  // takže aplikujeme jen width a hide vlastnosti bez delay pro pořadí
487
395
  const applyFunction = () => {
@@ -490,25 +398,28 @@ onLayoutLoaded, onLayoutSaved, onError, }) => {
490
398
  let result;
491
399
  try {
492
400
  const applyOrderEnabled = !waitForSavedFields; // Při waitForSavedFields už je pořadí v columnDefs
493
- // Získáme aktuální stav sloupců před aplikováním
494
- const currentColumnState = gridApiRef.current?.getColumnState?.() || [];
495
- const currentWidthMap = new Map();
496
- currentColumnState.forEach(colState => {
497
- if (colState.colId && colState.width) {
498
- currentWidthMap.set(colState.colId, colState.width);
499
- }
500
- });
501
- // Upravíme columnState tak, aby zachoval aktuální šířky pokud existují
502
- const adjustedColumnState = columnState.map(colState => {
503
- const currentWidth = currentWidthMap.get(colState.colId);
504
- if (currentWidth && currentWidth !== colState.width) {
401
+ // Upravíme columnState s aktuálními šířkami z ref map
402
+ // POUZE pokud ref mapa obsahuje hodnoty (tj. uživatel už manipuloval s šířkami)
403
+ // Při prvotním načítání z API zachováme API šířky
404
+ let adjustedColumnState = columnState.map(colState => {
405
+ // Zkontrolujeme, zda máme ref hodnotu pro tento sloupec
406
+ const refWidth = columnWidthRefsMap.current.get(colState.colId);
407
+ // Použijeme ref hodnotu POUZE pokud existuje A není to prvotní načítání
408
+ if (refWidth !== undefined && isInitialized) {
505
409
  return {
506
410
  ...colState,
507
- width: currentWidth
411
+ width: refWidth
508
412
  };
509
413
  }
414
+ // Při prvotním načítání nebo pokud nemáme ref hodnotu, zachováme API šířku
510
415
  return colState;
511
416
  });
417
+ console.log('[GridLayout] Aplikování column state s ref šířkami:', {
418
+ columnStateLength: adjustedColumnState?.length,
419
+ applyOrderEnabled,
420
+ firstColumnWidth: adjustedColumnState?.[0]?.width,
421
+ refWidthsCount: columnWidthRefsMap.current.size
422
+ });
512
423
  result = applyColumnStateApi.applyColumnState({
513
424
  state: adjustedColumnState,
514
425
  applyOrder: applyOrderEnabled, // Pořadí jen když není waitForSavedFields
@@ -519,6 +430,7 @@ onLayoutLoaded, onLayoutSaved, onError, }) => {
519
430
  rowGroup: null, // Reset row grouping
520
431
  },
521
432
  });
433
+ console.log('[GridLayout] Column state aplikován úspěšně');
522
434
  // Explicitně aktualizujeme headerName pro každý sloupec, protože AG-Grid
523
435
  // nepodporuje nastavení headerName přes applyColumnState
524
436
  if (savedFields.records &&
@@ -573,9 +485,18 @@ onLayoutLoaded, onLayoutSaved, onError, }) => {
573
485
  setTimeout(() => {
574
486
  try {
575
487
  const afterUpdateColDefs = gridApiRef.current.getColumnDefs();
488
+ const afterUpdateColumnState = gridApiRef.current.getColumnState?.() || [];
489
+ console.log('[GridLayout] Po aplikování - kontrola šířek:', {
490
+ columnCount: afterUpdateColumnState.length,
491
+ widths: afterUpdateColumnState.map(col => ({ field: col.colId, width: col.width }))
492
+ });
493
+ // DOM operace dokončeny, můžeme ukončit loading
494
+ setIsApplyingLayout(false);
576
495
  }
577
496
  catch (checkError) {
578
497
  console.error('[GridLayout] Error checking updated columns:', checkError);
498
+ // I při chybě ukončíme loading
499
+ setIsApplyingLayout(false);
579
500
  }
580
501
  }, 100);
581
502
  }
@@ -611,9 +532,13 @@ onLayoutLoaded, onLayoutSaved, onError, }) => {
611
532
  console.error('[GridLayout] Error updating header cell:', cellError);
612
533
  }
613
534
  });
535
+ // DOM manipulace dokončena, ukončíme loading
536
+ setIsApplyingLayout(false);
614
537
  }
615
538
  catch (domError) {
616
539
  console.error('[GridLayout] Error in DOM manipulation:', domError);
540
+ // I při chybě ukončíme loading
541
+ setIsApplyingLayout(false);
617
542
  }
618
543
  }, 200);
619
544
  }
@@ -635,6 +560,12 @@ onLayoutLoaded, onLayoutSaved, onError, }) => {
635
560
  // Removed console.error for production
636
561
  handleError(delayedError, 'při delayed aplikování layoutu');
637
562
  }
563
+ finally {
564
+ // Bezpečnostní ukončení loading pokud se nedokončilo jinde
565
+ setTimeout(() => {
566
+ setIsApplyingLayout(false);
567
+ }, 300);
568
+ }
638
569
  };
639
570
  // Pro waitForSavedFields aplikujeme okamžitě (pořadí je už v columnDefs)
640
571
  // Pro normální režim použijeme delay
@@ -653,6 +584,7 @@ onLayoutLoaded, onLayoutSaved, onError, }) => {
653
584
  setIsInitialized(true);
654
585
  setIsGridReady(true); // Obnovíme také isGridReady pro event handlery
655
586
  setIsLoading(false);
587
+ // setIsApplyingLayout(false) se nastaví až po dokončení všech DOM operací
656
588
  }
657
589
  }, [
658
590
  savedFields,
@@ -700,38 +632,24 @@ onLayoutLoaded, onLayoutSaved, onError, }) => {
700
632
  return;
701
633
  }
702
634
  setHasUnsavedChanges(true);
703
- // Aktualizujeme šířky sloupců v columnDefs před uložením
635
+ // Okamžitě uložit aktuální šířky sloupců do ref map (podobně jako v handleColumnResized)
704
636
  try {
705
637
  if (gridApiRef.current && typeof gridApiRef.current.getColumnState === 'function') {
706
638
  const currentColumnState = gridApiRef.current.getColumnState();
707
639
  if (currentColumnState && Array.isArray(currentColumnState)) {
708
- // Aktualizujeme šířky v původních columnDefs
709
- const widthUpdatesMap = new Map();
640
+ // Uložit šířky do ref map pro každý sloupec
710
641
  currentColumnState.forEach(colState => {
711
642
  if (colState.colId && colState.width) {
712
- widthUpdatesMap.set(colState.colId, colState.width);
643
+ columnWidthRefsMap.current.set(colState.colId, colState.width);
713
644
  }
714
645
  });
715
- // Aktualizujeme stableColumnDefsRef s novými šířkami
716
- if (stableColumnDefsRef.current && Array.isArray(stableColumnDefsRef.current)) {
717
- const updatedColumnDefs = stableColumnDefsRef.current.map(colDef => {
718
- const fieldId = colDef.field || colDef.colId;
719
- if (fieldId && widthUpdatesMap.has(fieldId)) {
720
- return {
721
- ...colDef,
722
- width: widthUpdatesMap.get(fieldId)
723
- };
724
- }
725
- return colDef;
726
- });
727
- stableColumnDefsRef.current = updatedColumnDefs;
728
- setColumnDefsVersion(prev => prev + 1); // Trigger useMemo přepočet
729
- }
646
+ console.log('[GridLayout] Updated columnWidthRefsMap in dragStopped:', Object.fromEntries(columnWidthRefsMap.current));
647
+ setColumnWidthsVersion(prev => prev + 1); // Trigger preTransformedColumnDefs přepočet
730
648
  }
731
649
  }
732
650
  }
733
651
  catch (error) {
734
- console.error('[GridLayout] Error updating columnDefs widths in handleDragStopped:', error);
652
+ console.error('[GridLayout] Error updating columnWidthRefsMap in handleDragStopped:', error);
735
653
  }
736
654
  // Pokud ještě není inicializované, přidáme akci do pending
737
655
  if (!isInitialized && autoSave) {
@@ -741,49 +659,61 @@ onLayoutLoaded, onLayoutSaved, onError, }) => {
741
659
  ]);
742
660
  return;
743
661
  }
744
- // Normální proces pokud je vše inicializované
745
662
  if (autoSave && debouncedSave) {
746
663
  debouncedSave();
747
664
  }
748
- else {
749
- }
750
665
  }, [enabled, isGridReady, isInitialized, autoSave, debouncedSave]);
666
+ // Handler pro PRŮBĚŽNÉ resize - ukládá šířky během resize operace
667
+ const handleColumnResizing = useCallback(() => {
668
+ if (!enabled || !isGridReady)
669
+ return;
670
+ // Okamžitě uložit aktuální šířky sloupců do ref map BĚHEM resize operace
671
+ try {
672
+ if (gridApiRef.current && typeof gridApiRef.current.getColumnState === 'function') {
673
+ const currentColumnState = gridApiRef.current.getColumnState();
674
+ if (currentColumnState && Array.isArray(currentColumnState)) {
675
+ // Uložit šířky do ref map pro každý sloupec
676
+ currentColumnState.forEach(colState => {
677
+ if (colState.colId && colState.width) {
678
+ columnWidthRefsMap.current.set(colState.colId, colState.width);
679
+ }
680
+ });
681
+ // Log pouze při výrazných změnách pro omezení logů
682
+ if (Math.random() < 0.1) { // 10% šance na log
683
+ console.log('[GridLayout] Updating columnWidthRefsMap during resize:', Object.fromEntries(columnWidthRefsMap.current));
684
+ }
685
+ setColumnWidthsVersion(prev => prev + 1); // Trigger preTransformedColumnDefs přepočet
686
+ }
687
+ }
688
+ }
689
+ catch (error) {
690
+ console.error('[GridLayout] Error updating columnWidthRefsMap in handleColumnResizing:', error);
691
+ }
692
+ // NEPOUŽÍVÁME setHasUnsavedChanges zde - to až v handleColumnResized
693
+ }, [enabled, isGridReady]);
694
+ // Handler pro DOKONČENÍ resize - spouští auto-save
751
695
  const handleColumnResized = useCallback(() => {
752
696
  if (!enabled || !isGridReady)
753
697
  return;
754
698
  setHasUnsavedChanges(true);
755
- // Aktualizujeme šířky sloupců v columnDefs při resize
699
+ // Ještě jednou uložit aktuální šířky sloupců do ref map (pro jistotu)
756
700
  try {
757
701
  if (gridApiRef.current && typeof gridApiRef.current.getColumnState === 'function') {
758
702
  const currentColumnState = gridApiRef.current.getColumnState();
759
703
  if (currentColumnState && Array.isArray(currentColumnState)) {
760
- // Aktualizujeme šířky v původních columnDefs
761
- const widthUpdatesMap = new Map();
704
+ // Uložit šířky do ref map pro každý sloupec
762
705
  currentColumnState.forEach(colState => {
763
706
  if (colState.colId && colState.width) {
764
- widthUpdatesMap.set(colState.colId, colState.width);
707
+ columnWidthRefsMap.current.set(colState.colId, colState.width);
765
708
  }
766
709
  });
767
- // Aktualizujeme stableColumnDefsRef s novými šířkami
768
- if (stableColumnDefsRef.current && Array.isArray(stableColumnDefsRef.current)) {
769
- const updatedColumnDefs = stableColumnDefsRef.current.map(colDef => {
770
- const fieldId = colDef.field || colDef.colId;
771
- if (fieldId && widthUpdatesMap.has(fieldId)) {
772
- return {
773
- ...colDef,
774
- width: widthUpdatesMap.get(fieldId)
775
- };
776
- }
777
- return colDef;
778
- });
779
- stableColumnDefsRef.current = updatedColumnDefs;
780
- setColumnDefsVersion(prev => prev + 1); // Trigger useMemo přepočet
781
- }
710
+ console.log('[GridLayout] Final columnWidthRefsMap after resize:', Object.fromEntries(columnWidthRefsMap.current));
711
+ setColumnWidthsVersion(prev => prev + 1); // Trigger preTransformedColumnDefs přepočet
782
712
  }
783
713
  }
784
714
  }
785
715
  catch (error) {
786
- console.error('[GridLayout] Error updating columnDefs widths in handleColumnResized:', error);
716
+ console.error('[GridLayout] Error updating columnWidthRefsMap in handleColumnResized:', error);
787
717
  }
788
718
  if (autoSave && isInitialized && debouncedSave) {
789
719
  debouncedSave();
@@ -818,161 +748,28 @@ onLayoutLoaded, onLayoutSaved, onError, }) => {
818
748
  columnApiRef.current = params.columnApi || params.api;
819
749
  // Okamžitě označíme grid jako připravený pro event handlery
820
750
  setIsGridReady(true);
821
- // Po reloadLayout() NEPOTŘEBUJEME znovu aplikovat layout - je v DB a grid ho má správně
822
- // Jen nastavíme isInitialized na true
823
- if (isInitialized === false) {
824
- setIsInitialized(true);
825
- }
751
+ // POZOR: Nenastavujeme isInitialized zde, protože by to zabránilo aplikování layoutu!
752
+ // isInitialized se nastaví po aplikování layoutu v applySavedLayout
826
753
  }, [enabled, savedFields, isInitialized, applySavedLayout]);
827
754
  /**
828
- * Effect pro aplikování layoutu když se načtou data po reloadLayout
829
- * ODSTRANĚNO - způsobovalo předčasné volání applySavedLayout() před grid ready
755
+ * Effect pro aplikování layoutu při prvotním načtení savedFields
830
756
  */
831
- // useEffect(() => {
832
- // // Po refetchFields() (z reloadLayout) potřebujeme aplikovat layout a obnovit isGridReady
833
- // if (savedFields?.records && !isInitialized && gridApiRef.current) {
834
- // console.log('[GridLayout] Effect: Applying layout after refetch and setting isGridReady=true');
835
- // applySavedLayout();
836
- // }
837
- // }, [savedFields, isInitialized, applySavedLayout]);
838
- /**
839
- * Původní zakomentovaný effect pro aplikování layoutu když se načtou data
840
- */
841
- // useEffect(() => {
842
- // if (savedFields?.records && columnApiRef.current && !isInitialized) {
843
- // console.log('[GridLayout] Layout will be applied from effect');
844
- // applySavedLayout();
845
- // } else if (savedFields?.records && columnApiRef.current && isInitialized) {
846
- // // Pokud je již inicializován, ale přišla nová data, aktualizujeme jen DOM (ne celý layout)
847
- // console.log('[GridLayout] Already initialized, but received new data - updating headers directly');
848
- // // Počkáme moment, než se nová data zpracují, a pak aktualizujeme headerName
849
- // setTimeout(() => {
850
- // try {
851
- // // Vytvoříme mapu fieldName -> headerName z API dat
852
- // const headerNameMap = new Map();
853
- // savedFields.records.forEach(field => {
854
- // if (field.fieldName && field.headerName) {
855
- // headerNameMap.set(field.fieldName, field.headerName);
856
- // console.log(`[GridLayout] Update after reload: '${field.fieldName}' -> '${field.headerName}'`);
857
- // }
858
- // });
859
- // // Najdeme všechny hlavičky sloupců v DOM
860
- // const headerCells = document.querySelectorAll('.ag-header-cell');
861
- // console.log('[GridLayout] Found header cells after reload:', headerCells.length);
862
- // // Aktualizujeme texty hlaviček
863
- // headerCells.forEach(headerCell => {
864
- // try {
865
- // // Získáme ID sloupce z DOM atributů
866
- // const colId = headerCell.getAttribute('col-id');
867
- // if (colId && headerNameMap.has(colId)) {
868
- // // Najdeme element s textem hlavičky
869
- // const headerTextEl = headerCell.querySelector('.ag-header-cell-text');
870
- // if (headerTextEl) {
871
- // const newHeaderName = headerNameMap.get(colId);
872
- // const currentText = headerTextEl.textContent;
873
- // console.log(`[GridLayout] DOM update after reload: Column '${colId}' header from '${currentText}' to '${newHeaderName}'`);
874
- // headerTextEl.textContent = newHeaderName;
875
- // }
876
- // }
877
- // } catch (cellError) {
878
- // console.error('[GridLayout] Error updating header cell after reload:', cellError);
879
- // }
880
- // });
881
- // // Zkusíme vynutit překreslení hlavičky
882
- // if (typeof gridApiRef.current.refreshHeader === 'function') {
883
- // console.log('[GridLayout] Forcing header refresh after reload');
884
- // gridApiRef.current.refreshHeader();
885
- // }
886
- // } catch (error) {
887
- // console.error('[GridLayout] Error updating headers after reload:', error);
888
- // }
889
- // }, 200);
890
- // }
891
- // }, [savedFields, applySavedLayout, isInitialized, columnApiRef]);
892
- /**
893
- * Alternativní metoda pro aktualizaci headerName po inicializaci gridu
894
- */
895
- // useEffect(() => {
896
- // // Pokud již proběhla inicializace a máme uložená data z API
897
- // if (isInitialized && savedFields?.records && gridApiRef.current) {
898
- // console.log('[GridLayout] Attempting alternative header update after initialization');
899
- // // Zkusíme použít metodu columnDefHeaderNameChanged, pokud je dostupná
900
- // try {
901
- // if (typeof gridApiRef.current.columnDefHeaderNameChanged === 'function') {
902
- // console.log('[GridLayout] Trying columnDefHeaderNameChanged method');
903
- // // Vytvoříme mapu fieldName -> headerName z API dat
904
- // const headerNameMap = new Map();
905
- // savedFields.records.forEach(field => {
906
- // if (field.fieldName && field.headerName) {
907
- // headerNameMap.set(field.fieldName, field.headerName);
908
- // }
909
- // });
910
- // // Získáme aktuální definice sloupců
911
- // const currentColumnDefs = gridApiRef.current.getColumnDefs();
912
- // if (currentColumnDefs && Array.isArray(currentColumnDefs)) {
913
- // currentColumnDefs.forEach(colDef => {
914
- // if (colDef.field && headerNameMap.has(colDef.field)) {
915
- // const newHeaderName = headerNameMap.get(colDef.field);
916
- // if (colDef.headerName !== newHeaderName) {
917
- // console.log(`[GridLayout] Using columnDefHeaderNameChanged for '${colDef.field}': '${colDef.headerName}' -> '${newHeaderName}'`);
918
- // colDef.headerName = newHeaderName;
919
- // gridApiRef.current.columnDefHeaderNameChanged(colDef);
920
- // }
921
- // }
922
- // });
923
- // }
924
- // }
925
- // } catch (error) {
926
- // console.error('[GridLayout] Error using columnDefHeaderNameChanged:', error);
927
- // }
928
- // // Počkáme chvíli, aby se grid stihl plně vyrenderovat
929
- // setTimeout(() => {
930
- // try {
931
- // // Získáme aktuální definice sloupců z gridu
932
- // const currentColDefs = gridApiRef.current.getColumnDefs ? gridApiRef.current.getColumnDefs() : null;
933
- // if (currentColDefs && Array.isArray(currentColDefs)) {
934
- // // Vytvoříme mapu fieldName -> headerName z API dat
935
- // const headerNameMap = new Map();
936
- // savedFields.records.forEach(field => {
937
- // if (field.fieldName && field.headerName) {
938
- // headerNameMap.set(field.fieldName, field.headerName);
939
- // }
940
- // });
941
- // // Zkontrolujeme, zda je třeba aktualizovat nějaké headery
942
- // let needsUpdate = false;
943
- // const updatedColDefs = currentColDefs.map(colDef => {
944
- // const fieldName = colDef.field;
945
- // if (fieldName && headerNameMap.has(fieldName)) {
946
- // const newHeaderName = headerNameMap.get(fieldName);
947
- // if (colDef.headerName !== newHeaderName) {
948
- // needsUpdate = true;
949
- // console.log(`[GridLayout] Alternative update: Column '${fieldName}' header from '${colDef.headerName}' to '${newHeaderName}'`);
950
- // return {
951
- // ...colDef,
952
- // headerName: newHeaderName
953
- // };
954
- // }
955
- // }
956
- // return colDef;
957
- // });
958
- // if (needsUpdate && typeof gridApiRef.current.setColumnDefs === 'function') {
959
- // console.log('[GridLayout] Applying alternative column header update');
960
- // gridApiRef.current.setColumnDefs(updatedColDefs);
961
- // // Zkusíme vynutit překreslení hlavičky
962
- // setTimeout(() => {
963
- // if (typeof gridApiRef.current.refreshHeader === 'function') {
964
- // console.log('[GridLayout] Forcing header refresh');
965
- // gridApiRef.current.refreshHeader();
966
- // }
967
- // }, 50);
968
- // }
969
- // }
970
- // } catch (error) {
971
- // console.error('[GridLayout] Error in alternative header update:', error);
972
- // }
973
- // }, 300);
974
- // }
975
- // }, [isInitialized, savedFields, gridApiRef]);
757
+ useEffect(() => {
758
+ // Aplikujeme layout pokud:
759
+ // 1. Máme savedFields z API (i když prázdné - první zobrazení modulu)
760
+ // 2. Grid je ready (má API references)
761
+ // 3. Ještě jsme neinicializovali layout
762
+ if (savedFields?.records !== undefined && gridApiRef.current && !isInitialized) {
763
+ if (savedFields.records.length > 0) {
764
+ applySavedLayout();
765
+ }
766
+ else {
767
+ // Pro prázdné savedFields jen nastavíme inicializaci
768
+ setIsInitialized(true);
769
+ setIsGridReady(true);
770
+ }
771
+ }
772
+ }, [savedFields?.records, isInitialized, applySavedLayout]);
976
773
  /**
977
774
  * Effect pro error handling
978
775
  */
@@ -1298,113 +1095,111 @@ onLayoutLoaded, onLayoutSaved, onError, }) => {
1298
1095
  return !isFieldsLoading && !isLoading;
1299
1096
  }, [waitForSavedFields, isFieldsLoading, isLoading]);
1300
1097
  // Pre-transformované columnDefs podle savedFields pro waitForSavedFields mode
1098
+ // DŮLEŽITÉ: Toto useMemo nyní obsahuje i AgGridColumns transformaci pro eliminaci circular dependency
1301
1099
  const preTransformedColumnDefs = useMemo(() => {
1302
1100
  // Použijeme aktualizované columnDefs ze stableColumnDefsRef pokud existují
1303
- const columnDefsToUse = stableColumnDefsRef.current || columnDefs;
1304
- if (!waitForSavedFields ||
1305
- !savedFields?.records ||
1306
- !Array.isArray(columnDefsToUse)) {
1307
- return columnDefsToUse; // Aktualizované columnDefs
1308
- }
1309
- // Transformujeme columnDefs podle savedFields PŘED zobrazením gridu
1310
- const columnState = gridLayoutApi.transformFieldsToColumnState(savedFields.records, columnDefsToUse);
1311
- if (!columnState || columnState.length === 0) {
1312
- return columnDefsToUse; // Pokud není co transformovat, vrátíme aktualizované
1101
+ const columnDefsToUse = stableColumnDefsRef.current; // || columnDefs; //HLO
1102
+ let baseColumnDefs = columnDefsToUse;
1103
+ // Pro waitForSavedFields aplikujeme grid layout transformace
1104
+ if (waitForSavedFields &&
1105
+ savedFields?.records &&
1106
+ Array.isArray(columnDefsToUse)) {
1107
+ // Transformujeme columnDefs podle savedFields PŘED zobrazením gridu
1108
+ const columnState = gridLayoutApi.transformFieldsToColumnState(savedFields.records, columnDefsToUse);
1109
+ if (columnState && columnState.length > 0) {
1110
+ // Vytvoříme mapu pro rychlé vyhledávání
1111
+ const columnStateMap = new Map();
1112
+ columnState.forEach((colState, index) => {
1113
+ columnStateMap.set(colState.colId, { ...colState, __order: index });
1114
+ });
1115
+ // Vytvoříme mapu fieldName -> headerName z API dat
1116
+ const headerNameMap = new Map();
1117
+ savedFields.records.forEach((field) => {
1118
+ if (field.fieldName && field.headerName) {
1119
+ headerNameMap.set(field.fieldName, field.headerName);
1120
+ }
1121
+ });
1122
+ // Seřadíme columnDefs podle pořadí z columnState a upravíme headerName podle savedFields
1123
+ baseColumnDefs = [...columnDefsToUse]
1124
+ .sort((a, b) => {
1125
+ const fieldA = a.field || a.colId;
1126
+ const fieldB = b.field || b.colId;
1127
+ const aState = columnStateMap.get(fieldA);
1128
+ const bState = columnStateMap.get(fieldB);
1129
+ const aOrder = aState?.__order ?? 999;
1130
+ const bOrder = bState?.__order ?? 999;
1131
+ return aOrder - bOrder;
1132
+ })
1133
+ .map((colDef) => {
1134
+ const fieldId = colDef.field || colDef.colId;
1135
+ const columnStateItem = columnStateMap.get(fieldId);
1136
+ // Aplikujeme headerName, hide i width z API pro waitForSavedFields režim
1137
+ if (fieldId && (headerNameMap.has(fieldId) || columnStateItem)) {
1138
+ return {
1139
+ ...colDef,
1140
+ // Aplikujeme headerName z savedFields
1141
+ ...(headerNameMap.has(fieldId) && { headerName: headerNameMap.get(fieldId) }),
1142
+ // Aplikujeme hide hodnotu z columnState (z API show hodnoty)
1143
+ ...(columnStateItem && { hide: columnStateItem.hide }),
1144
+ // Aplikujeme také width pro konzistentní zobrazení
1145
+ ...(columnStateItem && columnStateItem.width && { width: columnStateItem.width }),
1146
+ };
1147
+ }
1148
+ return colDef;
1149
+ });
1150
+ }
1313
1151
  }
1314
- // Vytvoříme mapu pro rychlé vyhledávání
1315
- const columnStateMap = new Map();
1316
- columnState.forEach((colState, index) => {
1317
- columnStateMap.set(colState.colId, { ...colState, __order: index });
1318
- });
1319
- // Vytvoříme mapu fieldName -> headerName z API dat
1320
- const headerNameMap = new Map();
1321
- savedFields.records.forEach((field) => {
1322
- if (field.fieldName && field.headerName) {
1323
- headerNameMap.set(field.fieldName, field.headerName);
1152
+ // Nyní VŽDY aplikujeme AgGridColumns transformaci PŘÍMO zde místo v props
1153
+ // Tím eliminujeme circular dependency pro všechny případy použití
1154
+ if (!Array.isArray(baseColumnDefs)) {
1155
+ // Pokud baseColumnDefs není array, mohlo by to být ColumnBuilder
1156
+ if (baseColumnDefs && typeof baseColumnDefs.build === 'function') {
1157
+ baseColumnDefs = baseColumnDefs.build();
1324
1158
  }
1325
- });
1326
- // Seřadíme columnDefs podle pořadí z columnState a upravíme headerName podle savedFields
1327
- const sortedColumnDefs = [...columnDefsToUse]
1328
- .sort((a, b) => {
1329
- const fieldA = a.field || a.colId;
1330
- const fieldB = b.field || b.colId;
1331
- const aState = columnStateMap.get(fieldA);
1332
- const bState = columnStateMap.get(fieldB);
1333
- const aOrder = aState?.__order ?? 999;
1334
- const bOrder = bState?.__order ?? 999;
1335
- return aOrder - bOrder;
1336
- })
1337
- .map((colDef) => {
1159
+ else {
1160
+ return [];
1161
+ }
1162
+ }
1163
+ // Aplikujeme ref šířky jako poslední krok POUZE pokud už byla provedena inicializace
1164
+ // Při prvotním načítání zachováváme API šířky (které jsou už v baseColumnDefs pro waitForSavedFields)
1165
+ const finalColumnDefs = baseColumnDefs.map(colDef => {
1338
1166
  const fieldId = colDef.field || colDef.colId;
1339
- const columnStateItem = columnStateMap.get(fieldId);
1340
- // Aplikujeme jak headerName tak hide hodnotu z API
1341
- if (fieldId && (headerNameMap.has(fieldId) || columnStateItem)) {
1167
+ const refWidth = columnWidthRefsMap.current.get(fieldId);
1168
+ // Použijeme ref šířku POUZE pokud je grid inicializovaný (uživatel manipuloval s šířkami)
1169
+ if (refWidth !== undefined && isInitialized) {
1342
1170
  return {
1343
1171
  ...colDef,
1344
- // Aplikujeme headerName z savedFields
1345
- ...(headerNameMap.has(fieldId) && { headerName: headerNameMap.get(fieldId) }),
1346
- // Aplikujeme hide hodnotu z columnState (z API show hodnoty)
1347
- ...(columnStateItem && { hide: columnStateItem.hide }),
1172
+ width: refWidth
1348
1173
  };
1349
1174
  }
1350
1175
  return colDef;
1351
1176
  });
1352
- return sortedColumnDefs;
1353
- }, [waitForSavedFields, savedFields?.records, columnDefs, gridLayoutApi, columnDefsVersion]);
1354
- /**
1355
- * Handler pro změnu zobrazených dat - aktualizace headerName
1356
- */
1357
- const handleRowDataChanged = useCallback((params) => {
1358
- // if (!enabled || !savedFields?.records || !isInitialized) return;
1359
- // console.log('[GridLayout] Row data changed - ensuring header names stay updated');
1360
- // // Počkáme chvíli, až se vše vyrenderuje, a pak aktualizujeme headerName
1361
- // setTimeout(() => {
1362
- // try {
1363
- // // Vytvoříme mapu fieldName -> headerName z API dat
1364
- // const headerNameMap = new Map();
1365
- // savedFields.records.forEach(field => {
1366
- // if (field.fieldName && field.headerName) {
1367
- // headerNameMap.set(field.fieldName, field.headerName);
1368
- // }
1369
- // });
1370
- // // Najdeme všechny hlavičky sloupců v DOM
1371
- // const headerCells = document.querySelectorAll('.ag-header-cell');
1372
- // console.log('[GridLayout] Found header cells after data change:', headerCells.length);
1373
- // // Aktualizujeme texty hlaviček
1374
- // headerCells.forEach(headerCell => {
1375
- // try {
1376
- // // Získáme ID sloupce z DOM atributů
1377
- // const colId = headerCell.getAttribute('col-id');
1378
- // if (colId && headerNameMap.has(colId)) {
1379
- // // Najdeme element s textem hlavičky
1380
- // const headerTextEl = headerCell.querySelector('.ag-header-cell-text');
1381
- // if (headerTextEl) {
1382
- // const newHeaderName = headerNameMap.get(colId);
1383
- // const currentText = headerTextEl.textContent;
1384
- // if (currentText !== newHeaderName) {
1385
- // console.log(`[GridLayout] Data change update: Column '${colId}' header from '${currentText}' to '${newHeaderName}'`);
1386
- // headerTextEl.textContent = newHeaderName;
1387
- // }
1388
- // }
1389
- // }
1390
- // } catch (cellError) {
1391
- // console.error('[GridLayout] Error updating header cell after data change:', cellError);
1392
- // }
1393
- // });
1394
- // // Zkusíme vynutit překreslení hlavičky
1395
- // if (typeof gridApiRef.current.refreshHeader === 'function') {
1396
- // console.log('[GridLayout] Forcing header refresh after data change');
1397
- // gridApiRef.current.refreshHeader();
1177
+ return finalColumnDefs;
1178
+ // // Replikujeme AgGridColumns logiku pro všechny column definitions:
1179
+ // const processedColumns = baseColumnDefs?.map((column) => {
1180
+ // // Zkopírujeme column aby se předešlo mutacím
1181
+ // const processedColumn = { ...column };
1182
+ // // Aplikujeme tooltip logiku pokud je potřeba
1183
+ // if (processedColumn.contentTooltip) {
1184
+ // // Statické mapování tooltipů pro lepší performance a eliminaci circular dependency
1185
+ // switch (processedColumn.contentTooltip) {
1186
+ // case 'User':
1187
+ // processedColumn.tooltipComponent = Tooltip.User;
1188
+ // break;
1189
+ // default:
1190
+ // console.warn(`[GridLayout] Unknown tooltip component: ${processedColumn.contentTooltip}`);
1398
1191
  // }
1399
- // } catch (error) {
1400
- // console.error('[GridLayout] Error updating headers after data change:', error);
1401
1192
  // }
1402
- // }, 100);
1403
- }, [enabled, savedFields, isInitialized]);
1193
+ // return processedColumn;
1194
+ // }) || [];
1195
+ // return processedColumns;
1196
+ }, [waitForSavedFields, savedFields?.records, columnDefs, gridLayoutApi, columnWidthsVersion, isInitialized]);
1197
+ console.log("rerender");
1404
1198
  return {
1405
1199
  // State
1406
1200
  isLoading: isLoading || isFieldsLoading,
1407
1201
  isSaving,
1202
+ isApplyingLayout, // Nový specifický state pro aplikování layoutu
1408
1203
  error,
1409
1204
  hasUnsavedChanges,
1410
1205
  isInitialized,
@@ -1415,10 +1210,11 @@ onLayoutLoaded, onLayoutSaved, onError, }) => {
1415
1210
  onGridReady: handleGridReady,
1416
1211
  onColumnMoved: handleColumnMoved,
1417
1212
  onDragStopped: handleDragStopped, // Nový handler pro konec drag operace
1213
+ onColumnResizing: handleColumnResizing, // NOVÝ handler pro průběžné resize
1418
1214
  onColumnResized: handleColumnResized,
1419
1215
  onColumnVisible: handleColumnVisible,
1420
1216
  onColumnPinned: handleColumnPinned,
1421
- onRowDataChanged: handleRowDataChanged, // Nový handler pro změnu dat
1217
+ // onRowDataChanged: handleRowDataChanged, // Nový handler pro změnu dat
1422
1218
  // Manual actions
1423
1219
  saveLayout,
1424
1220
  resetToDefault,