@bit.rhplus/ui.grid-layout 0.0.3 → 0.0.5

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
@@ -1,625 +1,1788 @@
1
- /* eslint-disable */
2
- /**
3
- * Hlavní hook pro správu grid layout - automatické ukládání a načítání rozvržení sloupců
4
- * Kombinuje AG-Grid API s Grid Layout službou pro persistence uživatelských preferencí
5
- */
6
-
7
- import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
8
- import { useGridLayoutApi } from './useGridLayoutApi';
9
- import { debounce } from 'lodash';
10
-
11
- /**
12
- * Hook pro správu grid layout s automatickým ukládáním
13
- * @param {Object} config - Konfigurace grid layout
14
- * @param {string} config.userKey - Identifikátor uživatele
15
- * @param {string} config.applicationName - Název aplikace
16
- * @param {string} config.gridName - Název gridu
17
- * @param {string} [config.filterName] - Název filtru (volitelné)
18
- * @param {boolean} [config.enabled=true] - Zapnout/vypnout layout management
19
- * @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
21
- * @param {Array} config.columnDefs - AG-Grid column definitions
22
- * @param {string} [config.accessToken] - Přístupový token
23
- * @param {boolean} [config.waitForSavedFields=false] - Skrýt columnDefs dokud nejsou načtena savedFields
24
- * @param {Function} [config.onLayoutLoaded] - Callback při načtení layoutu
25
- * @param {Function} [config.onLayoutSaved] - Callback při uložení layoutu
26
- * @param {Function} [config.onError] - Callback při chybě
27
- * @returns {Object} Grid layout management interface
28
- */
29
- export const useGridLayout = ({
30
- userKey,
31
- applicationName,
32
- gridName,
33
- filterName,
34
- enabled = true,
35
- autoSave = true,
36
- autoSaveDelay = 2000,
37
- columnDefs = [],
38
- accessToken,
39
- waitForSavedFields = false, // Nový prop pro odložené zobrazení columnDefs
40
- onLayoutLoaded,
41
- onLayoutSaved,
42
- onError
43
- }) => {
44
- // Validace columnDefs - musí být array
45
- if (columnDefs !== undefined && columnDefs !== null && !Array.isArray(columnDefs)) {
46
- console.error('useGridLayout: columnDefs musí být array, získán:', typeof columnDefs, columnDefs);
47
- throw new Error('useGridLayout: columnDefs musí být array');
48
- }
49
-
50
- // Refs pro AG-Grid API
51
- const gridApiRef = useRef(null);
52
- const columnApiRef = useRef(null);
53
-
54
- // State
55
- const [isInitialized, setIsInitialized] = useState(false);
56
- const [isLoading, setIsLoading] = useState(false);
57
- const [isSaving, setIsSaving] = useState(false);
58
- const [error, setError] = useState(null);
59
- const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
60
- const [isColumnEditorOpen, setIsColumnEditorOpen] = useState(false);
61
-
62
- // Grid Layout API hook
63
- const gridLayoutApi = useGridLayoutApi({
64
- userKey,
65
- applicationName,
66
- gridName,
67
- filterName,
68
- accessToken
69
- });
70
-
71
- // Query pro načtení saved layoutu
72
- const {
73
- data: savedFields,
74
- isLoading: isFieldsLoading,
75
- error: fieldsError,
76
- refetch: refetchFields
77
- } = gridLayoutApi.useUserFields(
78
- (columnDefs || []).map((colDef, index) => ({
79
- name: colDef.field || `column_${index}`,
80
- displayName: colDef.headerName || colDef.field || `Column ${index + 1}`,
81
- dataType: colDef.type || 'string',
82
- isVisible: !colDef.hide,
83
- width: colDef.width || 100,
84
- order: index
85
- })),
86
- { enabled: enabled && !!userKey && !!gridName && Array.isArray(columnDefs) && columnDefs.length > 0 }
87
- );
88
-
89
- /**
90
- * Error handler
91
- */
92
- const handleError = useCallback((error, context = '') => {
93
- console.error(`Grid Layout Error ${context}:`, error);
94
- setError(error);
95
- if (onError) {
96
- onError(error, context);
97
- }
98
- }, [onError]);
99
-
100
- /**
101
- * Uloží současný stav sloupců do API
102
- */
103
- const saveCurrentLayout = useCallback(async () => {
104
- if (!enabled || !gridApiRef.current || !columnApiRef.current) return;
105
-
106
- try {
107
- setIsSaving(true);
108
- setError(null);
109
-
110
- // Získáme současný column state z AG-Grid
111
- const columnState = columnApiRef.current.getColumnState();
112
-
113
- // Transformujeme na Grid API format
114
- const fields = gridLayoutApi.transformColumnStateToFields(columnState, columnDefs);
115
-
116
- // Uložíme do API
117
- const result = await gridLayoutApi.saveGridLayout(fields);
118
-
119
- if (result.success) {
120
- setHasUnsavedChanges(false);
121
- if (onLayoutSaved) {
122
- onLayoutSaved(fields, columnState);
123
- }
124
- }
125
- } catch (error) {
126
- handleError(error, 'při ukládání layoutu');
127
- } finally {
128
- setIsSaving(false);
129
- }
130
- }, [enabled, columnDefs, gridLayoutApi, onLayoutSaved, handleError]);
131
-
132
- /**
133
- * Debounced auto-save pro předcházení častým voláním API
134
- */
135
- const debouncedSave = useMemo(
136
- () => debounce(saveCurrentLayout, autoSaveDelay),
137
- [saveCurrentLayout, autoSaveDelay]
138
- );
139
-
140
- /**
141
- * Aplikuje saved layout na AG-Grid
142
- */
143
- const applySavedLayout = useCallback(() => {
144
- if (!savedFields?.records || !columnApiRef.current || isInitialized) return;
145
-
146
- try {
147
- setIsLoading(true);
148
- // Transformujeme Grid API fields na AG-Grid column state
149
- const columnState = gridLayoutApi.transformFieldsToColumnState(savedFields.records, columnDefs);
150
- console.log("🚀 ~ useGridLayout ~ columnState:", savedFields.records, columnDefs, columnState)
151
-
152
- if (columnState && columnState.length > 0) {
153
- // Pokud je waitForSavedFields true, columnDefs jsou už pre-transformované,
154
- // takže aplikujeme jen width a hide vlastnosti bez delay pro pořadí
155
- const applyFunction = () => {
156
- try {
157
- console.log("🎯 Applying column state with delay:", {
158
- columnState,
159
- columnCount: columnState.length,
160
- columnIds: columnState.map(c => c.colId),
161
- hasOrder: true // pořadí je v poli implicitně
162
- });
163
-
164
- // Debug info o columnApiRef
165
- console.log("🔍 columnApiRef debug:", {
166
- hasColumnApiRef: !!columnApiRef.current,
167
- columnApiType: typeof columnApiRef.current,
168
- columnApiMethods: columnApiRef.current ? Object.getOwnPropertyNames(columnApiRef.current).filter(name => typeof columnApiRef.current[name] === 'function') : [],
169
- hasApplyColumnState: columnApiRef.current && typeof columnApiRef.current.applyColumnState === 'function'
170
- });
171
-
172
- // Ověření, že columnApi podporuje applyColumnState
173
- if (!columnApiRef.current) {
174
- throw new Error('columnApiRef.current is null or undefined');
175
- }
176
-
177
- if (typeof columnApiRef.current.applyColumnState !== 'function') {
178
- throw new Error(`applyColumnState is not a function. Available methods: ${Object.getOwnPropertyNames(columnApiRef.current).filter(name => typeof columnApiRef.current[name] === 'function').join(', ')}`);
179
- }
180
-
181
- console.log("test2");
182
- // Aplikujeme column state na AG-Grid
183
- let result;
184
- try {
185
- const applyOrderEnabled = !waitForSavedFields; // Při waitForSavedFields už je pořadí v columnDefs
186
-
187
- result = columnApiRef.current.applyColumnState({
188
- state: columnState,
189
- applyOrder: applyOrderEnabled, // Pořadí jen když není waitForSavedFields
190
- defaultState: {
191
- sort: null, // Reset sorting na všech sloupcích
192
- sortIndex: null, // Reset sort index
193
- pivot: null, // Reset pivot
194
- rowGroup: null // Reset row grouping
195
- }
196
- });
197
- console.log("test2.5 - applyColumnState executed");
198
- } catch (applyError) {
199
- console.error("❌ Error during applyColumnState:", applyError);
200
- throw applyError;
201
- }
202
-
203
- console.log("✅ Column state applied successfully:", {
204
- result,
205
- appliedColumns: columnState.length
206
- });
207
-
208
- // Ověření, že se sloupce skutečně přeuspořádaly
209
- const newColumnState = columnApiRef.current.getColumnState();
210
- console.log("🚀 ~ test3:", newColumnState)
211
- console.log("🔍 Current column state after apply:", newColumnState.map(c => c.colId));
212
-
213
- if (onLayoutLoaded) {
214
- onLayoutLoaded(savedFields.records, columnState);
215
- }
216
- } catch (delayedError) {
217
- console.error("❌ Error applying column state:", delayedError);
218
- handleError(delayedError, 'při delayed aplikování layoutu');
219
- }
220
- };
221
-
222
- // Pro waitForSavedFields aplikujeme okamžitě (pořadí je už v columnDefs)
223
- // Pro normální režim použijeme delay
224
- if (waitForSavedFields) {
225
- applyFunction();
226
- } else {
227
- setTimeout(applyFunction, 100); // 100ms delay jen pro normální režim
228
- }
229
- }
230
- } catch (error) {
231
- handleError(error, 'při aplikování layoutu');
232
- } finally {
233
- setIsInitialized(true);
234
- setIsLoading(false);
235
- }
236
- }, [savedFields, gridLayoutApi, isInitialized, onLayoutLoaded, handleError, columnDefs, waitForSavedFields]);
237
-
238
- /**
239
- * Event handlers pro AG-Grid
240
- */
241
- const handleColumnMoved = useCallback(() => {
242
- if (!enabled || !isInitialized) return;
243
- setHasUnsavedChanges(true);
244
- // Neukládáme při každém pohybu - čekáme na onDragStopped
245
- }, [enabled, isInitialized]);
246
-
247
- const handleDragStopped = useCallback(() => {
248
- if (!enabled || !isInitialized) return;
249
- setHasUnsavedChanges(true);
250
- if (autoSave) {
251
- console.log("🎯 Drag stopped - spouštím debounced save");
252
- debouncedSave();
253
- }
254
- }, [enabled, isInitialized, autoSave, debouncedSave]);
255
-
256
- const handleColumnResized = useCallback(() => {
257
- if (!enabled || !isInitialized) return;
258
- setHasUnsavedChanges(true);
259
- if (autoSave) {
260
- debouncedSave();
261
- }
262
- }, [enabled, isInitialized, autoSave, debouncedSave]);
263
-
264
- const handleColumnVisible = useCallback(() => {
265
- if (!enabled || !isInitialized) return;
266
- setHasUnsavedChanges(true);
267
- if (autoSave) {
268
- debouncedSave();
269
- }
270
- }, [enabled, isInitialized, autoSave, debouncedSave]);
271
-
272
- const handleColumnPinned = useCallback(() => {
273
- if (!enabled || !isInitialized) return;
274
- setHasUnsavedChanges(true);
275
- if (autoSave) {
276
- debouncedSave();
277
- }
278
- }, [enabled, isInitialized, autoSave, debouncedSave]);
279
-
280
- /**
281
- * AG-Grid Ready handler
282
- */
283
- const handleGridReady = useCallback((params) => {
284
- if (!enabled) return;
285
-
286
- gridApiRef.current = params.api;
287
- columnApiRef.current = params.columnApi;
288
-
289
- // Pokud už máme saved data, aplikujeme je
290
- if (savedFields?.records && !isInitialized) {
291
- applySavedLayout();
292
- }
293
- }, [enabled, savedFields, isInitialized, applySavedLayout]);
294
-
295
- /**
296
- * Effect pro aplikování layoutu když se načtou data
297
- */
298
- useEffect(() => {
299
- if (savedFields?.records && columnApiRef.current && !isInitialized) {
300
- applySavedLayout();
301
- }
302
- }, [savedFields, applySavedLayout, isInitialized]);
303
-
304
- /**
305
- * Effect pro error handling
306
- */
307
- useEffect(() => {
308
- if (fieldsError) {
309
- handleError(fieldsError, 'při načítání saved layoutu');
310
- }
311
- }, [fieldsError, handleError]);
312
-
313
- /**
314
- * Cleanup effect
315
- */
316
- useEffect(() => {
317
- return () => {
318
- debouncedSave.cancel();
319
- };
320
- }, [debouncedSave]);
321
-
322
- /**
323
- * Resetuje layout na default
324
- */
325
- const resetToDefault = useCallback(async () => {
326
- if (!enabled || !columnApiRef.current) return;
327
-
328
- try {
329
- setIsLoading(true);
330
-
331
- // Resetujeme AG-Grid na původní column definitions
332
- columnApiRef.current.resetColumnState();
333
-
334
- // Pokud je autoSave zapnuté, uložíme resetovaný stav
335
- if (autoSave) {
336
- await saveCurrentLayout();
337
- } else {
338
- setHasUnsavedChanges(true);
339
- }
340
- } catch (error) {
341
- handleError(error, 'při resetování layoutu');
342
- } finally {
343
- setIsLoading(false);
344
- }
345
- }, [enabled, autoSave, saveCurrentLayout, handleError]);
346
-
347
- /**
348
- * Manuální uložení
349
- */
350
- const saveLayout = useCallback(async () => {
351
- await saveCurrentLayout();
352
- }, [saveCurrentLayout]);
353
-
354
- /**
355
- * Manuální reload
356
- */
357
- const reloadLayout = useCallback(async () => {
358
- try {
359
- setIsInitialized(false);
360
- await refetchFields();
361
- } catch (error) {
362
- handleError(error, 'při reload layoutu');
363
- }
364
- }, [refetchFields, handleError]);
365
-
366
- /**
367
- * Získá aktuální column state pro Column Editor
368
- */
369
- const getCurrentColumnsForEditor = useCallback(() => {
370
- console.log("🔍 getCurrentColumnsForEditor called", {
371
- hasColumnApiRef: !!columnApiRef.current,
372
- columnDefs: columnDefs?.length || 0,
373
- savedFieldsCount: savedFields?.records?.length || 0
374
- });
375
-
376
- // Zajistíme, že máme validní columnDefs
377
- const validColumnDefs = Array.isArray(columnDefs) ? columnDefs : [];
378
-
379
- if (!columnApiRef.current || validColumnDefs.length === 0) {
380
- console.log("❌ Using fallback - AG-Grid not ready or no columnDefs");
381
- // Fallback na původní columnDefs pokud není AG-Grid připraven
382
- return validColumnDefs.map((col, index) => ({
383
- id: col.field,
384
- field: col.field,
385
- headerName: col.headerName || col.field,
386
- originalHeaderName: col.headerName || col.field,
387
- width: col.width || 100,
388
- originalWidth: col.width || 100,
389
- visible: !col.hide,
390
- order: index
391
- }));
392
- }
393
-
394
- // Získáme aktuální column state z AG-Grid
395
- const currentColumnState = columnApiRef.current.getColumnState();
396
- console.log("📊 AG-Grid column state:", currentColumnState?.map(cs => ({
397
- colId: cs.colId,
398
- width: cs.width,
399
- hide: cs.hide
400
- })));
401
-
402
- if (!Array.isArray(currentColumnState)) {
403
- console.log("❌ Using fallback - getColumnState() not valid array");
404
- // Fallback pokud getColumnState() nevrátí validní array
405
- return validColumnDefs.map((col, index) => ({
406
- id: col.field,
407
- field: col.field,
408
- headerName: col.headerName || col.field,
409
- originalHeaderName: col.headerName || col.field,
410
- width: col.width || 100,
411
- originalWidth: col.width || 100,
412
- visible: !col.hide,
413
- order: index
414
- }));
415
- }
416
-
417
- const result = currentColumnState.map((columnStateItem, index) => {
418
- // Najdeme odpovídající column definition podle colId
419
- const colDef = validColumnDefs.find(cd => cd.field === columnStateItem.colId) || {};
420
- // Pokusíme se najít saved hodnotu pro headerName
421
- const savedField = savedFields?.records?.find(sf => sf.fieldName === columnStateItem.colId);
422
-
423
- return {
424
- id: columnStateItem.colId,
425
- field: columnStateItem.colId,
426
- headerName: savedField?.headerName || colDef.headerName || columnStateItem.colId,
427
- originalHeaderName: colDef.headerName || columnStateItem.colId,
428
- width: columnStateItem.width || colDef.width || 100,
429
- originalWidth: colDef.width || 100,
430
- visible: !columnStateItem.hide,
431
- order: index
432
- };
433
- });
434
-
435
- console.log("✅ Using AG-Grid state - result:", result.map(r => ({
436
- field: r.field,
437
- headerName: r.headerName,
438
- width: r.width,
439
- visible: r.visible
440
- })));
441
-
442
- return result;
443
- }, [columnDefs, savedFields]);
444
-
445
- /**
446
- * Otevře Column Editor modal
447
- */
448
- const openColumnEditor = useCallback(() => {
449
- console.log("🚀 Opening Column Editor", {
450
- hasColumnApiRef: !!columnApiRef.current,
451
- isInitialized,
452
- columnDefsCount: columnDefs?.length || 0
453
- });
454
- setIsColumnEditorOpen(true);
455
- }, [isInitialized, columnDefs]);
456
-
457
- /**
458
- * Zavře Column Editor modal
459
- */
460
- const closeColumnEditor = useCallback(() => {
461
- setIsColumnEditorOpen(false);
462
- }, []);
463
-
464
- /**
465
- * Uloží změny z Column Editoru
466
- * @param {Array} editedColumns - Editované sloupce z modalu
467
- */
468
- const saveColumnEditorChanges = useCallback(async (editedColumns) => {
469
- console.log("🚀 ~ useGridLayout ~ editedColumns:", editedColumns)
470
- if (!enabled || !columnApiRef.current) return;
471
-
472
- try {
473
- setIsLoading(true);
474
-
475
- // Filtrujeme pouze validní sloupce (s definovaným field)
476
- const validColumns = editedColumns.filter(col => col.field && col.field !== undefined);
477
-
478
- // Převedeme editované sloupce na AG-Grid column state
479
- const columnState = validColumns.map((col, index) => ({
480
- colId: col.field,
481
- width: col.width,
482
- hide: !col.visible,
483
- pinned: null,
484
- sort: null,
485
- sortIndex: null
486
- }));
487
-
488
- // Aplikujeme změny na AG-Grid
489
- columnApiRef.current.applyColumnState({
490
- state: columnState,
491
- applyOrder: true,
492
- defaultState: {
493
- sort: null,
494
- sortIndex: null,
495
- pivot: null,
496
- rowGroup: null
497
- }
498
- });
499
-
500
- // Připravíme UserFields pro save podle C# SaveUserFieldModel struktury
501
- const userFields = validColumns.map((col, index) => ({
502
- FieldName: col.field,
503
- HeaderName: col.headerName || col.field, // Editovaný název nebo fallback na field
504
- Order: index, // Pořadí podle pozice v editedColumns
505
- Show: col.visible,
506
- Width: col.width || null
507
- }));
508
-
509
- // Uložíme změny přímo pomocí API
510
- if (autoSave) {
511
- const result = await gridLayoutApi.saveGridLayout(userFields);
512
- if (result.success) {
513
- setHasUnsavedChanges(false);
514
-
515
- // Explicitní refetch saved fields pro okamžité načtení nových dat
516
- try {
517
- await refetchFields();
518
- console.log("✅ Saved fields refetched after save");
519
- } catch (refetchError) {
520
- console.warn("⚠️ Refetch failed after save:", refetchError);
521
- }
522
-
523
- if (onLayoutSaved) {
524
- onLayoutSaved(userFields, columnState);
525
- }
526
- }
527
- } else {
528
- setHasUnsavedChanges(true);
529
- }
530
-
531
- console.log("✅ Column Editor changes applied successfully");
532
- } catch (error) {
533
- handleError(error, 'při aplikování změn z Column Editoru');
534
- } finally {
535
- setIsLoading(false);
536
- }
537
- }, [enabled, autoSave, gridLayoutApi, onLayoutSaved, handleError, userKey, applicationName, gridName, filterName, refetchFields]);
538
-
539
- // Logika pro zobrazení columnDefs
540
- const shouldShowColumns = useMemo(() => {
541
- if (!waitForSavedFields) return true; // Pokud není waitForSavedFields, vždy zobrazuj
542
-
543
- // Pokud je waitForSavedFields true, čekáme na dokončení načítání
544
- return !isFieldsLoading && !isLoading;
545
- }, [waitForSavedFields, isFieldsLoading, isLoading]);
546
-
547
- // Pre-transformované columnDefs podle savedFields pro waitForSavedFields mode
548
- const preTransformedColumnDefs = useMemo(() => {
549
- if (!waitForSavedFields || !savedFields?.records || !Array.isArray(columnDefs)) {
550
- return columnDefs; // Normální columnDefs
551
- }
552
-
553
- // Transformujeme columnDefs podle savedFields PŘED zobrazením gridu
554
- const columnState = gridLayoutApi.transformFieldsToColumnState(savedFields.records, columnDefs);
555
-
556
- if (!columnState || columnState.length === 0) {
557
- return columnDefs; // Pokud není co transformovat, vrátíme původní
558
- }
559
-
560
- // Vytvoříme mapu pro rychlé vyhledávání
561
- const columnStateMap = new Map();
562
- columnState.forEach((colState, index) => {
563
- columnStateMap.set(colState.colId, { ...colState, __order: index });
564
- });
565
-
566
- // Seřadíme columnDefs podle pořadí z columnState
567
- const sortedColumnDefs = [...columnDefs].sort((a, b) => {
568
- const aState = columnStateMap.get(a.field);
569
- const bState = columnStateMap.get(b.field);
570
-
571
- const aOrder = aState?.__order ?? 999;
572
- const bOrder = bState?.__order ?? 999;
573
-
574
- return aOrder - bOrder;
575
- });
576
-
577
- console.log("🔄 Pre-transformed columnDefs:", {
578
- original: columnDefs.map(cd => cd.field),
579
- transformed: sortedColumnDefs.map(cd => cd.field),
580
- columnStateOrder: columnState.map(cs => cs.colId)
581
- });
582
-
583
- return sortedColumnDefs;
584
- }, [waitForSavedFields, savedFields?.records, columnDefs, gridLayoutApi]);
585
-
586
- return {
587
- // State
588
- isLoading: isLoading || isFieldsLoading,
589
- isSaving,
590
- error,
591
- hasUnsavedChanges,
592
- isInitialized,
593
- shouldShowColumns, // Nové pole pro řízení zobrazení columnDefs
594
- preTransformedColumnDefs, // Pre-transformované columnDefs pro waitForSavedFields
595
-
596
- // AG-Grid event handlers
597
- onGridReady: handleGridReady,
598
- onColumnMoved: handleColumnMoved,
599
- onDragStopped: handleDragStopped, // Nový handler pro konec drag operace
600
- onColumnResized: handleColumnResized,
601
- onColumnVisible: handleColumnVisible,
602
- onColumnPinned: handleColumnPinned,
603
-
604
- // Manual actions
605
- saveLayout,
606
- resetToDefault,
607
- reloadLayout,
608
-
609
- // Column Editor
610
- isColumnEditorOpen,
611
- openColumnEditor,
612
- closeColumnEditor,
613
- saveColumnEditorChanges,
614
- getCurrentColumnsForEditor, // Nová funkce pro aktuální column state
615
-
616
- // Data
617
- savedFields: savedFields?.records || [],
618
- columnDefs, // Původní columnDefs pro Column Editor
619
-
620
- // Utils
621
- gridLayoutApi
622
- };
623
- };
624
-
625
- export default useGridLayout;
1
+ /* eslint-disable */
2
+ /**
3
+ * Hlavní hook pro správu grid layout - automatické ukládání a načítání rozvržení sloupců
4
+ * Kombinuje AG-Grid API s Grid Layout službou pro persistence uživatelských preferencí
5
+ */
6
+
7
+ import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
8
+ import { useGridLayoutApi } from './useGridLayoutApi';
9
+ import { debounce } from 'lodash';
10
+ // Import tooltip komponent pro eliminaci circular dependency
11
+ // import * as Tooltip from '../grid/tooltips';
12
+
13
+ /**
14
+ * Hook pro správu grid layout s automatickým ukládáním
15
+ * @param {Object} config - Konfigurace grid layout
16
+ * @param {string} config.userKey - Identifikátor uživatele
17
+ * @param {string} config.applicationName - Název aplikace
18
+ * @param {string} config.gridName - Název gridu
19
+ * @param {string} [config.filterName] - Název filtru (volitelné)
20
+ * @param {boolean} [config.enabled=true] - Zapnout/vypnout layout management
21
+ * @param {boolean} [config.autoSave=true] - Automatické ukládání při změnách
22
+ * @param {number} [config.autoSaveDelay=2000] - Zpoždění auto-save v ms
23
+ * @param {Array} config.columnDefs - AG-Grid column definitions
24
+ * @param {string} [config.accessToken] - Přístupový token
25
+ * @param {boolean} [config.waitForSavedFields=false] - Skrýt columnDefs dokud nejsou načtena savedFields
26
+ * @param {Function} [config.onLayoutLoaded] - Callback při načtení layoutu
27
+ * @param {Function} [config.onLayoutSaved] - Callback při uložení layoutu
28
+ * @param {Function} [config.onError] - Callback při chybě
29
+ * @returns {Object} Grid layout management interface
30
+ */
31
+ export const useGridLayout = ({
32
+ userKey,
33
+ applicationName,
34
+ gridName,
35
+ filterName,
36
+ enabled = true,
37
+ autoSave = true,
38
+ autoSaveDelay = 2000,
39
+ columnDefs = [],
40
+ accessToken,
41
+ waitForSavedFields = false, // Nový prop pro odložené zobrazení columnDefs
42
+ onLayoutLoaded,
43
+ onLayoutSaved,
44
+ onError,
45
+ }) => {
46
+
47
+ // Validace columnDefs - musí být array
48
+ if (
49
+ columnDefs !== undefined &&
50
+ columnDefs !== null &&
51
+ !Array.isArray(columnDefs)
52
+ ) {
53
+ console.error(
54
+ '[GridLayout] columnDefs is not an array:',
55
+ typeof columnDefs,
56
+ columnDefs
57
+ );
58
+ throw new Error('useGridLayout: columnDefs musí být array');
59
+ }
60
+
61
+ // Refs pro AG-Grid API
62
+ const gridApiRef = useRef(null);
63
+ const columnApiRef = useRef(null);
64
+
65
+ // State
66
+ const [isInitialized, setIsInitialized] = useState(false);
67
+ const [isGridReady, setIsGridReady] = useState(false);
68
+ const [isLoading, setIsLoading] = useState(false);
69
+ const [isSaving, setIsSaving] = useState(false);
70
+ const [isApplyingLayout, setIsApplyingLayout] = useState(false);
71
+ const [error, setError] = useState(null);
72
+ const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
73
+ const [isColumnEditorOpen, setIsColumnEditorOpen] = useState(false);
74
+ const [lastKnownColumnState, setLastKnownColumnState] = useState(null);
75
+ // Místo version counter použijeme stabilnější mechanismus pro tracking změn
76
+ const [lastColumnDefsHash, setLastColumnDefsHash] = useState('');
77
+
78
+ // Utility funkce pro generování hash z columnDefs
79
+ const generateColumnDefsHash = useCallback((colDefs) => {
80
+ if (!Array.isArray(colDefs)) return '';
81
+
82
+ // Vytvoříme hash z klíčových vlastností které ovlivňují rendering
83
+ return colDefs.map(col =>
84
+ `${col.field || col.colId}:${col.width || 'auto'}:${col.hide ? 'hidden' : 'visible'}:${col.headerName || ''}`
85
+ ).join('|');
86
+ }, []);
87
+
88
+
89
+ // Grid Layout API hook
90
+ const gridLayoutApi = useGridLayoutApi({
91
+ userKey,
92
+ applicationName,
93
+ gridName,
94
+ filterName,
95
+ accessToken,
96
+ });
97
+
98
+ // Query pro načtení saved layoutu
99
+ const {
100
+ data: savedFields,
101
+ isLoading: isFieldsLoading,
102
+ error: fieldsError,
103
+ refetch: refetchFields,
104
+ } = gridLayoutApi.useUserFields(
105
+ (columnDefs || []).map((colDef, index) => ({
106
+ name: colDef.field || colDef.colId || `column_${index}`,
107
+ displayName:
108
+ colDef.headerName ||
109
+ colDef.field ||
110
+ colDef.colId ||
111
+ `Column ${index + 1}`,
112
+ dataType: colDef.type || 'string',
113
+ isVisible: !colDef.hide,
114
+ width: colDef.width || 100,
115
+ order: index,
116
+ })),
117
+ {
118
+ enabled:
119
+ enabled &&
120
+ !!userKey &&
121
+ !!gridName &&
122
+ Array.isArray(columnDefs) &&
123
+ columnDefs.length > 0,
124
+ }
125
+ );
126
+
127
+ // // Bezpečné aktualizování lastKnownColumnState při otevření editoru
128
+ // useEffect(() => {
129
+ // if (isColumnEditorOpen && columnApiRef.current && typeof columnApiRef.current.getColumnState === 'function') {
130
+ // try {
131
+ // const currentState = columnApiRef.current.getColumnState();
132
+ // if (Array.isArray(currentState) && currentState.length > 0) {
133
+ // // Aktualizujeme lastKnownColumnState po otevření editoru
134
+ // const validColumnDefs = Array.isArray(columnDefs) ? columnDefs : [];
135
+ // const formattedState = currentState.map((columnStateItem, index) => {
136
+ // const colDef = validColumnDefs.find(cd => cd.field === columnStateItem.colId) || {};
137
+ // const savedField = savedFields?.records?.find(sf => sf.fieldName === columnStateItem.colId);
138
+
139
+ // return {
140
+ // id: columnStateItem.colId,
141
+ // field: columnStateItem.colId,
142
+ // headerName: savedField?.headerName || colDef.headerName || columnStateItem.colId,
143
+ // originalHeaderName: colDef.headerName || columnStateItem.colId,
144
+ // width: columnStateItem.width || colDef.width || 100,
145
+ // originalWidth: colDef.width || 100,
146
+ // visible: !columnStateItem.hide,
147
+ // order: index
148
+ // };
149
+ // });
150
+
151
+ // setLastKnownColumnState(formattedState);
152
+ // console.log('[GridLayout] Updated lastKnownColumnState after opening editor');
153
+ // }
154
+ // } catch (error) {
155
+ // console.error('[GridLayout] Error updating column state after opening editor:', error);
156
+ // }
157
+ // }
158
+ // }, [isColumnEditorOpen, columnDefs, savedFields, columnApiRef]);
159
+
160
+ // MutationObserver pro sledování změn v DOM a okamžitou aktualizaci headerName
161
+ // useEffect(() => {
162
+ // if (!savedFields?.records || !isInitialized || !enabled) return;
163
+
164
+ // // Reference na observer pro cleanup
165
+ // let observer = null;
166
+
167
+ // try {
168
+ // console.log('[GridLayout] Setting up MutationObserver for header changes');
169
+
170
+ // // Funkce pro aktualizaci headerName
171
+ // const updateHeaderNames = () => {
172
+ // try {
173
+ // // Vytvoříme mapu fieldName -> headerName z API dat
174
+ // const headerNameMap = new Map();
175
+ // savedFields.records.forEach(field => {
176
+ // if (field.fieldName && field.headerName) {
177
+ // headerNameMap.set(field.fieldName, field.headerName);
178
+ // }
179
+ // });
180
+
181
+ // // Najdeme všechny hlavičky sloupců v DOM
182
+ // const headerCells = document.querySelectorAll('.ag-header-cell');
183
+
184
+ // // Aktualizujeme texty hlaviček
185
+ // headerCells.forEach(headerCell => {
186
+ // try {
187
+ // // Získáme ID sloupce z DOM atributů
188
+ // const colId = headerCell.getAttribute('col-id');
189
+ // if (colId && headerNameMap.has(colId)) {
190
+ // // Najdeme element s textem hlavičky
191
+ // const headerTextEl = headerCell.querySelector('.ag-header-cell-text');
192
+ // if (headerTextEl) {
193
+ // const newHeaderName = headerNameMap.get(colId);
194
+ // const currentText = headerTextEl.textContent;
195
+ // if (currentText !== newHeaderName) {
196
+ // console.log(`[GridLayout] MutationObserver update: Column '${colId}' header from '${currentText}' to '${newHeaderName}'`);
197
+ // headerTextEl.textContent = newHeaderName;
198
+ // }
199
+ // }
200
+ // }
201
+ // } catch (cellError) {
202
+ // // Tiché selhání - nechceme, aby MutationObserver padal
203
+ // }
204
+ // });
205
+ // } catch (error) {
206
+ // // Tiché selhání - nechceme, aby MutationObserver padal
207
+ // }
208
+ // };
209
+
210
+ // // Najdeme element hlavičky
211
+ // const headerElement = document.querySelector('.ag-header');
212
+ // if (headerElement) {
213
+ // // Vytvoříme observer, který bude sledovat změny v hlavičce
214
+ // observer = new MutationObserver((mutations) => {
215
+ // // Detekovali jsme změnu v DOM hlavičky, aktualizujeme headerName
216
+ // updateHeaderNames();
217
+ // });
218
+
219
+ // // Začneme sledovat změny v hlavičce
220
+ // observer.observe(headerElement, {
221
+ // childList: true, // sledujeme přidání/odebírání elementů
222
+ // subtree: true, // sledujeme změny i v potomcích
223
+ // characterData: true, // sledujeme změny textu
224
+ // attributeFilter: ['col-id', 'class'] // sledujeme změny těchto atributů
225
+ // });
226
+
227
+ // console.log('[GridLayout] MutationObserver set up successfully');
228
+ // } else {
229
+ // console.log('[GridLayout] Header element not found for MutationObserver');
230
+ // }
231
+ // } catch (error) {
232
+ // console.error('[GridLayout] Error setting up MutationObserver:', error);
233
+ // }
234
+
235
+ // // Cleanup - odpojit observer při unmount
236
+ // return () => {
237
+ // if (observer) {
238
+ // observer.disconnect();
239
+ // console.log('[GridLayout] MutationObserver disconnected');
240
+ // }
241
+ // };
242
+ // }, [savedFields, isInitialized, enabled]);
243
+
244
+ /**
245
+ * Error handler
246
+ */
247
+ const handleError = useCallback(
248
+ (error, context = '') => {
249
+ // Removed console.error for production
250
+ setError(error);
251
+ if (onError) {
252
+ onError(error, context);
253
+ }
254
+ },
255
+ [onError]
256
+ );
257
+
258
+ // Stabilní reference pro debouncedSave
259
+ const stableColumnDefsRef = useRef(columnDefs);
260
+ const stableGridLayoutApiRef = useRef(gridLayoutApi);
261
+ const stableOnLayoutSavedRef = useRef(onLayoutSaved);
262
+ const stableHandleErrorRef = useRef(handleError);
263
+ // Reference pro ukládání stavu sloupců bez state update v render cyklu
264
+ const stableCurrentColumnsRef = useRef(null);
265
+
266
+ // Aktualizujeme ref hodnoty
267
+ useEffect(() => {
268
+ let updatedColumnDefs = columnDefs;
269
+
270
+ // Pro columnDefs zachováváme aktualizované šířky pokud už existují
271
+ if (stableColumnDefsRef.current && Array.isArray(stableColumnDefsRef.current) && Array.isArray(columnDefs)) {
272
+ // Vytvoříme mapu existujících šířek
273
+ const existingWidthsMap = new Map();
274
+ stableColumnDefsRef.current.forEach(colDef => {
275
+ const fieldId = colDef.field || colDef.colId;
276
+ if (fieldId && colDef.width) {
277
+ existingWidthsMap.set(fieldId, colDef.width);
278
+ }
279
+ });
280
+
281
+ // Aktualizujeme columnDefs s existujícími šířkami
282
+ updatedColumnDefs = columnDefs.map(colDef => {
283
+ const fieldId = colDef.field || colDef.colId;
284
+ if (fieldId && existingWidthsMap.has(fieldId)) {
285
+ return {
286
+ ...colDef,
287
+ width: existingWidthsMap.get(fieldId)
288
+ };
289
+ }
290
+ return colDef;
291
+ });
292
+ }
293
+
294
+ // Zkontrolujeme zda se columnDefs skutečně změnily pomocí hash
295
+ const newHash = generateColumnDefsHash(updatedColumnDefs);
296
+ if (newHash !== lastColumnDefsHash) {
297
+ stableColumnDefsRef.current = updatedColumnDefs;
298
+ setLastColumnDefsHash(newHash);
299
+ }
300
+
301
+ stableGridLayoutApiRef.current = gridLayoutApi;
302
+ stableOnLayoutSavedRef.current = onLayoutSaved;
303
+ stableHandleErrorRef.current = handleError;
304
+ });
305
+
306
+ // Efekt pro bezpečnou aktualizaci lastKnownColumnState z ref
307
+ // useEffect(() => {
308
+ // if (stableCurrentColumnsRef.current) {
309
+ // setLastKnownColumnState(stableCurrentColumnsRef.current);
310
+ // console.log('[GridLayout] Updated lastKnownColumnState from ref');
311
+ // // Vymazat po použití
312
+ // stableCurrentColumnsRef.current = null;
313
+ // }
314
+ // }, [stableCurrentColumnsRef.current]);
315
+
316
+ /**
317
+ * Uloží současný stav sloupců do API
318
+ */
319
+ const saveCurrentLayout = useCallback(async () => {
320
+
321
+
322
+ if (!enabled || !gridApiRef.current) {
323
+ return;
324
+ }
325
+ try {
326
+ setIsSaving(true);
327
+ setError(null);
328
+
329
+ // Získáme současný column state z AG-Grid s fallbackem pro AG Grid v31+
330
+ let columnState = null;
331
+ if (
332
+ gridApiRef.current &&
333
+ typeof gridApiRef.current.getColumnState === 'function'
334
+ ) {
335
+ // AG Grid v31+ - getColumnState je přímo v main API
336
+ try {
337
+ columnState = gridApiRef.current.getColumnState();
338
+
339
+ // Získáme aktuální headerName hodnoty z DOM pro každý sloupec
340
+ if (columnState && Array.isArray(columnState)) {
341
+ columnState = columnState.map(colState => {
342
+ // Pokusíme se získat aktuální headerName z DOM
343
+ try {
344
+ const headerCell = document.querySelector(`[col-id="${colState.colId}"]`);
345
+ if (headerCell) {
346
+ const headerTextEl = headerCell.querySelector('.ag-header-cell-text');
347
+ if (headerTextEl && headerTextEl.textContent) {
348
+ return {
349
+ ...colState,
350
+ headerName: headerTextEl.textContent
351
+ };
352
+ }
353
+ }
354
+ } catch (headerError) {
355
+ console.log(`[GridLayout] Could not get headerName from DOM for ${colState.colId}`);
356
+ }
357
+ return colState;
358
+ });
359
+ }
360
+
361
+ // Pokud getColumnState vrátí undefined, grid je v nekonzistentním stavu
362
+ // Zkusíme alternativní metodu přes getColumnDefs()
363
+ if (columnState === undefined || columnState === null) {
364
+ console.warn(
365
+ '[GridLayout] getColumnState returned undefined/null, trying getColumnDefs() alternative'
366
+ );
367
+ console.warn(
368
+ '[GridLayout] Full gridApiRef.current object:',
369
+ gridApiRef.current
370
+ );
371
+ console.warn(
372
+ '[GridLayout] Available methods on gridApiRef.current:',
373
+ gridApiRef.current
374
+ ? Object.getOwnPropertyNames(gridApiRef.current).filter(
375
+ (name) => typeof gridApiRef.current[name] === 'function'
376
+ )
377
+ : 'NO_GRID_API'
378
+ );
379
+
380
+ try {
381
+ // Alternativní přístup: použijeme getColumnDefs() a vytvoříme fake column state
382
+ const columnDefs = gridApiRef.current.getColumnDefs();
383
+
384
+ if (
385
+ columnDefs &&
386
+ Array.isArray(columnDefs) &&
387
+ columnDefs.length > 0
388
+ ) {
389
+ // Vytvoříme column state z column defs
390
+ columnState = columnDefs.map((colDef, index) => {
391
+ // Zkusíme získat aktuální šířku sloupce z DOM
392
+ let currentWidth = colDef.width || 100;
393
+ try {
394
+ const fieldId = colDef.field || colDef.colId;
395
+ const headerElement = document.querySelector(
396
+ `[col-id="${fieldId}"]`
397
+ );
398
+ if (headerElement && headerElement.offsetWidth) {
399
+ currentWidth = headerElement.offsetWidth;
400
+ }
401
+ } catch (domError) {
402
+ console.log(
403
+ '[GridLayout] Could not get width from DOM for',
404
+ colDef.field
405
+ );
406
+ }
407
+
408
+ return {
409
+ colId: colDef.field || colDef.colId,
410
+ hide: colDef.hide || false,
411
+ width: currentWidth,
412
+ headerName: colDef.headerName, // Přidáme aktuální headerName
413
+ sort: null, // Nebudeme zachovávat sort při této fallback metodě
414
+ sortIndex: null,
415
+ };
416
+ });
417
+
418
+ } else {
419
+ console.error(
420
+ '[GridLayout] getColumnDefs() also failed or returned empty array'
421
+ );
422
+ const cachedColumnDefs = stableColumnDefsRef.current;
423
+ if (
424
+ cachedColumnDefs &&
425
+ Array.isArray(cachedColumnDefs) &&
426
+ cachedColumnDefs.length > 0
427
+ ) {
428
+ columnState = cachedColumnDefs.map((colDef, index) => {
429
+ // Použijeme cached definice
430
+ let currentWidth = colDef.width || 100;
431
+
432
+ return {
433
+ colId: colDef.field || colDef.colId,
434
+ hide: colDef.hide || false,
435
+ width: currentWidth,
436
+ headerName: colDef.headerName, // Přidáme aktuální headerName
437
+ sort: null,
438
+ sortIndex: null,
439
+ };
440
+ });
441
+ } else {
442
+ console.error(
443
+ '[GridLayout] All fallback methods failed - no column data available'
444
+ );
445
+ return;
446
+ }
447
+ }
448
+ } catch (columnDefError) {
449
+ console.error(
450
+ '[GridLayout] getColumnDefs() alternative failed:',
451
+ columnDefError
452
+ );
453
+ return;
454
+ }
455
+ }
456
+ } catch (error) {
457
+ console.error(
458
+ '[GridLayout] Error calling gridApiRef.current.getColumnState():',
459
+ error
460
+ );
461
+ return;
462
+ }
463
+ } else if (
464
+ columnApiRef.current &&
465
+ typeof columnApiRef.current.getColumnState === 'function'
466
+ ) {
467
+ // Starší verze AG Grid - getColumnState je v columnApi
468
+ try {
469
+ columnState = columnApiRef.current.getColumnState();
470
+
471
+ // Získáme aktuální headerName hodnoty z DOM pro každý sloupec
472
+ if (columnState && Array.isArray(columnState)) {
473
+ columnState = columnState.map(colState => {
474
+ // Pokusíme se získat aktuální headerName z DOM
475
+ try {
476
+ const headerCell = document.querySelector(`[col-id="${colState.colId}"]`);
477
+ if (headerCell) {
478
+ const headerTextEl = headerCell.querySelector('.ag-header-cell-text');
479
+ if (headerTextEl && headerTextEl.textContent) {
480
+ return {
481
+ ...colState,
482
+ headerName: headerTextEl.textContent
483
+ };
484
+ }
485
+ }
486
+ } catch (headerError) {
487
+ console.log(`[GridLayout] Could not get headerName from DOM for ${colState.colId}`);
488
+ }
489
+ return colState;
490
+ });
491
+ }
492
+
493
+ } catch (error) {
494
+ console.error(
495
+ '[GridLayout] Error calling columnApiRef.current.getColumnState():',
496
+ error
497
+ );
498
+ return;
499
+ }
500
+ } else {
501
+ console.warn(
502
+ '[GridLayout] getColumnState method not available for saving layout'
503
+ );
504
+ console.warn('[GridLayout] Debug info:', {
505
+ gridApiRef: gridApiRef.current,
506
+ columnApiRef: columnApiRef.current,
507
+ gridApiRefKeys: gridApiRef.current
508
+ ? Object.keys(gridApiRef.current)
509
+ : 'undefined',
510
+ columnApiRefKeys: columnApiRef.current
511
+ ? Object.keys(columnApiRef.current)
512
+ : 'undefined',
513
+ });
514
+ return;
515
+ }
516
+
517
+ // Kontrola validity columnDefs a použití fallback pokud je potřeba
518
+ let columnDefsToUse = stableColumnDefsRef.current;
519
+ if (
520
+ !columnDefsToUse ||
521
+ !Array.isArray(columnDefsToUse) ||
522
+ columnDefsToUse.length === 0
523
+ ) {
524
+ console.warn(
525
+ '[GridLayout] stableColumnDefsRef is empty, using fallback from stableCurrentColumnsRef'
526
+ );
527
+ if (
528
+ stableCurrentColumnsRef.current &&
529
+ Array.isArray(stableCurrentColumnsRef.current)
530
+ ) {
531
+ // Převedeme cached sloupce zpět na columnDefs format
532
+ columnDefsToUse = stableCurrentColumnsRef.current.map((col) => ({
533
+ field: col.field,
534
+ headerName: col.headerName || col.field,
535
+ width: col.width || 100,
536
+ hide: !col.visible,
537
+ }));
538
+ } else {
539
+ console.error(
540
+ '[GridLayout] No valid columnDefs available for saving'
541
+ );
542
+ return;
543
+ }
544
+ }
545
+
546
+ // Transformujeme na Grid API format
547
+ const fields =
548
+ stableGridLayoutApiRef.current.transformColumnStateToFields(
549
+ columnState,
550
+ columnDefsToUse
551
+ );
552
+
553
+ // Uložíme do API
554
+ const result = stableGridLayoutApiRef.current
555
+ .saveGridLayout(fields)
556
+ .then((result) => {
557
+ if (result.success) {
558
+ setHasUnsavedChanges(false);
559
+ if (stableOnLayoutSavedRef.current) {
560
+ stableOnLayoutSavedRef.current(fields, columnState);
561
+ }
562
+ }
563
+ })
564
+ .catch((error) => {
565
+ stableHandleErrorRef.current(error, 'při ukládání layoutu');
566
+ });
567
+ } catch (error) {
568
+ stableHandleErrorRef.current(error, 'při ukládání layoutu');
569
+ } finally {
570
+ setIsSaving(false);
571
+ }
572
+ }, [enabled]);
573
+
574
+ /**
575
+ * Debounced auto-save pro předcházení častým voláním API
576
+ */
577
+ const debouncedSave = useMemo(() => {
578
+ const debouncedFn = debounce((...args) => {
579
+ return saveCurrentLayout(...args);
580
+ }, autoSaveDelay);
581
+ return debouncedFn;
582
+ }, [saveCurrentLayout, autoSaveDelay]);
583
+
584
+ /**
585
+ * Aplikuje saved layout na AG-Grid
586
+ * @param {boolean} forceApply - Vynucené aplikování ignorující isInitialized stav
587
+ */
588
+ const applySavedLayout = useCallback(
589
+ (forceApply = false) => {
590
+ // Ověříme dostupnost API a inicializaci
591
+ if (
592
+ !savedFields?.records ||
593
+ savedFields.records.length === 0 ||
594
+ (!forceApply && isInitialized)
595
+ ) {
596
+ return;
597
+ }
598
+
599
+ // Ověříme dostupnost applyColumnState metody (AG Grid v31+ má ji v main API)
600
+ let applyColumnStateApi = null;
601
+ if (
602
+ gridApiRef.current &&
603
+ typeof gridApiRef.current.applyColumnState === 'function'
604
+ ) {
605
+ applyColumnStateApi = gridApiRef.current;
606
+ } else if (
607
+ columnApiRef.current &&
608
+ typeof columnApiRef.current.applyColumnState === 'function'
609
+ ) {
610
+ applyColumnStateApi = columnApiRef.current;
611
+ }
612
+
613
+ if (!applyColumnStateApi) {
614
+ console.warn('[GridLayout] applyColumnState method not available');
615
+ return;
616
+ }
617
+
618
+ try {
619
+ setIsLoading(true);
620
+ setIsApplyingLayout(true);
621
+
622
+ // Transformujeme Grid API fields na AG-Grid column state
623
+ // Použijeme stableColumnDefsRef.current místo columnDefs pro zachování aktuálních šířek
624
+ const columnDefsToUse = stableColumnDefsRef.current || columnDefs;
625
+ const columnState = gridLayoutApi.transformFieldsToColumnState(
626
+ savedFields.records,
627
+ columnDefsToUse
628
+ );
629
+
630
+ if (columnState && columnState.length > 0) {
631
+ // Pokud je waitForSavedFields true, columnDefs jsou už pre-transformované,
632
+ // takže aplikujeme jen width a hide vlastnosti bez delay pro pořadí
633
+ const applyFunction = () => {
634
+ try {
635
+ // Aplikujeme column state na AG-Grid
636
+ let result;
637
+ try {
638
+ const applyOrderEnabled = !waitForSavedFields; // Při waitForSavedFields už je pořadí v columnDefs
639
+
640
+ // Při prvotním načtení (isInitialized=false) používáme přímo columnState z API
641
+ // Při následných aplikováních zachováváme aktuální šířky z gridu
642
+ let adjustedColumnState = columnState;
643
+
644
+ if (isInitialized) {
645
+ // Pouze pokud už je grid inicializován, zachováváme současné šířky
646
+ const currentColumnState = gridApiRef.current?.getColumnState?.() || [];
647
+ const currentWidthMap = new Map();
648
+ currentColumnState.forEach(colState => {
649
+ if (colState.colId && colState.width) {
650
+ currentWidthMap.set(colState.colId, colState.width);
651
+ }
652
+ });
653
+
654
+ // Upravíme columnState tak, aby zachoval aktuální šířky pokud existují
655
+ adjustedColumnState = columnState.map(colState => {
656
+ const currentWidth = currentWidthMap.get(colState.colId);
657
+ if (currentWidth && currentWidth !== colState.width) {
658
+ return {
659
+ ...colState,
660
+ width: currentWidth
661
+ };
662
+ }
663
+ return colState;
664
+ });
665
+ }
666
+
667
+ result = applyColumnStateApi.applyColumnState({
668
+ state: adjustedColumnState,
669
+ applyOrder: applyOrderEnabled, // Pořadí jen když není waitForSavedFields
670
+ defaultState: {
671
+ sort: null, // Reset sorting na všech sloupcích
672
+ sortIndex: null, // Reset sort index
673
+ pivot: null, // Reset pivot
674
+ rowGroup: null, // Reset row grouping
675
+ },
676
+ });
677
+
678
+ // Explicitně aktualizujeme headerName pro každý sloupec, protože AG-Grid
679
+ // nepodporuje nastavení headerName přes applyColumnState
680
+ if (
681
+ savedFields.records &&
682
+ Array.isArray(savedFields.records) &&
683
+ gridApiRef.current
684
+ ) {
685
+ // Nejprve zkusíme použít refreshHeader funkci, pokud je dostupná
686
+ try {
687
+ if (
688
+ typeof gridApiRef.current.refreshHeader === 'function'
689
+ ) {
690
+ gridApiRef.current.refreshHeader();
691
+ }
692
+ } catch (refreshError) {
693
+ console.error(
694
+ '[GridLayout] Error in refreshHeader:',
695
+ refreshError
696
+ );
697
+ }
698
+ // Získáme aktuální definice sloupců z gridu
699
+ let currentColDefs;
700
+ try {
701
+ currentColDefs = gridApiRef.current.getColumnDefs
702
+ ? gridApiRef.current.getColumnDefs()
703
+ : null;
704
+ } catch (error) {
705
+ console.error(
706
+ '[GridLayout] Error getting column definitions:',
707
+ error
708
+ );
709
+ currentColDefs = null;
710
+ }
711
+ if (currentColDefs && Array.isArray(currentColDefs)) {
712
+ // Vytvoříme mapu fieldName -> headerName z API dat
713
+ const headerNameMap = new Map();
714
+ savedFields.records.forEach((field) => {
715
+ if (field.fieldName && field.headerName) {
716
+ headerNameMap.set(field.fieldName, field.headerName);
717
+ }
718
+ });
719
+
720
+ // Aktualizujeme headerName pro každý sloupec
721
+ const updatedColDefs = currentColDefs.map((colDef) => {
722
+ const fieldName = colDef.field;
723
+ if (fieldName && headerNameMap.has(fieldName)) {
724
+ const newHeaderName = headerNameMap.get(fieldName);
725
+ // Vytvoříme novou kopii definice sloupce s aktualizovaným headerName
726
+ return {
727
+ ...colDef,
728
+ headerName: newHeaderName,
729
+ };
730
+ }
731
+ return colDef;
732
+ });
733
+ // Aplikujeme aktualizované definice sloupců zpět do gridu
734
+ try {
735
+ if (
736
+ typeof gridApiRef.current.setColumnDefs === 'function'
737
+ ) {
738
+ gridApiRef.current.setColumnDefs(updatedColDefs);
739
+
740
+ // Pro jistotu zkontrolujeme, zda byly změny aplikovány
741
+ setTimeout(() => {
742
+ try {
743
+ const afterUpdateColDefs =
744
+ gridApiRef.current.getColumnDefs();
745
+
746
+ // DOM operace dokončeny, můžeme ukončit loading
747
+ setIsApplyingLayout(false);
748
+ } catch (checkError) {
749
+ console.error(
750
+ '[GridLayout] Error checking updated columns:',
751
+ checkError
752
+ );
753
+ // I při chybě ukončíme loading
754
+ setIsApplyingLayout(false);
755
+ }
756
+ }, 100);
757
+ } else {
758
+ // Alternativní řešení - přímá manipulace s DOM
759
+
760
+ // Počkáme, až se grid vyrenderuje
761
+ setTimeout(() => {
762
+ try {
763
+ // Vytvoříme mapu pro rychlou identifikaci
764
+ const headerUpdates = new Map();
765
+ updatedColDefs.forEach((colDef) => {
766
+ if (colDef.field && colDef.headerName) {
767
+ headerUpdates.set(
768
+ colDef.field,
769
+ colDef.headerName
770
+ );
771
+ }
772
+ });
773
+
774
+ // Najdeme všechny hlavičky sloupců pomocí DOM
775
+ const headerCells =
776
+ document.querySelectorAll('.ag-header-cell');
777
+
778
+ headerCells.forEach((headerCell) => {
779
+ try {
780
+ // Získáme ID sloupce z DOM atributů
781
+ const colId = headerCell.getAttribute('col-id');
782
+ if (colId && headerUpdates.has(colId)) {
783
+ // Najdeme element s textem hlavičky
784
+ const headerTextEl = headerCell.querySelector(
785
+ '.ag-header-cell-text'
786
+ );
787
+ if (headerTextEl) {
788
+ const newHeaderName =
789
+ headerUpdates.get(colId);
790
+ const currentText =
791
+ headerTextEl.textContent;
792
+ headerTextEl.textContent = newHeaderName;
793
+ }
794
+ }
795
+ } catch (cellError) {
796
+ console.error(
797
+ '[GridLayout] Error updating header cell:',
798
+ cellError
799
+ );
800
+ }
801
+ });
802
+
803
+ // DOM manipulace dokončena, ukončíme loading
804
+ setIsApplyingLayout(false);
805
+ } catch (domError) {
806
+ console.error(
807
+ '[GridLayout] Error in DOM manipulation:',
808
+ domError
809
+ );
810
+ // I při chybě ukončíme loading
811
+ setIsApplyingLayout(false);
812
+ }
813
+ }, 200);
814
+ }
815
+ } catch (setError) {
816
+ console.error(
817
+ '[GridLayout] Error applying column definitions:',
818
+ setError
819
+ );
820
+ }
821
+ }
822
+ }
823
+ } catch (applyError) {
824
+ throw applyError;
825
+ }
826
+
827
+ if (onLayoutLoaded) {
828
+ onLayoutLoaded(savedFields.records, columnState);
829
+ }
830
+ } catch (delayedError) {
831
+ // Removed console.error for production
832
+ handleError(delayedError, 'při delayed aplikování layoutu');
833
+ } finally {
834
+ // Bezpečnostní ukončení loading pokud se nedokončilo jinde
835
+ setTimeout(() => {
836
+ setIsApplyingLayout(false);
837
+ }, 300);
838
+ }
839
+ };
840
+
841
+ // Pro waitForSavedFields aplikujeme okamžitě (pořadí je už v columnDefs)
842
+ // Pro normální režim použijeme delay
843
+ if (waitForSavedFields) {
844
+ applyFunction();
845
+ } else {
846
+ setTimeout(applyFunction, 100); // 100ms delay jen pro normální režim
847
+ }
848
+ }
849
+ } catch (error) {
850
+ handleError(error, 'při aplikování layoutu');
851
+ } finally {
852
+ setIsInitialized(true);
853
+ setIsGridReady(true); // Obnovíme také isGridReady pro event handlery
854
+ setIsLoading(false);
855
+ // setIsApplyingLayout(false) se nastaví až po dokončení všech DOM operací
856
+ }
857
+ },
858
+ [
859
+ savedFields,
860
+ gridLayoutApi,
861
+ isInitialized,
862
+ onLayoutLoaded,
863
+ handleError,
864
+ columnDefs,
865
+ waitForSavedFields,
866
+ ]
867
+ );
868
+
869
+ /**
870
+ * Odložené akce pro případ rychlé interakce před dokončením inicializace
871
+ */
872
+ const [pendingActions, setPendingActions] = useState([]);
873
+
874
+ // Effect pro zpracování pending actions po dokončení inicializace
875
+ useEffect(() => {
876
+ if (isInitialized && pendingActions.length > 0) {
877
+ // Zpracujeme pending akce s krátkým delay
878
+ setTimeout(() => {
879
+ pendingActions.forEach((action) => {
880
+ switch (action.type) {
881
+ case 'dragStopped':
882
+ if (autoSave && debouncedSave) {
883
+ debouncedSave();
884
+ }
885
+ break;
886
+ }
887
+ });
888
+ setPendingActions([]);
889
+ }, 100);
890
+ }
891
+ }, [isInitialized, pendingActions, autoSave, debouncedSave]);
892
+
893
+ /**
894
+ * Event handlers pro AG-Grid
895
+ */
896
+ const handleColumnMoved = useCallback(() => {
897
+ if (!enabled || !isGridReady) {
898
+ return;
899
+ }
900
+
901
+ setHasUnsavedChanges(true);
902
+ // Neukládáme při každém pohybu - čekáme na onDragStopped
903
+ }, [enabled, isGridReady]);
904
+
905
+ const handleDragStopped = useCallback(() => {
906
+ if (!enabled || !isGridReady) {
907
+ return;
908
+ }
909
+
910
+ setHasUnsavedChanges(true);
911
+
912
+ // Aktualizujeme šířky sloupců v columnDefs před uložením
913
+ try {
914
+ if (gridApiRef.current && typeof gridApiRef.current.getColumnState === 'function') {
915
+ const currentColumnState = gridApiRef.current.getColumnState();
916
+
917
+ if (currentColumnState && Array.isArray(currentColumnState)) {
918
+ // Aktualizujeme šířky v původních columnDefs
919
+ const widthUpdatesMap = new Map();
920
+ currentColumnState.forEach(colState => {
921
+ if (colState.colId && colState.width) {
922
+ widthUpdatesMap.set(colState.colId, colState.width);
923
+ }
924
+ });
925
+
926
+ // Aktualizujeme stableColumnDefsRef s novými šířkami
927
+ if (stableColumnDefsRef.current && Array.isArray(stableColumnDefsRef.current)) {
928
+ const updatedColumnDefs = stableColumnDefsRef.current.map(colDef => {
929
+ const fieldId = colDef.field || colDef.colId;
930
+ if (fieldId && widthUpdatesMap.has(fieldId)) {
931
+ return {
932
+ ...colDef,
933
+ width: widthUpdatesMap.get(fieldId)
934
+ };
935
+ }
936
+ return colDef;
937
+ });
938
+ stableColumnDefsRef.current = updatedColumnDefs;
939
+ // Hash se aktualizuje automaticky v hlavním useEffect
940
+ }
941
+ }
942
+ }
943
+ } catch (error) {
944
+ console.error('[GridLayout] Error updating columnDefs widths in handleDragStopped:', error);
945
+ }
946
+
947
+
948
+ // Pokud ještě není inicializované, přidáme akci do pending
949
+ if (!isInitialized && autoSave) {
950
+ setPendingActions((prev) => [
951
+ ...prev,
952
+ { type: 'dragStopped', timestamp: Date.now() },
953
+ ]);
954
+ return;
955
+ }
956
+
957
+ // Normální proces pokud je vše inicializované
958
+ if (autoSave && debouncedSave) {
959
+ debouncedSave();
960
+ } else {
961
+ }
962
+ }, [enabled, isGridReady, isInitialized, autoSave, debouncedSave]);
963
+
964
+ const handleColumnResized = useCallback(() => {
965
+ if (!enabled || !isGridReady) return;
966
+ setHasUnsavedChanges(true);
967
+
968
+ // Aktualizujeme šířky sloupců v columnDefs při resize
969
+ try {
970
+ if (gridApiRef.current && typeof gridApiRef.current.getColumnState === 'function') {
971
+ const currentColumnState = gridApiRef.current.getColumnState();
972
+
973
+ if (currentColumnState && Array.isArray(currentColumnState)) {
974
+ // Aktualizujeme šířky v původních columnDefs
975
+ const widthUpdatesMap = new Map();
976
+ currentColumnState.forEach(colState => {
977
+ if (colState.colId && colState.width) {
978
+ widthUpdatesMap.set(colState.colId, colState.width);
979
+ }
980
+ });
981
+
982
+ // Aktualizujeme stableColumnDefsRef s novými šířkami
983
+ if (stableColumnDefsRef.current && Array.isArray(stableColumnDefsRef.current)) {
984
+ const updatedColumnDefs = stableColumnDefsRef.current.map(colDef => {
985
+ const fieldId = colDef.field || colDef.colId;
986
+ if (fieldId && widthUpdatesMap.has(fieldId)) {
987
+ return {
988
+ ...colDef,
989
+ width: widthUpdatesMap.get(fieldId)
990
+ };
991
+ }
992
+ return colDef;
993
+ });
994
+ stableColumnDefsRef.current = updatedColumnDefs;
995
+ // Hash se aktualizuje automaticky v hlavním useEffect
996
+ }
997
+ }
998
+ }
999
+ } catch (error) {
1000
+ console.error('[GridLayout] Error updating columnDefs widths in handleColumnResized:', error);
1001
+ }
1002
+
1003
+ if (autoSave && isInitialized && debouncedSave) {
1004
+ debouncedSave();
1005
+ }
1006
+ }, [enabled, isGridReady, isInitialized, autoSave, debouncedSave]);
1007
+
1008
+ const handleColumnVisible = useCallback(() => {
1009
+ if (!enabled || !isGridReady) return;
1010
+ setHasUnsavedChanges(true);
1011
+ if (autoSave && isInitialized && debouncedSave) {
1012
+ debouncedSave();
1013
+ }
1014
+ }, [enabled, isGridReady, isInitialized, autoSave, debouncedSave]);
1015
+
1016
+ const handleColumnPinned = useCallback(() => {
1017
+ if (!enabled || !isGridReady) return;
1018
+ setHasUnsavedChanges(true);
1019
+ if (autoSave && isInitialized && debouncedSave) {
1020
+ debouncedSave();
1021
+ }
1022
+ }, [enabled, isGridReady, isInitialized, autoSave, debouncedSave]);
1023
+
1024
+ /**
1025
+ * AG-Grid Ready handler
1026
+ */
1027
+ const handleGridReady = useCallback(
1028
+ (params) => {
1029
+ if (!enabled) {
1030
+ return;
1031
+ }
1032
+
1033
+ gridApiRef.current = params.api;
1034
+ // AG Grid v31+ - columnApi je deprecated, všechny metody jsou v hlavním api
1035
+ // Pokud columnApi neexistuje, použijeme main api jako fallback
1036
+ columnApiRef.current = params.columnApi || params.api;
1037
+ // Okamžitě označíme grid jako připravený pro event handlery
1038
+ setIsGridReady(true);
1039
+
1040
+ // POZOR: Nenastavujeme isInitialized zde, protože by to zabránilo aplikování layoutu!
1041
+ // isInitialized se nastaví až po aplikování layoutu v applySavedLayout
1042
+ },
1043
+ [enabled, savedFields, isInitialized, applySavedLayout]
1044
+ );
1045
+
1046
+ /**
1047
+ * Effect pro aplikování layoutu při prvotním načtení savedFields
1048
+ */
1049
+ useEffect(() => {
1050
+ // Aplikujeme layout pokud:
1051
+ // 1. Máme savedFields z API
1052
+ // 2. Grid je ready (má API reference)
1053
+ // 3. Ještě jsme neinicializovali layout
1054
+ if (savedFields?.records && savedFields.records.length > 0 && gridApiRef.current && !isInitialized) {
1055
+ applySavedLayout();
1056
+ }
1057
+ }, [savedFields?.records, isInitialized, applySavedLayout]);
1058
+
1059
+ /**
1060
+ * Původní zakomentovaný effect pro aplikování layoutu když se načtou data
1061
+ */
1062
+ // useEffect(() => {
1063
+ // if (savedFields?.records && columnApiRef.current && !isInitialized) {
1064
+ // console.log('[GridLayout] Layout will be applied from effect');
1065
+ // applySavedLayout();
1066
+ // } else if (savedFields?.records && columnApiRef.current && isInitialized) {
1067
+ // // Pokud je již inicializován, ale přišla nová data, aktualizujeme jen DOM (ne celý layout)
1068
+ // console.log('[GridLayout] Already initialized, but received new data - updating headers directly');
1069
+
1070
+ // // Počkáme moment, než se nová data zpracují, a pak aktualizujeme headerName
1071
+ // setTimeout(() => {
1072
+ // try {
1073
+ // // Vytvoříme mapu fieldName -> headerName z API dat
1074
+ // const headerNameMap = new Map();
1075
+ // savedFields.records.forEach(field => {
1076
+ // if (field.fieldName && field.headerName) {
1077
+ // headerNameMap.set(field.fieldName, field.headerName);
1078
+ // console.log(`[GridLayout] Update after reload: '${field.fieldName}' -> '${field.headerName}'`);
1079
+ // }
1080
+ // });
1081
+
1082
+ // // Najdeme všechny hlavičky sloupců v DOM
1083
+ // const headerCells = document.querySelectorAll('.ag-header-cell');
1084
+ // console.log('[GridLayout] Found header cells after reload:', headerCells.length);
1085
+
1086
+ // // Aktualizujeme texty hlaviček
1087
+ // headerCells.forEach(headerCell => {
1088
+ // try {
1089
+ // // Získáme ID sloupce z DOM atributů
1090
+ // const colId = headerCell.getAttribute('col-id');
1091
+ // if (colId && headerNameMap.has(colId)) {
1092
+ // // Najdeme element s textem hlavičky
1093
+ // const headerTextEl = headerCell.querySelector('.ag-header-cell-text');
1094
+ // if (headerTextEl) {
1095
+ // const newHeaderName = headerNameMap.get(colId);
1096
+ // const currentText = headerTextEl.textContent;
1097
+ // console.log(`[GridLayout] DOM update after reload: Column '${colId}' header from '${currentText}' to '${newHeaderName}'`);
1098
+ // headerTextEl.textContent = newHeaderName;
1099
+ // }
1100
+ // }
1101
+ // } catch (cellError) {
1102
+ // console.error('[GridLayout] Error updating header cell after reload:', cellError);
1103
+ // }
1104
+ // });
1105
+
1106
+ // // Zkusíme vynutit překreslení hlavičky
1107
+ // if (typeof gridApiRef.current.refreshHeader === 'function') {
1108
+ // console.log('[GridLayout] Forcing header refresh after reload');
1109
+ // gridApiRef.current.refreshHeader();
1110
+ // }
1111
+ // } catch (error) {
1112
+ // console.error('[GridLayout] Error updating headers after reload:', error);
1113
+ // }
1114
+ // }, 200);
1115
+ // }
1116
+ // }, [savedFields, applySavedLayout, isInitialized, columnApiRef]);
1117
+
1118
+ /**
1119
+ * Alternativní metoda pro aktualizaci headerName po inicializaci gridu
1120
+ */
1121
+ // useEffect(() => {
1122
+ // // Pokud již proběhla inicializace a máme uložená data z API
1123
+ // if (isInitialized && savedFields?.records && gridApiRef.current) {
1124
+ // console.log('[GridLayout] Attempting alternative header update after initialization');
1125
+
1126
+ // // Zkusíme použít metodu columnDefHeaderNameChanged, pokud je dostupná
1127
+ // try {
1128
+ // if (typeof gridApiRef.current.columnDefHeaderNameChanged === 'function') {
1129
+ // console.log('[GridLayout] Trying columnDefHeaderNameChanged method');
1130
+
1131
+ // // Vytvoříme mapu fieldName -> headerName z API dat
1132
+ // const headerNameMap = new Map();
1133
+ // savedFields.records.forEach(field => {
1134
+ // if (field.fieldName && field.headerName) {
1135
+ // headerNameMap.set(field.fieldName, field.headerName);
1136
+ // }
1137
+ // });
1138
+
1139
+ // // Získáme aktuální definice sloupců
1140
+ // const currentColumnDefs = gridApiRef.current.getColumnDefs();
1141
+ // if (currentColumnDefs && Array.isArray(currentColumnDefs)) {
1142
+ // currentColumnDefs.forEach(colDef => {
1143
+ // if (colDef.field && headerNameMap.has(colDef.field)) {
1144
+ // const newHeaderName = headerNameMap.get(colDef.field);
1145
+ // if (colDef.headerName !== newHeaderName) {
1146
+ // console.log(`[GridLayout] Using columnDefHeaderNameChanged for '${colDef.field}': '${colDef.headerName}' -> '${newHeaderName}'`);
1147
+ // colDef.headerName = newHeaderName;
1148
+ // gridApiRef.current.columnDefHeaderNameChanged(colDef);
1149
+ // }
1150
+ // }
1151
+ // });
1152
+ // }
1153
+ // }
1154
+ // } catch (error) {
1155
+ // console.error('[GridLayout] Error using columnDefHeaderNameChanged:', error);
1156
+ // }
1157
+
1158
+ // // Počkáme chvíli, aby se grid stihl plně vyrenderovat
1159
+ // setTimeout(() => {
1160
+ // try {
1161
+ // // Získáme aktuální definice sloupců z gridu
1162
+ // const currentColDefs = gridApiRef.current.getColumnDefs ? gridApiRef.current.getColumnDefs() : null;
1163
+
1164
+ // if (currentColDefs && Array.isArray(currentColDefs)) {
1165
+ // // Vytvoříme mapu fieldName -> headerName z API dat
1166
+ // const headerNameMap = new Map();
1167
+ // savedFields.records.forEach(field => {
1168
+ // if (field.fieldName && field.headerName) {
1169
+ // headerNameMap.set(field.fieldName, field.headerName);
1170
+ // }
1171
+ // });
1172
+
1173
+ // // Zkontrolujeme, zda je třeba aktualizovat nějaké headery
1174
+ // let needsUpdate = false;
1175
+ // const updatedColDefs = currentColDefs.map(colDef => {
1176
+ // const fieldName = colDef.field;
1177
+ // if (fieldName && headerNameMap.has(fieldName)) {
1178
+ // const newHeaderName = headerNameMap.get(fieldName);
1179
+ // if (colDef.headerName !== newHeaderName) {
1180
+ // needsUpdate = true;
1181
+ // console.log(`[GridLayout] Alternative update: Column '${fieldName}' header from '${colDef.headerName}' to '${newHeaderName}'`);
1182
+ // return {
1183
+ // ...colDef,
1184
+ // headerName: newHeaderName
1185
+ // };
1186
+ // }
1187
+ // }
1188
+ // return colDef;
1189
+ // });
1190
+
1191
+ // if (needsUpdate && typeof gridApiRef.current.setColumnDefs === 'function') {
1192
+ // console.log('[GridLayout] Applying alternative column header update');
1193
+ // gridApiRef.current.setColumnDefs(updatedColDefs);
1194
+
1195
+ // // Zkusíme vynutit překreslení hlavičky
1196
+ // setTimeout(() => {
1197
+ // if (typeof gridApiRef.current.refreshHeader === 'function') {
1198
+ // console.log('[GridLayout] Forcing header refresh');
1199
+ // gridApiRef.current.refreshHeader();
1200
+ // }
1201
+ // }, 50);
1202
+ // }
1203
+ // }
1204
+ // } catch (error) {
1205
+ // console.error('[GridLayout] Error in alternative header update:', error);
1206
+ // }
1207
+ // }, 300);
1208
+ // }
1209
+ // }, [isInitialized, savedFields, gridApiRef]);
1210
+
1211
+ /**
1212
+ * Effect pro error handling
1213
+ */
1214
+ useEffect(() => {
1215
+ if (fieldsError) {
1216
+ handleError(fieldsError, 'při načítání saved layoutu');
1217
+ }
1218
+ }, [fieldsError, handleError]);
1219
+
1220
+ /**
1221
+ * Cleanup effect
1222
+ */
1223
+ useEffect(() => {
1224
+ return () => {
1225
+ debouncedSave.cancel();
1226
+ };
1227
+ }, [debouncedSave]);
1228
+
1229
+ /**
1230
+ * Resetuje layout na default
1231
+ */
1232
+ const resetToDefault = useCallback(async () => {
1233
+ if (!enabled) return;
1234
+
1235
+ // Ověříme dostupnost resetColumnState metody (AG Grid v31+ má ji v main API)
1236
+ let resetColumnStateApi = null;
1237
+ if (
1238
+ gridApiRef.current &&
1239
+ typeof gridApiRef.current.resetColumnState === 'function'
1240
+ ) {
1241
+ resetColumnStateApi = gridApiRef.current;
1242
+ } else if (
1243
+ columnApiRef.current &&
1244
+ typeof columnApiRef.current.resetColumnState === 'function'
1245
+ ) {
1246
+ resetColumnStateApi = columnApiRef.current;
1247
+ } else {
1248
+ console.warn('[GridLayout] resetColumnState method not available');
1249
+ return;
1250
+ }
1251
+
1252
+ try {
1253
+ setIsLoading(true);
1254
+
1255
+ // Resetujeme AG-Grid na původní column definitions
1256
+ resetColumnStateApi.resetColumnState();
1257
+
1258
+ // Pokud je autoSave zapnuté, uložíme resetovaný stav
1259
+ if (autoSave) {
1260
+ await saveCurrentLayout();
1261
+ } else {
1262
+ setHasUnsavedChanges(true);
1263
+ }
1264
+ } catch (error) {
1265
+ handleError(error, 'při resetování layoutu');
1266
+ } finally {
1267
+ setIsLoading(false);
1268
+ }
1269
+ }, [enabled, autoSave, saveCurrentLayout, handleError]);
1270
+
1271
+ /**
1272
+ * Manuální uložení
1273
+ */
1274
+ const saveLayout = useCallback(async () => {
1275
+ await saveCurrentLayout();
1276
+ }, [saveCurrentLayout]);
1277
+
1278
+ /**
1279
+ * Manuální reload
1280
+ */
1281
+ const reloadLayout = useCallback(async () => {
1282
+ try {
1283
+ // Save a reference to the current grid API before resetting states
1284
+ const savedGridApiRef = gridApiRef.current;
1285
+ const savedColumnApiRef = columnApiRef.current;
1286
+
1287
+ setIsInitialized(false);
1288
+ setIsGridReady(false);
1289
+ await refetchFields();
1290
+
1291
+ // Restore saved references if they were lost during the reload process
1292
+ if (!gridApiRef.current && savedGridApiRef) {
1293
+ gridApiRef.current = savedGridApiRef;
1294
+ }
1295
+
1296
+ if (!columnApiRef.current && savedColumnApiRef) {
1297
+ columnApiRef.current = savedColumnApiRef;
1298
+ }
1299
+
1300
+ // Po refetch dat obnovíme oba stavy (layout je už v DB)
1301
+ setIsInitialized(true);
1302
+ setIsGridReady(true);
1303
+ } catch (error) {
1304
+ handleError(error, 'při reload layoutu');
1305
+ }
1306
+ }, [refetchFields, handleError]);
1307
+
1308
+ /**
1309
+ * Získá aktuální column state pro Column Editor
1310
+ */
1311
+ const getCurrentColumnsForEditor = useCallback(() => {
1312
+ // Zajistíme, že máme validní columnDefs
1313
+ const validColumnDefs = Array.isArray(columnDefs) ? columnDefs : [];
1314
+
1315
+ try {
1316
+ let currentColumnState = null;
1317
+
1318
+ // Pokusíme se získat column state s různými fallbacky pro AG Grid v31+
1319
+ if (
1320
+ gridApiRef.current &&
1321
+ typeof gridApiRef.current.getColumnState === 'function'
1322
+ ) {
1323
+ // AG Grid v31+ - getColumnState je přímo v main API
1324
+ currentColumnState = gridApiRef.current.getColumnState();
1325
+ } else if (
1326
+ columnApiRef.current &&
1327
+ typeof columnApiRef.current.getColumnState === 'function'
1328
+ ) {
1329
+ // Starší verze AG Grid - getColumnState je v columnApi
1330
+ currentColumnState = columnApiRef.current.getColumnState();
1331
+ } else {
1332
+ throw new Error(
1333
+ 'getColumnState method is not available on gridApiRef or columnApiRef'
1334
+ );
1335
+ }
1336
+
1337
+ if (
1338
+ !Array.isArray(currentColumnState) ||
1339
+ currentColumnState.length === 0
1340
+ ) {
1341
+ // Fallback pokud getColumnState() nevrátí validní array
1342
+ if (lastKnownColumnState) {
1343
+ return lastKnownColumnState;
1344
+ }
1345
+
1346
+ const defaultColumns = validColumnDefs.map((col, index) => ({
1347
+ id: col.field,
1348
+ field: col.field,
1349
+ headerName: col.headerName || col.field,
1350
+ originalHeaderName: col.headerName || col.field,
1351
+ width: col.width || 100,
1352
+ originalWidth: col.width || 100,
1353
+ visible: !col.hide,
1354
+ order: index,
1355
+ originalOrder: index, // Původní pořadí z columnDefs
1356
+ }));
1357
+
1358
+ // POZN: Neukládáme do state, aby nedocházelo k infinite loop
1359
+ return defaultColumns;
1360
+ }
1361
+
1362
+ const result = currentColumnState.map((columnStateItem, index) => {
1363
+ // Najdeme odpovídající column definition podle colId (zohledníme field i colId)
1364
+ const colDef =
1365
+ validColumnDefs.find(
1366
+ (cd) =>
1367
+ cd.field === columnStateItem.colId ||
1368
+ cd.colId === columnStateItem.colId
1369
+ ) || {};
1370
+ // Pokusíme se najít saved hodnotu pro headerName
1371
+ const savedField = savedFields?.records?.find(
1372
+ (sf) => sf.fieldName === columnStateItem.colId
1373
+ );
1374
+
1375
+ // Najdeme původní index z columnDefs
1376
+ const originalIndex = validColumnDefs.findIndex(
1377
+ (cd) => (cd.field || cd.colId) === columnStateItem.colId
1378
+ );
1379
+
1380
+ return {
1381
+ id: columnStateItem.colId,
1382
+ field: columnStateItem.colId,
1383
+ headerName:
1384
+ savedField?.headerName ||
1385
+ colDef.headerName ||
1386
+ columnStateItem.colId,
1387
+ // Původní headerName z columnDefs (ne z uložených uživatelských preferencí)
1388
+ originalHeaderName: colDef.headerName || columnStateItem.colId,
1389
+ width: columnStateItem.width || colDef.width || 100,
1390
+ originalWidth: colDef.width || 100,
1391
+ visible: !columnStateItem.hide,
1392
+ order: index,
1393
+ originalOrder: originalIndex !== -1 ? originalIndex : index, // Původní pořadí z columnDefs
1394
+ };
1395
+ });
1396
+
1397
+ // POZN: Neukládáme do state, aby nedocházelo k infinite loop
1398
+ // Tato aktualizace proběhne v useEffect
1399
+ return result;
1400
+ } catch (error) {
1401
+ console.error('[GridLayout] Error in getCurrentColumnsForEditor:', error);
1402
+
1403
+ // Použijeme lastKnownColumnState, pokud existuje
1404
+ if (lastKnownColumnState) {
1405
+ return lastKnownColumnState;
1406
+ }
1407
+
1408
+ // Poslední fallback na columnDefs
1409
+ const defaultColumns = validColumnDefs.map((col, index) => ({
1410
+ id: col.field || col.colId,
1411
+ field: col.field || col.colId,
1412
+ headerName: col.headerName || col.field || col.colId,
1413
+ // Vždy používáme původní definici jako referenci pro porovnání
1414
+ originalHeaderName: col.headerName || col.field || col.colId,
1415
+ width: col.width || 100,
1416
+ originalWidth: col.width || 100,
1417
+ visible: !col.hide,
1418
+ order: index,
1419
+ originalOrder: index, // Původní pořadí z columnDefs
1420
+ }));
1421
+
1422
+ // POZN: Neukládáme do state, aby nedocházelo k infinite loop
1423
+ return defaultColumns;
1424
+ }
1425
+ }, [columnDefs, savedFields, lastKnownColumnState]);
1426
+
1427
+ /**
1428
+ * Otevře Column Editor modal
1429
+ */
1430
+ const openColumnEditor = useCallback(() => {
1431
+ // Před otevřením editoru si pouze zaznamenáme, že chceme aktualizovat stav sloupců
1432
+ // Samotná aktualizace proběhne v useEffect, ne během renderu
1433
+ setIsColumnEditorOpen(true);
1434
+ }, []);
1435
+
1436
+ /**
1437
+ * Zavře Column Editor modal
1438
+ */
1439
+ const closeColumnEditor = useCallback(() => {
1440
+ setIsColumnEditorOpen(false);
1441
+ }, []);
1442
+
1443
+ /**
1444
+ * Uloží změny z Column Editoru
1445
+ * @param {Array} editedColumns - Editované sloupce z modalu
1446
+ */
1447
+ const saveColumnEditorChanges = async (editedColumns) => {
1448
+ if (!enabled) return;
1449
+
1450
+ try {
1451
+ setIsLoading(true);
1452
+ setIsSaving(true);
1453
+
1454
+ // Filtrujeme pouze validní sloupce (s definovaným field)
1455
+ const validColumns = editedColumns.filter(
1456
+ (col) => col.field && col.field !== undefined
1457
+ );
1458
+
1459
+ // Připravíme UserFields pro API podle C# SaveUserFieldModel struktury
1460
+ const completeUserFields = validColumns.map((col, index) => ({
1461
+ Id: 0, // Nové pole má ID = 0, existující budou mít správné ID z API
1462
+ UserKey: userKey,
1463
+ ApplicationName: applicationName,
1464
+ GridName: gridName,
1465
+ FilterName: filterName || null,
1466
+ FieldName: col.field,
1467
+ HeaderName: col.headerName || col.field,
1468
+ Order: index, // Pořadí podle pozice v editedColumns
1469
+ Show: col.visible,
1470
+ Width: col.width != null ? col.width : null, // Zachováváme i nulovou šířku
1471
+ System: false,
1472
+ }));
1473
+
1474
+ // Uložení do API
1475
+ gridLayoutApi
1476
+ .saveGridLayout(completeUserFields)
1477
+ .then((result) => {
1478
+ if (result.success) {
1479
+ setHasUnsavedChanges(false);
1480
+
1481
+ // Znovunačtení layoutu z API a aplikování na grid
1482
+ reloadLayout().then(async () => {
1483
+ // Po reloadLayout() počkáme na aktualizaci savedFields a aplikujeme layout
1484
+ try {
1485
+ // Znovu načteme fieldy pro zajištění synchronizace
1486
+ const freshFields = await refetchFields();
1487
+
1488
+ setTimeout(() => {
1489
+ if (gridApiRef.current && freshFields?.data?.records) {
1490
+
1491
+ // Aplikujeme layout s novými daty
1492
+ const columnState = gridLayoutApi.transformFieldsToColumnState(
1493
+ freshFields.data.records,
1494
+ columnDefs
1495
+ );
1496
+
1497
+ if (columnState && columnState.length > 0) {
1498
+ // Najdeme správné API pro applyColumnState
1499
+ let applyColumnStateApi = null;
1500
+ if (gridApiRef.current?.applyColumnState) {
1501
+ applyColumnStateApi = gridApiRef.current;
1502
+ } else if (columnApiRef.current?.applyColumnState) {
1503
+ applyColumnStateApi = columnApiRef.current;
1504
+ }
1505
+
1506
+ if (applyColumnStateApi) {
1507
+ applyColumnStateApi.applyColumnState({
1508
+ state: columnState,
1509
+ applyOrder: true,
1510
+ defaultState: {
1511
+ sort: null,
1512
+ sortIndex: null,
1513
+ pivot: null,
1514
+ rowGroup: null,
1515
+ },
1516
+ });
1517
+
1518
+ // Aktualizujeme headerName pro každý sloupec
1519
+ const headerNameMap = new Map();
1520
+ freshFields.data.records.forEach((field) => {
1521
+ if (field.fieldName && field.headerName) {
1522
+ headerNameMap.set(field.fieldName, field.headerName);
1523
+ }
1524
+ });
1525
+
1526
+ // Aplikujeme nové headerName hodnoty přes DOM
1527
+ setTimeout(() => {
1528
+ try {
1529
+ const headerCells = document.querySelectorAll('.ag-header-cell');
1530
+ headerCells.forEach((headerCell) => {
1531
+ const colId = headerCell.getAttribute('col-id');
1532
+ if (colId && headerNameMap.has(colId)) {
1533
+ const headerTextEl = headerCell.querySelector('.ag-header-cell-text');
1534
+ if (headerTextEl) {
1535
+ headerTextEl.textContent = headerNameMap.get(colId);
1536
+ }
1537
+ }
1538
+ });
1539
+
1540
+ // Vynutíme refresh hlavičky
1541
+ if (gridApiRef.current?.refreshHeader) {
1542
+ gridApiRef.current.refreshHeader();
1543
+ }
1544
+ } catch (headerError) {
1545
+ console.error('[GridLayout] Error updating headers:', headerError);
1546
+ }
1547
+ }, 50);
1548
+ }
1549
+ }
1550
+ }
1551
+ }, 150);
1552
+ } catch (refreshError) {
1553
+ console.error('[GridLayout] Error in post-save refresh:', refreshError);
1554
+ // Fallback na standardní applySavedLayout
1555
+ setTimeout(() => {
1556
+ if (gridApiRef.current) {
1557
+ applySavedLayout(true);
1558
+ }
1559
+ }, 200);
1560
+ }
1561
+ });
1562
+
1563
+ if (onLayoutSaved) {
1564
+ onLayoutSaved(completeUserFields);
1565
+ }
1566
+ }
1567
+ })
1568
+ .catch((error) => {
1569
+ handleError(error, 'při ukládání změn z Column Editoru');
1570
+ });
1571
+ } catch (error) {
1572
+ handleError(error, 'při ukládání změn z Column Editoru');
1573
+ } finally {
1574
+ setIsLoading(false);
1575
+ setIsSaving(false);
1576
+ }
1577
+ };
1578
+ //, [enabled, gridLayoutApi, onLayoutSaved, handleError, userKey, applicationName, gridName, filterName, reloadLayout]);
1579
+
1580
+ // Logika pro zobrazení columnDefs
1581
+ const shouldShowColumns = useMemo(() => {
1582
+ if (!waitForSavedFields) return true; // Pokud není waitForSavedFields, vždy zobrazuj
1583
+
1584
+ // Pokud je waitForSavedFields true, čekáme na dokončení načítání
1585
+ return !isFieldsLoading && !isLoading;
1586
+ }, [waitForSavedFields, isFieldsLoading, isLoading]);
1587
+
1588
+ // Pre-transformované columnDefs podle savedFields pro waitForSavedFields mode
1589
+ // DŮLEŽITÉ: Toto useMemo nyní obsahuje i AgGridColumns transformaci pro eliminaci circular dependency
1590
+ const preTransformedColumnDefs = useMemo(() => {
1591
+ // Použijeme aktualizované columnDefs ze stableColumnDefsRef pokud existují
1592
+ const columnDefsToUse = stableColumnDefsRef.current || columnDefs;
1593
+
1594
+ let baseColumnDefs = columnDefsToUse;
1595
+
1596
+ // Pro waitForSavedFields aplikujeme grid layout transformace
1597
+ if (
1598
+ waitForSavedFields &&
1599
+ savedFields?.records &&
1600
+ Array.isArray(columnDefsToUse)
1601
+ ) {
1602
+ // Transformujeme columnDefs podle savedFields PŘED zobrazením gridu
1603
+ const columnState = gridLayoutApi.transformFieldsToColumnState(
1604
+ savedFields.records,
1605
+ columnDefsToUse
1606
+ );
1607
+
1608
+ if (columnState && columnState.length > 0) {
1609
+ // Vytvoříme mapu pro rychlé vyhledávání
1610
+ const columnStateMap = new Map();
1611
+ columnState.forEach((colState, index) => {
1612
+ columnStateMap.set(colState.colId, { ...colState, __order: index });
1613
+ });
1614
+
1615
+ // Vytvoříme mapu fieldName -> headerName z API dat
1616
+ const headerNameMap = new Map();
1617
+ savedFields.records.forEach((field) => {
1618
+ if (field.fieldName && field.headerName) {
1619
+ headerNameMap.set(field.fieldName, field.headerName);
1620
+ }
1621
+ });
1622
+
1623
+ // Seřadíme columnDefs podle pořadí z columnState a upravíme headerName podle savedFields
1624
+ baseColumnDefs = [...columnDefsToUse]
1625
+ .sort((a, b) => {
1626
+ const fieldA = a.field || a.colId;
1627
+ const fieldB = b.field || b.colId;
1628
+ const aState = columnStateMap.get(fieldA);
1629
+ const bState = columnStateMap.get(fieldB);
1630
+
1631
+ const aOrder = aState?.__order ?? 999;
1632
+ const bOrder = bState?.__order ?? 999;
1633
+
1634
+ return aOrder - bOrder;
1635
+ })
1636
+ .map((colDef) => {
1637
+ const fieldId = colDef.field || colDef.colId;
1638
+ const columnStateItem = columnStateMap.get(fieldId);
1639
+
1640
+ // Aplikujeme pouze headerName a hide z API (width se aplikuje až v applySavedLayout)
1641
+ if (fieldId && (headerNameMap.has(fieldId) || columnStateItem)) {
1642
+ return {
1643
+ ...colDef,
1644
+ // Aplikujeme headerName z savedFields
1645
+ ...(headerNameMap.has(fieldId) && { headerName: headerNameMap.get(fieldId) }),
1646
+ // Aplikujeme hide hodnotu z columnState (z API show hodnoty)
1647
+ ...(columnStateItem && { hide: columnStateItem.hide }),
1648
+ // Width se neaplikuje zde - aplikuje se až v applySavedLayout pro eliminaci konfliktu s resize
1649
+ };
1650
+ }
1651
+ return colDef;
1652
+ });
1653
+ }
1654
+ }
1655
+
1656
+ // Nyní VŽDY aplikujeme AgGridColumns transformaci PŘÍMO zde místo v props
1657
+ // Tím eliminujeme circular dependency pro všechny případy použití
1658
+ if (!Array.isArray(baseColumnDefs)) {
1659
+ // Pokud baseColumnDefs není array, mohlo by to být ColumnBuilder
1660
+ if (baseColumnDefs && typeof baseColumnDefs.build === 'function') {
1661
+ baseColumnDefs = baseColumnDefs.build();
1662
+ } else {
1663
+ return [];
1664
+ }
1665
+ }
1666
+
1667
+ return baseColumnDefs;
1668
+
1669
+ // // Replikujeme AgGridColumns logiku pro všechny column definitions:
1670
+ // const processedColumns = baseColumnDefs?.map((column) => {
1671
+ // // Zkopírujeme column aby se předešlo mutacím
1672
+ // const processedColumn = { ...column };
1673
+
1674
+ // // Aplikujeme tooltip logiku pokud je potřeba
1675
+ // if (processedColumn.contentTooltip) {
1676
+ // // Statické mapování tooltipů pro lepší performance a eliminaci circular dependency
1677
+ // switch (processedColumn.contentTooltip) {
1678
+ // case 'User':
1679
+ // processedColumn.tooltipComponent = Tooltip.User;
1680
+ // break;
1681
+ // default:
1682
+ // console.warn(`[GridLayout] Unknown tooltip component: ${processedColumn.contentTooltip}`);
1683
+ // }
1684
+ // }
1685
+
1686
+ // return processedColumn;
1687
+ // }) || [];
1688
+
1689
+ // return processedColumns;
1690
+ }, [waitForSavedFields, savedFields?.records, columnDefs, gridLayoutApi, lastColumnDefsHash]);
1691
+
1692
+ /**
1693
+ * Handler pro změnu zobrazených dat - aktualizace headerName
1694
+ */
1695
+ const handleRowDataChanged = useCallback(
1696
+ (params) => {
1697
+ // if (!enabled || !savedFields?.records || !isInitialized) return;
1698
+ // console.log('[GridLayout] Row data changed - ensuring header names stay updated');
1699
+ // // Počkáme chvíli, až se vše vyrenderuje, a pak aktualizujeme headerName
1700
+ // setTimeout(() => {
1701
+ // try {
1702
+ // // Vytvoříme mapu fieldName -> headerName z API dat
1703
+ // const headerNameMap = new Map();
1704
+ // savedFields.records.forEach(field => {
1705
+ // if (field.fieldName && field.headerName) {
1706
+ // headerNameMap.set(field.fieldName, field.headerName);
1707
+ // }
1708
+ // });
1709
+ // // Najdeme všechny hlavičky sloupců v DOM
1710
+ // const headerCells = document.querySelectorAll('.ag-header-cell');
1711
+ // console.log('[GridLayout] Found header cells after data change:', headerCells.length);
1712
+ // // Aktualizujeme texty hlaviček
1713
+ // headerCells.forEach(headerCell => {
1714
+ // try {
1715
+ // // Získáme ID sloupce z DOM atributů
1716
+ // const colId = headerCell.getAttribute('col-id');
1717
+ // if (colId && headerNameMap.has(colId)) {
1718
+ // // Najdeme element s textem hlavičky
1719
+ // const headerTextEl = headerCell.querySelector('.ag-header-cell-text');
1720
+ // if (headerTextEl) {
1721
+ // const newHeaderName = headerNameMap.get(colId);
1722
+ // const currentText = headerTextEl.textContent;
1723
+ // if (currentText !== newHeaderName) {
1724
+ // console.log(`[GridLayout] Data change update: Column '${colId}' header from '${currentText}' to '${newHeaderName}'`);
1725
+ // headerTextEl.textContent = newHeaderName;
1726
+ // }
1727
+ // }
1728
+ // }
1729
+ // } catch (cellError) {
1730
+ // console.error('[GridLayout] Error updating header cell after data change:', cellError);
1731
+ // }
1732
+ // });
1733
+ // // Zkusíme vynutit překreslení hlavičky
1734
+ // if (typeof gridApiRef.current.refreshHeader === 'function') {
1735
+ // console.log('[GridLayout] Forcing header refresh after data change');
1736
+ // gridApiRef.current.refreshHeader();
1737
+ // }
1738
+ // } catch (error) {
1739
+ // console.error('[GridLayout] Error updating headers after data change:', error);
1740
+ // }
1741
+ // }, 100);
1742
+ },
1743
+ [enabled, savedFields, isInitialized]
1744
+ );
1745
+
1746
+ return {
1747
+ // State
1748
+ isLoading: isLoading || isFieldsLoading,
1749
+ isSaving,
1750
+ isApplyingLayout, // Nový specifický state pro aplikování layoutu
1751
+ error,
1752
+ hasUnsavedChanges,
1753
+ isInitialized,
1754
+ isGridReady, // Nový stav pro rychlejší aktivaci event handlerů
1755
+ shouldShowColumns, // Nové pole pro řízení zobrazení columnDefs
1756
+ preTransformedColumnDefs, // Pre-transformované columnDefs pro waitForSavedFields
1757
+
1758
+ // AG-Grid event handlers
1759
+ onGridReady: handleGridReady,
1760
+ onColumnMoved: handleColumnMoved,
1761
+ onDragStopped: handleDragStopped, // Nový handler pro konec drag operace
1762
+ onColumnResized: handleColumnResized,
1763
+ onColumnVisible: handleColumnVisible,
1764
+ onColumnPinned: handleColumnPinned,
1765
+ onRowDataChanged: handleRowDataChanged, // Nový handler pro změnu dat
1766
+
1767
+ // Manual actions
1768
+ saveLayout,
1769
+ resetToDefault,
1770
+ reloadLayout,
1771
+
1772
+ // Column Editor
1773
+ isColumnEditorOpen,
1774
+ openColumnEditor,
1775
+ closeColumnEditor,
1776
+ saveColumnEditorChanges,
1777
+ getCurrentColumnsForEditor, // Nová funkce pro aktuální column state
1778
+
1779
+ // Data
1780
+ savedFields: savedFields?.records || [],
1781
+ columnDefs, // Původní columnDefs pro Column Editor
1782
+
1783
+ // Utils
1784
+ gridLayoutApi,
1785
+ };
1786
+ };
1787
+
1788
+ export default useGridLayout;