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

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,1714 @@
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 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
+
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
+
45
+ // Validace columnDefs - musí být array
46
+ if (
47
+ columnDefs !== undefined &&
48
+ columnDefs !== null &&
49
+ !Array.isArray(columnDefs)
50
+ ) {
51
+ console.error(
52
+ '[GridLayout] columnDefs is not an array:',
53
+ typeof columnDefs,
54
+ columnDefs
55
+ );
56
+ throw new Error('useGridLayout: columnDefs musí být array');
57
+ }
58
+
59
+ // Refs pro AG-Grid API
60
+ const gridApiRef = useRef(null);
61
+ const columnApiRef = useRef(null);
62
+
63
+ // State
64
+ const [isInitialized, setIsInitialized] = useState(false);
65
+ const [isGridReady, setIsGridReady] = useState(false);
66
+ const [isLoading, setIsLoading] = useState(false);
67
+ const [isSaving, setIsSaving] = useState(false);
68
+ const [error, setError] = useState(null);
69
+ const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
70
+ const [isColumnEditorOpen, setIsColumnEditorOpen] = useState(false);
71
+ const [lastKnownColumnState, setLastKnownColumnState] = useState(null);
72
+ const [columnDefsVersion, setColumnDefsVersion] = useState(0); // Pro tracking změn v stableColumnDefsRef
73
+
74
+ // Grid Layout API hook
75
+ const gridLayoutApi = useGridLayoutApi({
76
+ userKey,
77
+ applicationName,
78
+ gridName,
79
+ filterName,
80
+ accessToken,
81
+ });
82
+
83
+ // Query pro načtení saved layoutu
84
+ const {
85
+ data: savedFields,
86
+ isLoading: isFieldsLoading,
87
+ error: fieldsError,
88
+ refetch: refetchFields,
89
+ } = gridLayoutApi.useUserFields(
90
+ (columnDefs || []).map((colDef, index) => ({
91
+ name: colDef.field || colDef.colId || `column_${index}`,
92
+ displayName:
93
+ colDef.headerName ||
94
+ colDef.field ||
95
+ colDef.colId ||
96
+ `Column ${index + 1}`,
97
+ dataType: colDef.type || 'string',
98
+ isVisible: !colDef.hide,
99
+ width: colDef.width || 100,
100
+ order: index,
101
+ })),
102
+ {
103
+ enabled:
104
+ enabled &&
105
+ !!userKey &&
106
+ !!gridName &&
107
+ Array.isArray(columnDefs) &&
108
+ columnDefs.length > 0,
109
+ }
110
+ );
111
+
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
+ /**
230
+ * Error handler
231
+ */
232
+ const handleError = useCallback(
233
+ (error, context = '') => {
234
+ // Removed console.error for production
235
+ setError(error);
236
+ if (onError) {
237
+ onError(error, context);
238
+ }
239
+ },
240
+ [onError]
241
+ );
242
+
243
+ // Stabilní reference pro debouncedSave
244
+ const stableColumnDefsRef = useRef(columnDefs);
245
+ const stableGridLayoutApiRef = useRef(gridLayoutApi);
246
+ const stableOnLayoutSavedRef = useRef(onLayoutSaved);
247
+ const stableHandleErrorRef = useRef(handleError);
248
+ // Reference pro ukládání stavu sloupců bez state update v render cyklu
249
+ const stableCurrentColumnsRef = useRef(null);
250
+
251
+ // Aktualizujeme ref hodnoty
252
+ useEffect(() => {
253
+ // Pro columnDefs zachováváme aktualizované šířky pokud už 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
+ });
275
+
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;
281
+ }
282
+
283
+ stableGridLayoutApiRef.current = gridLayoutApi;
284
+ stableOnLayoutSavedRef.current = onLayoutSaved;
285
+ stableHandleErrorRef.current = handleError;
286
+ });
287
+
288
+ // Efekt pro bezpečnou aktualizaci lastKnownColumnState z ref
289
+ // useEffect(() => {
290
+ // if (stableCurrentColumnsRef.current) {
291
+ // setLastKnownColumnState(stableCurrentColumnsRef.current);
292
+ // console.log('[GridLayout] Updated lastKnownColumnState from ref');
293
+ // // Vymazat po použití
294
+ // stableCurrentColumnsRef.current = null;
295
+ // }
296
+ // }, [stableCurrentColumnsRef.current]);
297
+
298
+ /**
299
+ * Uloží současný stav sloupců do API
300
+ */
301
+ const saveCurrentLayout = useCallback(async () => {
302
+
303
+
304
+ if (!enabled || !gridApiRef.current) {
305
+ return;
306
+ }
307
+ try {
308
+ setIsSaving(true);
309
+ setError(null);
310
+
311
+ // Získáme současný column state z AG-Grid s fallbackem pro AG Grid v31+
312
+ let columnState = null;
313
+ if (
314
+ gridApiRef.current &&
315
+ typeof gridApiRef.current.getColumnState === 'function'
316
+ ) {
317
+ // AG Grid v31+ - getColumnState je přímo v main API
318
+ try {
319
+ columnState = gridApiRef.current.getColumnState();
320
+
321
+ // Získáme aktuální headerName hodnoty z DOM pro každý sloupec
322
+ if (columnState && Array.isArray(columnState)) {
323
+ columnState = columnState.map(colState => {
324
+ // Pokusíme se získat aktuální headerName z DOM
325
+ try {
326
+ const headerCell = document.querySelector(`[col-id="${colState.colId}"]`);
327
+ if (headerCell) {
328
+ const headerTextEl = headerCell.querySelector('.ag-header-cell-text');
329
+ if (headerTextEl && headerTextEl.textContent) {
330
+ return {
331
+ ...colState,
332
+ headerName: headerTextEl.textContent
333
+ };
334
+ }
335
+ }
336
+ } catch (headerError) {
337
+ console.log(`[GridLayout] Could not get headerName from DOM for ${colState.colId}`);
338
+ }
339
+ return colState;
340
+ });
341
+ }
342
+
343
+ // Pokud getColumnState vrátí undefined, grid je v nekonzistentním stavu
344
+ // Zkusíme alternativní metodu přes getColumnDefs()
345
+ if (columnState === undefined || columnState === null) {
346
+ console.warn(
347
+ '[GridLayout] getColumnState returned undefined/null, trying getColumnDefs() alternative'
348
+ );
349
+ console.warn(
350
+ '[GridLayout] Full gridApiRef.current object:',
351
+ gridApiRef.current
352
+ );
353
+ console.warn(
354
+ '[GridLayout] Available methods on gridApiRef.current:',
355
+ gridApiRef.current
356
+ ? Object.getOwnPropertyNames(gridApiRef.current).filter(
357
+ (name) => typeof gridApiRef.current[name] === 'function'
358
+ )
359
+ : 'NO_GRID_API'
360
+ );
361
+
362
+ try {
363
+ // Alternativní přístup: použijeme getColumnDefs() a vytvoříme fake column state
364
+ const columnDefs = gridApiRef.current.getColumnDefs();
365
+
366
+ if (
367
+ columnDefs &&
368
+ Array.isArray(columnDefs) &&
369
+ columnDefs.length > 0
370
+ ) {
371
+ // Vytvoříme column state z column defs
372
+ columnState = columnDefs.map((colDef, index) => {
373
+ // Zkusíme získat aktuální šířku sloupce z DOM
374
+ let currentWidth = colDef.width || 100;
375
+ try {
376
+ const fieldId = colDef.field || colDef.colId;
377
+ const headerElement = document.querySelector(
378
+ `[col-id="${fieldId}"]`
379
+ );
380
+ if (headerElement && headerElement.offsetWidth) {
381
+ currentWidth = headerElement.offsetWidth;
382
+ }
383
+ } catch (domError) {
384
+ console.log(
385
+ '[GridLayout] Could not get width from DOM for',
386
+ colDef.field
387
+ );
388
+ }
389
+
390
+ return {
391
+ colId: colDef.field || colDef.colId,
392
+ hide: colDef.hide || false,
393
+ width: currentWidth,
394
+ headerName: colDef.headerName, // Přidáme aktuální headerName
395
+ sort: null, // Nebudeme zachovávat sort při této fallback metodě
396
+ sortIndex: null,
397
+ };
398
+ });
399
+
400
+ } else {
401
+ console.error(
402
+ '[GridLayout] getColumnDefs() also failed or returned empty array'
403
+ );
404
+ const cachedColumnDefs = stableColumnDefsRef.current;
405
+ if (
406
+ cachedColumnDefs &&
407
+ Array.isArray(cachedColumnDefs) &&
408
+ cachedColumnDefs.length > 0
409
+ ) {
410
+ columnState = cachedColumnDefs.map((colDef, index) => {
411
+ // Použijeme cached definice
412
+ let currentWidth = colDef.width || 100;
413
+
414
+ return {
415
+ colId: colDef.field || colDef.colId,
416
+ hide: colDef.hide || false,
417
+ width: currentWidth,
418
+ headerName: colDef.headerName, // Přidáme aktuální headerName
419
+ sort: null,
420
+ sortIndex: null,
421
+ };
422
+ });
423
+ } else {
424
+ console.error(
425
+ '[GridLayout] All fallback methods failed - no column data available'
426
+ );
427
+ return;
428
+ }
429
+ }
430
+ } catch (columnDefError) {
431
+ console.error(
432
+ '[GridLayout] getColumnDefs() alternative failed:',
433
+ columnDefError
434
+ );
435
+ return;
436
+ }
437
+ }
438
+ } catch (error) {
439
+ console.error(
440
+ '[GridLayout] Error calling gridApiRef.current.getColumnState():',
441
+ error
442
+ );
443
+ return;
444
+ }
445
+ } else if (
446
+ columnApiRef.current &&
447
+ typeof columnApiRef.current.getColumnState === 'function'
448
+ ) {
449
+ // Starší verze AG Grid - getColumnState je v columnApi
450
+ try {
451
+ columnState = columnApiRef.current.getColumnState();
452
+
453
+ // Získáme aktuální headerName hodnoty z DOM pro každý sloupec
454
+ if (columnState && Array.isArray(columnState)) {
455
+ columnState = columnState.map(colState => {
456
+ // Pokusíme se získat aktuální headerName z DOM
457
+ try {
458
+ const headerCell = document.querySelector(`[col-id="${colState.colId}"]`);
459
+ if (headerCell) {
460
+ const headerTextEl = headerCell.querySelector('.ag-header-cell-text');
461
+ if (headerTextEl && headerTextEl.textContent) {
462
+ return {
463
+ ...colState,
464
+ headerName: headerTextEl.textContent
465
+ };
466
+ }
467
+ }
468
+ } catch (headerError) {
469
+ console.log(`[GridLayout] Could not get headerName from DOM for ${colState.colId}`);
470
+ }
471
+ return colState;
472
+ });
473
+ }
474
+
475
+ } catch (error) {
476
+ console.error(
477
+ '[GridLayout] Error calling columnApiRef.current.getColumnState():',
478
+ error
479
+ );
480
+ return;
481
+ }
482
+ } else {
483
+ console.warn(
484
+ '[GridLayout] getColumnState method not available for saving layout'
485
+ );
486
+ console.warn('[GridLayout] Debug info:', {
487
+ gridApiRef: gridApiRef.current,
488
+ columnApiRef: columnApiRef.current,
489
+ gridApiRefKeys: gridApiRef.current
490
+ ? Object.keys(gridApiRef.current)
491
+ : 'undefined',
492
+ columnApiRefKeys: columnApiRef.current
493
+ ? Object.keys(columnApiRef.current)
494
+ : 'undefined',
495
+ });
496
+ return;
497
+ }
498
+
499
+ // Kontrola validity columnDefs a použití fallback pokud je potřeba
500
+ let columnDefsToUse = stableColumnDefsRef.current;
501
+ if (
502
+ !columnDefsToUse ||
503
+ !Array.isArray(columnDefsToUse) ||
504
+ columnDefsToUse.length === 0
505
+ ) {
506
+ console.warn(
507
+ '[GridLayout] stableColumnDefsRef is empty, using fallback from stableCurrentColumnsRef'
508
+ );
509
+ if (
510
+ stableCurrentColumnsRef.current &&
511
+ Array.isArray(stableCurrentColumnsRef.current)
512
+ ) {
513
+ // Převedeme cached sloupce zpět na columnDefs format
514
+ columnDefsToUse = stableCurrentColumnsRef.current.map((col) => ({
515
+ field: col.field,
516
+ headerName: col.headerName || col.field,
517
+ width: col.width || 100,
518
+ hide: !col.visible,
519
+ }));
520
+ } else {
521
+ console.error(
522
+ '[GridLayout] No valid columnDefs available for saving'
523
+ );
524
+ return;
525
+ }
526
+ }
527
+
528
+ // Transformujeme na Grid API format
529
+ const fields =
530
+ stableGridLayoutApiRef.current.transformColumnStateToFields(
531
+ columnState,
532
+ columnDefsToUse
533
+ );
534
+
535
+ // Uložíme do API
536
+ const result = stableGridLayoutApiRef.current
537
+ .saveGridLayout(fields)
538
+ .then((result) => {
539
+ if (result.success) {
540
+ setHasUnsavedChanges(false);
541
+ if (stableOnLayoutSavedRef.current) {
542
+ stableOnLayoutSavedRef.current(fields, columnState);
543
+ }
544
+ }
545
+ })
546
+ .catch((error) => {
547
+ stableHandleErrorRef.current(error, 'při ukládání layoutu');
548
+ });
549
+ } catch (error) {
550
+ stableHandleErrorRef.current(error, 'při ukládání layoutu');
551
+ } finally {
552
+ setIsSaving(false);
553
+ }
554
+ }, [enabled]);
555
+
556
+ /**
557
+ * Debounced auto-save pro předcházení častým voláním API
558
+ */
559
+ const debouncedSave = useMemo(() => {
560
+ const debouncedFn = debounce((...args) => {
561
+ return saveCurrentLayout(...args);
562
+ }, autoSaveDelay);
563
+ return debouncedFn;
564
+ }, [saveCurrentLayout, autoSaveDelay]);
565
+
566
+ /**
567
+ * Aplikuje saved layout na AG-Grid
568
+ * @param {boolean} forceApply - Vynucené aplikování ignorující isInitialized stav
569
+ */
570
+ const applySavedLayout = useCallback(
571
+ (forceApply = false) => {
572
+ // Ověříme dostupnost API a inicializaci
573
+ if (
574
+ !savedFields?.records ||
575
+ savedFields.records.length === 0 ||
576
+ (!forceApply && isInitialized)
577
+ ) {
578
+ return;
579
+ }
580
+
581
+ // Ověříme dostupnost applyColumnState metody (AG Grid v31+ má ji v main API)
582
+ let applyColumnStateApi = null;
583
+ if (
584
+ gridApiRef.current &&
585
+ typeof gridApiRef.current.applyColumnState === 'function'
586
+ ) {
587
+ applyColumnStateApi = gridApiRef.current;
588
+ } else if (
589
+ columnApiRef.current &&
590
+ typeof columnApiRef.current.applyColumnState === 'function'
591
+ ) {
592
+ applyColumnStateApi = columnApiRef.current;
593
+ }
594
+
595
+ if (!applyColumnStateApi) {
596
+ console.warn('[GridLayout] applyColumnState method not available');
597
+ return;
598
+ }
599
+
600
+ try {
601
+
602
+ setIsLoading(true);
603
+ // Transformujeme Grid API fields na AG-Grid column state
604
+ // Použijeme stableColumnDefsRef.current místo columnDefs pro zachování aktuálních šířek
605
+ const columnDefsToUse = stableColumnDefsRef.current || columnDefs;
606
+ const columnState = gridLayoutApi.transformFieldsToColumnState(
607
+ savedFields.records,
608
+ columnDefsToUse
609
+ );
610
+
611
+ if (columnState && columnState.length > 0) {
612
+ // Pokud je waitForSavedFields true, columnDefs jsou už pre-transformované,
613
+ // takže aplikujeme jen width a hide vlastnosti bez delay pro pořadí
614
+ const applyFunction = () => {
615
+ try {
616
+ // Aplikujeme column state na AG-Grid
617
+ let result;
618
+ try {
619
+ const applyOrderEnabled = !waitForSavedFields; // Při waitForSavedFields už je pořadí v columnDefs
620
+
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) {
634
+ return {
635
+ ...colState,
636
+ width: currentWidth
637
+ };
638
+ }
639
+ return colState;
640
+ });
641
+
642
+ result = applyColumnStateApi.applyColumnState({
643
+ state: adjustedColumnState,
644
+ applyOrder: applyOrderEnabled, // Pořadí jen když není waitForSavedFields
645
+ defaultState: {
646
+ sort: null, // Reset sorting na všech sloupcích
647
+ sortIndex: null, // Reset sort index
648
+ pivot: null, // Reset pivot
649
+ rowGroup: null, // Reset row grouping
650
+ },
651
+ });
652
+
653
+ // Explicitně aktualizujeme headerName pro každý sloupec, protože AG-Grid
654
+ // nepodporuje nastavení headerName přes applyColumnState
655
+ if (
656
+ savedFields.records &&
657
+ Array.isArray(savedFields.records) &&
658
+ gridApiRef.current
659
+ ) {
660
+ // Nejprve zkusíme použít refreshHeader funkci, pokud je dostupná
661
+ try {
662
+ if (
663
+ typeof gridApiRef.current.refreshHeader === 'function'
664
+ ) {
665
+ gridApiRef.current.refreshHeader();
666
+ }
667
+ } catch (refreshError) {
668
+ console.error(
669
+ '[GridLayout] Error in refreshHeader:',
670
+ refreshError
671
+ );
672
+ }
673
+ // Získáme aktuální definice sloupců z gridu
674
+ let currentColDefs;
675
+ try {
676
+ currentColDefs = gridApiRef.current.getColumnDefs
677
+ ? gridApiRef.current.getColumnDefs()
678
+ : null;
679
+ } catch (error) {
680
+ console.error(
681
+ '[GridLayout] Error getting column definitions:',
682
+ error
683
+ );
684
+ currentColDefs = null;
685
+ }
686
+ if (currentColDefs && Array.isArray(currentColDefs)) {
687
+ // Vytvoříme mapu fieldName -> headerName z API dat
688
+ const headerNameMap = new Map();
689
+ savedFields.records.forEach((field) => {
690
+ if (field.fieldName && field.headerName) {
691
+ headerNameMap.set(field.fieldName, field.headerName);
692
+ }
693
+ });
694
+
695
+ // Aktualizujeme headerName pro každý sloupec
696
+ const updatedColDefs = currentColDefs.map((colDef) => {
697
+ const fieldName = colDef.field;
698
+ if (fieldName && headerNameMap.has(fieldName)) {
699
+ const newHeaderName = headerNameMap.get(fieldName);
700
+ // Vytvoříme novou kopii definice sloupce s aktualizovaným headerName
701
+ return {
702
+ ...colDef,
703
+ headerName: newHeaderName,
704
+ };
705
+ }
706
+ return colDef;
707
+ });
708
+ // Aplikujeme aktualizované definice sloupců zpět do gridu
709
+ try {
710
+ if (
711
+ typeof gridApiRef.current.setColumnDefs === 'function'
712
+ ) {
713
+ gridApiRef.current.setColumnDefs(updatedColDefs);
714
+
715
+ // Pro jistotu zkontrolujeme, zda byly změny aplikovány
716
+ setTimeout(() => {
717
+ try {
718
+ const afterUpdateColDefs =
719
+ gridApiRef.current.getColumnDefs();
720
+
721
+ } catch (checkError) {
722
+ console.error(
723
+ '[GridLayout] Error checking updated columns:',
724
+ checkError
725
+ );
726
+ }
727
+ }, 100);
728
+ } else {
729
+ // Alternativní řešení - přímá manipulace s DOM
730
+
731
+ // Počkáme, až se grid vyrenderuje
732
+ setTimeout(() => {
733
+ try {
734
+ // Vytvoříme mapu pro rychlou identifikaci
735
+ const headerUpdates = new Map();
736
+ updatedColDefs.forEach((colDef) => {
737
+ if (colDef.field && colDef.headerName) {
738
+ headerUpdates.set(
739
+ colDef.field,
740
+ colDef.headerName
741
+ );
742
+ }
743
+ });
744
+
745
+ // Najdeme všechny hlavičky sloupců pomocí DOM
746
+ const headerCells =
747
+ document.querySelectorAll('.ag-header-cell');
748
+
749
+ headerCells.forEach((headerCell) => {
750
+ try {
751
+ // Získáme ID sloupce z DOM atributů
752
+ const colId = headerCell.getAttribute('col-id');
753
+ if (colId && headerUpdates.has(colId)) {
754
+ // Najdeme element s textem hlavičky
755
+ const headerTextEl = headerCell.querySelector(
756
+ '.ag-header-cell-text'
757
+ );
758
+ if (headerTextEl) {
759
+ const newHeaderName =
760
+ headerUpdates.get(colId);
761
+ const currentText =
762
+ headerTextEl.textContent;
763
+ headerTextEl.textContent = newHeaderName;
764
+ }
765
+ }
766
+ } catch (cellError) {
767
+ console.error(
768
+ '[GridLayout] Error updating header cell:',
769
+ cellError
770
+ );
771
+ }
772
+ });
773
+ } catch (domError) {
774
+ console.error(
775
+ '[GridLayout] Error in DOM manipulation:',
776
+ domError
777
+ );
778
+ }
779
+ }, 200);
780
+ }
781
+ } catch (setError) {
782
+ console.error(
783
+ '[GridLayout] Error applying column definitions:',
784
+ setError
785
+ );
786
+ }
787
+ }
788
+ }
789
+ } catch (applyError) {
790
+ throw applyError;
791
+ }
792
+
793
+ if (onLayoutLoaded) {
794
+ onLayoutLoaded(savedFields.records, columnState);
795
+ }
796
+ } catch (delayedError) {
797
+ // Removed console.error for production
798
+ handleError(delayedError, 'při delayed aplikování layoutu');
799
+ }
800
+ };
801
+
802
+ // Pro waitForSavedFields aplikujeme okamžitě (pořadí je už v columnDefs)
803
+ // Pro normální režim použijeme delay
804
+ if (waitForSavedFields) {
805
+ applyFunction();
806
+ } else {
807
+ setTimeout(applyFunction, 100); // 100ms delay jen pro normální režim
808
+ }
809
+ }
810
+ } catch (error) {
811
+ handleError(error, 'při aplikování layoutu');
812
+ } finally {
813
+ setIsInitialized(true);
814
+ setIsGridReady(true); // Obnovíme také isGridReady pro event handlery
815
+ setIsLoading(false);
816
+ }
817
+ },
818
+ [
819
+ savedFields,
820
+ gridLayoutApi,
821
+ isInitialized,
822
+ onLayoutLoaded,
823
+ handleError,
824
+ columnDefs,
825
+ waitForSavedFields,
826
+ ]
827
+ );
828
+
829
+ /**
830
+ * Odložené akce pro případ rychlé interakce před dokončením inicializace
831
+ */
832
+ const [pendingActions, setPendingActions] = useState([]);
833
+
834
+ // Effect pro zpracování pending actions po dokončení inicializace
835
+ useEffect(() => {
836
+ if (isInitialized && pendingActions.length > 0) {
837
+ // Zpracujeme pending akce s krátkým delay
838
+ setTimeout(() => {
839
+ pendingActions.forEach((action) => {
840
+ switch (action.type) {
841
+ case 'dragStopped':
842
+ if (autoSave && debouncedSave) {
843
+ debouncedSave();
844
+ }
845
+ break;
846
+ }
847
+ });
848
+ setPendingActions([]);
849
+ }, 100);
850
+ }
851
+ }, [isInitialized, pendingActions, autoSave, debouncedSave]);
852
+
853
+ /**
854
+ * Event handlers pro AG-Grid
855
+ */
856
+ const handleColumnMoved = useCallback(() => {
857
+ if (!enabled || !isGridReady) {
858
+ return;
859
+ }
860
+
861
+ setHasUnsavedChanges(true);
862
+ // Neukládáme při každém pohybu - čekáme na onDragStopped
863
+ }, [enabled, isGridReady]);
864
+
865
+ const handleDragStopped = useCallback(() => {
866
+ if (!enabled || !isGridReady) {
867
+ return;
868
+ }
869
+
870
+ setHasUnsavedChanges(true);
871
+
872
+ // Aktualizujeme šířky sloupců v columnDefs před uložením
873
+ try {
874
+ if (gridApiRef.current && typeof gridApiRef.current.getColumnState === 'function') {
875
+ const currentColumnState = gridApiRef.current.getColumnState();
876
+
877
+ if (currentColumnState && Array.isArray(currentColumnState)) {
878
+ // Aktualizujeme šířky v původních columnDefs
879
+ const widthUpdatesMap = new Map();
880
+ currentColumnState.forEach(colState => {
881
+ if (colState.colId && colState.width) {
882
+ widthUpdatesMap.set(colState.colId, colState.width);
883
+ }
884
+ });
885
+
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
+ }
901
+ }
902
+ }
903
+ } catch (error) {
904
+ console.error('[GridLayout] Error updating columnDefs widths in handleDragStopped:', error);
905
+ }
906
+
907
+ // Pokud ještě není inicializované, přidáme akci do pending
908
+ if (!isInitialized && autoSave) {
909
+ setPendingActions((prev) => [
910
+ ...prev,
911
+ { type: 'dragStopped', timestamp: Date.now() },
912
+ ]);
913
+ return;
914
+ }
915
+
916
+ // Normální proces pokud je vše inicializované
917
+ if (autoSave && debouncedSave) {
918
+ debouncedSave();
919
+ } else {
920
+ }
921
+ }, [enabled, isGridReady, isInitialized, autoSave, debouncedSave]);
922
+
923
+ const handleColumnResized = useCallback(() => {
924
+ if (!enabled || !isGridReady) return;
925
+ setHasUnsavedChanges(true);
926
+
927
+ // Aktualizujeme šířky sloupců v columnDefs při resize
928
+ try {
929
+ if (gridApiRef.current && typeof gridApiRef.current.getColumnState === 'function') {
930
+ const currentColumnState = gridApiRef.current.getColumnState();
931
+
932
+ if (currentColumnState && Array.isArray(currentColumnState)) {
933
+ // Aktualizujeme šířky v původních columnDefs
934
+ const widthUpdatesMap = new Map();
935
+ currentColumnState.forEach(colState => {
936
+ if (colState.colId && colState.width) {
937
+ widthUpdatesMap.set(colState.colId, colState.width);
938
+ }
939
+ });
940
+
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
+ }
956
+ }
957
+ }
958
+ } catch (error) {
959
+ console.error('[GridLayout] Error updating columnDefs widths in handleColumnResized:', error);
960
+ }
961
+
962
+ if (autoSave && isInitialized && debouncedSave) {
963
+ debouncedSave();
964
+ }
965
+ }, [enabled, isGridReady, isInitialized, autoSave, debouncedSave]);
966
+
967
+ const handleColumnVisible = useCallback(() => {
968
+ if (!enabled || !isGridReady) return;
969
+ setHasUnsavedChanges(true);
970
+ if (autoSave && isInitialized && debouncedSave) {
971
+ debouncedSave();
972
+ }
973
+ }, [enabled, isGridReady, isInitialized, autoSave, debouncedSave]);
974
+
975
+ const handleColumnPinned = useCallback(() => {
976
+ if (!enabled || !isGridReady) return;
977
+ setHasUnsavedChanges(true);
978
+ if (autoSave && isInitialized && debouncedSave) {
979
+ debouncedSave();
980
+ }
981
+ }, [enabled, isGridReady, isInitialized, autoSave, debouncedSave]);
982
+
983
+ /**
984
+ * AG-Grid Ready handler
985
+ */
986
+ const handleGridReady = useCallback(
987
+ (params) => {
988
+ if (!enabled) {
989
+ return;
990
+ }
991
+
992
+ gridApiRef.current = params.api;
993
+ // AG Grid v31+ - columnApi je deprecated, všechny metody jsou v hlavním api
994
+ // Pokud columnApi neexistuje, použijeme main api jako fallback
995
+ columnApiRef.current = params.columnApi || params.api;
996
+ // Okamžitě označíme grid jako připravený pro event handlery
997
+ setIsGridReady(true);
998
+
999
+ // Po reloadLayout() NEPOTŘEBUJEME znovu aplikovat layout - už je v DB a grid ho má správně
1000
+ // Jen nastavíme isInitialized na true
1001
+ if (isInitialized === false) {
1002
+ setIsInitialized(true);
1003
+ }
1004
+ },
1005
+ [enabled, savedFields, isInitialized, applySavedLayout]
1006
+ );
1007
+
1008
+ /**
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
1011
+ */
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]);
1171
+
1172
+ /**
1173
+ * Effect pro error handling
1174
+ */
1175
+ useEffect(() => {
1176
+ if (fieldsError) {
1177
+ handleError(fieldsError, 'při načítání saved layoutu');
1178
+ }
1179
+ }, [fieldsError, handleError]);
1180
+
1181
+ /**
1182
+ * Cleanup effect
1183
+ */
1184
+ useEffect(() => {
1185
+ return () => {
1186
+ debouncedSave.cancel();
1187
+ };
1188
+ }, [debouncedSave]);
1189
+
1190
+ /**
1191
+ * Resetuje layout na default
1192
+ */
1193
+ const resetToDefault = useCallback(async () => {
1194
+ if (!enabled) return;
1195
+
1196
+ // Ověříme dostupnost resetColumnState metody (AG Grid v31+ má ji v main API)
1197
+ let resetColumnStateApi = null;
1198
+ if (
1199
+ gridApiRef.current &&
1200
+ typeof gridApiRef.current.resetColumnState === 'function'
1201
+ ) {
1202
+ resetColumnStateApi = gridApiRef.current;
1203
+ } else if (
1204
+ columnApiRef.current &&
1205
+ typeof columnApiRef.current.resetColumnState === 'function'
1206
+ ) {
1207
+ resetColumnStateApi = columnApiRef.current;
1208
+ } else {
1209
+ console.warn('[GridLayout] resetColumnState method not available');
1210
+ return;
1211
+ }
1212
+
1213
+ try {
1214
+ setIsLoading(true);
1215
+
1216
+ // Resetujeme AG-Grid na původní column definitions
1217
+ resetColumnStateApi.resetColumnState();
1218
+
1219
+ // Pokud je autoSave zapnuté, uložíme resetovaný stav
1220
+ if (autoSave) {
1221
+ await saveCurrentLayout();
1222
+ } else {
1223
+ setHasUnsavedChanges(true);
1224
+ }
1225
+ } catch (error) {
1226
+ handleError(error, 'při resetování layoutu');
1227
+ } finally {
1228
+ setIsLoading(false);
1229
+ }
1230
+ }, [enabled, autoSave, saveCurrentLayout, handleError]);
1231
+
1232
+ /**
1233
+ * Manuální uložení
1234
+ */
1235
+ const saveLayout = useCallback(async () => {
1236
+ await saveCurrentLayout();
1237
+ }, [saveCurrentLayout]);
1238
+
1239
+ /**
1240
+ * Manuální reload
1241
+ */
1242
+ const reloadLayout = useCallback(async () => {
1243
+ try {
1244
+ // Save a reference to the current grid API before resetting states
1245
+ const savedGridApiRef = gridApiRef.current;
1246
+ const savedColumnApiRef = columnApiRef.current;
1247
+
1248
+ setIsInitialized(false);
1249
+ setIsGridReady(false);
1250
+ await refetchFields();
1251
+
1252
+ // Restore saved references if they were lost during the reload process
1253
+ if (!gridApiRef.current && savedGridApiRef) {
1254
+ gridApiRef.current = savedGridApiRef;
1255
+ }
1256
+
1257
+ if (!columnApiRef.current && savedColumnApiRef) {
1258
+ columnApiRef.current = savedColumnApiRef;
1259
+ }
1260
+
1261
+ // Po refetch dat obnovíme oba stavy (layout je už v DB)
1262
+ setIsInitialized(true);
1263
+ setIsGridReady(true);
1264
+ } catch (error) {
1265
+ handleError(error, 'při reload layoutu');
1266
+ }
1267
+ }, [refetchFields, handleError]);
1268
+
1269
+ /**
1270
+ * Získá aktuální column state pro Column Editor
1271
+ */
1272
+ const getCurrentColumnsForEditor = useCallback(() => {
1273
+ // Zajistíme, že máme validní columnDefs
1274
+ const validColumnDefs = Array.isArray(columnDefs) ? columnDefs : [];
1275
+
1276
+ try {
1277
+ let currentColumnState = null;
1278
+
1279
+ // Pokusíme se získat column state s různými fallbacky pro AG Grid v31+
1280
+ if (
1281
+ gridApiRef.current &&
1282
+ typeof gridApiRef.current.getColumnState === 'function'
1283
+ ) {
1284
+ // AG Grid v31+ - getColumnState je přímo v main API
1285
+ currentColumnState = gridApiRef.current.getColumnState();
1286
+ } else if (
1287
+ columnApiRef.current &&
1288
+ typeof columnApiRef.current.getColumnState === 'function'
1289
+ ) {
1290
+ // Starší verze AG Grid - getColumnState je v columnApi
1291
+ currentColumnState = columnApiRef.current.getColumnState();
1292
+ } else {
1293
+ throw new Error(
1294
+ 'getColumnState method is not available on gridApiRef or columnApiRef'
1295
+ );
1296
+ }
1297
+
1298
+ if (
1299
+ !Array.isArray(currentColumnState) ||
1300
+ currentColumnState.length === 0
1301
+ ) {
1302
+ // Fallback pokud getColumnState() nevrátí validní array
1303
+ if (lastKnownColumnState) {
1304
+ return lastKnownColumnState;
1305
+ }
1306
+
1307
+ const defaultColumns = validColumnDefs.map((col, index) => ({
1308
+ id: col.field,
1309
+ field: col.field,
1310
+ headerName: col.headerName || col.field,
1311
+ originalHeaderName: col.headerName || col.field,
1312
+ width: col.width || 100,
1313
+ originalWidth: col.width || 100,
1314
+ visible: !col.hide,
1315
+ order: index,
1316
+ originalOrder: index, // Původní pořadí z columnDefs
1317
+ }));
1318
+
1319
+ // POZN: Neukládáme do state, aby nedocházelo k infinite loop
1320
+ return defaultColumns;
1321
+ }
1322
+
1323
+ const result = currentColumnState.map((columnStateItem, index) => {
1324
+ // Najdeme odpovídající column definition podle colId (zohledníme field i colId)
1325
+ const colDef =
1326
+ validColumnDefs.find(
1327
+ (cd) =>
1328
+ cd.field === columnStateItem.colId ||
1329
+ cd.colId === columnStateItem.colId
1330
+ ) || {};
1331
+ // Pokusíme se najít saved hodnotu pro headerName
1332
+ const savedField = savedFields?.records?.find(
1333
+ (sf) => sf.fieldName === columnStateItem.colId
1334
+ );
1335
+
1336
+ // Najdeme původní index z columnDefs
1337
+ const originalIndex = validColumnDefs.findIndex(
1338
+ (cd) => (cd.field || cd.colId) === columnStateItem.colId
1339
+ );
1340
+
1341
+ return {
1342
+ id: columnStateItem.colId,
1343
+ field: columnStateItem.colId,
1344
+ headerName:
1345
+ savedField?.headerName ||
1346
+ colDef.headerName ||
1347
+ columnStateItem.colId,
1348
+ // Původní headerName z columnDefs (ne z uložených uživatelských preferencí)
1349
+ originalHeaderName: colDef.headerName || columnStateItem.colId,
1350
+ width: columnStateItem.width || colDef.width || 100,
1351
+ originalWidth: colDef.width || 100,
1352
+ visible: !columnStateItem.hide,
1353
+ order: index,
1354
+ originalOrder: originalIndex !== -1 ? originalIndex : index, // Původní pořadí z columnDefs
1355
+ };
1356
+ });
1357
+
1358
+ // POZN: Neukládáme do state, aby nedocházelo k infinite loop
1359
+ // Tato aktualizace proběhne v useEffect
1360
+ return result;
1361
+ } catch (error) {
1362
+ console.error('[GridLayout] Error in getCurrentColumnsForEditor:', error);
1363
+
1364
+ // Použijeme lastKnownColumnState, pokud existuje
1365
+ if (lastKnownColumnState) {
1366
+ return lastKnownColumnState;
1367
+ }
1368
+
1369
+ // Poslední fallback na columnDefs
1370
+ const defaultColumns = validColumnDefs.map((col, index) => ({
1371
+ id: col.field || col.colId,
1372
+ field: col.field || col.colId,
1373
+ headerName: col.headerName || col.field || col.colId,
1374
+ // Vždy používáme původní definici jako referenci pro porovnání
1375
+ originalHeaderName: col.headerName || col.field || col.colId,
1376
+ width: col.width || 100,
1377
+ originalWidth: col.width || 100,
1378
+ visible: !col.hide,
1379
+ order: index,
1380
+ originalOrder: index, // Původní pořadí z columnDefs
1381
+ }));
1382
+
1383
+ // POZN: Neukládáme do state, aby nedocházelo k infinite loop
1384
+ return defaultColumns;
1385
+ }
1386
+ }, [columnDefs, savedFields, lastKnownColumnState]);
1387
+
1388
+ /**
1389
+ * Otevře Column Editor modal
1390
+ */
1391
+ const openColumnEditor = useCallback(() => {
1392
+ // Před otevřením editoru si pouze zaznamenáme, že chceme aktualizovat stav sloupců
1393
+ // Samotná aktualizace proběhne v useEffect, ne během renderu
1394
+ setIsColumnEditorOpen(true);
1395
+ }, []);
1396
+
1397
+ /**
1398
+ * Zavře Column Editor modal
1399
+ */
1400
+ const closeColumnEditor = useCallback(() => {
1401
+ setIsColumnEditorOpen(false);
1402
+ }, []);
1403
+
1404
+ /**
1405
+ * Uloží změny z Column Editoru
1406
+ * @param {Array} editedColumns - Editované sloupce z modalu
1407
+ */
1408
+ const saveColumnEditorChanges = async (editedColumns) => {
1409
+ if (!enabled) return;
1410
+
1411
+ try {
1412
+ setIsLoading(true);
1413
+ setIsSaving(true);
1414
+
1415
+ // Filtrujeme pouze validní sloupce (s definovaným field)
1416
+ const validColumns = editedColumns.filter(
1417
+ (col) => col.field && col.field !== undefined
1418
+ );
1419
+
1420
+ // Připravíme UserFields pro API podle C# SaveUserFieldModel struktury
1421
+ const completeUserFields = validColumns.map((col, index) => ({
1422
+ Id: 0, // Nové pole má ID = 0, existující budou mít správné ID z API
1423
+ UserKey: userKey,
1424
+ ApplicationName: applicationName,
1425
+ GridName: gridName,
1426
+ FilterName: filterName || null,
1427
+ FieldName: col.field,
1428
+ HeaderName: col.headerName || col.field,
1429
+ Order: index, // Pořadí podle pozice v editedColumns
1430
+ Show: col.visible,
1431
+ Width: col.width != null ? col.width : null, // Zachováváme i nulovou šířku
1432
+ System: false,
1433
+ }));
1434
+
1435
+ // Uložení do API
1436
+ gridLayoutApi
1437
+ .saveGridLayout(completeUserFields)
1438
+ .then((result) => {
1439
+ if (result.success) {
1440
+ setHasUnsavedChanges(false);
1441
+
1442
+ // Znovunačtení layoutu z API a aplikování na grid
1443
+ reloadLayout().then(async () => {
1444
+ // Po reloadLayout() počkáme na aktualizaci savedFields a aplikujeme layout
1445
+ try {
1446
+ // Znovu načteme fieldy pro zajištění synchronizace
1447
+ const freshFields = await refetchFields();
1448
+
1449
+ setTimeout(() => {
1450
+ if (gridApiRef.current && freshFields?.data?.records) {
1451
+
1452
+ // Aplikujeme layout s novými daty
1453
+ const columnState = gridLayoutApi.transformFieldsToColumnState(
1454
+ freshFields.data.records,
1455
+ columnDefs
1456
+ );
1457
+
1458
+ if (columnState && columnState.length > 0) {
1459
+ // Najdeme správné API pro applyColumnState
1460
+ let applyColumnStateApi = null;
1461
+ if (gridApiRef.current?.applyColumnState) {
1462
+ applyColumnStateApi = gridApiRef.current;
1463
+ } else if (columnApiRef.current?.applyColumnState) {
1464
+ applyColumnStateApi = columnApiRef.current;
1465
+ }
1466
+
1467
+ if (applyColumnStateApi) {
1468
+ applyColumnStateApi.applyColumnState({
1469
+ state: columnState,
1470
+ applyOrder: true,
1471
+ defaultState: {
1472
+ sort: null,
1473
+ sortIndex: null,
1474
+ pivot: null,
1475
+ rowGroup: null,
1476
+ },
1477
+ });
1478
+
1479
+ // Aktualizujeme headerName pro každý sloupec
1480
+ const headerNameMap = new Map();
1481
+ freshFields.data.records.forEach((field) => {
1482
+ if (field.fieldName && field.headerName) {
1483
+ headerNameMap.set(field.fieldName, field.headerName);
1484
+ }
1485
+ });
1486
+
1487
+ // Aplikujeme nové headerName hodnoty přes DOM
1488
+ setTimeout(() => {
1489
+ try {
1490
+ const headerCells = document.querySelectorAll('.ag-header-cell');
1491
+ headerCells.forEach((headerCell) => {
1492
+ const colId = headerCell.getAttribute('col-id');
1493
+ if (colId && headerNameMap.has(colId)) {
1494
+ const headerTextEl = headerCell.querySelector('.ag-header-cell-text');
1495
+ if (headerTextEl) {
1496
+ headerTextEl.textContent = headerNameMap.get(colId);
1497
+ }
1498
+ }
1499
+ });
1500
+
1501
+ // Vynutíme refresh hlavičky
1502
+ if (gridApiRef.current?.refreshHeader) {
1503
+ gridApiRef.current.refreshHeader();
1504
+ }
1505
+ } catch (headerError) {
1506
+ console.error('[GridLayout] Error updating headers:', headerError);
1507
+ }
1508
+ }, 50);
1509
+ }
1510
+ }
1511
+ }
1512
+ }, 150);
1513
+ } catch (refreshError) {
1514
+ console.error('[GridLayout] Error in post-save refresh:', refreshError);
1515
+ // Fallback na standardní applySavedLayout
1516
+ setTimeout(() => {
1517
+ if (gridApiRef.current) {
1518
+ applySavedLayout(true);
1519
+ }
1520
+ }, 200);
1521
+ }
1522
+ });
1523
+
1524
+ if (onLayoutSaved) {
1525
+ onLayoutSaved(completeUserFields);
1526
+ }
1527
+ }
1528
+ })
1529
+ .catch((error) => {
1530
+ handleError(error, 'při ukládání změn z Column Editoru');
1531
+ });
1532
+ } catch (error) {
1533
+ handleError(error, 'při ukládání změn z Column Editoru');
1534
+ } finally {
1535
+ setIsLoading(false);
1536
+ setIsSaving(false);
1537
+ }
1538
+ };
1539
+ //, [enabled, gridLayoutApi, onLayoutSaved, handleError, userKey, applicationName, gridName, filterName, reloadLayout]);
1540
+
1541
+ // Logika pro zobrazení columnDefs
1542
+ const shouldShowColumns = useMemo(() => {
1543
+ if (!waitForSavedFields) return true; // Pokud není waitForSavedFields, vždy zobrazuj
1544
+
1545
+ // Pokud je waitForSavedFields true, čekáme na dokončení načítání
1546
+ return !isFieldsLoading && !isLoading;
1547
+ }, [waitForSavedFields, isFieldsLoading, isLoading]);
1548
+
1549
+ // Pre-transformované columnDefs podle savedFields pro waitForSavedFields mode
1550
+ const preTransformedColumnDefs = useMemo(() => {
1551
+ // Použijeme aktualizované columnDefs ze stableColumnDefsRef pokud existují
1552
+ const columnDefsToUse = stableColumnDefsRef.current || columnDefs;
1553
+
1554
+ if (
1555
+ !waitForSavedFields ||
1556
+ !savedFields?.records ||
1557
+ !Array.isArray(columnDefsToUse)
1558
+ ) {
1559
+ return columnDefsToUse; // Aktualizované columnDefs
1560
+ }
1561
+
1562
+ // Transformujeme columnDefs podle savedFields PŘED zobrazením gridu
1563
+ const columnState = gridLayoutApi.transformFieldsToColumnState(
1564
+ savedFields.records,
1565
+ columnDefsToUse
1566
+ );
1567
+
1568
+ if (!columnState || columnState.length === 0) {
1569
+ return columnDefsToUse; // Pokud není co transformovat, vrátíme aktualizované
1570
+ }
1571
+
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
+ });
1577
+
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);
1583
+ }
1584
+ });
1585
+
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
+ });
1615
+
1616
+ return sortedColumnDefs;
1617
+ }, [waitForSavedFields, savedFields?.records, columnDefs, gridLayoutApi, columnDefsVersion]);
1618
+
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
+ );
1672
+
1673
+ return {
1674
+ // State
1675
+ isLoading: isLoading || isFieldsLoading,
1676
+ isSaving,
1677
+ error,
1678
+ hasUnsavedChanges,
1679
+ isInitialized,
1680
+ isGridReady, // Nový stav pro rychlejší aktivaci event handlerů
1681
+ shouldShowColumns, // Nové pole pro řízení zobrazení columnDefs
1682
+ preTransformedColumnDefs, // Pre-transformované columnDefs pro waitForSavedFields
1683
+
1684
+ // AG-Grid event handlers
1685
+ onGridReady: handleGridReady,
1686
+ onColumnMoved: handleColumnMoved,
1687
+ onDragStopped: handleDragStopped, // Nový handler pro konec drag operace
1688
+ onColumnResized: handleColumnResized,
1689
+ onColumnVisible: handleColumnVisible,
1690
+ onColumnPinned: handleColumnPinned,
1691
+ onRowDataChanged: handleRowDataChanged, // Nový handler pro změnu dat
1692
+
1693
+ // Manual actions
1694
+ saveLayout,
1695
+ resetToDefault,
1696
+ reloadLayout,
1697
+
1698
+ // Column Editor
1699
+ isColumnEditorOpen,
1700
+ openColumnEditor,
1701
+ closeColumnEditor,
1702
+ saveColumnEditorChanges,
1703
+ getCurrentColumnsForEditor, // Nová funkce pro aktuální column state
1704
+
1705
+ // Data
1706
+ savedFields: savedFields?.records || [],
1707
+ columnDefs, // Původní columnDefs pro Column Editor
1708
+
1709
+ // Utils
1710
+ gridLayoutApi,
1711
+ };
1712
+ };
1713
+
1714
+ export default useGridLayout;