@bit.rhplus/ui.grid-layout 0.0.58 → 0.0.60
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/dist/useGridLayout.d.ts +47 -10
- package/dist/useGridLayout.js +482 -892
- package/dist/useGridLayout.js.map +1 -1
- package/dist/useGridLayoutApi.js +1 -1
- package/dist/useGridLayoutApi.js.map +1 -1
- package/package.json +3 -3
- package/useGridLayout.js +536 -1084
- package/useGridLayoutApi.js +1 -1
- /package/dist/{preview-1769765664330.js → preview-1771153821287.js} +0 -0
package/dist/useGridLayout.js
CHANGED
|
@@ -2,18 +2,25 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Hlavní hook pro správu grid layout - automatické ukládání a načítání rozvržení sloupců
|
|
4
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.
|
|
5
15
|
*/
|
|
6
16
|
import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
|
|
7
17
|
import { useGridLayoutApi } from './useGridLayoutApi';
|
|
8
18
|
import { debounce } from 'lodash';
|
|
9
|
-
//
|
|
10
|
-
//
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// HELPER FUNKCE
|
|
21
|
+
// ============================================================================
|
|
11
22
|
/**
|
|
12
|
-
*
|
|
13
|
-
* Porovnává klíčové vlastnosti sloupců pro detekci změn
|
|
14
|
-
* @param {Array} state1 - První column state
|
|
15
|
-
* @param {Array} state2 - Druhý column state
|
|
16
|
-
* @returns {boolean} - true pokud jsou identické, false pokud se liší
|
|
23
|
+
* Porovnání dvou column state pro detekci skutečných změn
|
|
17
24
|
*/
|
|
18
25
|
const isColumnStateEqual = (state1, state2) => {
|
|
19
26
|
if (!state1 || !state2)
|
|
@@ -23,66 +30,100 @@ const isColumnStateEqual = (state1, state2) => {
|
|
|
23
30
|
for (let i = 0; i < state1.length; i++) {
|
|
24
31
|
const col1 = state1[i];
|
|
25
32
|
const col2 = state2[i];
|
|
26
|
-
// Porovnat klíčové vlastnosti
|
|
27
33
|
if (col1.colId !== col2.colId)
|
|
28
|
-
return false;
|
|
34
|
+
return false;
|
|
29
35
|
if (col1.width !== col2.width)
|
|
30
|
-
return false;
|
|
36
|
+
return false;
|
|
31
37
|
if (col1.hide !== col2.hide)
|
|
32
|
-
return false;
|
|
38
|
+
return false;
|
|
33
39
|
if (col1.pinned !== col2.pinned)
|
|
34
|
-
return false;
|
|
40
|
+
return false;
|
|
35
41
|
}
|
|
36
42
|
return true;
|
|
37
43
|
};
|
|
38
44
|
/**
|
|
39
|
-
*
|
|
40
|
-
* @param {Object} config - Konfigurace grid layout
|
|
41
|
-
* @param {string} config.userKey - Identifikátor uživatele
|
|
42
|
-
* @param {string} config.applicationName - Název aplikace
|
|
43
|
-
* @param {string} config.gridName - Název gridu
|
|
44
|
-
* @param {string} [config.filterName] - Název filtru (volitelné)
|
|
45
|
-
* @param {boolean} [config.enabled=true] - Zapnout/vypnout layout management
|
|
46
|
-
* @param {boolean} [config.autoSave=true] - Automatické ukládání při změnách
|
|
47
|
-
* @param {number} [config.autoSaveDelay=500] - Zpoždění auto-save v ms
|
|
48
|
-
* @param {Array} config.columnDefs - AG-Grid column definitions
|
|
49
|
-
* @param {string} [config.accessToken] - Přístupový token
|
|
50
|
-
* @param {boolean} [config.waitForSavedFields=false] - Skrýt columnDefs dokud nejsou načtena savedFields
|
|
51
|
-
* @param {Function} [config.onLayoutLoaded] - Callback při načtení layoutu
|
|
52
|
-
* @param {Function} [config.onLayoutSaved] - Callback při uložení layoutu
|
|
53
|
-
* @param {Function} [config.onError] - Callback při chybě
|
|
54
|
-
* @returns {Object} Grid layout management interface
|
|
45
|
+
* Získá API objekt s metodou applyColumnState (AG Grid v31+ nebo starší)
|
|
55
46
|
*/
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (
|
|
60
|
-
|
|
61
|
-
|
|
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)) {
|
|
62
85
|
console.error('[GridLayout] columnDefs is not an array:', typeof columnDefs, columnDefs);
|
|
63
86
|
throw new Error('useGridLayout: columnDefs musí být array');
|
|
64
87
|
}
|
|
65
|
-
//
|
|
88
|
+
// ==========================================================================
|
|
89
|
+
// REFS - stabilní reference pro event handlery a save funkce
|
|
90
|
+
// ==========================================================================
|
|
66
91
|
const gridApiRef = useRef(null);
|
|
67
92
|
const columnApiRef = useRef(null);
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
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
|
|
71
96
|
const isInitializedRef = useRef(false);
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
|
|
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
|
+
// ==========================================================================
|
|
75
116
|
const [isInitialized, setIsInitialized] = useState(false);
|
|
76
117
|
const [isGridReady, setIsGridReady] = useState(false);
|
|
77
118
|
const [isLoading, setIsLoading] = useState(false);
|
|
78
|
-
const [isSaving, setIsSaving] = useState(false);
|
|
79
|
-
const [isApplyingLayout, setIsApplyingLayout] = useState(false);
|
|
80
119
|
const [error, setError] = useState(null);
|
|
81
|
-
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
|
|
82
120
|
const [isColumnEditorOpen, setIsColumnEditorOpen] = useState(false);
|
|
83
|
-
const [
|
|
84
|
-
|
|
85
|
-
|
|
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
|
+
// ==========================================================================
|
|
86
127
|
const gridLayoutApi = useGridLayoutApi({
|
|
87
128
|
userKey,
|
|
88
129
|
applicationName,
|
|
@@ -90,13 +131,9 @@ onLayoutLoaded, onLayoutSaved, onError, }) => {
|
|
|
90
131
|
filterName,
|
|
91
132
|
accessToken,
|
|
92
133
|
});
|
|
93
|
-
// Query pro načtení saved layoutu
|
|
94
134
|
const { data: savedFields, isLoading: isFieldsLoading, error: fieldsError, refetch: refetchFields, } = gridLayoutApi.useUserFields((columnDefs || []).map((colDef, index) => ({
|
|
95
135
|
name: colDef.field || colDef.colId || `column_${index}`,
|
|
96
|
-
displayName: colDef.headerName ||
|
|
97
|
-
colDef.field ||
|
|
98
|
-
colDef.colId ||
|
|
99
|
-
`Column ${index + 1}`,
|
|
136
|
+
displayName: colDef.headerName || colDef.field || colDef.colId || `Column ${index + 1}`,
|
|
100
137
|
dataType: colDef.type || 'string',
|
|
101
138
|
isVisible: !colDef.hide,
|
|
102
139
|
width: colDef.width || 100,
|
|
@@ -108,737 +145,389 @@ onLayoutLoaded, onLayoutSaved, onError, }) => {
|
|
|
108
145
|
Array.isArray(columnDefs) &&
|
|
109
146
|
columnDefs.length > 0,
|
|
110
147
|
});
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
// Reference pro ukládání stavu sloupců bez state update v render cyklu
|
|
126
|
-
const stableCurrentColumnsRef = useRef(null);
|
|
127
|
-
// Jednoduchá aktualizace ref hodnot - BEZ aktualizace columnDefs
|
|
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
|
|
128
162
|
useEffect(() => {
|
|
129
|
-
// Při změně columnDefs vyčistíme columnWidthRefsMap a přeneseme existující šířky
|
|
130
163
|
if (columnDefs !== stableColumnDefsRef.current) {
|
|
131
164
|
const newWidthMap = new Map();
|
|
132
|
-
// Zachováme šířky pro sloupce, které stále existují
|
|
133
165
|
if (Array.isArray(columnDefs)) {
|
|
134
166
|
columnDefs.forEach(colDef => {
|
|
135
167
|
const fieldId = colDef.field || colDef.colId;
|
|
136
168
|
if (fieldId) {
|
|
137
|
-
// Zkusíme najít existující šířku v ref map
|
|
138
169
|
const existingWidth = columnWidthRefsMap.current.get(fieldId);
|
|
139
170
|
if (existingWidth !== undefined) {
|
|
140
171
|
newWidthMap.set(fieldId, existingWidth);
|
|
141
172
|
}
|
|
142
173
|
else if (colDef.width) {
|
|
143
|
-
// Použijeme šířku z nového columnDef
|
|
144
174
|
newWidthMap.set(fieldId, colDef.width);
|
|
145
175
|
}
|
|
146
176
|
}
|
|
147
177
|
});
|
|
148
178
|
}
|
|
149
179
|
columnWidthRefsMap.current = newWidthMap;
|
|
150
|
-
setColumnWidthsVersion(prev => prev + 1);
|
|
180
|
+
setColumnWidthsVersion(prev => prev + 1);
|
|
151
181
|
}
|
|
152
182
|
stableColumnDefsRef.current = columnDefs;
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
+
// ==========================================================================
|
|
158
199
|
useEffect(() => {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
//
|
|
164
|
-
//
|
|
165
|
-
//
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
+
// ==========================================================================
|
|
175
217
|
const saveCurrentLayout = useCallback(async () => {
|
|
176
|
-
|
|
177
|
-
if (!enabled || !gridApiRef.current || !isInitialized) {
|
|
218
|
+
if (!enabledRef.current || !gridApiRef.current || !isInitializedRef.current) {
|
|
178
219
|
return;
|
|
179
220
|
}
|
|
221
|
+
// Ochrana proti souběžným uložením
|
|
222
|
+
if (isSavingRef.current)
|
|
223
|
+
return;
|
|
180
224
|
try {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
if (gridApiRef.current &&
|
|
186
|
-
typeof gridApiRef.current.getColumnState === 'function') {
|
|
187
|
-
// AG Grid v31+ - getColumnState je přímo v main API
|
|
188
|
-
try {
|
|
189
|
-
columnState = gridApiRef.current.getColumnState();
|
|
190
|
-
// Získáme aktuální headerName hodnoty z DOM pro každý sloupec
|
|
191
|
-
if (columnState && Array.isArray(columnState)) {
|
|
192
|
-
columnState = columnState.map(colState => {
|
|
193
|
-
// Pokusíme se získat aktuální headerName z DOM
|
|
194
|
-
try {
|
|
195
|
-
const headerCell = document.querySelector(`[col-id="${colState.colId}"]`);
|
|
196
|
-
if (headerCell) {
|
|
197
|
-
const headerTextEl = headerCell.querySelector('.ag-header-cell-text');
|
|
198
|
-
if (headerTextEl && headerTextEl.textContent) {
|
|
199
|
-
return {
|
|
200
|
-
...colState,
|
|
201
|
-
headerName: headerTextEl.textContent
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
catch (headerError) {
|
|
207
|
-
console.log(`❌ [GridLayout] Could not get headerName from DOM for ${colState.colId}`);
|
|
208
|
-
}
|
|
209
|
-
return colState;
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
// Pokud getColumnState vrátí undefined, grid je v nekonzistentním stavu
|
|
213
|
-
// Zkusíme alternativní metodu přes getColumnDefs()
|
|
214
|
-
if (columnState === undefined || columnState === null) {
|
|
215
|
-
console.warn('[GridLayout] getColumnState returned undefined/null, trying getColumnDefs() alternative');
|
|
216
|
-
console.warn('[GridLayout] Full gridApiRef.current object:', gridApiRef.current);
|
|
217
|
-
console.warn('[GridLayout] Available methods on gridApiRef.current:', gridApiRef.current
|
|
218
|
-
? Object.getOwnPropertyNames(gridApiRef.current).filter((name) => typeof gridApiRef.current[name] === 'function')
|
|
219
|
-
: 'NO_GRID_API');
|
|
220
|
-
try {
|
|
221
|
-
// Alternativní přístup: použijeme getColumnDefs() a vytvoříme fake column state
|
|
222
|
-
const columnDefs = gridApiRef.current.getColumnDefs();
|
|
223
|
-
if (columnDefs &&
|
|
224
|
-
Array.isArray(columnDefs) &&
|
|
225
|
-
columnDefs.length > 0) {
|
|
226
|
-
// Vytvoříme column state z column defs
|
|
227
|
-
columnState = columnDefs.map((colDef, index) => {
|
|
228
|
-
// Zkusíme získat aktuální šířku sloupce z DOM
|
|
229
|
-
let currentWidth = colDef.width || 100;
|
|
230
|
-
try {
|
|
231
|
-
const fieldId = colDef.field || colDef.colId;
|
|
232
|
-
const headerElement = document.querySelector(`[col-id="${fieldId}"]`);
|
|
233
|
-
if (headerElement && headerElement.offsetWidth) {
|
|
234
|
-
currentWidth = headerElement.offsetWidth;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
catch (domError) {
|
|
238
|
-
console.log('❌ [GridLayout] Could not get width from DOM for', colDef.field);
|
|
239
|
-
}
|
|
240
|
-
return {
|
|
241
|
-
colId: colDef.field || colDef.colId,
|
|
242
|
-
hide: colDef.hide || false,
|
|
243
|
-
width: currentWidth,
|
|
244
|
-
headerName: colDef.headerName, // Přidáme aktuální headerName
|
|
245
|
-
sort: null, // Nebudeme zachovávat sort při této fallback metodě
|
|
246
|
-
sortIndex: null,
|
|
247
|
-
};
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
else {
|
|
251
|
-
console.error('[GridLayout] getColumnDefs() also failed or returned empty array');
|
|
252
|
-
const cachedColumnDefs = stableColumnDefsRef.current;
|
|
253
|
-
if (cachedColumnDefs &&
|
|
254
|
-
Array.isArray(cachedColumnDefs) &&
|
|
255
|
-
cachedColumnDefs.length > 0) {
|
|
256
|
-
columnState = cachedColumnDefs.map((colDef, index) => {
|
|
257
|
-
// Použijeme cached definice
|
|
258
|
-
let currentWidth = colDef.width || 100;
|
|
259
|
-
return {
|
|
260
|
-
colId: colDef.field || colDef.colId,
|
|
261
|
-
hide: colDef.hide || false,
|
|
262
|
-
width: currentWidth,
|
|
263
|
-
headerName: colDef.headerName, // Přidáme aktuální headerName
|
|
264
|
-
sort: null,
|
|
265
|
-
sortIndex: null,
|
|
266
|
-
};
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
else {
|
|
270
|
-
console.error('[GridLayout] All fallback methods failed - no column data available');
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
catch (columnDefError) {
|
|
276
|
-
console.error('[GridLayout] getColumnDefs() alternative failed:', columnDefError);
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
catch (error) {
|
|
282
|
-
console.error('[GridLayout] Error calling gridApiRef.current.getColumnState():', error);
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
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;
|
|
285
229
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
// Získáme aktuální headerName hodnoty z DOM pro každý sloupec
|
|
292
|
-
if (columnState && Array.isArray(columnState)) {
|
|
293
|
-
columnState = columnState.map(colState => {
|
|
294
|
-
// Pokusíme se získat aktuální headerName z DOM
|
|
295
|
-
try {
|
|
296
|
-
const headerCell = document.querySelector(`[col-id="${colState.colId}"]`);
|
|
297
|
-
if (headerCell) {
|
|
298
|
-
const headerTextEl = headerCell.querySelector('.ag-header-cell-text');
|
|
299
|
-
if (headerTextEl && headerTextEl.textContent) {
|
|
300
|
-
return {
|
|
301
|
-
...colState,
|
|
302
|
-
headerName: headerTextEl.textContent
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
catch (headerError) {
|
|
308
|
-
console.log(`❌ [GridLayout] Could not get headerName from DOM for ${colState.colId}`);
|
|
309
|
-
}
|
|
310
|
-
return colState;
|
|
311
|
-
});
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
catch (error) {
|
|
315
|
-
console.error('❌ [GridLayout] Error calling columnApiRef.current.getColumnState():', error);
|
|
316
|
-
return;
|
|
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 };
|
|
317
235
|
}
|
|
236
|
+
return colState;
|
|
237
|
+
});
|
|
238
|
+
const columnDefsToUse = stableColumnDefsRef.current;
|
|
239
|
+
if (!columnDefsToUse || !Array.isArray(columnDefsToUse) || columnDefsToUse.length === 0) {
|
|
240
|
+
return;
|
|
318
241
|
}
|
|
319
|
-
|
|
242
|
+
const fields = stableGridLayoutApiRef.current.transformColumnStateToFields(enrichedColumnState, columnDefsToUse);
|
|
243
|
+
if (!fields || fields.length === 0) {
|
|
320
244
|
return;
|
|
321
245
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
console.warn('[GridLayout] stableColumnDefsRef is empty, using fallback from stableCurrentColumnsRef');
|
|
328
|
-
if (stableCurrentColumnsRef.current &&
|
|
329
|
-
Array.isArray(stableCurrentColumnsRef.current)) {
|
|
330
|
-
// Převedeme cached sloupce zpět na columnDefs format
|
|
331
|
-
columnDefsToUse = stableCurrentColumnsRef.current.map((col) => ({
|
|
332
|
-
field: col.field,
|
|
333
|
-
headerName: col.headerName || col.field,
|
|
334
|
-
width: col.width || 100,
|
|
335
|
-
hide: !col.visible,
|
|
336
|
-
}));
|
|
337
|
-
}
|
|
338
|
-
else {
|
|
339
|
-
console.error('[GridLayout] No valid columnDefs available for saving');
|
|
340
|
-
return;
|
|
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);
|
|
341
251
|
}
|
|
342
252
|
}
|
|
343
|
-
// Transformujeme na Grid API format
|
|
344
|
-
const fields = stableGridLayoutApiRef.current.transformColumnStateToFields(columnState, columnDefsToUse);
|
|
345
|
-
// Uložíme do API
|
|
346
|
-
const result = stableGridLayoutApiRef.current
|
|
347
|
-
.saveGridLayout(fields)
|
|
348
|
-
.then((result) => {
|
|
349
|
-
if (result.success) {
|
|
350
|
-
setHasUnsavedChanges(false);
|
|
351
|
-
if (stableOnLayoutSavedRef.current) {
|
|
352
|
-
stableOnLayoutSavedRef.current(fields, columnState);
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
})
|
|
356
|
-
.catch((error) => {
|
|
357
|
-
stableHandleErrorRef.current(error, 'při ukládání layoutu');
|
|
358
|
-
});
|
|
359
253
|
}
|
|
360
254
|
catch (error) {
|
|
361
|
-
|
|
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
|
+
}
|
|
362
261
|
}
|
|
363
262
|
finally {
|
|
364
|
-
|
|
263
|
+
isSavingRef.current = false; // ref only → žádný re-render
|
|
365
264
|
}
|
|
366
|
-
}, [
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
265
|
+
}, []); // PRÁZDNÉ DEPS → stabilní funkce
|
|
266
|
+
// ==========================================================================
|
|
267
|
+
// DEBOUNCED SAVE
|
|
268
|
+
// ==========================================================================
|
|
269
|
+
const saveCurrentLayoutRef = useRef(saveCurrentLayout);
|
|
270
|
+
useEffect(() => {
|
|
271
|
+
saveCurrentLayoutRef.current = saveCurrentLayout;
|
|
272
|
+
}, [saveCurrentLayout]);
|
|
370
273
|
const debouncedSave = useMemo(() => {
|
|
371
|
-
|
|
372
|
-
|
|
274
|
+
return debounce(() => {
|
|
275
|
+
saveCurrentLayoutRef.current();
|
|
373
276
|
}, autoSaveDelay);
|
|
374
|
-
|
|
375
|
-
}, [saveCurrentLayout, autoSaveDelay]);
|
|
376
|
-
// ✅ FIX #3: Stabilní ref pro debouncedSave (eliminuje regeneraci event handlerů)
|
|
277
|
+
}, [autoSaveDelay]);
|
|
377
278
|
const debouncedSaveRef = useRef(debouncedSave);
|
|
378
|
-
// Aktualizovat ref když se debouncedSave změní
|
|
379
279
|
useEffect(() => {
|
|
380
280
|
debouncedSaveRef.current = debouncedSave;
|
|
381
281
|
}, [debouncedSave]);
|
|
382
|
-
// ✅ FIX #3+: Stabilní refs pro enabled, isGridReady, autoSave (KRITICKÉ pro stabilitu handlerů)
|
|
383
|
-
const enabledRef = useRef(enabled);
|
|
384
|
-
const isGridReadyRef = useRef(isGridReady);
|
|
385
|
-
const autoSaveRef = useRef(autoSave);
|
|
386
|
-
// Aktualizovat refs při změnách
|
|
387
282
|
useEffect(() => {
|
|
388
|
-
|
|
389
|
-
}, [
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
useEffect(() => {
|
|
394
|
-
autoSaveRef.current = autoSave;
|
|
395
|
-
}, [autoSave]);
|
|
396
|
-
/**
|
|
397
|
-
* Aplikuje saved layout na AG-Grid
|
|
398
|
-
* @param {boolean} forceApply - Vynucené aplikování ignorující isInitialized stav
|
|
399
|
-
*/
|
|
283
|
+
return () => { debouncedSave.cancel(); };
|
|
284
|
+
}, [debouncedSave]);
|
|
285
|
+
// ==========================================================================
|
|
286
|
+
// APPLY SAVED LAYOUT (pouze při inicializaci - re-render je OK)
|
|
287
|
+
// ==========================================================================
|
|
400
288
|
const applySavedLayout = useCallback((forceApply = false) => {
|
|
401
|
-
|
|
402
|
-
if (!savedFields?.records ||
|
|
403
|
-
savedFields.records.length === 0 ||
|
|
404
|
-
(!forceApply && isInitialized)) {
|
|
289
|
+
if (!savedFields?.records || savedFields.records.length === 0 || (!forceApply && isInitialized)) {
|
|
405
290
|
return;
|
|
406
291
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
if (gridApiRef.current &&
|
|
410
|
-
typeof gridApiRef.current.applyColumnState === 'function') {
|
|
411
|
-
applyColumnStateApi = gridApiRef.current;
|
|
412
|
-
}
|
|
413
|
-
else if (columnApiRef.current &&
|
|
414
|
-
typeof columnApiRef.current.applyColumnState === 'function') {
|
|
415
|
-
applyColumnStateApi = columnApiRef.current;
|
|
416
|
-
}
|
|
417
|
-
if (!applyColumnStateApi) {
|
|
418
|
-
console.warn('[GridLayout] applyColumnState method not available');
|
|
292
|
+
const applyApi = getApplyApi(gridApiRef, columnApiRef);
|
|
293
|
+
if (!applyApi) {
|
|
419
294
|
return;
|
|
420
295
|
}
|
|
421
296
|
try {
|
|
422
297
|
setIsLoading(true);
|
|
423
|
-
|
|
424
|
-
// Transformujeme Grid API fields na AG-Grid column state
|
|
425
|
-
// Použijeme stableColumnDefsRef.current místo columnDefs pro zachování aktuálních šířek
|
|
298
|
+
isApplyingLayoutRef.current = true;
|
|
426
299
|
const columnDefsToUse = stableColumnDefsRef.current || columnDefs;
|
|
427
300
|
const columnState = gridLayoutApi.transformFieldsToColumnState(savedFields.records, columnDefsToUse);
|
|
428
|
-
if (columnState
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
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);
|
|
436
318
|
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
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) {
|
|
443
332
|
try {
|
|
444
|
-
const
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
...colState,
|
|
455
|
-
width: refWidth
|
|
456
|
-
};
|
|
457
|
-
}
|
|
458
|
-
// Při prvotním načítání nebo pokud nemáme ref hodnotu, zachováme API šířku
|
|
459
|
-
return colState;
|
|
460
|
-
});
|
|
461
|
-
result = applyColumnStateApi.applyColumnState({
|
|
462
|
-
state: adjustedColumnState,
|
|
463
|
-
applyOrder: applyOrderEnabled, // Pořadí jen když není waitForSavedFields
|
|
464
|
-
defaultState: {
|
|
465
|
-
sort: null, // Reset sorting na všech sloupcích
|
|
466
|
-
sortIndex: null, // Reset sort index
|
|
467
|
-
pivot: null, // Reset pivot
|
|
468
|
-
rowGroup: null, // Reset row grouping
|
|
469
|
-
},
|
|
470
|
-
});
|
|
471
|
-
// ✅ FIX: Aktualizovat lastKnownColumnStateRef po aplikování layoutu z API
|
|
472
|
-
// Tím zajistíme, že handleDragStopped má správnou referenci pro porovnání
|
|
473
|
-
lastKnownColumnStateRef.current = adjustedColumnState;
|
|
474
|
-
// Explicitně aktualizujeme headerName pro každý sloupec, protože AG-Grid
|
|
475
|
-
// nepodporuje nastavení headerName přes applyColumnState
|
|
476
|
-
if (savedFields.records &&
|
|
477
|
-
Array.isArray(savedFields.records) &&
|
|
478
|
-
gridApiRef.current) {
|
|
479
|
-
// Nejprve zkusíme použít refreshHeader funkci, pokud je dostupná
|
|
480
|
-
try {
|
|
481
|
-
if (typeof gridApiRef.current.refreshHeader === 'function') {
|
|
482
|
-
gridApiRef.current.refreshHeader();
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
catch (refreshError) {
|
|
486
|
-
console.error('[GridLayout] Error in refreshHeader:', refreshError);
|
|
487
|
-
}
|
|
488
|
-
// Získáme aktuální definice sloupců z gridu
|
|
489
|
-
let currentColDefs;
|
|
490
|
-
try {
|
|
491
|
-
currentColDefs = gridApiRef.current.getColumnDefs
|
|
492
|
-
? gridApiRef.current.getColumnDefs()
|
|
493
|
-
: null;
|
|
494
|
-
}
|
|
495
|
-
catch (error) {
|
|
496
|
-
console.error('[GridLayout] Error getting column definitions:', error);
|
|
497
|
-
currentColDefs = null;
|
|
498
|
-
}
|
|
499
|
-
if (currentColDefs && Array.isArray(currentColDefs)) {
|
|
500
|
-
// Vytvoříme mapu fieldName -> headerName z API dat
|
|
501
|
-
const headerNameMap = new Map();
|
|
502
|
-
savedFields.records.forEach((field) => {
|
|
503
|
-
if (field.fieldName && field.headerName) {
|
|
504
|
-
headerNameMap.set(field.fieldName, field.headerName);
|
|
505
|
-
}
|
|
506
|
-
});
|
|
507
|
-
// Aktualizujeme headerName pro každý sloupec
|
|
508
|
-
const updatedColDefs = currentColDefs.map((colDef) => {
|
|
509
|
-
const fieldName = colDef.field;
|
|
510
|
-
if (fieldName && headerNameMap.has(fieldName)) {
|
|
511
|
-
const newHeaderName = headerNameMap.get(fieldName);
|
|
512
|
-
// Vytvoříme novou kopii definice sloupce s aktualizovaným headerName
|
|
513
|
-
return {
|
|
514
|
-
...colDef,
|
|
515
|
-
headerName: newHeaderName,
|
|
516
|
-
};
|
|
517
|
-
}
|
|
518
|
-
return colDef;
|
|
519
|
-
});
|
|
520
|
-
// Aplikujeme aktualizované definice sloupců zpět do gridu
|
|
521
|
-
try {
|
|
522
|
-
if (typeof gridApiRef.current.setColumnDefs === 'function') {
|
|
523
|
-
gridApiRef.current.setColumnDefs(updatedColDefs);
|
|
524
|
-
// Pro jistotu zkontrolujeme, zda byly změny aplikovány
|
|
525
|
-
setTimeout(() => {
|
|
526
|
-
try {
|
|
527
|
-
// DOM operace dokončeny, můžeme ukončit loading
|
|
528
|
-
setIsApplyingLayout(false);
|
|
529
|
-
}
|
|
530
|
-
catch (checkError) {
|
|
531
|
-
console.error('[GridLayout] Error checking updated columns:', checkError);
|
|
532
|
-
// I při chybě ukončíme loading
|
|
533
|
-
setIsApplyingLayout(false);
|
|
534
|
-
}
|
|
535
|
-
}, 100);
|
|
536
|
-
}
|
|
537
|
-
else {
|
|
538
|
-
// Alternativní řešení - přímá manipulace s DOM
|
|
539
|
-
// Počkáme, až se grid vyrenderuje
|
|
540
|
-
setTimeout(() => {
|
|
541
|
-
try {
|
|
542
|
-
// Vytvoříme mapu pro rychlou identifikaci
|
|
543
|
-
const headerUpdates = new Map();
|
|
544
|
-
updatedColDefs.forEach((colDef) => {
|
|
545
|
-
if (colDef.field && colDef.headerName) {
|
|
546
|
-
headerUpdates.set(colDef.field, colDef.headerName);
|
|
547
|
-
}
|
|
548
|
-
});
|
|
549
|
-
// Najdeme všechny hlavičky sloupců pomocí DOM
|
|
550
|
-
const headerCells = document.querySelectorAll('.ag-header-cell');
|
|
551
|
-
headerCells.forEach((headerCell) => {
|
|
552
|
-
try {
|
|
553
|
-
// Získáme ID sloupce z DOM atributů
|
|
554
|
-
const colId = headerCell.getAttribute('col-id');
|
|
555
|
-
if (colId && headerUpdates.has(colId)) {
|
|
556
|
-
// Najdeme element s textem hlavičky
|
|
557
|
-
const headerTextEl = headerCell.querySelector('.ag-header-cell-text');
|
|
558
|
-
if (headerTextEl) {
|
|
559
|
-
const newHeaderName = headerUpdates.get(colId);
|
|
560
|
-
headerTextEl.textContent = newHeaderName;
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
catch (cellError) {
|
|
565
|
-
console.error('[GridLayout] Error updating header cell:', cellError);
|
|
566
|
-
}
|
|
567
|
-
});
|
|
568
|
-
// DOM manipulace dokončena, ukončíme loading
|
|
569
|
-
setIsApplyingLayout(false);
|
|
570
|
-
}
|
|
571
|
-
catch (domError) {
|
|
572
|
-
console.error('[GridLayout] Error in DOM manipulation:', domError);
|
|
573
|
-
// I při chybě ukončíme loading
|
|
574
|
-
setIsApplyingLayout(false);
|
|
575
|
-
}
|
|
576
|
-
}, 200);
|
|
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 };
|
|
577
343
|
}
|
|
578
344
|
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
345
|
+
return colDef;
|
|
346
|
+
});
|
|
347
|
+
if (hasHeaderChanges && typeof gridApiRef.current.setColumnDefs === 'function') {
|
|
348
|
+
gridApiRef.current.setColumnDefs(updatedColDefs);
|
|
582
349
|
}
|
|
583
350
|
}
|
|
584
351
|
}
|
|
585
|
-
catch (
|
|
586
|
-
|
|
587
|
-
}
|
|
588
|
-
if (onLayoutLoaded) {
|
|
589
|
-
onLayoutLoaded(savedFields.records, columnState);
|
|
352
|
+
catch (e) {
|
|
353
|
+
console.error('[GridLayout] Chyba při aktualizaci headerName:', e);
|
|
590
354
|
}
|
|
591
355
|
}
|
|
592
|
-
|
|
593
|
-
|
|
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?.();
|
|
594
366
|
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
setIsApplyingLayout(false);
|
|
599
|
-
}, 300);
|
|
367
|
+
catch (e) { /* ignorujeme */ }
|
|
368
|
+
if (stableOnLayoutLoadedRef.current) {
|
|
369
|
+
stableOnLayoutLoadedRef.current(savedFields.records, columnState);
|
|
600
370
|
}
|
|
601
|
-
};
|
|
602
|
-
// Pro waitForSavedFields aplikujeme okamžitě (pořadí je už v columnDefs)
|
|
603
|
-
// Pro normální režim použijeme delay
|
|
604
|
-
if (waitForSavedFields) {
|
|
605
|
-
applyFunction();
|
|
606
371
|
}
|
|
607
|
-
|
|
608
|
-
|
|
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;
|
|
609
378
|
}
|
|
379
|
+
};
|
|
380
|
+
if (waitForSavedFields) {
|
|
381
|
+
applyFunction();
|
|
382
|
+
}
|
|
383
|
+
else {
|
|
384
|
+
setTimeout(applyFunction, 100);
|
|
610
385
|
}
|
|
611
386
|
}
|
|
612
387
|
catch (error) {
|
|
388
|
+
console.error('[GridLayout] Chyba při přípravě layoutu:', error);
|
|
613
389
|
handleError(error, 'při aplikování layoutu');
|
|
390
|
+
isApplyingLayoutRef.current = false;
|
|
614
391
|
}
|
|
615
392
|
finally {
|
|
616
393
|
setIsInitialized(true);
|
|
617
|
-
setIsGridReady(true);
|
|
394
|
+
setIsGridReady(true);
|
|
618
395
|
setIsLoading(false);
|
|
619
|
-
// setIsApplyingLayout(false) se nastaví až po dokončení všech DOM operací
|
|
620
|
-
}
|
|
621
|
-
}, [
|
|
622
|
-
savedFields,
|
|
623
|
-
gridLayoutApi,
|
|
624
|
-
isInitialized,
|
|
625
|
-
onLayoutLoaded,
|
|
626
|
-
handleError,
|
|
627
|
-
columnDefs,
|
|
628
|
-
waitForSavedFields,
|
|
629
|
-
]);
|
|
630
|
-
/**
|
|
631
|
-
* Odložené akce pro případ rychlé interakce před dokončením inicializace
|
|
632
|
-
*/
|
|
633
|
-
const [pendingActions, setPendingActions] = useState([]);
|
|
634
|
-
// Effect pro zpracování pending actions po dokončení inicializace
|
|
635
|
-
useEffect(() => {
|
|
636
|
-
if (isInitialized && pendingActions.length > 0) {
|
|
637
|
-
// Zpracujeme pending akce s krátkým delay
|
|
638
|
-
setTimeout(() => {
|
|
639
|
-
pendingActions.forEach((action) => {
|
|
640
|
-
switch (action.type) {
|
|
641
|
-
case 'dragStopped':
|
|
642
|
-
if (autoSaveRef.current && debouncedSaveRef.current) { // ✅ FIX #3+: Použít refs
|
|
643
|
-
debouncedSaveRef.current();
|
|
644
|
-
}
|
|
645
|
-
break;
|
|
646
|
-
}
|
|
647
|
-
});
|
|
648
|
-
setPendingActions([]);
|
|
649
|
-
}, 100);
|
|
650
396
|
}
|
|
651
|
-
}, [isInitialized,
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
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
|
+
// ==========================================================================
|
|
655
402
|
const handleColumnMoved = useCallback(() => {
|
|
656
|
-
if (!enabledRef.current || !isGridReadyRef.current)
|
|
403
|
+
if (!enabledRef.current || !isGridReadyRef.current)
|
|
657
404
|
return;
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
// Neukládáme při každém pohybu - čekáme na onDragStopped
|
|
661
|
-
}, []); // ✅ FIX #3+: ŽÁDNÉ dependencies → handler se nikdy neregeneruje
|
|
405
|
+
hasUnsavedChangesRef.current = true; // ref only → žádný re-render
|
|
406
|
+
}, []);
|
|
662
407
|
const handleDragStopped = useCallback(() => {
|
|
663
|
-
if (!enabledRef.current || !isGridReadyRef.current)
|
|
408
|
+
if (!enabledRef.current || !isGridReadyRef.current)
|
|
664
409
|
return;
|
|
665
|
-
|
|
666
|
-
// ✅ FIX: Detekovat, zda se skutečně něco změnilo (pořadí/šířky/viditelnost/pinning)
|
|
667
|
-
// Pokud se nic nezměnilo, SKIP celý handler (eliminuje zbytečné re-rendery při range selection)
|
|
410
|
+
// Detekce skutečné změny
|
|
668
411
|
try {
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
if (
|
|
672
|
-
//
|
|
673
|
-
if (isColumnStateEqual(lastKnownColumnStateRef.current, currentColumnState)) {
|
|
674
|
-
// Žádná změna - SKIP celý handler (nejčastější případ při range selection)
|
|
675
|
-
return;
|
|
676
|
-
}
|
|
677
|
-
// Uložit nový stav jako referenci pro příští porovnání
|
|
678
|
-
lastKnownColumnStateRef.current = currentColumnState;
|
|
679
|
-
// Uložit šířky do ref map pro každý sloupec (pro API save a další načtení)
|
|
680
|
-
currentColumnState.forEach(colState => {
|
|
681
|
-
if (colState.colId && colState.width) {
|
|
682
|
-
columnWidthRefsMap.current.set(colState.colId, colState.width);
|
|
683
|
-
}
|
|
684
|
-
});
|
|
685
|
-
// NEPOUŽÍVÁME setColumnWidthsVersion zde - způsobilo by reset pořadí/šířek!
|
|
412
|
+
const currentColumnState = getColumnState(gridApiRef, columnApiRef);
|
|
413
|
+
if (currentColumnState && Array.isArray(currentColumnState)) {
|
|
414
|
+
if (isColumnStateEqual(lastKnownColumnStateRef.current, currentColumnState)) {
|
|
415
|
+
return; // Žádná změna → skip
|
|
686
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
|
+
});
|
|
687
423
|
}
|
|
688
424
|
}
|
|
689
425
|
catch (error) {
|
|
690
|
-
console.error('[GridLayout]
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
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);
|
|
699
439
|
return;
|
|
700
440
|
}
|
|
701
|
-
|
|
702
|
-
if (autoSaveRef.current && debouncedSaveRef.current) { // ✅ FIX #3+: Použít refs
|
|
441
|
+
if (autoSaveRef.current && debouncedSaveRef.current) {
|
|
703
442
|
debouncedSaveRef.current();
|
|
704
443
|
}
|
|
705
|
-
}, []);
|
|
706
|
-
// Handler pro DOKONČENÍ resize - spouští auto-save
|
|
707
|
-
// DŮLEŽITÉ: Reaguje pouze na finished === true (po uvolnění myši)
|
|
444
|
+
}, []);
|
|
708
445
|
const handleColumnResized = useCallback((event) => {
|
|
709
446
|
if (!enabledRef.current || !isGridReadyRef.current)
|
|
710
|
-
return;
|
|
711
|
-
// Reagujeme POUZE na dokončení resize operace (po uvolnění myši)
|
|
712
|
-
// To zabrání poskakování sloupců během tažení
|
|
447
|
+
return;
|
|
713
448
|
if (!event || event.finished !== true)
|
|
714
449
|
return;
|
|
715
|
-
|
|
716
|
-
// Uložit aktuální šířky sloupců do ref map pro persistenci
|
|
717
|
-
// DŮLEŽITÉ: NEMĚNÍME columnWidthsVersion - AG-Grid už má správnou šířku ve svém interním stavu
|
|
718
|
-
// Změna columnWidthsVersion by triggerovala přepočet preTransformedColumnDefs a reset šířek
|
|
450
|
+
hasUnsavedChangesRef.current = true; // ref only → žádný re-render
|
|
719
451
|
try {
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
}
|
|
728
|
-
});
|
|
729
|
-
// NEPOUŽÍVÁME setColumnWidthsVersion zde - způsobilo by reset šířek!
|
|
730
|
-
}
|
|
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
|
+
});
|
|
731
459
|
}
|
|
732
460
|
}
|
|
733
461
|
catch (error) {
|
|
734
|
-
console.error('[GridLayout]
|
|
462
|
+
console.error('[GridLayout] Chyba při aktualizaci šířek v handleColumnResized:', error);
|
|
735
463
|
}
|
|
736
|
-
|
|
737
|
-
// OCHRANA: Ukládáme pouze pokud už byl layout načten z API (isInitialized)
|
|
738
|
-
if (autoSaveRef.current && isInitializedRef.current && debouncedSaveRef.current) { // ✅ FIX #3+: Použít refs
|
|
464
|
+
if (autoSaveRef.current && isInitializedRef.current && debouncedSaveRef.current) {
|
|
739
465
|
debouncedSaveRef.current();
|
|
740
466
|
}
|
|
741
|
-
}, []);
|
|
467
|
+
}, []);
|
|
742
468
|
const handleColumnVisible = useCallback(() => {
|
|
743
469
|
if (!enabledRef.current || !isGridReadyRef.current)
|
|
744
|
-
return;
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
if (autoSaveRef.current && isInitializedRef.current && debouncedSaveRef.current) { // ✅ FIX #3+: Použít refs
|
|
470
|
+
return;
|
|
471
|
+
hasUnsavedChangesRef.current = true; // ref only → žádný re-render
|
|
472
|
+
if (autoSaveRef.current && isInitializedRef.current && debouncedSaveRef.current) {
|
|
748
473
|
debouncedSaveRef.current();
|
|
749
474
|
}
|
|
750
|
-
}, []);
|
|
475
|
+
}, []);
|
|
751
476
|
const handleColumnPinned = useCallback(() => {
|
|
752
477
|
if (!enabledRef.current || !isGridReadyRef.current)
|
|
753
|
-
return;
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
if (autoSaveRef.current && isInitializedRef.current && debouncedSaveRef.current) { // ✅ FIX #3+: Použít refs
|
|
478
|
+
return;
|
|
479
|
+
hasUnsavedChangesRef.current = true; // ref only → žádný re-render
|
|
480
|
+
if (autoSaveRef.current && isInitializedRef.current && debouncedSaveRef.current) {
|
|
757
481
|
debouncedSaveRef.current();
|
|
758
482
|
}
|
|
759
|
-
}, []);
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
483
|
+
}, []);
|
|
484
|
+
// ==========================================================================
|
|
485
|
+
// AG-GRID READY HANDLER
|
|
486
|
+
// ==========================================================================
|
|
763
487
|
const handleGridReady = useCallback((params) => {
|
|
764
|
-
if (!enabledRef.current)
|
|
488
|
+
if (!enabledRef.current)
|
|
765
489
|
return;
|
|
766
|
-
}
|
|
767
490
|
gridApiRef.current = params.api;
|
|
768
|
-
// AG Grid v31+ - columnApi je deprecated, všechny metody jsou v hlavním api
|
|
769
|
-
// Pokud columnApi neexistuje, použijeme main api jako fallback
|
|
770
491
|
columnApiRef.current = params.columnApi || params.api;
|
|
771
|
-
// Okamžitě označíme grid jako připravený pro event handlery
|
|
772
492
|
setIsGridReady(true);
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
/**
|
|
778
|
-
* Effect pro aplikování layoutu při prvotním načtení savedFields
|
|
779
|
-
*/
|
|
493
|
+
}, []);
|
|
494
|
+
// ==========================================================================
|
|
495
|
+
// EFFECTS
|
|
496
|
+
// ==========================================================================
|
|
780
497
|
useEffect(() => {
|
|
781
|
-
// Aplikujeme layout pokud:
|
|
782
|
-
// 1. Máme savedFields z API (i když prázdné - první zobrazení modulu)
|
|
783
|
-
// 2. Grid je ready (má API references)
|
|
784
|
-
// 3. Ještě jsme neinicializovali layout
|
|
785
498
|
if (savedFields?.records !== undefined && gridApiRef.current && !isInitialized) {
|
|
786
499
|
if (savedFields.records.length > 0) {
|
|
787
500
|
applySavedLayout();
|
|
788
501
|
}
|
|
789
502
|
else {
|
|
790
|
-
// Pro prázdné savedFields jen nastavíme inicializaci
|
|
791
503
|
setIsInitialized(true);
|
|
792
504
|
setIsGridReady(true);
|
|
793
505
|
}
|
|
794
506
|
}
|
|
795
507
|
}, [savedFields?.records, isInitialized, applySavedLayout]);
|
|
796
|
-
/**
|
|
797
|
-
* Effect pro error handling
|
|
798
|
-
*/
|
|
799
508
|
useEffect(() => {
|
|
800
509
|
if (fieldsError) {
|
|
801
510
|
handleError(fieldsError, 'při načítání saved layoutu');
|
|
802
511
|
}
|
|
803
512
|
}, [fieldsError, handleError]);
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
useEffect(() => {
|
|
808
|
-
return () => {
|
|
809
|
-
debouncedSave.cancel();
|
|
810
|
-
};
|
|
811
|
-
}, [debouncedSave]);
|
|
812
|
-
/**
|
|
813
|
-
* Resetuje layout na default
|
|
814
|
-
*/
|
|
513
|
+
// ==========================================================================
|
|
514
|
+
// MANUÁLNÍ AKCE (re-render je zde OK - explicitní uživatelská akce)
|
|
515
|
+
// ==========================================================================
|
|
815
516
|
const resetToDefault = useCallback(async () => {
|
|
816
517
|
if (!enabled)
|
|
817
518
|
return;
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
if (gridApiRef.current &&
|
|
821
|
-
typeof gridApiRef.current.resetColumnState === 'function') {
|
|
822
|
-
resetColumnStateApi = gridApiRef.current;
|
|
823
|
-
}
|
|
824
|
-
else if (columnApiRef.current &&
|
|
825
|
-
typeof columnApiRef.current.resetColumnState === 'function') {
|
|
826
|
-
resetColumnStateApi = columnApiRef.current;
|
|
827
|
-
}
|
|
828
|
-
else {
|
|
829
|
-
console.warn('[GridLayout] resetColumnState method not available');
|
|
519
|
+
const resetApi = getResetApi(gridApiRef, columnApiRef);
|
|
520
|
+
if (!resetApi)
|
|
830
521
|
return;
|
|
831
|
-
}
|
|
832
522
|
try {
|
|
833
523
|
setIsLoading(true);
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
// Pokud je autoSave zapnuté, uložíme resetovaný stav
|
|
524
|
+
resetApi.resetColumnState();
|
|
525
|
+
headerNameMapRef.current.clear();
|
|
837
526
|
if (autoSave) {
|
|
838
527
|
await saveCurrentLayout();
|
|
839
528
|
}
|
|
840
529
|
else {
|
|
841
|
-
|
|
530
|
+
hasUnsavedChangesRef.current = true;
|
|
842
531
|
}
|
|
843
532
|
}
|
|
844
533
|
catch (error) {
|
|
@@ -848,31 +537,25 @@ onLayoutLoaded, onLayoutSaved, onError, }) => {
|
|
|
848
537
|
setIsLoading(false);
|
|
849
538
|
}
|
|
850
539
|
}, [enabled, autoSave, saveCurrentLayout, handleError]);
|
|
851
|
-
/**
|
|
852
|
-
* Manuální uložení
|
|
853
|
-
*/
|
|
854
540
|
const saveLayout = useCallback(async () => {
|
|
855
541
|
await saveCurrentLayout();
|
|
856
542
|
}, [saveCurrentLayout]);
|
|
857
|
-
/**
|
|
858
|
-
* Manuální reload
|
|
859
|
-
*/
|
|
860
543
|
const reloadLayout = useCallback(async () => {
|
|
861
544
|
try {
|
|
862
|
-
|
|
863
|
-
const
|
|
864
|
-
|
|
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;
|
|
865
549
|
setIsInitialized(false);
|
|
866
550
|
setIsGridReady(false);
|
|
867
|
-
await refetchFields();
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
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);
|
|
871
558
|
}
|
|
872
|
-
if (!columnApiRef.current && savedColumnApiRef) {
|
|
873
|
-
columnApiRef.current = savedColumnApiRef;
|
|
874
|
-
}
|
|
875
|
-
// Po refetch dat obnovíme oba stavy (layout je už v DB)
|
|
876
559
|
setIsInitialized(true);
|
|
877
560
|
setIsGridReady(true);
|
|
878
561
|
}
|
|
@@ -880,35 +563,15 @@ onLayoutLoaded, onLayoutSaved, onError, }) => {
|
|
|
880
563
|
handleError(error, 'při reload layoutu');
|
|
881
564
|
}
|
|
882
565
|
}, [refetchFields, handleError]);
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
566
|
+
// ==========================================================================
|
|
567
|
+
// COLUMN EDITOR
|
|
568
|
+
// ==========================================================================
|
|
886
569
|
const getCurrentColumnsForEditor = useCallback(() => {
|
|
887
|
-
// Zajistíme, že máme validní columnDefs
|
|
888
570
|
const validColumnDefs = Array.isArray(columnDefs) ? columnDefs : [];
|
|
889
571
|
try {
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
typeof gridApiRef.current.getColumnState === 'function') {
|
|
894
|
-
// AG Grid v31+ - getColumnState je přímo v main API
|
|
895
|
-
currentColumnState = gridApiRef.current.getColumnState();
|
|
896
|
-
}
|
|
897
|
-
else if (columnApiRef.current &&
|
|
898
|
-
typeof columnApiRef.current.getColumnState === 'function') {
|
|
899
|
-
// Starší verze AG Grid - getColumnState je v columnApi
|
|
900
|
-
currentColumnState = columnApiRef.current.getColumnState();
|
|
901
|
-
}
|
|
902
|
-
else {
|
|
903
|
-
throw new Error('getColumnState method is not available on gridApiRef or columnApiRef');
|
|
904
|
-
}
|
|
905
|
-
if (!Array.isArray(currentColumnState) ||
|
|
906
|
-
currentColumnState.length === 0) {
|
|
907
|
-
// Fallback pokud getColumnState() nevrátí validní array
|
|
908
|
-
if (lastKnownColumnState) {
|
|
909
|
-
return lastKnownColumnState;
|
|
910
|
-
}
|
|
911
|
-
const defaultColumns = validColumnDefs.map((col, index) => ({
|
|
572
|
+
const currentColumnState = getColumnState(gridApiRef, columnApiRef);
|
|
573
|
+
if (!Array.isArray(currentColumnState) || currentColumnState.length === 0) {
|
|
574
|
+
return validColumnDefs.map((col, index) => ({
|
|
912
575
|
id: col.field,
|
|
913
576
|
field: col.field,
|
|
914
577
|
headerName: col.headerName || col.field,
|
|
@@ -917,265 +580,207 @@ onLayoutLoaded, onLayoutSaved, onError, }) => {
|
|
|
917
580
|
originalWidth: col.width || 100,
|
|
918
581
|
visible: !col.hide,
|
|
919
582
|
order: index,
|
|
920
|
-
originalOrder: index,
|
|
583
|
+
originalOrder: index,
|
|
921
584
|
}));
|
|
922
|
-
// POZN: Neukládáme do state, aby nedocházelo k infinite loop
|
|
923
|
-
return defaultColumns;
|
|
924
585
|
}
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
const colDef = validColumnDefs.find((cd) => cd.field === columnStateItem.colId ||
|
|
928
|
-
cd.colId === columnStateItem.colId) || {};
|
|
929
|
-
// Pokusíme se najít saved hodnotu pro headerName
|
|
586
|
+
return currentColumnState.map((columnStateItem, index) => {
|
|
587
|
+
const colDef = validColumnDefs.find((cd) => cd.field === columnStateItem.colId || cd.colId === columnStateItem.colId) || {};
|
|
930
588
|
const savedField = savedFields?.records?.find((sf) => sf.fieldName === columnStateItem.colId);
|
|
931
|
-
|
|
589
|
+
const headerName = headerNameMapRef.current.get(columnStateItem.colId) ||
|
|
590
|
+
savedField?.headerName ||
|
|
591
|
+
colDef.headerName ||
|
|
592
|
+
columnStateItem.colId;
|
|
932
593
|
const originalIndex = validColumnDefs.findIndex((cd) => (cd.field || cd.colId) === columnStateItem.colId);
|
|
933
594
|
return {
|
|
934
595
|
id: columnStateItem.colId,
|
|
935
596
|
field: columnStateItem.colId,
|
|
936
|
-
headerName
|
|
937
|
-
colDef.headerName ||
|
|
938
|
-
columnStateItem.colId,
|
|
939
|
-
// Původní headerName z columnDefs (ne z uložených uživatelských preferencí)
|
|
597
|
+
headerName,
|
|
940
598
|
originalHeaderName: colDef.headerName || columnStateItem.colId,
|
|
941
599
|
width: columnStateItem.width || colDef.width || 100,
|
|
942
600
|
originalWidth: colDef.width || 100,
|
|
943
601
|
visible: !columnStateItem.hide,
|
|
944
602
|
order: index,
|
|
945
|
-
originalOrder: originalIndex !== -1 ? originalIndex : index,
|
|
603
|
+
originalOrder: originalIndex !== -1 ? originalIndex : index,
|
|
946
604
|
};
|
|
947
605
|
});
|
|
948
|
-
// POZN: Neukládáme do state, aby nedocházelo k infinite loop
|
|
949
|
-
// Tato aktualizace proběhne v useEffect
|
|
950
|
-
return result;
|
|
951
606
|
}
|
|
952
607
|
catch (error) {
|
|
953
|
-
console.error('[GridLayout]
|
|
954
|
-
|
|
955
|
-
if (lastKnownColumnState) {
|
|
956
|
-
return lastKnownColumnState;
|
|
957
|
-
}
|
|
958
|
-
// Poslední fallback na columnDefs
|
|
959
|
-
const defaultColumns = validColumnDefs.map((col, index) => ({
|
|
608
|
+
console.error('[GridLayout] Chyba v getCurrentColumnsForEditor:', error);
|
|
609
|
+
return validColumnDefs.map((col, index) => ({
|
|
960
610
|
id: col.field || col.colId,
|
|
961
611
|
field: col.field || col.colId,
|
|
962
612
|
headerName: col.headerName || col.field || col.colId,
|
|
963
|
-
// Vždy používáme původní definici jako referenci pro porovnání
|
|
964
613
|
originalHeaderName: col.headerName || col.field || col.colId,
|
|
965
614
|
width: col.width || 100,
|
|
966
615
|
originalWidth: col.width || 100,
|
|
967
616
|
visible: !col.hide,
|
|
968
617
|
order: index,
|
|
969
|
-
originalOrder: index,
|
|
618
|
+
originalOrder: index,
|
|
970
619
|
}));
|
|
971
|
-
// POZN: Neukládáme do state, aby nedocházelo k infinite loop
|
|
972
|
-
return defaultColumns;
|
|
973
620
|
}
|
|
974
|
-
}, [columnDefs, savedFields
|
|
975
|
-
/**
|
|
976
|
-
* Otevře Column Editor modal
|
|
977
|
-
*/
|
|
621
|
+
}, [columnDefs, savedFields]);
|
|
978
622
|
const openColumnEditor = useCallback(() => {
|
|
979
|
-
// Před otevřením editoru si pouze zaznamenáme, že chceme aktualizovat stav sloupců
|
|
980
|
-
// Samotná aktualizace proběhne v useEffect, ne během renderu
|
|
981
623
|
setIsColumnEditorOpen(true);
|
|
982
624
|
}, []);
|
|
983
|
-
/**
|
|
984
|
-
* Zavře Column Editor modal
|
|
985
|
-
*/
|
|
986
625
|
const closeColumnEditor = useCallback(() => {
|
|
987
626
|
setIsColumnEditorOpen(false);
|
|
988
627
|
}, []);
|
|
989
|
-
/**
|
|
990
|
-
* Uloží změny z Column Editoru
|
|
991
|
-
* @param {Array} editedColumns - Editované sloupce z modalu
|
|
992
|
-
*/
|
|
993
628
|
const saveColumnEditorChanges = async (editedColumns) => {
|
|
994
629
|
if (!enabled)
|
|
995
630
|
return;
|
|
996
631
|
try {
|
|
997
632
|
setIsLoading(true);
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
const validColumns = editedColumns.filter((col) => col.field && col.field !== undefined);
|
|
1001
|
-
// Připravíme UserFields pro API podle C# SaveUserFieldModel struktury
|
|
633
|
+
isSavingRef.current = true;
|
|
634
|
+
const validColumns = editedColumns.filter(col => col.field && col.field !== undefined);
|
|
1002
635
|
const completeUserFields = validColumns.map((col, index) => ({
|
|
1003
|
-
Id: 0,
|
|
636
|
+
Id: 0,
|
|
1004
637
|
UserKey: userKey,
|
|
1005
638
|
ApplicationName: applicationName,
|
|
1006
639
|
GridName: gridName,
|
|
1007
640
|
FilterName: filterName || null,
|
|
1008
641
|
FieldName: col.field,
|
|
1009
642
|
HeaderName: col.headerName || col.field,
|
|
1010
|
-
Order: index,
|
|
643
|
+
Order: index,
|
|
1011
644
|
Show: col.visible,
|
|
1012
|
-
Width: col.width != null ? col.width : null,
|
|
645
|
+
Width: col.width != null ? col.width : null,
|
|
1013
646
|
System: false,
|
|
1014
647
|
}));
|
|
1015
|
-
//
|
|
1016
|
-
|
|
1017
|
-
.
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
pivot: null,
|
|
1048
|
-
rowGroup: null,
|
|
1049
|
-
},
|
|
1050
|
-
});
|
|
1051
|
-
// Aktualizujeme headerName pro každý sloupec
|
|
1052
|
-
const headerNameMap = new Map();
|
|
1053
|
-
freshFields.data.records.forEach((field) => {
|
|
1054
|
-
if (field.fieldName && field.headerName) {
|
|
1055
|
-
headerNameMap.set(field.fieldName, field.headerName);
|
|
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 };
|
|
1056
680
|
}
|
|
681
|
+
return colDef;
|
|
1057
682
|
});
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
const headerCells = document.querySelectorAll('.ag-header-cell');
|
|
1062
|
-
headerCells.forEach((headerCell) => {
|
|
1063
|
-
const colId = headerCell.getAttribute('col-id');
|
|
1064
|
-
if (colId && headerNameMap.has(colId)) {
|
|
1065
|
-
const headerTextEl = headerCell.querySelector('.ag-header-cell-text');
|
|
1066
|
-
if (headerTextEl) {
|
|
1067
|
-
headerTextEl.textContent = headerNameMap.get(colId);
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
});
|
|
1071
|
-
// Vynutíme refresh hlavičky
|
|
1072
|
-
if (gridApiRef.current?.refreshHeader) {
|
|
1073
|
-
gridApiRef.current.refreshHeader();
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
catch (headerError) {
|
|
1077
|
-
console.error('[GridLayout] Error updating headers:', headerError);
|
|
1078
|
-
}
|
|
1079
|
-
}, 50);
|
|
683
|
+
if (typeof gridApiRef.current.setColumnDefs === 'function') {
|
|
684
|
+
gridApiRef.current.setColumnDefs(updatedColDefs);
|
|
685
|
+
}
|
|
1080
686
|
}
|
|
1081
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 */ }
|
|
1082
701
|
}
|
|
1083
|
-
}
|
|
1084
|
-
}
|
|
1085
|
-
catch (refreshError) {
|
|
1086
|
-
console.error('[GridLayout] Error in post-save refresh:', refreshError);
|
|
1087
|
-
// Fallback na standardní applySavedLayout
|
|
1088
|
-
setTimeout(() => {
|
|
1089
|
-
if (gridApiRef.current) {
|
|
1090
|
-
applySavedLayout(true);
|
|
1091
|
-
}
|
|
1092
|
-
}, 200);
|
|
702
|
+
}
|
|
1093
703
|
}
|
|
1094
|
-
});
|
|
1095
|
-
if (onLayoutSaved) {
|
|
1096
|
-
onLayoutSaved(completeUserFields);
|
|
1097
|
-
}
|
|
704
|
+
}, 150);
|
|
1098
705
|
}
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
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
|
+
}
|
|
1103
717
|
}
|
|
1104
718
|
catch (error) {
|
|
1105
719
|
handleError(error, 'při ukládání změn z Column Editoru');
|
|
1106
720
|
}
|
|
1107
721
|
finally {
|
|
1108
722
|
setIsLoading(false);
|
|
1109
|
-
|
|
723
|
+
isSavingRef.current = false;
|
|
1110
724
|
}
|
|
1111
725
|
};
|
|
1112
|
-
|
|
1113
|
-
//
|
|
726
|
+
// ==========================================================================
|
|
727
|
+
// PRE-TRANSFORMED COLUMN DEFS
|
|
728
|
+
// ZAMKNUTÍ PO INICIALIZACI → stabilní reference, žádné zbytečné přepočty
|
|
729
|
+
// ==========================================================================
|
|
1114
730
|
const shouldShowColumns = useMemo(() => {
|
|
1115
731
|
if (!waitForSavedFields)
|
|
1116
|
-
return true;
|
|
1117
|
-
// Pokud je waitForSavedFields true, čekáme na dokončení načítání
|
|
732
|
+
return true;
|
|
1118
733
|
return !isFieldsLoading && !isLoading;
|
|
1119
734
|
}, [waitForSavedFields, isFieldsLoading, isLoading]);
|
|
1120
|
-
// Pre-transformované columnDefs podle savedFields pro waitForSavedFields mode
|
|
1121
|
-
// DŮLEŽITÉ: Toto useMemo nyní obsahuje i AgGridColumns transformaci pro eliminaci circular dependency
|
|
1122
735
|
const preTransformedColumnDefs = useMemo(() => {
|
|
1123
|
-
//
|
|
1124
|
-
|
|
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;
|
|
1125
747
|
let baseColumnDefs = columnDefsToUse;
|
|
1126
|
-
// Pro waitForSavedFields
|
|
1127
|
-
if (waitForSavedFields &&
|
|
1128
|
-
|
|
1129
|
-
Array.isArray(columnDefsToUse)) {
|
|
1130
|
-
// Transformujeme columnDefs podle savedFields PŘED zobrazením gridu
|
|
1131
|
-
const columnState = gridLayoutApi.transformFieldsToColumnState(savedFields.records, columnDefsToUse);
|
|
748
|
+
// Pro waitForSavedFields: seřadíme columnDefs podle frozenSavedRecords
|
|
749
|
+
if (waitForSavedFields && frozenSavedRecords && Array.isArray(columnDefsToUse)) {
|
|
750
|
+
const columnState = gridLayoutApi.transformFieldsToColumnState(frozenSavedRecords, columnDefsToUse);
|
|
1132
751
|
if (columnState && columnState.length > 0) {
|
|
1133
|
-
// Vytvoříme mapu pro rychlé vyhledávání
|
|
1134
752
|
const columnStateMap = new Map();
|
|
1135
753
|
columnState.forEach((colState, index) => {
|
|
1136
754
|
columnStateMap.set(colState.colId, { ...colState, __order: index });
|
|
1137
755
|
});
|
|
1138
|
-
// Vytvoříme mapu fieldName -> headerName z API dat
|
|
1139
756
|
const headerNameMap = new Map();
|
|
1140
|
-
|
|
757
|
+
frozenSavedRecords.forEach(field => {
|
|
1141
758
|
if (field.fieldName && field.headerName) {
|
|
1142
759
|
headerNameMap.set(field.fieldName, field.headerName);
|
|
1143
760
|
}
|
|
1144
761
|
});
|
|
1145
|
-
// Seřadíme columnDefs podle pořadí z columnState a upravíme headerName podle savedFields
|
|
1146
762
|
baseColumnDefs = [...columnDefsToUse]
|
|
1147
763
|
.sort((a, b) => {
|
|
1148
764
|
const fieldA = a.field || a.colId;
|
|
1149
765
|
const fieldB = b.field || b.colId;
|
|
1150
|
-
const
|
|
1151
|
-
const
|
|
1152
|
-
const aOrder = aState?.__order ?? 999;
|
|
1153
|
-
const bOrder = bState?.__order ?? 999;
|
|
766
|
+
const aOrder = columnStateMap.get(fieldA)?.__order ?? 999;
|
|
767
|
+
const bOrder = columnStateMap.get(fieldB)?.__order ?? 999;
|
|
1154
768
|
return aOrder - bOrder;
|
|
1155
769
|
})
|
|
1156
|
-
.map(
|
|
770
|
+
.map(colDef => {
|
|
1157
771
|
const fieldId = colDef.field || colDef.colId;
|
|
1158
772
|
const columnStateItem = columnStateMap.get(fieldId);
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
// Aplikujeme hide hodnotu z columnState (z API show hodnoty)
|
|
1166
|
-
...(columnStateItem && { hide: columnStateItem.hide }),
|
|
1167
|
-
// Aplikujeme také width pro konzistentní zobrazení
|
|
1168
|
-
...(columnStateItem && columnStateItem.width && { width: columnStateItem.width }),
|
|
1169
|
-
};
|
|
1170
|
-
}
|
|
1171
|
-
return colDef;
|
|
773
|
+
return {
|
|
774
|
+
...colDef,
|
|
775
|
+
...(headerNameMap.has(fieldId) && { headerName: headerNameMap.get(fieldId) }),
|
|
776
|
+
...(columnStateItem && { hide: columnStateItem.hide }),
|
|
777
|
+
...(columnStateItem?.width && { width: columnStateItem.width }),
|
|
778
|
+
};
|
|
1172
779
|
});
|
|
1173
780
|
}
|
|
1174
781
|
}
|
|
1175
|
-
//
|
|
1176
|
-
// Tím eliminujeme circular dependency pro všechny případy použití
|
|
782
|
+
// Konverze ColumnBuilder na array
|
|
1177
783
|
if (!Array.isArray(baseColumnDefs)) {
|
|
1178
|
-
// Pokud baseColumnDefs není array, mohlo by to být ColumnBuilder
|
|
1179
784
|
if (baseColumnDefs && typeof baseColumnDefs.build === 'function') {
|
|
1180
785
|
baseColumnDefs = baseColumnDefs.build();
|
|
1181
786
|
}
|
|
@@ -1183,62 +788,47 @@ onLayoutLoaded, onLayoutSaved, onError, }) => {
|
|
|
1183
788
|
return [];
|
|
1184
789
|
}
|
|
1185
790
|
}
|
|
1186
|
-
//
|
|
1187
|
-
// Při prvotním načítání zachováváme API šířky (které jsou už v baseColumnDefs pro waitForSavedFields)
|
|
791
|
+
// Aplikace ref šířek (POUZE po inicializaci)
|
|
1188
792
|
const finalColumnDefs = baseColumnDefs.map(colDef => {
|
|
1189
793
|
const fieldId = colDef.field || colDef.colId;
|
|
1190
794
|
const refWidth = columnWidthRefsMap.current.get(fieldId);
|
|
1191
|
-
// Použijeme ref šířku POUZE pokud už je grid inicializovaný (uživatel manipuloval s šířkami)
|
|
1192
795
|
if (refWidth !== undefined && isInitialized) {
|
|
1193
|
-
return {
|
|
1194
|
-
...colDef,
|
|
1195
|
-
width: refWidth
|
|
1196
|
-
};
|
|
796
|
+
return { ...colDef, width: refWidth };
|
|
1197
797
|
}
|
|
1198
798
|
return colDef;
|
|
1199
799
|
});
|
|
800
|
+
// ── Zamknout po inicializaci ──
|
|
801
|
+
if (isInitialized) {
|
|
802
|
+
lockedColumnDefsRef.current = finalColumnDefs;
|
|
803
|
+
lockedForColumnDefsRef.current = columnDefs;
|
|
804
|
+
lockedForFrozenRecordsRef.current = frozenSavedRecords;
|
|
805
|
+
}
|
|
1200
806
|
return finalColumnDefs;
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
// if (processedColumn.contentTooltip) {
|
|
1207
|
-
// // Statické mapování tooltipů pro lepší performance a eliminaci circular dependency
|
|
1208
|
-
// switch (processedColumn.contentTooltip) {
|
|
1209
|
-
// case 'User':
|
|
1210
|
-
// processedColumn.tooltipComponent = Tooltip.User;
|
|
1211
|
-
// break;
|
|
1212
|
-
// default:
|
|
1213
|
-
// console.warn(`[GridLayout] Unknown tooltip component: ${processedColumn.contentTooltip}`);
|
|
1214
|
-
// }
|
|
1215
|
-
// }
|
|
1216
|
-
// return processedColumn;
|
|
1217
|
-
// }) || [];
|
|
1218
|
-
// return processedColumns;
|
|
1219
|
-
}, [waitForSavedFields, savedFields?.records, columnDefs, gridLayoutApi, columnWidthsVersion, isInitialized]);
|
|
1220
|
-
// ✅ FIX #10: NEPOUŽÍVAT memoizaci - spoléháme se na refs v Grid komponentě
|
|
1221
|
-
// Memoizace s dependencies způsobuje rerender při změně JAKÉKOLIV hodnoty (např. hasUnsavedChanges)
|
|
1222
|
-
// Refs v Grid komponentě zajišťují, že i když se tento objekt změní, Grid nevidí změnu
|
|
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
|
+
// ==========================================================================
|
|
1223
812
|
return {
|
|
1224
|
-
// State
|
|
813
|
+
// State (re-render pouze při explicitních akcích)
|
|
1225
814
|
isLoading: isLoading || isFieldsLoading,
|
|
1226
|
-
isSaving,
|
|
1227
|
-
isApplyingLayout,
|
|
1228
815
|
error,
|
|
1229
|
-
hasUnsavedChanges,
|
|
1230
816
|
isInitialized,
|
|
1231
817
|
isGridReady,
|
|
1232
818
|
shouldShowColumns,
|
|
1233
819
|
preTransformedColumnDefs,
|
|
1234
|
-
//
|
|
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)
|
|
1235
825
|
onGridReady: handleGridReady,
|
|
1236
826
|
onColumnMoved: handleColumnMoved,
|
|
1237
827
|
onDragStopped: handleDragStopped,
|
|
1238
828
|
onColumnResized: handleColumnResized,
|
|
1239
829
|
onColumnVisible: handleColumnVisible,
|
|
1240
830
|
onColumnPinned: handleColumnPinned,
|
|
1241
|
-
//
|
|
831
|
+
// Manuální akce
|
|
1242
832
|
saveLayout,
|
|
1243
833
|
resetToDefault,
|
|
1244
834
|
reloadLayout,
|