@bit.rhplus/ui.grid-layout 0.0.63 → 0.0.65

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.
@@ -1,849 +0,0 @@
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
- * ARCHITEKTURA - ZERO RE-RENDER PATTERN:
7
- * ─────────────────────────────────────
8
- * Při drag/resize/auto-save se NEPOUŽÍVÁ žádný useState.
9
- * Všechny "pozadí" operace (save, detekce změn) běží přes useRef.
10
- * Re-rendery se spouštějí POUZE při explicitních akcích (init, reload, editor save).
11
- *
12
- * preTransformedColumnDefs se po inicializaci ZAMKNE (stabilní reference).
13
- * Zámek se uvolní jen při: změně columnDefs od parenta, editor save, reload.
14
- * Tím se zabrání tomu, aby AG-Grid dostal columnDefs s původním pořadím.
15
- */
16
- import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
17
- import { useGridLayoutApi } from './useGridLayoutApi';
18
- import { debounce } from 'lodash';
19
- // ============================================================================
20
- // HELPER FUNKCE
21
- // ============================================================================
22
- /**
23
- * Porovnání dvou column state pro detekci skutečných změn
24
- */
25
- const isColumnStateEqual = (state1, state2) => {
26
- if (!state1 || !state2)
27
- return false;
28
- if (state1.length !== state2.length)
29
- return false;
30
- for (let i = 0; i < state1.length; i++) {
31
- const col1 = state1[i];
32
- const col2 = state2[i];
33
- if (col1.colId !== col2.colId)
34
- return false;
35
- if (col1.width !== col2.width)
36
- return false;
37
- if (col1.hide !== col2.hide)
38
- return false;
39
- if (col1.pinned !== col2.pinned)
40
- return false;
41
- }
42
- return true;
43
- };
44
- /**
45
- * Získá API objekt s metodou applyColumnState (AG Grid v31+ nebo starší)
46
- */
47
- const getApplyApi = (gridApiRef, columnApiRef) => {
48
- if (gridApiRef.current?.applyColumnState)
49
- return gridApiRef.current;
50
- if (columnApiRef.current?.applyColumnState)
51
- return columnApiRef.current;
52
- return null;
53
- };
54
- /**
55
- * Získá aktuální column state z AG-Grid (AG Grid v31+ nebo starší)
56
- */
57
- const getColumnState = (gridApiRef, columnApiRef) => {
58
- try {
59
- if (gridApiRef.current?.getColumnState)
60
- return gridApiRef.current.getColumnState();
61
- if (columnApiRef.current?.getColumnState)
62
- return columnApiRef.current.getColumnState();
63
- }
64
- catch (e) {
65
- console.error('[GridLayout] Chyba při čtení column state:', e);
66
- }
67
- return null;
68
- };
69
- /**
70
- * Získá API objekt s metodou resetColumnState
71
- */
72
- const getResetApi = (gridApiRef, columnApiRef) => {
73
- if (gridApiRef.current?.resetColumnState)
74
- return gridApiRef.current;
75
- if (columnApiRef.current?.resetColumnState)
76
- return columnApiRef.current;
77
- return null;
78
- };
79
- // ============================================================================
80
- // HLAVNÍ HOOK
81
- // ============================================================================
82
- export const useGridLayout = ({ userKey, applicationName, gridName, filterName, enabled = true, autoSave = true, autoSaveDelay = 500, columnDefs = [], accessToken, waitForSavedFields = false, onLayoutLoaded, onLayoutSaved, onError, }) => {
83
- // Validace columnDefs
84
- if (columnDefs !== undefined && columnDefs !== null && !Array.isArray(columnDefs)) {
85
- console.error('[GridLayout] columnDefs is not an array:', typeof columnDefs, columnDefs);
86
- throw new Error('useGridLayout: columnDefs musí být array');
87
- }
88
- // ==========================================================================
89
- // REFS - stabilní reference pro event handlery a save funkce
90
- // ==========================================================================
91
- const gridApiRef = useRef(null);
92
- const columnApiRef = useRef(null);
93
- const columnWidthRefsMap = useRef(new Map()); // fieldId -> aktuální šířka
94
- const headerNameMapRef = useRef(new Map()); // fieldId -> aktuální headerName
95
- const lastKnownColumnStateRef = useRef(null); // Poslední známý stav pro detekci změn
96
- const isInitializedRef = useRef(false);
97
- const enabledRef = useRef(enabled);
98
- const isGridReadyRef = useRef(false);
99
- const autoSaveRef = useRef(autoSave);
100
- const stableColumnDefsRef = useRef(columnDefs);
101
- const stableGridLayoutApiRef = useRef(null);
102
- const stableOnLayoutSavedRef = useRef(onLayoutSaved);
103
- const stableOnErrorRef = useRef(onError);
104
- const stableOnLayoutLoadedRef = useRef(onLayoutLoaded);
105
- // "Pozadí" refs - ŽÁDNÉ useState pro tyto hodnoty (zero re-render pattern)
106
- const isSavingRef = useRef(false);
107
- const hasUnsavedChangesRef = useRef(false);
108
- const isApplyingLayoutRef = useRef(false);
109
- // Zámek pro preTransformedColumnDefs (stabilní reference po inicializaci)
110
- const lockedColumnDefsRef = useRef(null);
111
- const lockedForColumnDefsRef = useRef(null);
112
- const lockedForFrozenRecordsRef = useRef(null);
113
- // ==========================================================================
114
- // STATE - POUZE pro hodnoty kde re-render je žádoucí
115
- // ==========================================================================
116
- const [isInitialized, setIsInitialized] = useState(false);
117
- const [isGridReady, setIsGridReady] = useState(false);
118
- const [isLoading, setIsLoading] = useState(false);
119
- const [error, setError] = useState(null);
120
- const [isColumnEditorOpen, setIsColumnEditorOpen] = useState(false);
121
- const [columnWidthsVersion, setColumnWidthsVersion] = useState(0);
122
- // Zamrazené savedFields records - aktualizují se POUZE při init, reload a editor save
123
- const [frozenSavedRecords, setFrozenSavedRecords] = useState(null);
124
- // ==========================================================================
125
- // API HOOK & QUERY
126
- // ==========================================================================
127
- const gridLayoutApi = useGridLayoutApi({
128
- userKey,
129
- applicationName,
130
- gridName,
131
- filterName,
132
- accessToken,
133
- });
134
- const { data: savedFields, isLoading: isFieldsLoading, error: fieldsError, refetch: refetchFields, } = gridLayoutApi.useUserFields((columnDefs || []).map((colDef, index) => ({
135
- name: colDef.field || colDef.colId || `column_${index}`,
136
- displayName: colDef.headerName || colDef.field || colDef.colId || `Column ${index + 1}`,
137
- dataType: colDef.type || 'string',
138
- isVisible: !colDef.hide,
139
- width: colDef.width || 100,
140
- order: index,
141
- })), {
142
- enabled: enabled &&
143
- !!userKey &&
144
- !!gridName &&
145
- Array.isArray(columnDefs) &&
146
- columnDefs.length > 0,
147
- });
148
- // ==========================================================================
149
- // SYNCHRONIZACE REFS
150
- // ==========================================================================
151
- useEffect(() => { enabledRef.current = enabled; }, [enabled]);
152
- useEffect(() => { isGridReadyRef.current = isGridReady; }, [isGridReady]);
153
- useEffect(() => { autoSaveRef.current = autoSave; }, [autoSave]);
154
- useEffect(() => { isInitializedRef.current = isInitialized; }, [isInitialized]);
155
- useEffect(() => { stableGridLayoutApiRef.current = gridLayoutApi; }, [gridLayoutApi]);
156
- useEffect(() => {
157
- stableOnLayoutSavedRef.current = onLayoutSaved;
158
- stableOnErrorRef.current = onError;
159
- stableOnLayoutLoadedRef.current = onLayoutLoaded;
160
- }, [onLayoutSaved, onError, onLayoutLoaded]);
161
- // Synchronizace columnDefs ref + zachování existujících šířek
162
- useEffect(() => {
163
- if (columnDefs !== stableColumnDefsRef.current) {
164
- const newWidthMap = new Map();
165
- if (Array.isArray(columnDefs)) {
166
- columnDefs.forEach(colDef => {
167
- const fieldId = colDef.field || colDef.colId;
168
- if (fieldId) {
169
- const existingWidth = columnWidthRefsMap.current.get(fieldId);
170
- if (existingWidth !== undefined) {
171
- newWidthMap.set(fieldId, existingWidth);
172
- }
173
- else if (colDef.width) {
174
- newWidthMap.set(fieldId, colDef.width);
175
- }
176
- }
177
- });
178
- }
179
- columnWidthRefsMap.current = newWidthMap;
180
- setColumnWidthsVersion(prev => prev + 1);
181
- }
182
- stableColumnDefsRef.current = columnDefs;
183
- }, [columnDefs]);
184
- // ==========================================================================
185
- // HEADER NAME MAPA - plní se z savedFields při načtení
186
- // ==========================================================================
187
- useEffect(() => {
188
- if (savedFields?.records && Array.isArray(savedFields.records)) {
189
- savedFields.records.forEach(field => {
190
- if (field.fieldName && field.headerName) {
191
- headerNameMapRef.current.set(field.fieldName, field.headerName);
192
- }
193
- });
194
- }
195
- }, [savedFields?.records]);
196
- // ==========================================================================
197
- // FROZEN SAVED RECORDS - aktualizují se pouze při prvním načtení
198
- // ==========================================================================
199
- useEffect(() => {
200
- if (!frozenSavedRecords && savedFields?.records?.length > 0) {
201
- setFrozenSavedRecords(savedFields.records);
202
- }
203
- }, [savedFields?.records, frozenSavedRecords]);
204
- // ==========================================================================
205
- // ERROR HANDLER (pouze pro explicitní akce - ne auto-save)
206
- // ==========================================================================
207
- const handleError = useCallback((error, context = '') => {
208
- setError(error);
209
- if (stableOnErrorRef.current) {
210
- stableOnErrorRef.current(error, context);
211
- }
212
- }, []);
213
- // ==========================================================================
214
- // SAVE CURRENT LAYOUT
215
- // ZERO RE-RENDER: používá POUZE refs, žádné setState
216
- // ==========================================================================
217
- const saveCurrentLayout = useCallback(async () => {
218
- if (!enabledRef.current || !gridApiRef.current || !isInitializedRef.current) {
219
- return;
220
- }
221
- // Ochrana proti souběžným uložením
222
- if (isSavingRef.current)
223
- return;
224
- try {
225
- isSavingRef.current = true; // ref only → žádný re-render
226
- const columnState = getColumnState(gridApiRef, columnApiRef);
227
- if (!columnState || !Array.isArray(columnState) || columnState.length === 0) {
228
- return;
229
- }
230
- // Obohatíme column state o headerName z ref mapy (BEZ čtení z DOM)
231
- const enrichedColumnState = columnState.map(colState => {
232
- const savedHeaderName = headerNameMapRef.current.get(colState.colId);
233
- if (savedHeaderName) {
234
- return { ...colState, headerName: savedHeaderName };
235
- }
236
- return colState;
237
- });
238
- const columnDefsToUse = stableColumnDefsRef.current;
239
- if (!columnDefsToUse || !Array.isArray(columnDefsToUse) || columnDefsToUse.length === 0) {
240
- return;
241
- }
242
- const fields = stableGridLayoutApiRef.current.transformColumnStateToFields(enrichedColumnState, columnDefsToUse);
243
- if (!fields || fields.length === 0) {
244
- return;
245
- }
246
- const result = await stableGridLayoutApiRef.current.saveGridLayout(fields);
247
- if (result.success) {
248
- hasUnsavedChangesRef.current = false; // ref only → žádný re-render
249
- if (stableOnLayoutSavedRef.current) {
250
- stableOnLayoutSavedRef.current(fields, enrichedColumnState);
251
- }
252
- }
253
- }
254
- catch (error) {
255
- console.error('[GridLayout] Chyba při ukládání layoutu:', error);
256
- // Při auto-save NENASTAVUJEME error state (žádný re-render)
257
- // Error se loguje do konzole a volá se onError callback
258
- if (stableOnErrorRef.current) {
259
- stableOnErrorRef.current(error, 'při ukládání layoutu');
260
- }
261
- }
262
- finally {
263
- isSavingRef.current = false; // ref only → žádný re-render
264
- }
265
- }, []); // PRÁZDNÉ DEPS → stabilní funkce
266
- // ==========================================================================
267
- // DEBOUNCED SAVE
268
- // ==========================================================================
269
- const saveCurrentLayoutRef = useRef(saveCurrentLayout);
270
- useEffect(() => {
271
- saveCurrentLayoutRef.current = saveCurrentLayout;
272
- }, [saveCurrentLayout]);
273
- const debouncedSave = useMemo(() => {
274
- return debounce(() => {
275
- saveCurrentLayoutRef.current();
276
- }, autoSaveDelay);
277
- }, [autoSaveDelay]);
278
- const debouncedSaveRef = useRef(debouncedSave);
279
- useEffect(() => {
280
- debouncedSaveRef.current = debouncedSave;
281
- }, [debouncedSave]);
282
- useEffect(() => {
283
- return () => { debouncedSave.cancel(); };
284
- }, [debouncedSave]);
285
- // ==========================================================================
286
- // APPLY SAVED LAYOUT (pouze při inicializaci - re-render je OK)
287
- // ==========================================================================
288
- const applySavedLayout = useCallback((forceApply = false) => {
289
- if (!savedFields?.records || savedFields.records.length === 0 || (!forceApply && isInitialized)) {
290
- return;
291
- }
292
- const applyApi = getApplyApi(gridApiRef, columnApiRef);
293
- if (!applyApi) {
294
- return;
295
- }
296
- try {
297
- setIsLoading(true);
298
- isApplyingLayoutRef.current = true;
299
- const columnDefsToUse = stableColumnDefsRef.current || columnDefs;
300
- const columnState = gridLayoutApi.transformFieldsToColumnState(savedFields.records, columnDefsToUse);
301
- if (!columnState || columnState.length === 0) {
302
- return;
303
- }
304
- // Inicializace width ref mapy s API šířkami
305
- if (!isInitialized) {
306
- columnState.forEach(colState => {
307
- if (colState.colId && colState.width) {
308
- columnWidthRefsMap.current.set(colState.colId, colState.width);
309
- }
310
- });
311
- }
312
- // Sestavení headerName mapy z API dat
313
- const headerNameMap = new Map();
314
- savedFields.records.forEach(field => {
315
- if (field.fieldName && field.headerName) {
316
- headerNameMap.set(field.fieldName, field.headerName);
317
- headerNameMapRef.current.set(field.fieldName, field.headerName);
318
- }
319
- });
320
- // Adjusted column state (s ref šířkami pro re-apply)
321
- const adjustedColumnState = columnState.map(colState => {
322
- const refWidth = columnWidthRefsMap.current.get(colState.colId);
323
- if (refWidth !== undefined && isInitialized) {
324
- return { ...colState, width: refWidth };
325
- }
326
- return colState;
327
- });
328
- const applyFunction = () => {
329
- try {
330
- // KROK 1: Aktualizace headerName přes setColumnDefs PRVNÍ
331
- if (headerNameMap.size > 0 && gridApiRef.current) {
332
- try {
333
- const currentColDefs = gridApiRef.current.getColumnDefs?.();
334
- if (currentColDefs && Array.isArray(currentColDefs)) {
335
- let hasHeaderChanges = false;
336
- const updatedColDefs = currentColDefs.map(colDef => {
337
- const fieldName = colDef.field;
338
- if (fieldName && headerNameMap.has(fieldName)) {
339
- const newName = headerNameMap.get(fieldName);
340
- if (colDef.headerName !== newName) {
341
- hasHeaderChanges = true;
342
- return { ...colDef, headerName: newName };
343
- }
344
- }
345
- return colDef;
346
- });
347
- if (hasHeaderChanges && typeof gridApiRef.current.setColumnDefs === 'function') {
348
- gridApiRef.current.setColumnDefs(updatedColDefs);
349
- }
350
- }
351
- }
352
- catch (e) {
353
- console.error('[GridLayout] Chyba při aktualizaci headerName:', e);
354
- }
355
- }
356
- // KROK 2: Aplikovat column state (pořadí, šířky) PO setColumnDefs
357
- const applyOrderEnabled = !waitForSavedFields;
358
- applyApi.applyColumnState({
359
- state: adjustedColumnState,
360
- applyOrder: applyOrderEnabled,
361
- defaultState: { sort: null, sortIndex: null, pivot: null, rowGroup: null },
362
- });
363
- lastKnownColumnStateRef.current = adjustedColumnState;
364
- try {
365
- gridApiRef.current?.refreshHeader?.();
366
- }
367
- catch (e) { /* ignorujeme */ }
368
- if (stableOnLayoutLoadedRef.current) {
369
- stableOnLayoutLoadedRef.current(savedFields.records, columnState);
370
- }
371
- }
372
- catch (error) {
373
- console.error('[GridLayout] Chyba při aplikování layoutu:', error);
374
- handleError(error, 'při aplikování layoutu');
375
- }
376
- finally {
377
- isApplyingLayoutRef.current = false;
378
- }
379
- };
380
- if (waitForSavedFields) {
381
- applyFunction();
382
- }
383
- else {
384
- setTimeout(applyFunction, 100);
385
- }
386
- }
387
- catch (error) {
388
- console.error('[GridLayout] Chyba při přípravě layoutu:', error);
389
- handleError(error, 'při aplikování layoutu');
390
- isApplyingLayoutRef.current = false;
391
- }
392
- finally {
393
- setIsInitialized(true);
394
- setIsGridReady(true);
395
- setIsLoading(false);
396
- }
397
- }, [savedFields, gridLayoutApi, isInitialized, handleError, columnDefs, waitForSavedFields]);
398
- // ==========================================================================
399
- // AG-GRID EVENT HANDLERS
400
- // ZERO RE-RENDER: všechny mají prázdné deps a používají POUZE refs
401
- // ==========================================================================
402
- const handleColumnMoved = useCallback(() => {
403
- if (!enabledRef.current || !isGridReadyRef.current)
404
- return;
405
- hasUnsavedChangesRef.current = true; // ref only → žádný re-render
406
- }, []);
407
- const handleDragStopped = useCallback(() => {
408
- if (!enabledRef.current || !isGridReadyRef.current)
409
- return;
410
- // Detekce skutečné změny
411
- try {
412
- const currentColumnState = getColumnState(gridApiRef, columnApiRef);
413
- if (currentColumnState && Array.isArray(currentColumnState)) {
414
- if (isColumnStateEqual(lastKnownColumnStateRef.current, currentColumnState)) {
415
- return; // Žádná změna → skip
416
- }
417
- lastKnownColumnStateRef.current = currentColumnState;
418
- currentColumnState.forEach(colState => {
419
- if (colState.colId && colState.width) {
420
- columnWidthRefsMap.current.set(colState.colId, colState.width);
421
- }
422
- });
423
- }
424
- }
425
- catch (error) {
426
- console.error('[GridLayout] Chyba při detekci změn v handleDragStopped:', error);
427
- }
428
- hasUnsavedChangesRef.current = true; // ref only → žádný re-render
429
- if (!isInitializedRef.current) {
430
- const checkInterval = setInterval(() => {
431
- if (isInitializedRef.current) {
432
- clearInterval(checkInterval);
433
- if (autoSaveRef.current && debouncedSaveRef.current) {
434
- debouncedSaveRef.current();
435
- }
436
- }
437
- }, 100);
438
- setTimeout(() => clearInterval(checkInterval), 5000);
439
- return;
440
- }
441
- if (autoSaveRef.current && debouncedSaveRef.current) {
442
- debouncedSaveRef.current();
443
- }
444
- }, []);
445
- const handleColumnResized = useCallback((event) => {
446
- if (!enabledRef.current || !isGridReadyRef.current)
447
- return;
448
- if (!event || event.finished !== true)
449
- return;
450
- hasUnsavedChangesRef.current = true; // ref only → žádný re-render
451
- try {
452
- const currentColumnState = getColumnState(gridApiRef, columnApiRef);
453
- if (currentColumnState && Array.isArray(currentColumnState)) {
454
- currentColumnState.forEach(colState => {
455
- if (colState.colId && colState.width) {
456
- columnWidthRefsMap.current.set(colState.colId, colState.width);
457
- }
458
- });
459
- }
460
- }
461
- catch (error) {
462
- console.error('[GridLayout] Chyba při aktualizaci šířek v handleColumnResized:', error);
463
- }
464
- if (autoSaveRef.current && isInitializedRef.current && debouncedSaveRef.current) {
465
- debouncedSaveRef.current();
466
- }
467
- }, []);
468
- const handleColumnVisible = useCallback(() => {
469
- if (!enabledRef.current || !isGridReadyRef.current)
470
- return;
471
- hasUnsavedChangesRef.current = true; // ref only → žádný re-render
472
- if (autoSaveRef.current && isInitializedRef.current && debouncedSaveRef.current) {
473
- debouncedSaveRef.current();
474
- }
475
- }, []);
476
- const handleColumnPinned = useCallback(() => {
477
- if (!enabledRef.current || !isGridReadyRef.current)
478
- return;
479
- hasUnsavedChangesRef.current = true; // ref only → žádný re-render
480
- if (autoSaveRef.current && isInitializedRef.current && debouncedSaveRef.current) {
481
- debouncedSaveRef.current();
482
- }
483
- }, []);
484
- // ==========================================================================
485
- // AG-GRID READY HANDLER
486
- // ==========================================================================
487
- const handleGridReady = useCallback((params) => {
488
- if (!enabledRef.current)
489
- return;
490
- gridApiRef.current = params.api;
491
- columnApiRef.current = params.columnApi || params.api;
492
- setIsGridReady(true);
493
- }, []);
494
- // ==========================================================================
495
- // EFFECTS
496
- // ==========================================================================
497
- useEffect(() => {
498
- if (savedFields?.records !== undefined && gridApiRef.current && !isInitialized) {
499
- if (savedFields.records.length > 0) {
500
- applySavedLayout();
501
- }
502
- else {
503
- setIsInitialized(true);
504
- setIsGridReady(true);
505
- }
506
- }
507
- }, [savedFields?.records, isInitialized, applySavedLayout]);
508
- useEffect(() => {
509
- if (fieldsError) {
510
- handleError(fieldsError, 'při načítání saved layoutu');
511
- }
512
- }, [fieldsError, handleError]);
513
- // ==========================================================================
514
- // MANUÁLNÍ AKCE (re-render je zde OK - explicitní uživatelská akce)
515
- // ==========================================================================
516
- const resetToDefault = useCallback(async () => {
517
- if (!enabled)
518
- return;
519
- const resetApi = getResetApi(gridApiRef, columnApiRef);
520
- if (!resetApi)
521
- return;
522
- try {
523
- setIsLoading(true);
524
- resetApi.resetColumnState();
525
- headerNameMapRef.current.clear();
526
- if (autoSave) {
527
- await saveCurrentLayout();
528
- }
529
- else {
530
- hasUnsavedChangesRef.current = true;
531
- }
532
- }
533
- catch (error) {
534
- handleError(error, 'při resetování layoutu');
535
- }
536
- finally {
537
- setIsLoading(false);
538
- }
539
- }, [enabled, autoSave, saveCurrentLayout, handleError]);
540
- const saveLayout = useCallback(async () => {
541
- await saveCurrentLayout();
542
- }, [saveCurrentLayout]);
543
- const reloadLayout = useCallback(async () => {
544
- try {
545
- const savedGridApi = gridApiRef.current;
546
- const savedColumnApi = columnApiRef.current;
547
- // Reset zámku - při reloadu chceme přepočítat preTransformedColumnDefs
548
- lockedColumnDefsRef.current = null;
549
- setIsInitialized(false);
550
- setIsGridReady(false);
551
- const freshData = await refetchFields();
552
- if (!gridApiRef.current && savedGridApi)
553
- gridApiRef.current = savedGridApi;
554
- if (!columnApiRef.current && savedColumnApi)
555
- columnApiRef.current = savedColumnApi;
556
- if (freshData?.data?.records) {
557
- setFrozenSavedRecords(freshData.data.records);
558
- }
559
- setIsInitialized(true);
560
- setIsGridReady(true);
561
- }
562
- catch (error) {
563
- handleError(error, 'při reload layoutu');
564
- }
565
- }, [refetchFields, handleError]);
566
- // ==========================================================================
567
- // COLUMN EDITOR
568
- // ==========================================================================
569
- const getCurrentColumnsForEditor = useCallback(() => {
570
- const validColumnDefs = Array.isArray(columnDefs) ? columnDefs : [];
571
- try {
572
- const currentColumnState = getColumnState(gridApiRef, columnApiRef);
573
- if (!Array.isArray(currentColumnState) || currentColumnState.length === 0) {
574
- return validColumnDefs.map((col, index) => ({
575
- id: col.field,
576
- field: col.field,
577
- headerName: col.headerName || col.field,
578
- originalHeaderName: col.headerName || col.field,
579
- width: col.width || 100,
580
- originalWidth: col.width || 100,
581
- visible: !col.hide,
582
- order: index,
583
- originalOrder: index,
584
- }));
585
- }
586
- return currentColumnState.map((columnStateItem, index) => {
587
- const colDef = validColumnDefs.find((cd) => cd.field === columnStateItem.colId || cd.colId === columnStateItem.colId) || {};
588
- const savedField = savedFields?.records?.find((sf) => sf.fieldName === columnStateItem.colId);
589
- const headerName = headerNameMapRef.current.get(columnStateItem.colId) ||
590
- savedField?.headerName ||
591
- colDef.headerName ||
592
- columnStateItem.colId;
593
- const originalIndex = validColumnDefs.findIndex((cd) => (cd.field || cd.colId) === columnStateItem.colId);
594
- return {
595
- id: columnStateItem.colId,
596
- field: columnStateItem.colId,
597
- headerName,
598
- originalHeaderName: colDef.headerName || columnStateItem.colId,
599
- width: columnStateItem.width || colDef.width || 100,
600
- originalWidth: colDef.width || 100,
601
- visible: !columnStateItem.hide,
602
- order: index,
603
- originalOrder: originalIndex !== -1 ? originalIndex : index,
604
- };
605
- });
606
- }
607
- catch (error) {
608
- console.error('[GridLayout] Chyba v getCurrentColumnsForEditor:', error);
609
- return validColumnDefs.map((col, index) => ({
610
- id: col.field || col.colId,
611
- field: col.field || col.colId,
612
- headerName: col.headerName || col.field || col.colId,
613
- originalHeaderName: col.headerName || col.field || col.colId,
614
- width: col.width || 100,
615
- originalWidth: col.width || 100,
616
- visible: !col.hide,
617
- order: index,
618
- originalOrder: index,
619
- }));
620
- }
621
- }, [columnDefs, savedFields]);
622
- const openColumnEditor = useCallback(() => {
623
- setIsColumnEditorOpen(true);
624
- }, []);
625
- const closeColumnEditor = useCallback(() => {
626
- setIsColumnEditorOpen(false);
627
- }, []);
628
- const saveColumnEditorChanges = async (editedColumns) => {
629
- if (!enabled)
630
- return;
631
- try {
632
- setIsLoading(true);
633
- isSavingRef.current = true;
634
- const validColumns = editedColumns.filter(col => col.field && col.field !== undefined);
635
- const completeUserFields = validColumns.map((col, index) => ({
636
- Id: 0,
637
- UserKey: userKey,
638
- ApplicationName: applicationName,
639
- GridName: gridName,
640
- FilterName: filterName || null,
641
- FieldName: col.field,
642
- HeaderName: col.headerName || col.field,
643
- Order: index,
644
- Show: col.visible,
645
- Width: col.width != null ? col.width : null,
646
- System: false,
647
- }));
648
- // Aktualizace headerName ref mapy
649
- validColumns.forEach(col => {
650
- if (col.field && col.headerName) {
651
- headerNameMapRef.current.set(col.field, col.headerName);
652
- }
653
- });
654
- const result = await gridLayoutApi.saveGridLayout(completeUserFields);
655
- if (result.success) {
656
- hasUnsavedChangesRef.current = false;
657
- try {
658
- const freshFields = await refetchFields();
659
- // Reset zámku a aktualizace frozenSavedRecords → uvolní zámek v preTransformedColumnDefs
660
- lockedColumnDefsRef.current = null;
661
- if (freshFields?.data?.records) {
662
- setFrozenSavedRecords(freshFields.data.records);
663
- }
664
- // Aplikujeme nový layout na grid
665
- setTimeout(() => {
666
- if (gridApiRef.current && freshFields?.data?.records) {
667
- const columnState = gridLayoutApi.transformFieldsToColumnState(freshFields.data.records, columnDefs);
668
- if (columnState && columnState.length > 0) {
669
- const applyApi = getApplyApi(gridApiRef, columnApiRef);
670
- if (applyApi) {
671
- // KROK 1: Aktualizace headerName
672
- try {
673
- const currentColDefs = gridApiRef.current.getColumnDefs?.();
674
- if (currentColDefs && Array.isArray(currentColDefs)) {
675
- const updatedColDefs = currentColDefs.map(colDef => {
676
- const fieldName = colDef.field;
677
- const newName = headerNameMapRef.current.get(fieldName);
678
- if (newName && colDef.headerName !== newName) {
679
- return { ...colDef, headerName: newName };
680
- }
681
- return colDef;
682
- });
683
- if (typeof gridApiRef.current.setColumnDefs === 'function') {
684
- gridApiRef.current.setColumnDefs(updatedColDefs);
685
- }
686
- }
687
- }
688
- catch (e) {
689
- console.error('[GridLayout] Chyba při aktualizaci headerName v editoru:', e);
690
- }
691
- // KROK 2: Aplikovat column state
692
- applyApi.applyColumnState({
693
- state: columnState,
694
- applyOrder: true,
695
- defaultState: { sort: null, sortIndex: null, pivot: null, rowGroup: null },
696
- });
697
- try {
698
- gridApiRef.current?.refreshHeader?.();
699
- }
700
- catch (e) { /* ignorujeme */ }
701
- }
702
- }
703
- }
704
- }, 150);
705
- }
706
- catch (refreshError) {
707
- console.error('[GridLayout] Chyba při obnově po uložení z editoru:', refreshError);
708
- setTimeout(() => {
709
- if (gridApiRef.current)
710
- applySavedLayout(true);
711
- }, 200);
712
- }
713
- if (onLayoutSaved) {
714
- onLayoutSaved(completeUserFields);
715
- }
716
- }
717
- }
718
- catch (error) {
719
- handleError(error, 'při ukládání změn z Column Editoru');
720
- }
721
- finally {
722
- setIsLoading(false);
723
- isSavingRef.current = false;
724
- }
725
- };
726
- // ==========================================================================
727
- // PRE-TRANSFORMED COLUMN DEFS
728
- // ZAMKNUTÍ PO INICIALIZACI → stabilní reference, žádné zbytečné přepočty
729
- // ==========================================================================
730
- const shouldShowColumns = useMemo(() => {
731
- if (!waitForSavedFields)
732
- return true;
733
- return !isFieldsLoading && !isLoading;
734
- }, [waitForSavedFields, isFieldsLoading, isLoading]);
735
- const preTransformedColumnDefs = useMemo(() => {
736
- // ── ZÁMEK: Po inicializaci vrátíme zamknutou referenci ──
737
- // Zámek se uvolní jen při: změně columnDefs od parenta, frozenSavedRecords (editor/reload)
738
- // Tím se zabrání tomu, aby AG-Grid dostal nové columnDefs při jakémkoliv re-renderu
739
- if (isInitialized
740
- && lockedColumnDefsRef.current
741
- && lockedForColumnDefsRef.current === columnDefs
742
- && lockedForFrozenRecordsRef.current === frozenSavedRecords) {
743
- return lockedColumnDefsRef.current;
744
- }
745
- // ── Normální výpočet ──
746
- const columnDefsToUse = stableColumnDefsRef.current;
747
- let baseColumnDefs = columnDefsToUse;
748
- // Pro waitForSavedFields: seřadíme columnDefs podle frozenSavedRecords
749
- if (waitForSavedFields && frozenSavedRecords && Array.isArray(columnDefsToUse)) {
750
- const columnState = gridLayoutApi.transformFieldsToColumnState(frozenSavedRecords, columnDefsToUse);
751
- if (columnState && columnState.length > 0) {
752
- const columnStateMap = new Map();
753
- columnState.forEach((colState, index) => {
754
- columnStateMap.set(colState.colId, { ...colState, __order: index });
755
- });
756
- const headerNameMap = new Map();
757
- frozenSavedRecords.forEach(field => {
758
- if (field.fieldName && field.headerName) {
759
- headerNameMap.set(field.fieldName, field.headerName);
760
- }
761
- });
762
- baseColumnDefs = [...columnDefsToUse]
763
- .sort((a, b) => {
764
- const fieldA = a.field || a.colId;
765
- const fieldB = b.field || b.colId;
766
- const aOrder = columnStateMap.get(fieldA)?.__order ?? 999;
767
- const bOrder = columnStateMap.get(fieldB)?.__order ?? 999;
768
- return aOrder - bOrder;
769
- })
770
- .map(colDef => {
771
- const fieldId = colDef.field || colDef.colId;
772
- const columnStateItem = columnStateMap.get(fieldId);
773
- return {
774
- ...colDef,
775
- ...(headerNameMap.has(fieldId) && { headerName: headerNameMap.get(fieldId) }),
776
- ...(columnStateItem && { hide: columnStateItem.hide }),
777
- ...(columnStateItem?.width && { width: columnStateItem.width }),
778
- };
779
- });
780
- }
781
- }
782
- // Konverze ColumnBuilder na array
783
- if (!Array.isArray(baseColumnDefs)) {
784
- if (baseColumnDefs && typeof baseColumnDefs.build === 'function') {
785
- baseColumnDefs = baseColumnDefs.build();
786
- }
787
- else {
788
- return [];
789
- }
790
- }
791
- // Aplikace ref šířek (POUZE po inicializaci)
792
- const finalColumnDefs = baseColumnDefs.map(colDef => {
793
- const fieldId = colDef.field || colDef.colId;
794
- const refWidth = columnWidthRefsMap.current.get(fieldId);
795
- if (refWidth !== undefined && isInitialized) {
796
- return { ...colDef, width: refWidth };
797
- }
798
- return colDef;
799
- });
800
- // ── Zamknout po inicializaci ──
801
- if (isInitialized) {
802
- lockedColumnDefsRef.current = finalColumnDefs;
803
- lockedForColumnDefsRef.current = columnDefs;
804
- lockedForFrozenRecordsRef.current = frozenSavedRecords;
805
- }
806
- return finalColumnDefs;
807
- }, [waitForSavedFields, frozenSavedRecords, columnDefs, gridLayoutApi, columnWidthsVersion, isInitialized]);
808
- // ==========================================================================
809
- // RETURN - API interface
810
- // Pozadí hodnoty se čtou z refs (aktuální hodnota v okamžiku renderu)
811
- // ==========================================================================
812
- return {
813
- // State (re-render pouze při explicitních akcích)
814
- isLoading: isLoading || isFieldsLoading,
815
- error,
816
- isInitialized,
817
- isGridReady,
818
- shouldShowColumns,
819
- preTransformedColumnDefs,
820
- // Refs čtené při renderu (ŽÁDNÝ re-render při změně)
821
- isSaving: isSavingRef.current,
822
- hasUnsavedChanges: hasUnsavedChangesRef.current,
823
- isApplyingLayout: isApplyingLayoutRef.current,
824
- // AG-Grid event handlers (stabilní - prázdné deps)
825
- onGridReady: handleGridReady,
826
- onColumnMoved: handleColumnMoved,
827
- onDragStopped: handleDragStopped,
828
- onColumnResized: handleColumnResized,
829
- onColumnVisible: handleColumnVisible,
830
- onColumnPinned: handleColumnPinned,
831
- // Manuální akce
832
- saveLayout,
833
- resetToDefault,
834
- reloadLayout,
835
- // Column Editor
836
- isColumnEditorOpen,
837
- openColumnEditor,
838
- closeColumnEditor,
839
- saveColumnEditorChanges,
840
- getCurrentColumnsForEditor,
841
- // Data
842
- savedFields: savedFields?.records || [],
843
- columnDefs,
844
- // Utils
845
- gridLayoutApi,
846
- };
847
- };
848
- export default useGridLayout;
849
- //# sourceMappingURL=useGridLayout.js.map