@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.
package/useGridLayout.js CHANGED
@@ -7,6 +7,8 @@
7
7
  import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
8
8
  import { useGridLayoutApi } from './useGridLayoutApi';
9
9
  import { debounce } from 'lodash';
10
+ // Import tooltip komponent pro eliminaci circular dependency
11
+ // import * as Tooltip from '../grid/tooltips';
10
12
 
11
13
  /**
12
14
  * Hook pro správu grid layout s automatickým ukládáním
@@ -17,7 +19,7 @@ import { debounce } from 'lodash';
17
19
  * @param {string} [config.filterName] - Název filtru (volitelné)
18
20
  * @param {boolean} [config.enabled=true] - Zapnout/vypnout layout management
19
21
  * @param {boolean} [config.autoSave=true] - Automatické ukládání při změnách
20
- * @param {number} [config.autoSaveDelay=2000] - Zpoždění auto-save v ms
22
+ * @param {number} [config.autoSaveDelay=500] - Zpoždění auto-save v ms
21
23
  * @param {Array} config.columnDefs - AG-Grid column definitions
22
24
  * @param {string} [config.accessToken] - Přístupový token
23
25
  * @param {boolean} [config.waitForSavedFields=false] - Skrýt columnDefs dokud nejsou načtena savedFields
@@ -33,7 +35,7 @@ export const useGridLayout = ({
33
35
  filterName,
34
36
  enabled = true,
35
37
  autoSave = true,
36
- autoSaveDelay = 2000,
38
+ autoSaveDelay = 500,
37
39
  columnDefs = [],
38
40
  accessToken,
39
41
  waitForSavedFields = false, // Nový prop pro odložené zobrazení columnDefs
@@ -59,17 +61,22 @@ export const useGridLayout = ({
59
61
  // Refs pro AG-Grid API
60
62
  const gridApiRef = useRef(null);
61
63
  const columnApiRef = useRef(null);
64
+ // Ref mapa pro ukládání aktuálních šířek sloupců (fieldId -> šířka)
65
+ const columnWidthRefsMap = useRef(new Map());
62
66
 
63
67
  // State
64
68
  const [isInitialized, setIsInitialized] = useState(false);
65
69
  const [isGridReady, setIsGridReady] = useState(false);
66
70
  const [isLoading, setIsLoading] = useState(false);
67
71
  const [isSaving, setIsSaving] = useState(false);
72
+ const [isApplyingLayout, setIsApplyingLayout] = useState(false);
68
73
  const [error, setError] = useState(null);
69
74
  const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
70
75
  const [isColumnEditorOpen, setIsColumnEditorOpen] = useState(false);
71
76
  const [lastKnownColumnState, setLastKnownColumnState] = useState(null);
72
- const [columnDefsVersion, setColumnDefsVersion] = useState(0); // Pro tracking změn v stableColumnDefsRef
77
+ const [columnWidthsVersion, setColumnWidthsVersion] = useState(0); // Pro trigger preTransformedColumnDefs
78
+
79
+
73
80
 
74
81
  // Grid Layout API hook
75
82
  const gridLayoutApi = useGridLayoutApi({
@@ -109,123 +116,6 @@ export const useGridLayout = ({
109
116
  }
110
117
  );
111
118
 
112
- // // Bezpečné aktualizování lastKnownColumnState při otevření editoru
113
- // useEffect(() => {
114
- // if (isColumnEditorOpen && columnApiRef.current && typeof columnApiRef.current.getColumnState === 'function') {
115
- // try {
116
- // const currentState = columnApiRef.current.getColumnState();
117
- // if (Array.isArray(currentState) && currentState.length > 0) {
118
- // // Aktualizujeme lastKnownColumnState po otevření editoru
119
- // const validColumnDefs = Array.isArray(columnDefs) ? columnDefs : [];
120
- // const formattedState = currentState.map((columnStateItem, index) => {
121
- // const colDef = validColumnDefs.find(cd => cd.field === columnStateItem.colId) || {};
122
- // const savedField = savedFields?.records?.find(sf => sf.fieldName === columnStateItem.colId);
123
-
124
- // return {
125
- // id: columnStateItem.colId,
126
- // field: columnStateItem.colId,
127
- // headerName: savedField?.headerName || colDef.headerName || columnStateItem.colId,
128
- // originalHeaderName: colDef.headerName || columnStateItem.colId,
129
- // width: columnStateItem.width || colDef.width || 100,
130
- // originalWidth: colDef.width || 100,
131
- // visible: !columnStateItem.hide,
132
- // order: index
133
- // };
134
- // });
135
-
136
- // setLastKnownColumnState(formattedState);
137
- // console.log('[GridLayout] Updated lastKnownColumnState after opening editor');
138
- // }
139
- // } catch (error) {
140
- // console.error('[GridLayout] Error updating column state after opening editor:', error);
141
- // }
142
- // }
143
- // }, [isColumnEditorOpen, columnDefs, savedFields, columnApiRef]);
144
-
145
- // MutationObserver pro sledování změn v DOM a okamžitou aktualizaci headerName
146
- // useEffect(() => {
147
- // if (!savedFields?.records || !isInitialized || !enabled) return;
148
-
149
- // // Reference na observer pro cleanup
150
- // let observer = null;
151
-
152
- // try {
153
- // console.log('[GridLayout] Setting up MutationObserver for header changes');
154
-
155
- // // Funkce pro aktualizaci headerName
156
- // const updateHeaderNames = () => {
157
- // try {
158
- // // Vytvoříme mapu fieldName -> headerName z API dat
159
- // const headerNameMap = new Map();
160
- // savedFields.records.forEach(field => {
161
- // if (field.fieldName && field.headerName) {
162
- // headerNameMap.set(field.fieldName, field.headerName);
163
- // }
164
- // });
165
-
166
- // // Najdeme všechny hlavičky sloupců v DOM
167
- // const headerCells = document.querySelectorAll('.ag-header-cell');
168
-
169
- // // Aktualizujeme texty hlaviček
170
- // headerCells.forEach(headerCell => {
171
- // try {
172
- // // Získáme ID sloupce z DOM atributů
173
- // const colId = headerCell.getAttribute('col-id');
174
- // if (colId && headerNameMap.has(colId)) {
175
- // // Najdeme element s textem hlavičky
176
- // const headerTextEl = headerCell.querySelector('.ag-header-cell-text');
177
- // if (headerTextEl) {
178
- // const newHeaderName = headerNameMap.get(colId);
179
- // const currentText = headerTextEl.textContent;
180
- // if (currentText !== newHeaderName) {
181
- // console.log(`[GridLayout] MutationObserver update: Column '${colId}' header from '${currentText}' to '${newHeaderName}'`);
182
- // headerTextEl.textContent = newHeaderName;
183
- // }
184
- // }
185
- // }
186
- // } catch (cellError) {
187
- // // Tiché selhání - nechceme, aby MutationObserver padal
188
- // }
189
- // });
190
- // } catch (error) {
191
- // // Tiché selhání - nechceme, aby MutationObserver padal
192
- // }
193
- // };
194
-
195
- // // Najdeme element hlavičky
196
- // const headerElement = document.querySelector('.ag-header');
197
- // if (headerElement) {
198
- // // Vytvoříme observer, který bude sledovat změny v hlavičce
199
- // observer = new MutationObserver((mutations) => {
200
- // // Detekovali jsme změnu v DOM hlavičky, aktualizujeme headerName
201
- // updateHeaderNames();
202
- // });
203
-
204
- // // Začneme sledovat změny v hlavičce
205
- // observer.observe(headerElement, {
206
- // childList: true, // sledujeme přidání/odebírání elementů
207
- // subtree: true, // sledujeme změny i v potomcích
208
- // characterData: true, // sledujeme změny textu
209
- // attributeFilter: ['col-id', 'class'] // sledujeme změny těchto atributů
210
- // });
211
-
212
- // console.log('[GridLayout] MutationObserver set up successfully');
213
- // } else {
214
- // console.log('[GridLayout] Header element not found for MutationObserver');
215
- // }
216
- // } catch (error) {
217
- // console.error('[GridLayout] Error setting up MutationObserver:', error);
218
- // }
219
-
220
- // // Cleanup - odpojit observer při unmount
221
- // return () => {
222
- // if (observer) {
223
- // observer.disconnect();
224
- // console.log('[GridLayout] MutationObserver disconnected');
225
- // }
226
- // };
227
- // }, [savedFields, isInitialized, enabled]);
228
-
229
119
  /**
230
120
  * Error handler
231
121
  */
@@ -248,42 +138,39 @@ export const useGridLayout = ({
248
138
  // Reference pro ukládání stavu sloupců bez state update v render cyklu
249
139
  const stableCurrentColumnsRef = useRef(null);
250
140
 
251
- // Aktualizujeme ref hodnoty
141
+ // Jednoduchá aktualizace ref hodnot - BEZ aktualizace columnDefs
252
142
  useEffect(() => {
253
- // Pro columnDefs zachováváme aktualizované šířky pokud existují
254
- if (stableColumnDefsRef.current && Array.isArray(stableColumnDefsRef.current) && Array.isArray(columnDefs)) {
255
- // Vytvoříme mapu existujících šířek
256
- const existingWidthsMap = new Map();
257
- stableColumnDefsRef.current.forEach(colDef => {
258
- const fieldId = colDef.field || colDef.colId;
259
- if (fieldId && colDef.width) {
260
- existingWidthsMap.set(fieldId, colDef.width);
261
- }
262
- });
263
-
264
- // Aktualizujeme columnDefs s existujícími šířkami
265
- const mergedColumnDefs = columnDefs.map(colDef => {
266
- const fieldId = colDef.field || colDef.colId;
267
- if (fieldId && existingWidthsMap.has(fieldId)) {
268
- return {
269
- ...colDef,
270
- width: existingWidthsMap.get(fieldId)
271
- };
272
- }
273
- return colDef;
274
- });
143
+ // Při změně columnDefs vyčistíme columnWidthRefsMap a přeneseme existující šířky
144
+ if (columnDefs !== stableColumnDefsRef.current) {
145
+ const newWidthMap = new Map();
275
146
 
276
- stableColumnDefsRef.current = mergedColumnDefs;
277
- setColumnDefsVersion(prev => prev + 1); // Trigger useMemo přepočet
278
- } else {
279
- // První nastavení nebo pokud nejsou dostupné předchozí data
280
- stableColumnDefsRef.current = columnDefs;
147
+ // Zachováme šířky pro sloupce, které stále existují
148
+ if (Array.isArray(columnDefs)) {
149
+ columnDefs.forEach(colDef => {
150
+ const fieldId = colDef.field || colDef.colId;
151
+ if (fieldId) {
152
+ // Zkusíme najít existující šířku v ref map
153
+ const existingWidth = columnWidthRefsMap.current.get(fieldId);
154
+ if (existingWidth !== undefined) {
155
+ newWidthMap.set(fieldId, existingWidth);
156
+ } else if (colDef.width) {
157
+ // Použijeme šířku z nového columnDef
158
+ newWidthMap.set(fieldId, colDef.width);
159
+ }
160
+ }
161
+ });
162
+ }
163
+
164
+ columnWidthRefsMap.current = newWidthMap;
165
+ console.log('[GridLayout] Cleanup columnWidthRefsMap při změně columnDefs:', Object.fromEntries(newWidthMap));
166
+ setColumnWidthsVersion(prev => prev + 1); // Trigger preTransformedColumnDefs přepočet
281
167
  }
282
-
168
+
169
+ stableColumnDefsRef.current = columnDefs;
283
170
  stableGridLayoutApiRef.current = gridLayoutApi;
284
171
  stableOnLayoutSavedRef.current = onLayoutSaved;
285
172
  stableHandleErrorRef.current = handleError;
286
- });
173
+ }, [columnDefs, gridLayoutApi, onLayoutSaved, handleError]);
287
174
 
288
175
  // Efekt pro bezpečnou aktualizaci lastKnownColumnState z ref
289
176
  // useEffect(() => {
@@ -299,8 +186,6 @@ export const useGridLayout = ({
299
186
  * Uloží současný stav sloupců do API
300
187
  */
301
188
  const saveCurrentLayout = useCallback(async () => {
302
-
303
-
304
189
  if (!enabled || !gridApiRef.current) {
305
190
  return;
306
191
  }
@@ -598,8 +483,9 @@ export const useGridLayout = ({
598
483
  }
599
484
 
600
485
  try {
601
-
602
486
  setIsLoading(true);
487
+ setIsApplyingLayout(true);
488
+
603
489
  // Transformujeme Grid API fields na AG-Grid column state
604
490
  // Použijeme stableColumnDefsRef.current místo columnDefs pro zachování aktuálních šířek
605
491
  const columnDefsToUse = stableColumnDefsRef.current || columnDefs;
@@ -609,6 +495,16 @@ export const useGridLayout = ({
609
495
  );
610
496
 
611
497
  if (columnState && columnState.length > 0) {
498
+ // Při prvotním načítání inicializujeme ref mapu s API šířkami
499
+ if (!isInitialized) {
500
+ columnState.forEach(colState => {
501
+ if (colState.colId && colState.width) {
502
+ columnWidthRefsMap.current.set(colState.colId, colState.width);
503
+ }
504
+ });
505
+ console.log('[GridLayout] Inicializace columnWidthRefsMap z API:', Object.fromEntries(columnWidthRefsMap.current));
506
+ }
507
+
612
508
  // Pokud je waitForSavedFields true, columnDefs jsou už pre-transformované,
613
509
  // takže aplikujeme jen width a hide vlastnosti bez delay pro pořadí
614
510
  const applyFunction = () => {
@@ -618,27 +514,30 @@ export const useGridLayout = ({
618
514
  try {
619
515
  const applyOrderEnabled = !waitForSavedFields; // Při waitForSavedFields už je pořadí v columnDefs
620
516
 
621
- // Získáme aktuální stav sloupců před aplikováním
622
- const currentColumnState = gridApiRef.current?.getColumnState?.() || [];
623
- const currentWidthMap = new Map();
624
- currentColumnState.forEach(colState => {
625
- if (colState.colId && colState.width) {
626
- currentWidthMap.set(colState.colId, colState.width);
627
- }
628
- });
629
-
630
- // Upravíme columnState tak, aby zachoval aktuální šířky pokud existují
631
- const adjustedColumnState = columnState.map(colState => {
632
- const currentWidth = currentWidthMap.get(colState.colId);
633
- if (currentWidth && currentWidth !== colState.width) {
517
+ // Upravíme columnState s aktuálními šířkami z ref map
518
+ // POUZE pokud ref mapa obsahuje hodnoty (tj. uživatel už manipuloval s šířkami)
519
+ // Při prvotním načítání z API zachováme API šířky
520
+ let adjustedColumnState = columnState.map(colState => {
521
+ // Zkontrolujeme, zda máme ref hodnotu pro tento sloupec
522
+ const refWidth = columnWidthRefsMap.current.get(colState.colId);
523
+ // Použijeme ref hodnotu POUZE pokud existuje A není to prvotní načítání
524
+ if (refWidth !== undefined && isInitialized) {
634
525
  return {
635
526
  ...colState,
636
- width: currentWidth
527
+ width: refWidth
637
528
  };
638
529
  }
530
+ // Při prvotním načítání nebo pokud nemáme ref hodnotu, zachováme API šířku
639
531
  return colState;
640
532
  });
641
533
 
534
+ console.log('[GridLayout] Aplikování column state s ref šířkami:', {
535
+ columnStateLength: adjustedColumnState?.length,
536
+ applyOrderEnabled,
537
+ firstColumnWidth: adjustedColumnState?.[0]?.width,
538
+ refWidthsCount: columnWidthRefsMap.current.size
539
+ });
540
+
642
541
  result = applyColumnStateApi.applyColumnState({
643
542
  state: adjustedColumnState,
644
543
  applyOrder: applyOrderEnabled, // Pořadí jen když není waitForSavedFields
@@ -650,6 +549,8 @@ export const useGridLayout = ({
650
549
  },
651
550
  });
652
551
 
552
+ console.log('[GridLayout] Column state aplikován úspěšně');
553
+
653
554
  // Explicitně aktualizujeme headerName pro každý sloupec, protože AG-Grid
654
555
  // nepodporuje nastavení headerName přes applyColumnState
655
556
  if (
@@ -715,14 +616,23 @@ export const useGridLayout = ({
715
616
  // Pro jistotu zkontrolujeme, zda byly změny aplikovány
716
617
  setTimeout(() => {
717
618
  try {
718
- const afterUpdateColDefs =
719
- gridApiRef.current.getColumnDefs();
720
-
619
+ const afterUpdateColDefs = gridApiRef.current.getColumnDefs();
620
+ const afterUpdateColumnState = gridApiRef.current.getColumnState?.() || [];
621
+
622
+ console.log('[GridLayout] Po aplikování - kontrola šířek:', {
623
+ columnCount: afterUpdateColumnState.length,
624
+ widths: afterUpdateColumnState.map(col => ({ field: col.colId, width: col.width }))
625
+ });
626
+
627
+ // DOM operace dokončeny, můžeme ukončit loading
628
+ setIsApplyingLayout(false);
721
629
  } catch (checkError) {
722
630
  console.error(
723
631
  '[GridLayout] Error checking updated columns:',
724
632
  checkError
725
633
  );
634
+ // I při chybě ukončíme loading
635
+ setIsApplyingLayout(false);
726
636
  }
727
637
  }, 100);
728
638
  } else {
@@ -770,11 +680,16 @@ export const useGridLayout = ({
770
680
  );
771
681
  }
772
682
  });
683
+
684
+ // DOM manipulace dokončena, ukončíme loading
685
+ setIsApplyingLayout(false);
773
686
  } catch (domError) {
774
687
  console.error(
775
688
  '[GridLayout] Error in DOM manipulation:',
776
689
  domError
777
690
  );
691
+ // I při chybě ukončíme loading
692
+ setIsApplyingLayout(false);
778
693
  }
779
694
  }, 200);
780
695
  }
@@ -796,6 +711,11 @@ export const useGridLayout = ({
796
711
  } catch (delayedError) {
797
712
  // Removed console.error for production
798
713
  handleError(delayedError, 'při delayed aplikování layoutu');
714
+ } finally {
715
+ // Bezpečnostní ukončení loading pokud se nedokončilo jinde
716
+ setTimeout(() => {
717
+ setIsApplyingLayout(false);
718
+ }, 300);
799
719
  }
800
720
  };
801
721
 
@@ -813,6 +733,7 @@ export const useGridLayout = ({
813
733
  setIsInitialized(true);
814
734
  setIsGridReady(true); // Obnovíme také isGridReady pro event handlery
815
735
  setIsLoading(false);
736
+ // setIsApplyingLayout(false) se nastaví až po dokončení všech DOM operací
816
737
  }
817
738
  },
818
739
  [
@@ -869,39 +790,25 @@ export const useGridLayout = ({
869
790
 
870
791
  setHasUnsavedChanges(true);
871
792
 
872
- // Aktualizujeme šířky sloupců v columnDefs před uložením
793
+ // Okamžitě uložit aktuální šířky sloupců do ref map (podobně jako v handleColumnResized)
873
794
  try {
874
795
  if (gridApiRef.current && typeof gridApiRef.current.getColumnState === 'function') {
875
796
  const currentColumnState = gridApiRef.current.getColumnState();
876
797
 
877
798
  if (currentColumnState && Array.isArray(currentColumnState)) {
878
- // Aktualizujeme šířky v původních columnDefs
879
- const widthUpdatesMap = new Map();
799
+ // Uložit šířky do ref map pro každý sloupec
880
800
  currentColumnState.forEach(colState => {
881
801
  if (colState.colId && colState.width) {
882
- widthUpdatesMap.set(colState.colId, colState.width);
802
+ columnWidthRefsMap.current.set(colState.colId, colState.width);
883
803
  }
884
804
  });
885
805
 
886
- // Aktualizujeme stableColumnDefsRef s novými šířkami
887
- if (stableColumnDefsRef.current && Array.isArray(stableColumnDefsRef.current)) {
888
- const updatedColumnDefs = stableColumnDefsRef.current.map(colDef => {
889
- const fieldId = colDef.field || colDef.colId;
890
- if (fieldId && widthUpdatesMap.has(fieldId)) {
891
- return {
892
- ...colDef,
893
- width: widthUpdatesMap.get(fieldId)
894
- };
895
- }
896
- return colDef;
897
- });
898
- stableColumnDefsRef.current = updatedColumnDefs;
899
- setColumnDefsVersion(prev => prev + 1); // Trigger useMemo přepočet
900
- }
806
+ console.log('[GridLayout] Updated columnWidthRefsMap in dragStopped:', Object.fromEntries(columnWidthRefsMap.current));
807
+ setColumnWidthsVersion(prev => prev + 1); // Trigger preTransformedColumnDefs přepočet
901
808
  }
902
809
  }
903
810
  } catch (error) {
904
- console.error('[GridLayout] Error updating columnDefs widths in handleDragStopped:', error);
811
+ console.error('[GridLayout] Error updating columnWidthRefsMap in handleDragStopped:', error);
905
812
  }
906
813
 
907
814
  // Pokud ještě není inicializované, přidáme akci do pending
@@ -913,50 +820,66 @@ export const useGridLayout = ({
913
820
  return;
914
821
  }
915
822
 
916
- // Normální proces pokud je vše inicializované
917
823
  if (autoSave && debouncedSave) {
918
824
  debouncedSave();
919
- } else {
920
825
  }
921
826
  }, [enabled, isGridReady, isInitialized, autoSave, debouncedSave]);
922
827
 
828
+ // Handler pro PRŮBĚŽNÉ resize - ukládá šířky během resize operace
829
+ const handleColumnResizing = useCallback(() => {
830
+ if (!enabled || !isGridReady) return;
831
+
832
+ // Okamžitě uložit aktuální šířky sloupců do ref map BĚHEM resize operace
833
+ try {
834
+ if (gridApiRef.current && typeof gridApiRef.current.getColumnState === 'function') {
835
+ const currentColumnState = gridApiRef.current.getColumnState();
836
+
837
+ if (currentColumnState && Array.isArray(currentColumnState)) {
838
+ // Uložit šířky do ref map pro každý sloupec
839
+ currentColumnState.forEach(colState => {
840
+ if (colState.colId && colState.width) {
841
+ columnWidthRefsMap.current.set(colState.colId, colState.width);
842
+ }
843
+ });
844
+
845
+ // Log pouze při výrazných změnách pro omezení logů
846
+ if (Math.random() < 0.1) { // 10% šance na log
847
+ console.log('[GridLayout] Updating columnWidthRefsMap during resize:', Object.fromEntries(columnWidthRefsMap.current));
848
+ }
849
+ setColumnWidthsVersion(prev => prev + 1); // Trigger preTransformedColumnDefs přepočet
850
+ }
851
+ }
852
+ } catch (error) {
853
+ console.error('[GridLayout] Error updating columnWidthRefsMap in handleColumnResizing:', error);
854
+ }
855
+
856
+ // NEPOUŽÍVÁME setHasUnsavedChanges zde - to až v handleColumnResized
857
+ }, [enabled, isGridReady]);
858
+
859
+ // Handler pro DOKONČENÍ resize - spouští auto-save
923
860
  const handleColumnResized = useCallback(() => {
924
861
  if (!enabled || !isGridReady) return;
925
862
  setHasUnsavedChanges(true);
926
863
 
927
- // Aktualizujeme šířky sloupců v columnDefs při resize
864
+ // Ještě jednou uložit aktuální šířky sloupců do ref map (pro jistotu)
928
865
  try {
929
866
  if (gridApiRef.current && typeof gridApiRef.current.getColumnState === 'function') {
930
867
  const currentColumnState = gridApiRef.current.getColumnState();
931
868
 
932
869
  if (currentColumnState && Array.isArray(currentColumnState)) {
933
- // Aktualizujeme šířky v původních columnDefs
934
- const widthUpdatesMap = new Map();
870
+ // Uložit šířky do ref map pro každý sloupec
935
871
  currentColumnState.forEach(colState => {
936
872
  if (colState.colId && colState.width) {
937
- widthUpdatesMap.set(colState.colId, colState.width);
873
+ columnWidthRefsMap.current.set(colState.colId, colState.width);
938
874
  }
939
875
  });
940
876
 
941
- // Aktualizujeme stableColumnDefsRef s novými šířkami
942
- if (stableColumnDefsRef.current && Array.isArray(stableColumnDefsRef.current)) {
943
- const updatedColumnDefs = stableColumnDefsRef.current.map(colDef => {
944
- const fieldId = colDef.field || colDef.colId;
945
- if (fieldId && widthUpdatesMap.has(fieldId)) {
946
- return {
947
- ...colDef,
948
- width: widthUpdatesMap.get(fieldId)
949
- };
950
- }
951
- return colDef;
952
- });
953
- stableColumnDefsRef.current = updatedColumnDefs;
954
- setColumnDefsVersion(prev => prev + 1); // Trigger useMemo přepočet
955
- }
877
+ console.log('[GridLayout] Final columnWidthRefsMap after resize:', Object.fromEntries(columnWidthRefsMap.current));
878
+ setColumnWidthsVersion(prev => prev + 1); // Trigger preTransformedColumnDefs přepočet
956
879
  }
957
880
  }
958
881
  } catch (error) {
959
- console.error('[GridLayout] Error updating columnDefs widths in handleColumnResized:', error);
882
+ console.error('[GridLayout] Error updating columnWidthRefsMap in handleColumnResized:', error);
960
883
  }
961
884
 
962
885
  if (autoSave && isInitialized && debouncedSave) {
@@ -996,179 +919,32 @@ export const useGridLayout = ({
996
919
  // Okamžitě označíme grid jako připravený pro event handlery
997
920
  setIsGridReady(true);
998
921
 
999
- // Po reloadLayout() NEPOTŘEBUJEME znovu aplikovat layout - je v DB a grid ho má správně
1000
- // Jen nastavíme isInitialized na true
1001
- if (isInitialized === false) {
1002
- setIsInitialized(true);
1003
- }
922
+ // POZOR: Nenastavujeme isInitialized zde, protože by to zabránilo aplikování layoutu!
923
+ // isInitialized se nastaví po aplikování layoutu v applySavedLayout
1004
924
  },
1005
925
  [enabled, savedFields, isInitialized, applySavedLayout]
1006
926
  );
1007
927
 
1008
928
  /**
1009
- * Effect pro aplikování layoutu když se načtou data po reloadLayout
1010
- * ODSTRANĚNO - způsobovalo předčasné volání applySavedLayout() před grid ready
929
+ * Effect pro aplikování layoutu při prvotním načtení savedFields
1011
930
  */
1012
- // useEffect(() => {
1013
- // // Po refetchFields() (z reloadLayout) potřebujeme aplikovat layout a obnovit isGridReady
1014
- // if (savedFields?.records && !isInitialized && gridApiRef.current) {
1015
- // console.log('[GridLayout] Effect: Applying layout after refetch and setting isGridReady=true');
1016
- // applySavedLayout();
1017
- // }
1018
- // }, [savedFields, isInitialized, applySavedLayout]);
1019
-
1020
- /**
1021
- * Původní zakomentovaný effect pro aplikování layoutu když se načtou data
1022
- */
1023
- // useEffect(() => {
1024
- // if (savedFields?.records && columnApiRef.current && !isInitialized) {
1025
- // console.log('[GridLayout] Layout will be applied from effect');
1026
- // applySavedLayout();
1027
- // } else if (savedFields?.records && columnApiRef.current && isInitialized) {
1028
- // // Pokud je již inicializován, ale přišla nová data, aktualizujeme jen DOM (ne celý layout)
1029
- // console.log('[GridLayout] Already initialized, but received new data - updating headers directly');
1030
-
1031
- // // Počkáme moment, než se nová data zpracují, a pak aktualizujeme headerName
1032
- // setTimeout(() => {
1033
- // try {
1034
- // // Vytvoříme mapu fieldName -> headerName z API dat
1035
- // const headerNameMap = new Map();
1036
- // savedFields.records.forEach(field => {
1037
- // if (field.fieldName && field.headerName) {
1038
- // headerNameMap.set(field.fieldName, field.headerName);
1039
- // console.log(`[GridLayout] Update after reload: '${field.fieldName}' -> '${field.headerName}'`);
1040
- // }
1041
- // });
1042
-
1043
- // // Najdeme všechny hlavičky sloupců v DOM
1044
- // const headerCells = document.querySelectorAll('.ag-header-cell');
1045
- // console.log('[GridLayout] Found header cells after reload:', headerCells.length);
1046
-
1047
- // // Aktualizujeme texty hlaviček
1048
- // headerCells.forEach(headerCell => {
1049
- // try {
1050
- // // Získáme ID sloupce z DOM atributů
1051
- // const colId = headerCell.getAttribute('col-id');
1052
- // if (colId && headerNameMap.has(colId)) {
1053
- // // Najdeme element s textem hlavičky
1054
- // const headerTextEl = headerCell.querySelector('.ag-header-cell-text');
1055
- // if (headerTextEl) {
1056
- // const newHeaderName = headerNameMap.get(colId);
1057
- // const currentText = headerTextEl.textContent;
1058
- // console.log(`[GridLayout] DOM update after reload: Column '${colId}' header from '${currentText}' to '${newHeaderName}'`);
1059
- // headerTextEl.textContent = newHeaderName;
1060
- // }
1061
- // }
1062
- // } catch (cellError) {
1063
- // console.error('[GridLayout] Error updating header cell after reload:', cellError);
1064
- // }
1065
- // });
1066
-
1067
- // // Zkusíme vynutit překreslení hlavičky
1068
- // if (typeof gridApiRef.current.refreshHeader === 'function') {
1069
- // console.log('[GridLayout] Forcing header refresh after reload');
1070
- // gridApiRef.current.refreshHeader();
1071
- // }
1072
- // } catch (error) {
1073
- // console.error('[GridLayout] Error updating headers after reload:', error);
1074
- // }
1075
- // }, 200);
1076
- // }
1077
- // }, [savedFields, applySavedLayout, isInitialized, columnApiRef]);
1078
-
1079
- /**
1080
- * Alternativní metoda pro aktualizaci headerName po inicializaci gridu
1081
- */
1082
- // useEffect(() => {
1083
- // // Pokud již proběhla inicializace a máme uložená data z API
1084
- // if (isInitialized && savedFields?.records && gridApiRef.current) {
1085
- // console.log('[GridLayout] Attempting alternative header update after initialization');
1086
-
1087
- // // Zkusíme použít metodu columnDefHeaderNameChanged, pokud je dostupná
1088
- // try {
1089
- // if (typeof gridApiRef.current.columnDefHeaderNameChanged === 'function') {
1090
- // console.log('[GridLayout] Trying columnDefHeaderNameChanged method');
1091
-
1092
- // // Vytvoříme mapu fieldName -> headerName z API dat
1093
- // const headerNameMap = new Map();
1094
- // savedFields.records.forEach(field => {
1095
- // if (field.fieldName && field.headerName) {
1096
- // headerNameMap.set(field.fieldName, field.headerName);
1097
- // }
1098
- // });
1099
-
1100
- // // Získáme aktuální definice sloupců
1101
- // const currentColumnDefs = gridApiRef.current.getColumnDefs();
1102
- // if (currentColumnDefs && Array.isArray(currentColumnDefs)) {
1103
- // currentColumnDefs.forEach(colDef => {
1104
- // if (colDef.field && headerNameMap.has(colDef.field)) {
1105
- // const newHeaderName = headerNameMap.get(colDef.field);
1106
- // if (colDef.headerName !== newHeaderName) {
1107
- // console.log(`[GridLayout] Using columnDefHeaderNameChanged for '${colDef.field}': '${colDef.headerName}' -> '${newHeaderName}'`);
1108
- // colDef.headerName = newHeaderName;
1109
- // gridApiRef.current.columnDefHeaderNameChanged(colDef);
1110
- // }
1111
- // }
1112
- // });
1113
- // }
1114
- // }
1115
- // } catch (error) {
1116
- // console.error('[GridLayout] Error using columnDefHeaderNameChanged:', error);
1117
- // }
1118
-
1119
- // // Počkáme chvíli, aby se grid stihl plně vyrenderovat
1120
- // setTimeout(() => {
1121
- // try {
1122
- // // Získáme aktuální definice sloupců z gridu
1123
- // const currentColDefs = gridApiRef.current.getColumnDefs ? gridApiRef.current.getColumnDefs() : null;
1124
-
1125
- // if (currentColDefs && Array.isArray(currentColDefs)) {
1126
- // // Vytvoříme mapu fieldName -> headerName z API dat
1127
- // const headerNameMap = new Map();
1128
- // savedFields.records.forEach(field => {
1129
- // if (field.fieldName && field.headerName) {
1130
- // headerNameMap.set(field.fieldName, field.headerName);
1131
- // }
1132
- // });
1133
-
1134
- // // Zkontrolujeme, zda je třeba aktualizovat nějaké headery
1135
- // let needsUpdate = false;
1136
- // const updatedColDefs = currentColDefs.map(colDef => {
1137
- // const fieldName = colDef.field;
1138
- // if (fieldName && headerNameMap.has(fieldName)) {
1139
- // const newHeaderName = headerNameMap.get(fieldName);
1140
- // if (colDef.headerName !== newHeaderName) {
1141
- // needsUpdate = true;
1142
- // console.log(`[GridLayout] Alternative update: Column '${fieldName}' header from '${colDef.headerName}' to '${newHeaderName}'`);
1143
- // return {
1144
- // ...colDef,
1145
- // headerName: newHeaderName
1146
- // };
1147
- // }
1148
- // }
1149
- // return colDef;
1150
- // });
1151
-
1152
- // if (needsUpdate && typeof gridApiRef.current.setColumnDefs === 'function') {
1153
- // console.log('[GridLayout] Applying alternative column header update');
1154
- // gridApiRef.current.setColumnDefs(updatedColDefs);
1155
-
1156
- // // Zkusíme vynutit překreslení hlavičky
1157
- // setTimeout(() => {
1158
- // if (typeof gridApiRef.current.refreshHeader === 'function') {
1159
- // console.log('[GridLayout] Forcing header refresh');
1160
- // gridApiRef.current.refreshHeader();
1161
- // }
1162
- // }, 50);
1163
- // }
1164
- // }
1165
- // } catch (error) {
1166
- // console.error('[GridLayout] Error in alternative header update:', error);
1167
- // }
1168
- // }, 300);
1169
- // }
1170
- // }, [isInitialized, savedFields, gridApiRef]);
931
+ useEffect(() => {
932
+ // Aplikujeme layout pokud:
933
+ // 1. Máme savedFields z API (i když prázdné - první zobrazení modulu)
934
+ // 2. Grid je ready (má API references)
935
+ // 3. Ještě jsme neinicializovali layout
936
+ if (savedFields?.records !== undefined && gridApiRef.current && !isInitialized) {
937
+ if (savedFields.records.length > 0) {
938
+ applySavedLayout();
939
+ } else {
940
+ // Pro prázdné savedFields jen nastavíme inicializaci
941
+ setIsInitialized(true);
942
+ setIsGridReady(true);
943
+ }
944
+ }
945
+ }, [savedFields?.records, isInitialized, applySavedLayout]);
1171
946
 
947
+
1172
948
  /**
1173
949
  * Effect pro error handling
1174
950
  */
@@ -1547,133 +1323,132 @@ export const useGridLayout = ({
1547
1323
  }, [waitForSavedFields, isFieldsLoading, isLoading]);
1548
1324
 
1549
1325
  // Pre-transformované columnDefs podle savedFields pro waitForSavedFields mode
1326
+ // DŮLEŽITÉ: Toto useMemo nyní obsahuje i AgGridColumns transformaci pro eliminaci circular dependency
1550
1327
  const preTransformedColumnDefs = useMemo(() => {
1551
1328
  // Použijeme aktualizované columnDefs ze stableColumnDefsRef pokud existují
1552
- const columnDefsToUse = stableColumnDefsRef.current || columnDefs;
1329
+ const columnDefsToUse = stableColumnDefsRef.current; // || columnDefs; //HLO
1553
1330
 
1331
+ let baseColumnDefs = columnDefsToUse;
1332
+
1333
+ // Pro waitForSavedFields aplikujeme grid layout transformace
1554
1334
  if (
1555
- !waitForSavedFields ||
1556
- !savedFields?.records ||
1557
- !Array.isArray(columnDefsToUse)
1335
+ waitForSavedFields &&
1336
+ savedFields?.records &&
1337
+ Array.isArray(columnDefsToUse)
1558
1338
  ) {
1559
- return columnDefsToUse; // Aktualizované columnDefs
1560
- }
1339
+ // Transformujeme columnDefs podle savedFields PŘED zobrazením gridu
1340
+ const columnState = gridLayoutApi.transformFieldsToColumnState(
1341
+ savedFields.records,
1342
+ columnDefsToUse
1343
+ );
1561
1344
 
1562
- // Transformujeme columnDefs podle savedFields PŘED zobrazením gridu
1563
- const columnState = gridLayoutApi.transformFieldsToColumnState(
1564
- savedFields.records,
1565
- columnDefsToUse
1566
- );
1345
+ if (columnState && columnState.length > 0) {
1346
+ // Vytvoříme mapu pro rychlé vyhledávání
1347
+ const columnStateMap = new Map();
1348
+ columnState.forEach((colState, index) => {
1349
+ columnStateMap.set(colState.colId, { ...colState, __order: index });
1350
+ });
1351
+
1352
+ // Vytvoříme mapu fieldName -> headerName z API dat
1353
+ const headerNameMap = new Map();
1354
+ savedFields.records.forEach((field) => {
1355
+ if (field.fieldName && field.headerName) {
1356
+ headerNameMap.set(field.fieldName, field.headerName);
1357
+ }
1358
+ });
1567
1359
 
1568
- if (!columnState || columnState.length === 0) {
1569
- return columnDefsToUse; // Pokud není co transformovat, vrátíme aktualizované
1360
+ // Seřadíme columnDefs podle pořadí z columnState a upravíme headerName podle savedFields
1361
+ baseColumnDefs = [...columnDefsToUse]
1362
+ .sort((a, b) => {
1363
+ const fieldA = a.field || a.colId;
1364
+ const fieldB = b.field || b.colId;
1365
+ const aState = columnStateMap.get(fieldA);
1366
+ const bState = columnStateMap.get(fieldB);
1367
+
1368
+ const aOrder = aState?.__order ?? 999;
1369
+ const bOrder = bState?.__order ?? 999;
1370
+
1371
+ return aOrder - bOrder;
1372
+ })
1373
+ .map((colDef) => {
1374
+ const fieldId = colDef.field || colDef.colId;
1375
+ const columnStateItem = columnStateMap.get(fieldId);
1376
+
1377
+ // Aplikujeme headerName, hide i width z API pro waitForSavedFields režim
1378
+ if (fieldId && (headerNameMap.has(fieldId) || columnStateItem)) {
1379
+ return {
1380
+ ...colDef,
1381
+ // Aplikujeme headerName z savedFields
1382
+ ...(headerNameMap.has(fieldId) && { headerName: headerNameMap.get(fieldId) }),
1383
+ // Aplikujeme hide hodnotu z columnState (z API show hodnoty)
1384
+ ...(columnStateItem && { hide: columnStateItem.hide }),
1385
+ // Aplikujeme také width pro konzistentní zobrazení
1386
+ ...(columnStateItem && columnStateItem.width && { width: columnStateItem.width }),
1387
+ };
1388
+ }
1389
+ return colDef;
1390
+ });
1391
+ }
1570
1392
  }
1571
1393
 
1572
- // Vytvoříme mapu pro rychlé vyhledávání
1573
- const columnStateMap = new Map();
1574
- columnState.forEach((colState, index) => {
1575
- columnStateMap.set(colState.colId, { ...colState, __order: index });
1576
- });
1394
+ // Nyní VŽDY aplikujeme AgGridColumns transformaci PŘÍMO zde místo v props
1395
+ // Tím eliminujeme circular dependency pro všechny případy použití
1396
+ if (!Array.isArray(baseColumnDefs)) {
1397
+ // Pokud baseColumnDefs není array, mohlo by to být ColumnBuilder
1398
+ if (baseColumnDefs && typeof baseColumnDefs.build === 'function') {
1399
+ baseColumnDefs = baseColumnDefs.build();
1400
+ } else {
1401
+ return [];
1402
+ }
1403
+ }
1577
1404
 
1578
- // Vytvoříme mapu fieldName -> headerName z API dat
1579
- const headerNameMap = new Map();
1580
- savedFields.records.forEach((field) => {
1581
- if (field.fieldName && field.headerName) {
1582
- headerNameMap.set(field.fieldName, field.headerName);
1405
+ // Aplikujeme ref šířky jako poslední krok POUZE pokud už byla provedena inicializace
1406
+ // Při prvotním načítání zachováváme API šířky (které jsou už v baseColumnDefs pro waitForSavedFields)
1407
+ const finalColumnDefs = baseColumnDefs.map(colDef => {
1408
+ const fieldId = colDef.field || colDef.colId;
1409
+ const refWidth = columnWidthRefsMap.current.get(fieldId);
1410
+
1411
+ // Použijeme ref šířku POUZE pokud už je grid inicializovaný (uživatel manipuloval s šířkami)
1412
+ if (refWidth !== undefined && isInitialized) {
1413
+ return {
1414
+ ...colDef,
1415
+ width: refWidth
1416
+ };
1583
1417
  }
1418
+ return colDef;
1584
1419
  });
1585
1420
 
1586
- // Seřadíme columnDefs podle pořadí z columnState a upravíme headerName podle savedFields
1587
- const sortedColumnDefs = [...columnDefsToUse]
1588
- .sort((a, b) => {
1589
- const fieldA = a.field || a.colId;
1590
- const fieldB = b.field || b.colId;
1591
- const aState = columnStateMap.get(fieldA);
1592
- const bState = columnStateMap.get(fieldB);
1593
-
1594
- const aOrder = aState?.__order ?? 999;
1595
- const bOrder = bState?.__order ?? 999;
1596
-
1597
- return aOrder - bOrder;
1598
- })
1599
- .map((colDef) => {
1600
- const fieldId = colDef.field || colDef.colId;
1601
- const columnStateItem = columnStateMap.get(fieldId);
1602
-
1603
- // Aplikujeme jak headerName tak hide hodnotu z API
1604
- if (fieldId && (headerNameMap.has(fieldId) || columnStateItem)) {
1605
- return {
1606
- ...colDef,
1607
- // Aplikujeme headerName z savedFields
1608
- ...(headerNameMap.has(fieldId) && { headerName: headerNameMap.get(fieldId) }),
1609
- // Aplikujeme hide hodnotu z columnState (z API show hodnoty)
1610
- ...(columnStateItem && { hide: columnStateItem.hide }),
1611
- };
1612
- }
1613
- return colDef;
1614
- });
1421
+ return finalColumnDefs;
1615
1422
 
1616
- return sortedColumnDefs;
1617
- }, [waitForSavedFields, savedFields?.records, columnDefs, gridLayoutApi, columnDefsVersion]);
1423
+ // // Replikujeme AgGridColumns logiku pro všechny column definitions:
1424
+ // const processedColumns = baseColumnDefs?.map((column) => {
1425
+ // // Zkopírujeme column aby se předešlo mutacím
1426
+ // const processedColumn = { ...column };
1618
1427
 
1619
- /**
1620
- * Handler pro změnu zobrazených dat - aktualizace headerName
1621
- */
1622
- const handleRowDataChanged = useCallback(
1623
- (params) => {
1624
- // if (!enabled || !savedFields?.records || !isInitialized) return;
1625
- // console.log('[GridLayout] Row data changed - ensuring header names stay updated');
1626
- // // Počkáme chvíli, až se vše vyrenderuje, a pak aktualizujeme headerName
1627
- // setTimeout(() => {
1628
- // try {
1629
- // // Vytvoříme mapu fieldName -> headerName z API dat
1630
- // const headerNameMap = new Map();
1631
- // savedFields.records.forEach(field => {
1632
- // if (field.fieldName && field.headerName) {
1633
- // headerNameMap.set(field.fieldName, field.headerName);
1634
- // }
1635
- // });
1636
- // // Najdeme všechny hlavičky sloupců v DOM
1637
- // const headerCells = document.querySelectorAll('.ag-header-cell');
1638
- // console.log('[GridLayout] Found header cells after data change:', headerCells.length);
1639
- // // Aktualizujeme texty hlaviček
1640
- // headerCells.forEach(headerCell => {
1641
- // try {
1642
- // // Získáme ID sloupce z DOM atributů
1643
- // const colId = headerCell.getAttribute('col-id');
1644
- // if (colId && headerNameMap.has(colId)) {
1645
- // // Najdeme element s textem hlavičky
1646
- // const headerTextEl = headerCell.querySelector('.ag-header-cell-text');
1647
- // if (headerTextEl) {
1648
- // const newHeaderName = headerNameMap.get(colId);
1649
- // const currentText = headerTextEl.textContent;
1650
- // if (currentText !== newHeaderName) {
1651
- // console.log(`[GridLayout] Data change update: Column '${colId}' header from '${currentText}' to '${newHeaderName}'`);
1652
- // headerTextEl.textContent = newHeaderName;
1653
- // }
1654
- // }
1655
- // }
1656
- // } catch (cellError) {
1657
- // console.error('[GridLayout] Error updating header cell after data change:', cellError);
1658
- // }
1659
- // });
1660
- // // Zkusíme vynutit překreslení hlavičky
1661
- // if (typeof gridApiRef.current.refreshHeader === 'function') {
1662
- // console.log('[GridLayout] Forcing header refresh after data change');
1663
- // gridApiRef.current.refreshHeader();
1664
- // }
1665
- // } catch (error) {
1666
- // console.error('[GridLayout] Error updating headers after data change:', error);
1667
- // }
1668
- // }, 100);
1669
- },
1670
- [enabled, savedFields, isInitialized]
1671
- );
1428
+ // // Aplikujeme tooltip logiku pokud je potřeba
1429
+ // if (processedColumn.contentTooltip) {
1430
+ // // Statické mapování tooltipů pro lepší performance a eliminaci circular dependency
1431
+ // switch (processedColumn.contentTooltip) {
1432
+ // case 'User':
1433
+ // processedColumn.tooltipComponent = Tooltip.User;
1434
+ // break;
1435
+ // default:
1436
+ // console.warn(`[GridLayout] Unknown tooltip component: ${processedColumn.contentTooltip}`);
1437
+ // }
1438
+ // }
1439
+
1440
+ // return processedColumn;
1441
+ // }) || [];
1442
+
1443
+ // return processedColumns;
1444
+ }, [waitForSavedFields, savedFields?.records, columnDefs, gridLayoutApi, columnWidthsVersion, isInitialized]);
1672
1445
 
1446
+ console.log("rerender");
1673
1447
  return {
1674
1448
  // State
1675
1449
  isLoading: isLoading || isFieldsLoading,
1676
1450
  isSaving,
1451
+ isApplyingLayout, // Nový specifický state pro aplikování layoutu
1677
1452
  error,
1678
1453
  hasUnsavedChanges,
1679
1454
  isInitialized,
@@ -1685,10 +1460,11 @@ export const useGridLayout = ({
1685
1460
  onGridReady: handleGridReady,
1686
1461
  onColumnMoved: handleColumnMoved,
1687
1462
  onDragStopped: handleDragStopped, // Nový handler pro konec drag operace
1463
+ onColumnResizing: handleColumnResizing, // NOVÝ handler pro průběžné resize
1688
1464
  onColumnResized: handleColumnResized,
1689
1465
  onColumnVisible: handleColumnVisible,
1690
1466
  onColumnPinned: handleColumnPinned,
1691
- onRowDataChanged: handleRowDataChanged, // Nový handler pro změnu dat
1467
+ // onRowDataChanged: handleRowDataChanged, // Nový handler pro změnu dat
1692
1468
 
1693
1469
  // Manual actions
1694
1470
  saveLayout,