@bit.rhplus/ag-grid 0.0.100 → 0.0.101
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/index.jsx +1120 -1120
- package/package.json +4 -4
- /package/dist/{preview-1770910641021.js → preview-1770997133325.js} +0 -0
package/index.jsx
CHANGED
|
@@ -1,1121 +1,1121 @@
|
|
|
1
|
-
/* eslint-disable */
|
|
2
|
-
import * as React from 'react';
|
|
3
|
-
import { AgGridReact } from 'ag-grid-react';
|
|
4
|
-
import { AgGridColumns } from './AgGridColumn';
|
|
5
|
-
import { RhPlusOnCellEditingStarted } from './OnCellEditingStarted';
|
|
6
|
-
import { RhPlusOnCellDoubleClicked } from './OnCellDoubleClicked';
|
|
7
|
-
import { RhPlusOnCellValueChanged } from './OnCellValueChanged';
|
|
8
|
-
import { AgGridPostSort } from './AgGridPostSort';
|
|
9
|
-
import { AgGridOnRowDataChanged } from './AgGridOnRowDataChanged';
|
|
10
|
-
import { AgGridOnRowDataUpdated } from './AgGridOnRowDataUpdated';
|
|
11
|
-
import CheckboxRenderer from './Renderers/CheckboxRenderer';
|
|
12
|
-
import BooleanRenderer from './Renderers/BooleanRenderer';
|
|
13
|
-
import { createGridComparison } from '@bit.rhplus/react-memo';
|
|
14
|
-
|
|
15
|
-
import IconRenderer from './Renderers/IconRenderer';
|
|
16
|
-
import ImageRenderer from './Renderers/ImageRenderer';
|
|
17
|
-
import StateRenderer from './Renderers/StateRenderer';
|
|
18
|
-
import SelectRenderer from './Renderers/SelectRenderer';
|
|
19
|
-
import ButtonRenderer from './Renderers/ButtonRenderer';
|
|
20
|
-
import CountrySelectRenderer from './Renderers/CountrySelectRenderer';
|
|
21
|
-
import ObjectRenderer from './Renderers/ObjectRenderer';
|
|
22
|
-
import LinkRenderer from './Renderers/LinkRenderer';
|
|
23
|
-
import NotificationOptionsInit from "./NotificationOptions";
|
|
24
|
-
import AggregationStatusBar from "./AggregationStatusBar";
|
|
25
|
-
import { notification, Button } from "antd";
|
|
26
|
-
import { CompressOutlined } from '@ant-design/icons';
|
|
27
|
-
import Aggregations, { hashRanges } from "./Aggregations";
|
|
28
|
-
import { useBulkCellEdit, BulkEditButton } from './BulkEdit';
|
|
29
|
-
import {
|
|
30
|
-
ModuleRegistry,
|
|
31
|
-
themeAlpine,
|
|
32
|
-
themeBalham,
|
|
33
|
-
themeMaterial,
|
|
34
|
-
themeQuartz,
|
|
35
|
-
ClientSideRowModelModule,
|
|
36
|
-
QuickFilterModule,
|
|
37
|
-
ValidationModule,
|
|
38
|
-
} from "ag-grid-community";
|
|
39
|
-
|
|
40
|
-
// Registrace AG-Grid modulů (nutné pro Quick Filter a další funkce)
|
|
41
|
-
ModuleRegistry.registerModules([
|
|
42
|
-
QuickFilterModule,
|
|
43
|
-
ClientSideRowModelModule,
|
|
44
|
-
...(process.env.NODE_ENV !== "production" ? [ValidationModule] : []),
|
|
45
|
-
]);
|
|
46
|
-
|
|
47
|
-
const themes = [
|
|
48
|
-
{ id: "themeQuartz", theme: themeQuartz },
|
|
49
|
-
{ id: "themeBalham", theme: themeBalham },
|
|
50
|
-
{ id: "themeMaterial", theme: themeMaterial },
|
|
51
|
-
{ id: "themeAlpine", theme: themeAlpine },
|
|
52
|
-
];
|
|
53
|
-
|
|
54
|
-
const AgGrid = React.forwardRef((props, ref) => {
|
|
55
|
-
const internalRef = React.useRef();
|
|
56
|
-
const {
|
|
57
|
-
theme = "themeAlpine", // Default theme
|
|
58
|
-
rowData = [],
|
|
59
|
-
newRowFlash = true,
|
|
60
|
-
updatedRowFlash = false,
|
|
61
|
-
onGridReady,
|
|
62
|
-
// Notification props
|
|
63
|
-
notificationMode = 'full', // 'full' | 'simple' | 'none'
|
|
64
|
-
notificationOptions: {
|
|
65
|
-
notificationHead = NotificationOptionsInit.head,
|
|
66
|
-
notificationBody = NotificationOptionsInit.body,
|
|
67
|
-
style = NotificationOptionsInit.style,
|
|
68
|
-
placement = NotificationOptionsInit.placement,
|
|
69
|
-
} = {},
|
|
70
|
-
// Status Bar props (pro simple mode)
|
|
71
|
-
statusBarMetrics = [
|
|
72
|
-
'count',
|
|
73
|
-
'sum',
|
|
74
|
-
'min',
|
|
75
|
-
'max',
|
|
76
|
-
'avg',
|
|
77
|
-
'median',
|
|
78
|
-
'range',
|
|
79
|
-
'geometry',
|
|
80
|
-
'dateRange'
|
|
81
|
-
],
|
|
82
|
-
statusBarHeight = 36,
|
|
83
|
-
// Bulk Edit props
|
|
84
|
-
enableBulkEdit = false,
|
|
85
|
-
bulkEditOptions = {},
|
|
86
|
-
bulkEditAccessToken,
|
|
87
|
-
onBulkEditStart,
|
|
88
|
-
onBulkEditComplete,
|
|
89
|
-
// SignalR transaction support
|
|
90
|
-
queryKey, // Identifikátor pro SignalR transactions
|
|
91
|
-
// Quick Filter
|
|
92
|
-
quickFilterText = "", // Text pro fulltextové vyhledávání
|
|
93
|
-
} = props;
|
|
94
|
-
|
|
95
|
-
// Najít theme objekt podle názvu z props
|
|
96
|
-
const themeObject = React.useMemo(() => {
|
|
97
|
-
const foundTheme = themes.find(t => t.id === theme);
|
|
98
|
-
return foundTheme ? foundTheme.theme : themeQuartz; // Fallback na themeQuartz
|
|
99
|
-
}, [theme]);
|
|
100
|
-
const [, setIsGridReady] = React.useState(false);
|
|
101
|
-
const isSelectingRef = React.useRef(false); // ✅ FIX: Změna ze state na ref pro eliminaci rerenderů
|
|
102
|
-
const aggregationDataRef = React.useRef(null); // // Pro simple mode status bar
|
|
103
|
-
const activeNotificationModeRef = React.useRef(notificationMode); // // Aktivní mód (může být dočasně přepsán uživatelem)
|
|
104
|
-
const previousRowDataRef = React.useRef(rowData);
|
|
105
|
-
|
|
106
|
-
// Synchronizovat activeNotificationModeRef s notificationMode prop
|
|
107
|
-
React.useEffect(() => {
|
|
108
|
-
activeNotificationModeRef.current = notificationMode;
|
|
109
|
-
}, [notificationMode]);
|
|
110
|
-
|
|
111
|
-
// Bulk Edit hook
|
|
112
|
-
const {
|
|
113
|
-
floatingButton,
|
|
114
|
-
editPopover,
|
|
115
|
-
handleRangeChange,
|
|
116
|
-
handleOpenPopover,
|
|
117
|
-
handleSubmitEdit,
|
|
118
|
-
handleCancelEdit,
|
|
119
|
-
handleValueChange,
|
|
120
|
-
} = useBulkCellEdit(internalRef, {
|
|
121
|
-
enabled: enableBulkEdit,
|
|
122
|
-
accessToken: bulkEditAccessToken,
|
|
123
|
-
onBulkEditStart,
|
|
124
|
-
onBulkEditComplete,
|
|
125
|
-
...bulkEditOptions,
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
// ========== PERFORMANCE OPTIMIZATION: Shallow comparison helper ==========
|
|
130
|
-
// Shallow porovnání objektů - 100x rychlejší než JSON.stringify
|
|
131
|
-
const shallowEqual = React.useCallback((obj1, obj2) => {
|
|
132
|
-
if (obj1 === obj2) return true;
|
|
133
|
-
if (!obj1 || !obj2) return false;
|
|
134
|
-
|
|
135
|
-
const keys1 = Object.keys(obj1);
|
|
136
|
-
const keys2 = Object.keys(obj2);
|
|
137
|
-
|
|
138
|
-
if (keys1.length !== keys2.length) return false;
|
|
139
|
-
|
|
140
|
-
for (let key of keys1) {
|
|
141
|
-
if (obj1[key] !== obj2[key]) return false;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
return true;
|
|
145
|
-
}, []);
|
|
146
|
-
|
|
147
|
-
// Memoizované funkce pro detekci změn v rowData
|
|
148
|
-
const findNewRows = React.useCallback((oldData, newData) => {
|
|
149
|
-
const oldIds = new Set(oldData.map((row) => row.id));
|
|
150
|
-
return newData.filter((row) => !oldIds.has(row.id));
|
|
151
|
-
}, []);
|
|
152
|
-
|
|
153
|
-
const findUpdatedRows = React.useCallback((oldData, newData) => {
|
|
154
|
-
const oldDataMap = new Map(oldData.map((row) => [row.id, row]));
|
|
155
|
-
return newData.filter((newRow) => {
|
|
156
|
-
const oldRow = oldDataMap.get(newRow.id);
|
|
157
|
-
if (!oldRow) return false; // Nový řádek, ne aktualizovaný
|
|
158
|
-
|
|
159
|
-
// ✅ OPTIMALIZACE: Shallow comparison místo JSON.stringify (100x rychlejší!)
|
|
160
|
-
return !shallowEqual(oldRow, newRow);
|
|
161
|
-
});
|
|
162
|
-
}, [shallowEqual]);
|
|
163
|
-
|
|
164
|
-
React.useImperativeHandle(ref, () => internalRef.current, [internalRef]);
|
|
165
|
-
|
|
166
|
-
// Throttle timer pro notifikace - zobrazuje se BĚHEM označování s 100ms throttle
|
|
167
|
-
const notificationThrottleRef = React.useRef(null);
|
|
168
|
-
const notificationLastCallRef = React.useRef(0);
|
|
169
|
-
const lastRangeHashRef = React.useRef(null);
|
|
170
|
-
|
|
171
|
-
// ✅ Callback pro přepnutí z full na simple mód
|
|
172
|
-
const handleSwitchToSimple = React.useCallback(() => {
|
|
173
|
-
const gridId = props.gridName || props.id || 'default';
|
|
174
|
-
const key = `aggregation-grid-${gridId}`;
|
|
175
|
-
|
|
176
|
-
// Zavřít full notifikaci
|
|
177
|
-
notification.destroy(key);
|
|
178
|
-
|
|
179
|
-
// Znovu spočítat a zobrazit simple status bar
|
|
180
|
-
if (internalRef?.current?.api) {
|
|
181
|
-
const ranges = internalRef.current.api.getCellRanges();
|
|
182
|
-
if (ranges && ranges.length > 0 && ranges[0]?.startRow) {
|
|
183
|
-
const messageInfo = Aggregations(internalRef);
|
|
184
|
-
if (messageInfo.count > 1) {
|
|
185
|
-
aggregationDataRef.current = messageInfo;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
activeNotificationModeRef.current = 'simple';
|
|
191
|
-
}, [props, internalRef]);
|
|
192
|
-
|
|
193
|
-
// ✅ Helper funkce pro vytvoření custom description s tlačítkem pro přepnutí na simple
|
|
194
|
-
const createNotificationDescription = React.useCallback((messageInfo, showSwitchButton = false) => {
|
|
195
|
-
const bodyContent = notificationBody(messageInfo);
|
|
196
|
-
|
|
197
|
-
if (!showSwitchButton) {
|
|
198
|
-
return bodyContent;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
return (
|
|
202
|
-
<div>
|
|
203
|
-
{bodyContent}
|
|
204
|
-
<div style={{ marginTop: '12px', borderTop: '1px solid #f0f0f0', paddingTop: '8px' }}>
|
|
205
|
-
<Button
|
|
206
|
-
type="link"
|
|
207
|
-
size="small"
|
|
208
|
-
icon={<CompressOutlined />}
|
|
209
|
-
onClick={handleSwitchToSimple}
|
|
210
|
-
style={{ padding: 0 }}
|
|
211
|
-
>
|
|
212
|
-
Zobrazit kompaktní režim
|
|
213
|
-
</Button>
|
|
214
|
-
</div>
|
|
215
|
-
</div>
|
|
216
|
-
);
|
|
217
|
-
}, [notificationBody, handleSwitchToSimple]);
|
|
218
|
-
|
|
219
|
-
// ✅ Callback pro přepnutí z simple na full mód
|
|
220
|
-
const handleSwitchToFull = React.useCallback(() => {
|
|
221
|
-
if (!aggregationDataRef.current) return;
|
|
222
|
-
|
|
223
|
-
const gridId = props.gridName || props.id || 'default';
|
|
224
|
-
const key = `aggregation-grid-${gridId}`;
|
|
225
|
-
|
|
226
|
-
// Zobrazit full notifikaci s aktuálními daty a tlačítkem pro přepnutí
|
|
227
|
-
notification.info({
|
|
228
|
-
key,
|
|
229
|
-
message: notificationHead(aggregationDataRef.current),
|
|
230
|
-
description: createNotificationDescription(aggregationDataRef.current, true),
|
|
231
|
-
duration: 0,
|
|
232
|
-
style,
|
|
233
|
-
placement,
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
// Skrýt simple status bar
|
|
237
|
-
aggregationDataRef.current = null;
|
|
238
|
-
activeNotificationModeRef.current = 'full';
|
|
239
|
-
}, [ props, notificationHead, createNotificationDescription, style, placement]);
|
|
240
|
-
|
|
241
|
-
// ========== PERFORMANCE FIX: Stabilní refs pro updateAggregationNotification ==========
|
|
242
|
-
// ✅ FIX: isSelectingRef je už deklarovaný na řádku 107 jako React.useRef(false)
|
|
243
|
-
const notificationHeadRef = React.useRef(notificationHead);
|
|
244
|
-
const createNotificationDescriptionRef = React.useRef(createNotificationDescription);
|
|
245
|
-
const styleRef = React.useRef(style);
|
|
246
|
-
const placementRef = React.useRef(placement);
|
|
247
|
-
const propsRef = React.useRef(props);
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
// ✅ FIX: Odstraněn useEffect pro isSelecting - isSelectingRef je nyní řízený přímo v updatePointerEventsForSelecting
|
|
251
|
-
|
|
252
|
-
React.useEffect(() => {
|
|
253
|
-
notificationHeadRef.current = notificationHead;
|
|
254
|
-
}, [notificationHead]);
|
|
255
|
-
|
|
256
|
-
React.useEffect(() => {
|
|
257
|
-
createNotificationDescriptionRef.current = createNotificationDescription;
|
|
258
|
-
}, [createNotificationDescription]);
|
|
259
|
-
|
|
260
|
-
React.useEffect(() => {
|
|
261
|
-
styleRef.current = style;
|
|
262
|
-
}, [style]);
|
|
263
|
-
|
|
264
|
-
React.useEffect(() => {
|
|
265
|
-
placementRef.current = placement;
|
|
266
|
-
}, [placement]);
|
|
267
|
-
|
|
268
|
-
React.useEffect(() => {
|
|
269
|
-
propsRef.current = props;
|
|
270
|
-
}, [props]);
|
|
271
|
-
|
|
272
|
-
// ✅ OPTIMALIZACE: Helper funkce pro aktualizaci aggregace notifikace s cache check
|
|
273
|
-
// Volá se BĚHEM označování s 100ms throttle pro real-time feedback
|
|
274
|
-
// STABILNÍ callback - používá pouze refs!
|
|
275
|
-
const updateAggregationNotification = React.useCallback((event) => {
|
|
276
|
-
// Pokud je notificationMode 'none', nedělat nic
|
|
277
|
-
if (notificationModeRef.current === 'none') {
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// Lightweight cache check PŘED voláním Aggregations()
|
|
282
|
-
const ranges = event.api.getCellRanges();
|
|
283
|
-
const currentRangeHash = hashRanges(ranges);
|
|
284
|
-
|
|
285
|
-
// Žádné ranges nebo jen jedna buňka - vyčistit vše
|
|
286
|
-
if (!ranges || ranges.length === 0 || !ranges[0]?.startRow) {
|
|
287
|
-
lastRangeHashRef.current = null;
|
|
288
|
-
|
|
289
|
-
// V 'full' módu zavřít notifikaci
|
|
290
|
-
if (activeNotificationModeRef.current === 'full') {
|
|
291
|
-
const gridId = propsRef.current.gridName || propsRef.current.id || 'default';
|
|
292
|
-
const key = `aggregation-grid-${gridId}`;
|
|
293
|
-
notification.destroy(key);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// V 'simple' módu vyčistit aggregationData
|
|
297
|
-
if (activeNotificationModeRef.current === 'simple') {
|
|
298
|
-
aggregationDataRef.current = null;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// Cache hit - ranges se nezměnily, skip
|
|
305
|
-
if (currentRangeHash === lastRangeHashRef.current) {
|
|
306
|
-
return;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// Cache miss - spočítat aggregace
|
|
310
|
-
const messageInfo = Aggregations(internalRef);
|
|
311
|
-
|
|
312
|
-
if (messageInfo.count > 1) {
|
|
313
|
-
// Uložit hash pro příští porovnání
|
|
314
|
-
lastRangeHashRef.current = currentRangeHash;
|
|
315
|
-
|
|
316
|
-
// Zavolat onAggregationChanged callback pokud existuje
|
|
317
|
-
if (propsRef.current.onAggregationChanged) {
|
|
318
|
-
propsRef.current.onAggregationChanged(messageInfo);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// Podle aktivního módu zobrazit buď notifikaci nebo aktualizovat status bar
|
|
322
|
-
if (activeNotificationModeRef.current === 'full') {
|
|
323
|
-
// FULL mód - zobrazit plovoucí notifikaci s tlačítkem pro přepnutí na simple
|
|
324
|
-
const gridId = propsRef.current.gridName || propsRef.current.id || 'default';
|
|
325
|
-
const key = `aggregation-grid-${gridId}`;
|
|
326
|
-
|
|
327
|
-
const dynamicStyle = {
|
|
328
|
-
...styleRef.current,
|
|
329
|
-
pointerEvents: isSelectingRef.current ? 'none' : 'auto'
|
|
330
|
-
};
|
|
331
|
-
|
|
332
|
-
notification.info({
|
|
333
|
-
key,
|
|
334
|
-
message: notificationHeadRef.current(messageInfo),
|
|
335
|
-
description: createNotificationDescriptionRef.current(messageInfo, true),
|
|
336
|
-
duration: 0,
|
|
337
|
-
style: dynamicStyle,
|
|
338
|
-
placement: placementRef.current,
|
|
339
|
-
});
|
|
340
|
-
} else if (activeNotificationModeRef.current === 'simple') {
|
|
341
|
-
// SIMPLE mód - aktualizovat state pro status bar
|
|
342
|
-
aggregationDataRef.current = messageInfo;
|
|
343
|
-
}
|
|
344
|
-
} else {
|
|
345
|
-
// Jen jedna buňka - zavřít/vyčistit vše
|
|
346
|
-
lastRangeHashRef.current = null;
|
|
347
|
-
|
|
348
|
-
if (activeNotificationModeRef.current === 'full') {
|
|
349
|
-
const gridId = propsRef.current.gridName || propsRef.current.id || 'default';
|
|
350
|
-
const key = `aggregation-grid-${gridId}`;
|
|
351
|
-
notification.destroy(key);
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
if (activeNotificationModeRef.current === 'simple') {
|
|
355
|
-
aggregationDataRef.current = null;
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
}, [internalRef]); // ✅ Pouze internalRef v dependencies!
|
|
359
|
-
|
|
360
|
-
// Helper funkce pro nastavení pointer-events na notifikace
|
|
361
|
-
const setNotificationPointerEvents = React.useCallback((enable) => {
|
|
362
|
-
const notifications = document.querySelectorAll('.ant-notification');
|
|
363
|
-
|
|
364
|
-
notifications.forEach((notif) => {
|
|
365
|
-
if (enable) {
|
|
366
|
-
// Obnovit pointer-events na notifikaci
|
|
367
|
-
const original = notif.dataset.originalPointerEvents || 'auto';
|
|
368
|
-
notif.style.pointerEvents = original;
|
|
369
|
-
notif.style.removeProperty('pointer-events');
|
|
370
|
-
delete notif.dataset.originalPointerEvents;
|
|
371
|
-
|
|
372
|
-
// Obnovit pointer-events na všechny child elementy
|
|
373
|
-
const allChildren = notif.querySelectorAll('*');
|
|
374
|
-
allChildren.forEach((child) => {
|
|
375
|
-
child.style.removeProperty('pointer-events');
|
|
376
|
-
delete child.dataset.originalPointerEvents;
|
|
377
|
-
});
|
|
378
|
-
} else {
|
|
379
|
-
// Zakázat pointer-events na notifikaci
|
|
380
|
-
if (!notif.dataset.originalPointerEvents) {
|
|
381
|
-
notif.dataset.originalPointerEvents = notif.style.pointerEvents || 'auto';
|
|
382
|
-
}
|
|
383
|
-
notif.style.setProperty('pointer-events', 'none', 'important');
|
|
384
|
-
|
|
385
|
-
// Zakázat pointer-events na všechny child elementy
|
|
386
|
-
const allChildren = notif.querySelectorAll('*');
|
|
387
|
-
allChildren.forEach((child) => {
|
|
388
|
-
if (!child.dataset.originalPointerEvents) {
|
|
389
|
-
child.dataset.originalPointerEvents = child.style.pointerEvents || '';
|
|
390
|
-
}
|
|
391
|
-
child.style.setProperty('pointer-events', 'none', 'important');
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
|
-
});
|
|
395
|
-
}, []);
|
|
396
|
-
|
|
397
|
-
// ✅ FIX: Helper funkce pro update isSelecting bez state změny (eliminuje rerender)
|
|
398
|
-
const updatePointerEventsForSelecting = React.useCallback((selecting) => {
|
|
399
|
-
isSelectingRef.current = selecting;
|
|
400
|
-
|
|
401
|
-
if (selecting) {
|
|
402
|
-
document.body.classList.add('ag-grid-selecting');
|
|
403
|
-
setNotificationPointerEvents(false);
|
|
404
|
-
|
|
405
|
-
// KRITICKÉ: Sledovat DOM změny - když se objeví nová notifikace během označování
|
|
406
|
-
const observer = new MutationObserver((mutations) => {
|
|
407
|
-
mutations.forEach((mutation) => {
|
|
408
|
-
mutation.addedNodes.forEach((node) => {
|
|
409
|
-
if (node.nodeType === 1) {
|
|
410
|
-
if (node.classList?.contains('ant-notification') ||
|
|
411
|
-
node.querySelector?.('.ant-notification')) {
|
|
412
|
-
setNotificationPointerEvents(false);
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
});
|
|
416
|
-
});
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
observer.observe(document.body, {
|
|
420
|
-
childList: true,
|
|
421
|
-
subtree: true
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
// Uložit observer do ref pro případný cleanup
|
|
425
|
-
if (!updatePointerEventsForSelecting.observer) {
|
|
426
|
-
updatePointerEventsForSelecting.observer = observer;
|
|
427
|
-
}
|
|
428
|
-
} else {
|
|
429
|
-
document.body.classList.remove('ag-grid-selecting');
|
|
430
|
-
|
|
431
|
-
// Cleanup observer
|
|
432
|
-
if (updatePointerEventsForSelecting.observer) {
|
|
433
|
-
updatePointerEventsForSelecting.observer.disconnect();
|
|
434
|
-
updatePointerEventsForSelecting.observer = null;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
setTimeout(() => setNotificationPointerEvents(true), 100);
|
|
438
|
-
}
|
|
439
|
-
}, [setNotificationPointerEvents]);
|
|
440
|
-
|
|
441
|
-
// Detekce konce označování pomocí mouseup event
|
|
442
|
-
React.useEffect(() => {
|
|
443
|
-
const handleMouseUp = () => {
|
|
444
|
-
// ✅ FIX: Ukončit isSelecting pomocí ref (bez state update → žádný rerender)
|
|
445
|
-
setTimeout(() => {
|
|
446
|
-
updatePointerEventsForSelecting(false);
|
|
447
|
-
|
|
448
|
-
// ✅ FIX: Zkontrolovat jestli jsou stále nějaké ranges, pokud ne - vyčistit status bar
|
|
449
|
-
if (internalRef?.current?.api && activeNotificationModeRef.current === 'simple') {
|
|
450
|
-
const ranges = internalRef.current.api.getCellRanges();
|
|
451
|
-
if (!ranges || ranges.length === 0 || !ranges[0]?.startRow) {
|
|
452
|
-
aggregationDataRef.current = null;
|
|
453
|
-
// Reset na původní notificationMode když jsou všechny buňky odznačeny
|
|
454
|
-
activeNotificationModeRef.current = notificationMode;
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
}, 50);
|
|
458
|
-
};
|
|
459
|
-
|
|
460
|
-
document.addEventListener('mouseup', handleMouseUp);
|
|
461
|
-
document.addEventListener('touchend', handleMouseUp); // Pro touch zařízení
|
|
462
|
-
|
|
463
|
-
return () => {
|
|
464
|
-
document.removeEventListener('mouseup', handleMouseUp);
|
|
465
|
-
document.removeEventListener('touchend', handleMouseUp);
|
|
466
|
-
};
|
|
467
|
-
}, [notificationMode, internalRef, updatePointerEventsForSelecting]);
|
|
468
|
-
|
|
469
|
-
// Cleanup notifikací a timerů při zničení komponenty
|
|
470
|
-
React.useEffect(() => {
|
|
471
|
-
return () => {
|
|
472
|
-
// Clear throttle timer
|
|
473
|
-
if (notificationThrottleRef.current) {
|
|
474
|
-
clearTimeout(notificationThrottleRef.current);
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
if (notificationMode === 'full') {
|
|
478
|
-
const gridId = props.gridName || props.id || 'default';
|
|
479
|
-
const key = `aggregation-grid-${gridId}`;
|
|
480
|
-
notification.destroy(key);
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
// Cleanup SignalR transaction callback při unmount
|
|
484
|
-
if (queryKey && window.agGridTransactionCallbacks) {
|
|
485
|
-
delete window.agGridTransactionCallbacks[queryKey];
|
|
486
|
-
}
|
|
487
|
-
};
|
|
488
|
-
}, [notificationMode, props.gridName, props.id, queryKey]);
|
|
489
|
-
|
|
490
|
-
React.useEffect(() => {
|
|
491
|
-
// VYPNUTO: Cell flash je globálně vypnutý
|
|
492
|
-
return;
|
|
493
|
-
|
|
494
|
-
if (!newRowFlash && !updatedRowFlash) return;
|
|
495
|
-
|
|
496
|
-
const previousRowData = previousRowDataRef.current;
|
|
497
|
-
const addedRows = newRowFlash ? findNewRows(previousRowData, rowData) : [];
|
|
498
|
-
const updatedRows = updatedRowFlash ? findUpdatedRows(previousRowData, rowData) : [];
|
|
499
|
-
|
|
500
|
-
if (addedRows.length > 0 || updatedRows.length > 0) {
|
|
501
|
-
setTimeout(() => {
|
|
502
|
-
try {
|
|
503
|
-
// Bezpečnostní kontrola API dostupnosti
|
|
504
|
-
if (!internalRef.current?.api || internalRef.current.api.isDestroyed?.()) {
|
|
505
|
-
return;
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
// Modern AG-Grid (33+): getColumnState() moved to main api
|
|
509
|
-
const columnApi = internalRef.current?.columnApi || internalRef.current?.api;
|
|
510
|
-
if (!columnApi?.getColumnState) {
|
|
511
|
-
return;
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
const columnStates = columnApi.getColumnState();
|
|
515
|
-
const allColumns = columnStates
|
|
516
|
-
.filter(colState => colState.colId) // Filtrujeme pouze platné colId
|
|
517
|
-
.map((colState) => colState.colId);
|
|
518
|
-
|
|
519
|
-
// Kontrola, že máme platné sloupce
|
|
520
|
-
if (allColumns.length === 0) {
|
|
521
|
-
return;
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
// Flash efekt pro nové řádky (používá defaultní zelená barva AG Grid)
|
|
525
|
-
if (addedRows.length > 0) {
|
|
526
|
-
const newRowNodes = [];
|
|
527
|
-
internalRef.current.api.forEachNode((node) => {
|
|
528
|
-
if (addedRows.some((row) => row.id === node.data.id)) {
|
|
529
|
-
newRowNodes.push(node);
|
|
530
|
-
}
|
|
531
|
-
});
|
|
532
|
-
|
|
533
|
-
if (newRowNodes.length > 0) {
|
|
534
|
-
internalRef.current.api.flashCells({
|
|
535
|
-
rowNodes: newRowNodes,
|
|
536
|
-
columns: allColumns,
|
|
537
|
-
flashDelay: 0,
|
|
538
|
-
fadeDelay: 1000,
|
|
539
|
-
});
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
// Flash efekt pro aktualizované řádky (modrá barva)
|
|
544
|
-
if (updatedRows.length > 0) {
|
|
545
|
-
const updatedRowNodes = [];
|
|
546
|
-
internalRef.current.api.forEachNode((node) => {
|
|
547
|
-
if (updatedRows.some((row) => row.id === node.data.id)) {
|
|
548
|
-
updatedRowNodes.push(node);
|
|
549
|
-
}
|
|
550
|
-
});
|
|
551
|
-
|
|
552
|
-
if (updatedRowNodes.length > 0) {
|
|
553
|
-
// Použijeme vlastní CSS animaci pro modrou flash
|
|
554
|
-
updatedRowNodes.forEach(node => {
|
|
555
|
-
const rowElement = node.eGridRow || document.querySelector(`[row-id="${node.data.id}"]`);
|
|
556
|
-
if (rowElement) {
|
|
557
|
-
rowElement.classList.add('ag-row-flash-updated');
|
|
558
|
-
setTimeout(() => {
|
|
559
|
-
rowElement.classList.remove('ag-row-flash-updated');
|
|
560
|
-
}, 1000);
|
|
561
|
-
}
|
|
562
|
-
});
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
} catch (error) {
|
|
566
|
-
// Ignorujeme chybu a pokračujeme bez crash aplikace
|
|
567
|
-
}
|
|
568
|
-
}, 100); // Zvýšený timeout pro stabilizaci gridu
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
previousRowDataRef.current = rowData;
|
|
572
|
-
}, [rowData, newRowFlash, updatedRowFlash]);
|
|
573
|
-
|
|
574
|
-
// ========== PERFORMANCE FIX: Stabilní ref pattern pro callbacks ==========
|
|
575
|
-
// Refs pro aktuální hodnoty - zabraňuje re-creation RhPlusRangeSelectionChanged při změně stavu
|
|
576
|
-
const notificationModeRef = React.useRef(notificationMode);
|
|
577
|
-
const enableBulkEditRef = React.useRef(enableBulkEdit);
|
|
578
|
-
const handleRangeChangeRef = React.useRef(handleRangeChange);
|
|
579
|
-
const onRangeSelectionChangedRef = React.useRef(props.onRangeSelectionChanged);
|
|
580
|
-
const updateAggregationNotificationRef = React.useRef(updateAggregationNotification);
|
|
581
|
-
|
|
582
|
-
// ✅ FIX #12: Refs pro grid layout handlery (eliminuje rerendery při změně props)
|
|
583
|
-
const onColumnMovedRef = React.useRef(props.onColumnMoved);
|
|
584
|
-
const onDragStoppedRef = React.useRef(props.onDragStopped);
|
|
585
|
-
const onColumnVisibleRef = React.useRef(props.onColumnVisible);
|
|
586
|
-
const onColumnPinnedRef = React.useRef(props.onColumnPinned);
|
|
587
|
-
const onColumnResizedRef = React.useRef(props.onColumnResized);
|
|
588
|
-
|
|
589
|
-
// Aktualizovat refs při změně hodnot
|
|
590
|
-
React.useEffect(() => {
|
|
591
|
-
notificationModeRef.current = notificationMode;
|
|
592
|
-
}, [notificationMode]);
|
|
593
|
-
|
|
594
|
-
React.useEffect(() => {
|
|
595
|
-
enableBulkEditRef.current = enableBulkEdit;
|
|
596
|
-
}, [enableBulkEdit]);
|
|
597
|
-
|
|
598
|
-
React.useEffect(() => {
|
|
599
|
-
handleRangeChangeRef.current = handleRangeChange;
|
|
600
|
-
}, [handleRangeChange]);
|
|
601
|
-
|
|
602
|
-
React.useEffect(() => {
|
|
603
|
-
onRangeSelectionChangedRef.current = props.onRangeSelectionChanged;
|
|
604
|
-
}, [props.onRangeSelectionChanged]);
|
|
605
|
-
|
|
606
|
-
React.useEffect(() => {
|
|
607
|
-
updateAggregationNotificationRef.current = updateAggregationNotification;
|
|
608
|
-
}, [updateAggregationNotification]);
|
|
609
|
-
|
|
610
|
-
// ✅ FIX #12: Aktualizovat grid layout handler refs
|
|
611
|
-
React.useEffect(() => {
|
|
612
|
-
onColumnMovedRef.current = props.onColumnMoved;
|
|
613
|
-
}, [props.onColumnMoved]);
|
|
614
|
-
|
|
615
|
-
React.useEffect(() => {
|
|
616
|
-
onDragStoppedRef.current = props.onDragStopped;
|
|
617
|
-
}, [props.onDragStopped]);
|
|
618
|
-
|
|
619
|
-
React.useEffect(() => {
|
|
620
|
-
onColumnVisibleRef.current = props.onColumnVisible;
|
|
621
|
-
}, [props.onColumnVisible]);
|
|
622
|
-
|
|
623
|
-
React.useEffect(() => {
|
|
624
|
-
onColumnPinnedRef.current = props.onColumnPinned;
|
|
625
|
-
}, [props.onColumnPinned]);
|
|
626
|
-
|
|
627
|
-
React.useEffect(() => {
|
|
628
|
-
onColumnResizedRef.current = props.onColumnResized;
|
|
629
|
-
}, [props.onColumnResized]);
|
|
630
|
-
|
|
631
|
-
// Stabilní callback s prázdnými dependencies - používá pouze refs
|
|
632
|
-
const RhPlusRangeSelectionChanged = React.useCallback(
|
|
633
|
-
(event) => {
|
|
634
|
-
// ✅ FIX: Detekovat začátek označování pomocí ref (bez state update → žádný rerender)
|
|
635
|
-
updatePointerEventsForSelecting(true);
|
|
636
|
-
|
|
637
|
-
// 1. ✅ OPTIMALIZACE: Zobrazení notifikace BĚHEM označování s THROTTLE
|
|
638
|
-
// Simple mode: 300ms throttle (méně častá aktualizace, lepší výkon)
|
|
639
|
-
// Full mode: 100ms throttle (rychlejší feedback, plovoucí notifikace je levná)
|
|
640
|
-
if (notificationModeRef.current !== 'none') {
|
|
641
|
-
const throttleInterval = notificationModeRef.current === 'simple' ? 300 : 100;
|
|
642
|
-
const now = Date.now();
|
|
643
|
-
const timeSinceLastCall = now - notificationLastCallRef.current;
|
|
644
|
-
|
|
645
|
-
// První volání NEBO uplynul throttle interval
|
|
646
|
-
if (timeSinceLastCall >= throttleInterval) {
|
|
647
|
-
// Okamžité volání
|
|
648
|
-
notificationLastCallRef.current = now;
|
|
649
|
-
if (internalRef?.current) {
|
|
650
|
-
updateAggregationNotificationRef.current(event);
|
|
651
|
-
}
|
|
652
|
-
} else {
|
|
653
|
-
// Naplánovat volání za zbývající čas (trailing edge)
|
|
654
|
-
if (notificationThrottleRef.current) {
|
|
655
|
-
clearTimeout(notificationThrottleRef.current);
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
notificationThrottleRef.current = setTimeout(() => {
|
|
659
|
-
notificationLastCallRef.current = Date.now();
|
|
660
|
-
if (internalRef?.current) {
|
|
661
|
-
updateAggregationNotificationRef.current(event);
|
|
662
|
-
}
|
|
663
|
-
}, throttleInterval - timeSinceLastCall);
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
// 2. ✅ OPTIMALIZACE: Bulk edit handler BEZ debounce - okamžité zobrazení ikony
|
|
668
|
-
// Lightweight validace v useBulkCellEdit zajistí okamžité zobrazení
|
|
669
|
-
// Těžká validace proběhne s 15ms debounce uvnitř useBulkCellEdit
|
|
670
|
-
if (enableBulkEditRef.current) {
|
|
671
|
-
handleRangeChangeRef.current(event);
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
// 3. Custom onRangeSelectionChanged callback - bez debounce
|
|
675
|
-
if (onRangeSelectionChangedRef.current) {
|
|
676
|
-
onRangeSelectionChangedRef.current(event);
|
|
677
|
-
}
|
|
678
|
-
},
|
|
679
|
-
[] // ✅ PRÁZDNÉ dependencies - stabilní reference!
|
|
680
|
-
);
|
|
681
|
-
|
|
682
|
-
// ✅ FIX #12: Stabilní wrappery pro grid layout handlery (prázdné dependencies)
|
|
683
|
-
const stableOnColumnMoved = React.useCallback(
|
|
684
|
-
(params) => {
|
|
685
|
-
if (onColumnMovedRef.current) {
|
|
686
|
-
onColumnMovedRef.current(params);
|
|
687
|
-
}
|
|
688
|
-
},
|
|
689
|
-
[]
|
|
690
|
-
);
|
|
691
|
-
|
|
692
|
-
const stableOnDragStopped = React.useCallback(
|
|
693
|
-
(params) => {
|
|
694
|
-
if (onDragStoppedRef.current) {
|
|
695
|
-
onDragStoppedRef.current(params);
|
|
696
|
-
}
|
|
697
|
-
},
|
|
698
|
-
[]
|
|
699
|
-
);
|
|
700
|
-
|
|
701
|
-
const stableOnColumnVisible = React.useCallback(
|
|
702
|
-
(params) => {
|
|
703
|
-
if (onColumnVisibleRef.current) {
|
|
704
|
-
onColumnVisibleRef.current(params);
|
|
705
|
-
}
|
|
706
|
-
},
|
|
707
|
-
[]
|
|
708
|
-
);
|
|
709
|
-
|
|
710
|
-
const stableOnColumnPinned = React.useCallback(
|
|
711
|
-
(params) => {
|
|
712
|
-
if (onColumnPinnedRef.current) {
|
|
713
|
-
onColumnPinnedRef.current(params);
|
|
714
|
-
}
|
|
715
|
-
},
|
|
716
|
-
[]
|
|
717
|
-
);
|
|
718
|
-
|
|
719
|
-
const stableOnColumnResized = React.useCallback(
|
|
720
|
-
(params) => {
|
|
721
|
-
if (onColumnResizedRef.current) {
|
|
722
|
-
onColumnResizedRef.current(params);
|
|
723
|
-
}
|
|
724
|
-
},
|
|
725
|
-
[]
|
|
726
|
-
);
|
|
727
|
-
|
|
728
|
-
const AgGridOnGridReady = (event, options) => {
|
|
729
|
-
if (onGridReady) {
|
|
730
|
-
onGridReady(event, options);
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
// Nejprve nastavíme API reference pro interní použití
|
|
734
|
-
if (internalRef.current) {
|
|
735
|
-
internalRef.current.api = event.api;
|
|
736
|
-
internalRef.current.columnApi = event.columnApi || event.api; // fallback for modern AG-Grid
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
// Nastavíme API ready flag pro quick filter useEffect
|
|
740
|
-
setIsApiReady(true);
|
|
741
|
-
|
|
742
|
-
// Registruj callback pro AG Grid transactions (SignalR optimalizace)
|
|
743
|
-
// Inicializace globálního registru pro více AG-Grid instancí
|
|
744
|
-
if (!window.agGridTransactionCallbacks) {
|
|
745
|
-
window.agGridTransactionCallbacks = {};
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
// Registrace callbacku pro tuto konkrétní AG-Grid instanci (podle queryKey)
|
|
749
|
-
if (event.api && queryKey) {
|
|
750
|
-
window.agGridTransactionCallbacks[queryKey] = (transactionData) => {
|
|
751
|
-
try {
|
|
752
|
-
if (!event.api || event.api.isDestroyed?.()) return;
|
|
753
|
-
|
|
754
|
-
const { operation, records } = transactionData;
|
|
755
|
-
|
|
756
|
-
switch (operation) {
|
|
757
|
-
case 'add':
|
|
758
|
-
// Nastavíme flag _rh_plus_ag_grid_signal_new (NE _rh_plus_ag_grid_new_item),
|
|
759
|
-
// aby postSort přesunul řádek na začátek, ale renderery obsah nezahovaly
|
|
760
|
-
const addRecords = records.map(r => ({ ...r, _rh_plus_ag_grid_signal_new: true }));
|
|
761
|
-
event.api.applyTransaction({ add: addRecords, addIndex: 0 });
|
|
762
|
-
// Flash efekt pro nové řádky + po 5s odebrat signal flag a re-sort
|
|
763
|
-
setTimeout(() => {
|
|
764
|
-
records.forEach(record => {
|
|
765
|
-
const rowNode = event.api.getRowNode(record.id);
|
|
766
|
-
if (rowNode) {
|
|
767
|
-
if (rowNode.rowElement) {
|
|
768
|
-
rowNode.rowElement.classList.add('ag-row-flash-created');
|
|
769
|
-
setTimeout(() => {
|
|
770
|
-
rowNode.rowElement.classList.remove('ag-row-flash-created');
|
|
771
|
-
}, 1000);
|
|
772
|
-
}
|
|
773
|
-
// Po 5 sekundách odebrat signal flag a refreshnout sort
|
|
774
|
-
setTimeout(() => {
|
|
775
|
-
if (rowNode.data) {
|
|
776
|
-
delete rowNode.data._rh_plus_ag_grid_signal_new;
|
|
777
|
-
// Refresh sort, aby se řádek zařadil podle aktuálního řazení
|
|
778
|
-
if (event.api && !event.api.isDestroyed?.()) {
|
|
779
|
-
event.api.refreshClientSideRowModel('sort');
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
}, 5000);
|
|
783
|
-
}
|
|
784
|
-
});
|
|
785
|
-
}, 100);
|
|
786
|
-
break;
|
|
787
|
-
|
|
788
|
-
case 'update':
|
|
789
|
-
event.api.applyTransaction({ update: records });
|
|
790
|
-
// Flash efekt pro aktualizované řádky
|
|
791
|
-
setTimeout(() => {
|
|
792
|
-
records.forEach(record => {
|
|
793
|
-
const rowNode = event.api.getRowNode(record.id);
|
|
794
|
-
if (rowNode && rowNode.rowElement) {
|
|
795
|
-
rowNode.rowElement.classList.add('ag-row-flash-updated');
|
|
796
|
-
setTimeout(() => {
|
|
797
|
-
rowNode.rowElement.classList.remove('ag-row-flash-updated');
|
|
798
|
-
}, 1000);
|
|
799
|
-
}
|
|
800
|
-
});
|
|
801
|
-
}, 100);
|
|
802
|
-
break;
|
|
803
|
-
|
|
804
|
-
case 'remove':
|
|
805
|
-
// Flash efekt před smazáním
|
|
806
|
-
records.forEach(record => {
|
|
807
|
-
const rowNode = event.api.getRowNode(record.id);
|
|
808
|
-
if (rowNode && rowNode.rowElement) {
|
|
809
|
-
rowNode.rowElement.classList.add('ag-row-flash-deleted');
|
|
810
|
-
}
|
|
811
|
-
});
|
|
812
|
-
|
|
813
|
-
setTimeout(() => {
|
|
814
|
-
event.api.applyTransaction({ remove: records });
|
|
815
|
-
}, 500); // Krátké zpoždění pro zobrazení flash efektu
|
|
816
|
-
break;
|
|
817
|
-
}
|
|
818
|
-
} catch (error) {
|
|
819
|
-
// Ignorujeme chyby
|
|
820
|
-
}
|
|
821
|
-
};
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
// Pak zavoláme parent onGridReady handler, pokud existuje
|
|
825
|
-
// Toto je kritické pro správné fungování bit/ui/grid a GridLayout
|
|
826
|
-
if (options.onGridReady) {
|
|
827
|
-
try {
|
|
828
|
-
options.onGridReady(event);
|
|
829
|
-
} catch (error) {
|
|
830
|
-
// Error handling without console output
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
// Nakonec nastavíme grid ready state s timeout
|
|
835
|
-
setTimeout(() => {
|
|
836
|
-
setIsGridReady(true);
|
|
837
|
-
}, 1000);
|
|
838
|
-
};
|
|
839
|
-
|
|
840
|
-
const components = React.useMemo(() => {
|
|
841
|
-
return {
|
|
842
|
-
checkboxRenderer: CheckboxRenderer,
|
|
843
|
-
selectRenderer: SelectRenderer,
|
|
844
|
-
countrySelectRenderer: CountrySelectRenderer,
|
|
845
|
-
booleanRenderer: BooleanRenderer,
|
|
846
|
-
buttonRenderer: ButtonRenderer,
|
|
847
|
-
iconRenderer: IconRenderer,
|
|
848
|
-
imageRenderer: ImageRenderer,
|
|
849
|
-
stateRenderer: StateRenderer,
|
|
850
|
-
objectRenderer: ObjectRenderer,
|
|
851
|
-
linkRenderer: LinkRenderer,
|
|
852
|
-
...props.frameworkComponents,
|
|
853
|
-
};
|
|
854
|
-
}, [props.frameworkComponents]);
|
|
855
|
-
|
|
856
|
-
// ========== PERFORMANCE OPTIMIZATION: Memoizované event handlers ==========
|
|
857
|
-
const memoizedOnCellEditingStarted = React.useCallback(
|
|
858
|
-
(event) => RhPlusOnCellEditingStarted(event, props),
|
|
859
|
-
[props.onCellEditingStarted]
|
|
860
|
-
);
|
|
861
|
-
|
|
862
|
-
const memoizedOnCellDoubleClicked = React.useCallback(
|
|
863
|
-
(event) => RhPlusOnCellDoubleClicked(event, props),
|
|
864
|
-
[props.onCellDoubleClicked]
|
|
865
|
-
);
|
|
866
|
-
|
|
867
|
-
const memoizedOnCellValueChanged = React.useCallback(
|
|
868
|
-
(event) => RhPlusOnCellValueChanged(event, props),
|
|
869
|
-
[props.onCellValueChanged]
|
|
870
|
-
);
|
|
871
|
-
|
|
872
|
-
const memoizedPostSort = React.useCallback(
|
|
873
|
-
(event) => AgGridPostSort(event, props),
|
|
874
|
-
[props.postSort]
|
|
875
|
-
);
|
|
876
|
-
|
|
877
|
-
const memoizedOnGridReady = React.useCallback(
|
|
878
|
-
(event, options) => AgGridOnGridReady(event, props),
|
|
879
|
-
[onGridReady]
|
|
880
|
-
);
|
|
881
|
-
|
|
882
|
-
const memoizedOnRowDataChanged = React.useCallback(
|
|
883
|
-
(event) => AgGridOnRowDataChanged(event, props),
|
|
884
|
-
[props.onRowDataChanged]
|
|
885
|
-
);
|
|
886
|
-
|
|
887
|
-
const memoizedOnRowDataUpdated = React.useCallback(
|
|
888
|
-
(event) => AgGridOnRowDataUpdated(event, props),
|
|
889
|
-
[props.onRowDataUpdated]
|
|
890
|
-
);
|
|
891
|
-
|
|
892
|
-
// Memoizovaný context object
|
|
893
|
-
// ✅ OPTIMALIZACE: Použít pouze relevantní props pro context - neměnit při změně props
|
|
894
|
-
const memoizedContext = React.useMemo(() => ({
|
|
895
|
-
componentParent: props
|
|
896
|
-
}), [props.context, props.gridName, props.id]);
|
|
897
|
-
|
|
898
|
-
// Memoizovaný defaultColDef
|
|
899
|
-
const memoizedDefaultColDef = React.useMemo(() => ({
|
|
900
|
-
filter: 'agTextColumnFilter',
|
|
901
|
-
floatingFilter: true,
|
|
902
|
-
filterParams: {
|
|
903
|
-
defaultOption: 'contains',
|
|
904
|
-
},
|
|
905
|
-
...props.defaultColDef,
|
|
906
|
-
suppressHeaderMenuButton: true,
|
|
907
|
-
suppressHeaderFilterButton: true,
|
|
908
|
-
suppressMenu: true,
|
|
909
|
-
// ✅ FIX AG-Grid v35: Bezpečné zpracování null hodnot v Quick Filter
|
|
910
|
-
// AG-Grid v35 změnil implementaci Quick Filter - nyní volá .toString() na hodnotách buněk bez null check
|
|
911
|
-
// Tento callback zajistí že nikdy nedojde k chybě "Cannot read properties of null (reading 'toString')"
|
|
912
|
-
getQuickFilterText: (params) => {
|
|
913
|
-
const value = params.value;
|
|
914
|
-
// Null/undefined → prázdný string (bez chyby)
|
|
915
|
-
if (value == null) return '';
|
|
916
|
-
// Objekty a pole → JSON string
|
|
917
|
-
if (typeof value === 'object') {
|
|
918
|
-
try {
|
|
919
|
-
return JSON.stringify(value);
|
|
920
|
-
} catch {
|
|
921
|
-
// Cirkulární reference nebo jiná chyba při serializaci → prázdný string
|
|
922
|
-
return '';
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
// Primitivní typy → toString
|
|
926
|
-
return value.toString();
|
|
927
|
-
},
|
|
928
|
-
}), [props.defaultColDef]);
|
|
929
|
-
|
|
930
|
-
// ========== PERFORMANCE FIX: Memoizovat columnDefs ==========
|
|
931
|
-
// AgGridColumns vrací nový array při každém volání, i když jsou vstupy stejné
|
|
932
|
-
const memoizedColumnDefs = React.useMemo(() => {
|
|
933
|
-
return AgGridColumns(props.columnDefs, props);
|
|
934
|
-
}, [props.columnDefs, props.getRowId, props.frameworkComponents]);
|
|
935
|
-
|
|
936
|
-
// ========== CRITICAL FIX: Stabilní allGridProps objekt pomocí ref ==========
|
|
937
|
-
// Problém: Jakékoli použití useMemo vytváří nový objekt při změně dependencies
|
|
938
|
-
// Řešení: Použít ref pro VŽDY stejný objekt
|
|
939
|
-
// AG-Grid САМО detekuje změny rowData a columnDefs → NENÍ potřeba forceUpdate!
|
|
940
|
-
|
|
941
|
-
const allGridPropsRef = React.useRef(null);
|
|
942
|
-
|
|
943
|
-
// Inicializace objektu
|
|
944
|
-
if (!allGridPropsRef.current) {
|
|
945
|
-
allGridPropsRef.current = {};
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
// ✅ Aktualizovat props v EXISTUJÍCÍM objektu (mutace)
|
|
949
|
-
// AG Grid interně detekuje změny props, nepotřebuje nový objekt
|
|
950
|
-
allGridPropsRef.current.ref = internalRef;
|
|
951
|
-
allGridPropsRef.current.rowData = props.rowData;
|
|
952
|
-
allGridPropsRef.current.getRowId = props.getRowId;
|
|
953
|
-
allGridPropsRef.current.theme = themeObject;
|
|
954
|
-
allGridPropsRef.current.columnDefs = memoizedColumnDefs;
|
|
955
|
-
allGridPropsRef.current.defaultColDef = memoizedDefaultColDef;
|
|
956
|
-
allGridPropsRef.current.onCellEditingStarted = memoizedOnCellEditingStarted;
|
|
957
|
-
allGridPropsRef.current.onCellDoubleClicked = memoizedOnCellDoubleClicked;
|
|
958
|
-
allGridPropsRef.current.onCellValueChanged = memoizedOnCellValueChanged;
|
|
959
|
-
allGridPropsRef.current.postSort = memoizedPostSort;
|
|
960
|
-
allGridPropsRef.current.onGridReady = memoizedOnGridReady;
|
|
961
|
-
allGridPropsRef.current.onRowDataChanged = memoizedOnRowDataChanged;
|
|
962
|
-
allGridPropsRef.current.onRowDataUpdated = memoizedOnRowDataUpdated;
|
|
963
|
-
allGridPropsRef.current.onRangeSelectionChanged = RhPlusRangeSelectionChanged;
|
|
964
|
-
allGridPropsRef.current.context = memoizedContext;
|
|
965
|
-
allGridPropsRef.current.components = components;
|
|
966
|
-
|
|
967
|
-
// Další AG Grid props
|
|
968
|
-
allGridPropsRef.current.rowModelType = props.rowModelType;
|
|
969
|
-
allGridPropsRef.current.rowSelection = props.rowSelection;
|
|
970
|
-
allGridPropsRef.current.enableRangeSelection = props.enableRangeSelection;
|
|
971
|
-
allGridPropsRef.current.enableRangeHandle = props.enableRangeHandle;
|
|
972
|
-
allGridPropsRef.current.enableFillHandle = props.enableFillHandle;
|
|
973
|
-
allGridPropsRef.current.suppressRowClickSelection = props.suppressRowClickSelection;
|
|
974
|
-
allGridPropsRef.current.singleClickEdit = props.singleClickEdit;
|
|
975
|
-
allGridPropsRef.current.stopEditingWhenCellsLoseFocus = props.stopEditingWhenCellsLoseFocus;
|
|
976
|
-
allGridPropsRef.current.rowClass = props.rowClass;
|
|
977
|
-
allGridPropsRef.current.rowStyle = props.rowStyle;
|
|
978
|
-
allGridPropsRef.current.getRowClass = props.getRowClass;
|
|
979
|
-
allGridPropsRef.current.getRowStyle = props.getRowStyle;
|
|
980
|
-
allGridPropsRef.current.animateRows = props.animateRows;
|
|
981
|
-
allGridPropsRef.current.suppressCellFocus = props.suppressCellFocus;
|
|
982
|
-
allGridPropsRef.current.suppressMenuHide = props.suppressMenuHide;
|
|
983
|
-
allGridPropsRef.current.enableCellTextSelection = props.enableCellTextSelection;
|
|
984
|
-
allGridPropsRef.current.ensureDomOrder = props.ensureDomOrder;
|
|
985
|
-
allGridPropsRef.current.suppressRowTransform = props.suppressRowTransform;
|
|
986
|
-
allGridPropsRef.current.suppressColumnVirtualisation = props.suppressColumnVirtualisation;
|
|
987
|
-
allGridPropsRef.current.suppressRowVirtualisation = props.suppressRowVirtualisation;
|
|
988
|
-
allGridPropsRef.current.tooltipShowDelay = props.tooltipShowDelay;
|
|
989
|
-
allGridPropsRef.current.tooltipHideDelay = props.tooltipHideDelay;
|
|
990
|
-
allGridPropsRef.current.tooltipMouseTrack = props.tooltipMouseTrack;
|
|
991
|
-
allGridPropsRef.current.gridId = props.gridId;
|
|
992
|
-
allGridPropsRef.current.id = props.id;
|
|
993
|
-
allGridPropsRef.current.gridName = props.gridName;
|
|
994
|
-
allGridPropsRef.current.getContextMenuItems = props.getContextMenuItems;
|
|
995
|
-
|
|
996
|
-
// Grid Layout event handlers - stabilní wrappery (eliminuje rerendery)
|
|
997
|
-
// ✅ FIX #12: Používáme stabilní wrappery místo props → eliminuje rerendery při změně props
|
|
998
|
-
allGridPropsRef.current.onColumnMoved = stableOnColumnMoved;
|
|
999
|
-
allGridPropsRef.current.onDragStopped = stableOnDragStopped;
|
|
1000
|
-
allGridPropsRef.current.onColumnVisible = stableOnColumnVisible;
|
|
1001
|
-
allGridPropsRef.current.onColumnPinned = stableOnColumnPinned;
|
|
1002
|
-
allGridPropsRef.current.onColumnResized = stableOnColumnResized;
|
|
1003
|
-
|
|
1004
|
-
// ✅ gridOptions support - spread additional AG-Grid props
|
|
1005
|
-
// Přidává POUZE hodnoty které ještě NEJSOU nastaveny výše (nejnižší priorita)
|
|
1006
|
-
if (props.gridOptions && typeof props.gridOptions === 'object') {
|
|
1007
|
-
for (const key in props.gridOptions) {
|
|
1008
|
-
if (props.gridOptions[key] !== undefined && allGridPropsRef.current[key] === undefined) {
|
|
1009
|
-
allGridPropsRef.current[key] = props.gridOptions[key];
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
|
-
// ✅ AG-Grid САМО detekuje změny rowData a columnDefs
|
|
1015
|
-
// NENÍ potřeba forceUpdate - AG-Grid reaguje na změny v props automaticky!
|
|
1016
|
-
|
|
1017
|
-
// ✅ Vracíme VŽDY stejný objekt (stabilní reference)
|
|
1018
|
-
const allGridProps = allGridPropsRef.current;
|
|
1019
|
-
|
|
1020
|
-
// State pro sledování, kdy je API ready
|
|
1021
|
-
const [isApiReady, setIsApiReady] = React.useState(false);
|
|
1022
|
-
|
|
1023
|
-
// Nastavení Quick Filter přes API (podle AG-Grid v33 dokumentace)
|
|
1024
|
-
// Tento useEffect se volá při změně quickFilterText NEBO když API je ready
|
|
1025
|
-
React.useEffect(() => {
|
|
1026
|
-
if (internalRef.current?.api && !internalRef.current.api.isDestroyed?.()) {
|
|
1027
|
-
// ✅ FIX AG-Grid v35: Zajistit že quickFilterText není null/undefined
|
|
1028
|
-
// AG-Grid v35 interně volá .toString() na této hodnotě bez null checku
|
|
1029
|
-
const safeQuickFilterText = quickFilterText ?? '';
|
|
1030
|
-
internalRef.current.api.setGridOption("quickFilterText", safeQuickFilterText);
|
|
1031
|
-
}
|
|
1032
|
-
}, [quickFilterText, isApiReady]);
|
|
1033
|
-
|
|
1034
|
-
// Status bar se zobrazuje pouze v simple mode
|
|
1035
|
-
const showStatusBar = notificationMode === 'simple' && aggregationDataRef.current;
|
|
1036
|
-
|
|
1037
|
-
// ✅ FIX: Rezervovat místo pro status bar pouze když má data
|
|
1038
|
-
// Grid se dynamicky rozšíří/zmenší podle přítomnosti status baru
|
|
1039
|
-
const shouldReserveSpace = showStatusBar;
|
|
1040
|
-
const gridContainerStyle = shouldReserveSpace
|
|
1041
|
-
? { height: `calc(100% - ${statusBarHeight}px)`, width: '100%', position: 'relative' }
|
|
1042
|
-
: { height: '100%', width: '100%', position: 'relative' };
|
|
1043
|
-
|
|
1044
|
-
return (
|
|
1045
|
-
<div style={{ height: '100%', width: '100%', position: 'relative' }}>
|
|
1046
|
-
{/* AG Grid - fixní výška, nikdy se nemění */}
|
|
1047
|
-
<div style={gridContainerStyle}>
|
|
1048
|
-
<AgGridReact {...allGridProps} />
|
|
1049
|
-
</div>
|
|
1050
|
-
|
|
1051
|
-
{/* Aggregation Status Bar - absolutně pozicovaný na spodku */}
|
|
1052
|
-
{shouldReserveSpace && (
|
|
1053
|
-
<div style={{
|
|
1054
|
-
position: 'absolute',
|
|
1055
|
-
bottom: 0,
|
|
1056
|
-
left: 0,
|
|
1057
|
-
right: 0,
|
|
1058
|
-
height: `${statusBarHeight}px`,
|
|
1059
|
-
opacity: showStatusBar ? 1 : 0,
|
|
1060
|
-
visibility: showStatusBar ? 'visible' : 'hidden',
|
|
1061
|
-
pointerEvents: showStatusBar ? 'auto' : 'none',
|
|
1062
|
-
zIndex: 10,
|
|
1063
|
-
// GPU acceleration pro plynulejší zobrazení
|
|
1064
|
-
transform: 'translateZ(0)',
|
|
1065
|
-
willChange: 'opacity'
|
|
1066
|
-
}}>
|
|
1067
|
-
<AggregationStatusBar
|
|
1068
|
-
data={aggregationDataRef.current || {}}
|
|
1069
|
-
metrics={statusBarMetrics}
|
|
1070
|
-
height={statusBarHeight}
|
|
1071
|
-
onSwitchToFull={handleSwitchToFull}
|
|
1072
|
-
/>
|
|
1073
|
-
</div>
|
|
1074
|
-
)}
|
|
1075
|
-
|
|
1076
|
-
{/* Bulk Edit Floating Button */}
|
|
1077
|
-
{enableBulkEdit && floatingButton.visible && (
|
|
1078
|
-
<BulkEditButton
|
|
1079
|
-
visible={floatingButton.visible}
|
|
1080
|
-
position={floatingButton.position}
|
|
1081
|
-
range={floatingButton.range}
|
|
1082
|
-
column={floatingButton.column}
|
|
1083
|
-
cellCount={floatingButton.cellCount}
|
|
1084
|
-
rowsContainer={floatingButton.rowsContainer}
|
|
1085
|
-
gridApi={internalRef.current?.api}
|
|
1086
|
-
editPopover={editPopover}
|
|
1087
|
-
onOpenPopover={handleOpenPopover}
|
|
1088
|
-
onValueChange={handleValueChange}
|
|
1089
|
-
onSubmit={handleSubmitEdit}
|
|
1090
|
-
onCancel={handleCancelEdit}
|
|
1091
|
-
/>
|
|
1092
|
-
)}
|
|
1093
|
-
</div>
|
|
1094
|
-
);
|
|
1095
|
-
});
|
|
1096
|
-
|
|
1097
|
-
// ========== PERFORMANCE OPTIMIZATION: React.memo ==========
|
|
1098
|
-
// Refactored: Používá createGridComparison utility místo manuálního deep comparison
|
|
1099
|
-
// Eliminováno ~120 řádků duplicitního kódu, zachována stejná funkcionalita
|
|
1100
|
-
// Diagnostic mode: zapnutý pouze ve development módu
|
|
1101
|
-
export default React.memo(AgGrid, createGridComparison(
|
|
1102
|
-
process.env.NODE_ENV !== 'production' // Diagnostic mode pouze ve development
|
|
1103
|
-
));
|
|
1104
|
-
|
|
1105
|
-
export {
|
|
1106
|
-
useBulkCellEdit,
|
|
1107
|
-
BulkEditButton,
|
|
1108
|
-
BulkEditPopover,
|
|
1109
|
-
BulkEditSelect,
|
|
1110
|
-
BulkEditDatePicker,
|
|
1111
|
-
BulkEditModule,
|
|
1112
|
-
BulkEditInput,
|
|
1113
|
-
BulkEditTagsSelect
|
|
1114
|
-
} from './BulkEdit';
|
|
1115
|
-
|
|
1116
|
-
export {
|
|
1117
|
-
default as CheckboxRenderer
|
|
1118
|
-
} from './Renderers/CheckboxRenderer';
|
|
1119
|
-
|
|
1120
|
-
export * from './Renderers';
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { AgGridReact } from 'ag-grid-react';
|
|
4
|
+
import { AgGridColumns } from './AgGridColumn';
|
|
5
|
+
import { RhPlusOnCellEditingStarted } from './OnCellEditingStarted';
|
|
6
|
+
import { RhPlusOnCellDoubleClicked } from './OnCellDoubleClicked';
|
|
7
|
+
import { RhPlusOnCellValueChanged } from './OnCellValueChanged';
|
|
8
|
+
import { AgGridPostSort } from './AgGridPostSort';
|
|
9
|
+
import { AgGridOnRowDataChanged } from './AgGridOnRowDataChanged';
|
|
10
|
+
import { AgGridOnRowDataUpdated } from './AgGridOnRowDataUpdated';
|
|
11
|
+
import CheckboxRenderer from './Renderers/CheckboxRenderer';
|
|
12
|
+
import BooleanRenderer from './Renderers/BooleanRenderer';
|
|
13
|
+
import { createGridComparison } from '@bit.rhplus/react-memo';
|
|
14
|
+
|
|
15
|
+
import IconRenderer from './Renderers/IconRenderer';
|
|
16
|
+
import ImageRenderer from './Renderers/ImageRenderer';
|
|
17
|
+
import StateRenderer from './Renderers/StateRenderer';
|
|
18
|
+
import SelectRenderer from './Renderers/SelectRenderer';
|
|
19
|
+
import ButtonRenderer from './Renderers/ButtonRenderer';
|
|
20
|
+
import CountrySelectRenderer from './Renderers/CountrySelectRenderer';
|
|
21
|
+
import ObjectRenderer from './Renderers/ObjectRenderer';
|
|
22
|
+
import LinkRenderer from './Renderers/LinkRenderer';
|
|
23
|
+
import NotificationOptionsInit from "./NotificationOptions";
|
|
24
|
+
import AggregationStatusBar from "./AggregationStatusBar";
|
|
25
|
+
import { notification, Button } from "antd";
|
|
26
|
+
import { CompressOutlined } from '@ant-design/icons';
|
|
27
|
+
import Aggregations, { hashRanges } from "./Aggregations";
|
|
28
|
+
import { useBulkCellEdit, BulkEditButton } from './BulkEdit';
|
|
29
|
+
import {
|
|
30
|
+
ModuleRegistry,
|
|
31
|
+
themeAlpine,
|
|
32
|
+
themeBalham,
|
|
33
|
+
themeMaterial,
|
|
34
|
+
themeQuartz,
|
|
35
|
+
ClientSideRowModelModule,
|
|
36
|
+
QuickFilterModule,
|
|
37
|
+
ValidationModule,
|
|
38
|
+
} from "ag-grid-community";
|
|
39
|
+
|
|
40
|
+
// Registrace AG-Grid modulů (nutné pro Quick Filter a další funkce)
|
|
41
|
+
ModuleRegistry.registerModules([
|
|
42
|
+
QuickFilterModule,
|
|
43
|
+
ClientSideRowModelModule,
|
|
44
|
+
...(process.env.NODE_ENV !== "production" ? [ValidationModule] : []),
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
const themes = [
|
|
48
|
+
{ id: "themeQuartz", theme: themeQuartz },
|
|
49
|
+
{ id: "themeBalham", theme: themeBalham },
|
|
50
|
+
{ id: "themeMaterial", theme: themeMaterial },
|
|
51
|
+
{ id: "themeAlpine", theme: themeAlpine },
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
const AgGrid = React.forwardRef((props, ref) => {
|
|
55
|
+
const internalRef = React.useRef();
|
|
56
|
+
const {
|
|
57
|
+
theme = "themeAlpine", // Default theme
|
|
58
|
+
rowData = [],
|
|
59
|
+
newRowFlash = true,
|
|
60
|
+
updatedRowFlash = false,
|
|
61
|
+
onGridReady,
|
|
62
|
+
// Notification props
|
|
63
|
+
notificationMode = 'full', // 'full' | 'simple' | 'none'
|
|
64
|
+
notificationOptions: {
|
|
65
|
+
notificationHead = NotificationOptionsInit.head,
|
|
66
|
+
notificationBody = NotificationOptionsInit.body,
|
|
67
|
+
style = NotificationOptionsInit.style,
|
|
68
|
+
placement = NotificationOptionsInit.placement,
|
|
69
|
+
} = {},
|
|
70
|
+
// Status Bar props (pro simple mode)
|
|
71
|
+
statusBarMetrics = [
|
|
72
|
+
'count',
|
|
73
|
+
'sum',
|
|
74
|
+
'min',
|
|
75
|
+
'max',
|
|
76
|
+
'avg',
|
|
77
|
+
'median',
|
|
78
|
+
'range',
|
|
79
|
+
'geometry',
|
|
80
|
+
'dateRange'
|
|
81
|
+
],
|
|
82
|
+
statusBarHeight = 36,
|
|
83
|
+
// Bulk Edit props
|
|
84
|
+
enableBulkEdit = false,
|
|
85
|
+
bulkEditOptions = {},
|
|
86
|
+
bulkEditAccessToken,
|
|
87
|
+
onBulkEditStart,
|
|
88
|
+
onBulkEditComplete,
|
|
89
|
+
// SignalR transaction support
|
|
90
|
+
queryKey, // Identifikátor pro SignalR transactions
|
|
91
|
+
// Quick Filter
|
|
92
|
+
quickFilterText = "", // Text pro fulltextové vyhledávání
|
|
93
|
+
} = props;
|
|
94
|
+
|
|
95
|
+
// Najít theme objekt podle názvu z props
|
|
96
|
+
const themeObject = React.useMemo(() => {
|
|
97
|
+
const foundTheme = themes.find(t => t.id === theme);
|
|
98
|
+
return foundTheme ? foundTheme.theme : themeQuartz; // Fallback na themeQuartz
|
|
99
|
+
}, [theme]);
|
|
100
|
+
const [, setIsGridReady] = React.useState(false);
|
|
101
|
+
const isSelectingRef = React.useRef(false); // ✅ FIX: Změna ze state na ref pro eliminaci rerenderů
|
|
102
|
+
const aggregationDataRef = React.useRef(null); // // Pro simple mode status bar
|
|
103
|
+
const activeNotificationModeRef = React.useRef(notificationMode); // // Aktivní mód (může být dočasně přepsán uživatelem)
|
|
104
|
+
const previousRowDataRef = React.useRef(rowData);
|
|
105
|
+
|
|
106
|
+
// Synchronizovat activeNotificationModeRef s notificationMode prop
|
|
107
|
+
React.useEffect(() => {
|
|
108
|
+
activeNotificationModeRef.current = notificationMode;
|
|
109
|
+
}, [notificationMode]);
|
|
110
|
+
|
|
111
|
+
// Bulk Edit hook
|
|
112
|
+
const {
|
|
113
|
+
floatingButton,
|
|
114
|
+
editPopover,
|
|
115
|
+
handleRangeChange,
|
|
116
|
+
handleOpenPopover,
|
|
117
|
+
handleSubmitEdit,
|
|
118
|
+
handleCancelEdit,
|
|
119
|
+
handleValueChange,
|
|
120
|
+
} = useBulkCellEdit(internalRef, {
|
|
121
|
+
enabled: enableBulkEdit,
|
|
122
|
+
accessToken: bulkEditAccessToken,
|
|
123
|
+
onBulkEditStart,
|
|
124
|
+
onBulkEditComplete,
|
|
125
|
+
...bulkEditOptions,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
// ========== PERFORMANCE OPTIMIZATION: Shallow comparison helper ==========
|
|
130
|
+
// Shallow porovnání objektů - 100x rychlejší než JSON.stringify
|
|
131
|
+
const shallowEqual = React.useCallback((obj1, obj2) => {
|
|
132
|
+
if (obj1 === obj2) return true;
|
|
133
|
+
if (!obj1 || !obj2) return false;
|
|
134
|
+
|
|
135
|
+
const keys1 = Object.keys(obj1);
|
|
136
|
+
const keys2 = Object.keys(obj2);
|
|
137
|
+
|
|
138
|
+
if (keys1.length !== keys2.length) return false;
|
|
139
|
+
|
|
140
|
+
for (let key of keys1) {
|
|
141
|
+
if (obj1[key] !== obj2[key]) return false;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return true;
|
|
145
|
+
}, []);
|
|
146
|
+
|
|
147
|
+
// Memoizované funkce pro detekci změn v rowData
|
|
148
|
+
const findNewRows = React.useCallback((oldData, newData) => {
|
|
149
|
+
const oldIds = new Set(oldData.map((row) => row.id));
|
|
150
|
+
return newData.filter((row) => !oldIds.has(row.id));
|
|
151
|
+
}, []);
|
|
152
|
+
|
|
153
|
+
const findUpdatedRows = React.useCallback((oldData, newData) => {
|
|
154
|
+
const oldDataMap = new Map(oldData.map((row) => [row.id, row]));
|
|
155
|
+
return newData.filter((newRow) => {
|
|
156
|
+
const oldRow = oldDataMap.get(newRow.id);
|
|
157
|
+
if (!oldRow) return false; // Nový řádek, ne aktualizovaný
|
|
158
|
+
|
|
159
|
+
// ✅ OPTIMALIZACE: Shallow comparison místo JSON.stringify (100x rychlejší!)
|
|
160
|
+
return !shallowEqual(oldRow, newRow);
|
|
161
|
+
});
|
|
162
|
+
}, [shallowEqual]);
|
|
163
|
+
|
|
164
|
+
React.useImperativeHandle(ref, () => internalRef.current, [internalRef]);
|
|
165
|
+
|
|
166
|
+
// Throttle timer pro notifikace - zobrazuje se BĚHEM označování s 100ms throttle
|
|
167
|
+
const notificationThrottleRef = React.useRef(null);
|
|
168
|
+
const notificationLastCallRef = React.useRef(0);
|
|
169
|
+
const lastRangeHashRef = React.useRef(null);
|
|
170
|
+
|
|
171
|
+
// ✅ Callback pro přepnutí z full na simple mód
|
|
172
|
+
const handleSwitchToSimple = React.useCallback(() => {
|
|
173
|
+
const gridId = props.gridName || props.id || 'default';
|
|
174
|
+
const key = `aggregation-grid-${gridId}`;
|
|
175
|
+
|
|
176
|
+
// Zavřít full notifikaci
|
|
177
|
+
notification.destroy(key);
|
|
178
|
+
|
|
179
|
+
// Znovu spočítat a zobrazit simple status bar
|
|
180
|
+
if (internalRef?.current?.api) {
|
|
181
|
+
const ranges = internalRef.current.api.getCellRanges();
|
|
182
|
+
if (ranges && ranges.length > 0 && ranges[0]?.startRow) {
|
|
183
|
+
const messageInfo = Aggregations(internalRef);
|
|
184
|
+
if (messageInfo.count > 1) {
|
|
185
|
+
aggregationDataRef.current = messageInfo;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
activeNotificationModeRef.current = 'simple';
|
|
191
|
+
}, [props, internalRef]);
|
|
192
|
+
|
|
193
|
+
// ✅ Helper funkce pro vytvoření custom description s tlačítkem pro přepnutí na simple
|
|
194
|
+
const createNotificationDescription = React.useCallback((messageInfo, showSwitchButton = false) => {
|
|
195
|
+
const bodyContent = notificationBody(messageInfo);
|
|
196
|
+
|
|
197
|
+
if (!showSwitchButton) {
|
|
198
|
+
return bodyContent;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return (
|
|
202
|
+
<div>
|
|
203
|
+
{bodyContent}
|
|
204
|
+
<div style={{ marginTop: '12px', borderTop: '1px solid #f0f0f0', paddingTop: '8px' }}>
|
|
205
|
+
<Button
|
|
206
|
+
type="link"
|
|
207
|
+
size="small"
|
|
208
|
+
icon={<CompressOutlined />}
|
|
209
|
+
onClick={handleSwitchToSimple}
|
|
210
|
+
style={{ padding: 0 }}
|
|
211
|
+
>
|
|
212
|
+
Zobrazit kompaktní režim
|
|
213
|
+
</Button>
|
|
214
|
+
</div>
|
|
215
|
+
</div>
|
|
216
|
+
);
|
|
217
|
+
}, [notificationBody, handleSwitchToSimple]);
|
|
218
|
+
|
|
219
|
+
// ✅ Callback pro přepnutí z simple na full mód
|
|
220
|
+
const handleSwitchToFull = React.useCallback(() => {
|
|
221
|
+
if (!aggregationDataRef.current) return;
|
|
222
|
+
|
|
223
|
+
const gridId = props.gridName || props.id || 'default';
|
|
224
|
+
const key = `aggregation-grid-${gridId}`;
|
|
225
|
+
|
|
226
|
+
// Zobrazit full notifikaci s aktuálními daty a tlačítkem pro přepnutí
|
|
227
|
+
notification.info({
|
|
228
|
+
key,
|
|
229
|
+
message: notificationHead(aggregationDataRef.current),
|
|
230
|
+
description: createNotificationDescription(aggregationDataRef.current, true),
|
|
231
|
+
duration: 0,
|
|
232
|
+
style,
|
|
233
|
+
placement,
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// Skrýt simple status bar
|
|
237
|
+
aggregationDataRef.current = null;
|
|
238
|
+
activeNotificationModeRef.current = 'full';
|
|
239
|
+
}, [ props, notificationHead, createNotificationDescription, style, placement]);
|
|
240
|
+
|
|
241
|
+
// ========== PERFORMANCE FIX: Stabilní refs pro updateAggregationNotification ==========
|
|
242
|
+
// ✅ FIX: isSelectingRef je už deklarovaný na řádku 107 jako React.useRef(false)
|
|
243
|
+
const notificationHeadRef = React.useRef(notificationHead);
|
|
244
|
+
const createNotificationDescriptionRef = React.useRef(createNotificationDescription);
|
|
245
|
+
const styleRef = React.useRef(style);
|
|
246
|
+
const placementRef = React.useRef(placement);
|
|
247
|
+
const propsRef = React.useRef(props);
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
// ✅ FIX: Odstraněn useEffect pro isSelecting - isSelectingRef je nyní řízený přímo v updatePointerEventsForSelecting
|
|
251
|
+
|
|
252
|
+
React.useEffect(() => {
|
|
253
|
+
notificationHeadRef.current = notificationHead;
|
|
254
|
+
}, [notificationHead]);
|
|
255
|
+
|
|
256
|
+
React.useEffect(() => {
|
|
257
|
+
createNotificationDescriptionRef.current = createNotificationDescription;
|
|
258
|
+
}, [createNotificationDescription]);
|
|
259
|
+
|
|
260
|
+
React.useEffect(() => {
|
|
261
|
+
styleRef.current = style;
|
|
262
|
+
}, [style]);
|
|
263
|
+
|
|
264
|
+
React.useEffect(() => {
|
|
265
|
+
placementRef.current = placement;
|
|
266
|
+
}, [placement]);
|
|
267
|
+
|
|
268
|
+
React.useEffect(() => {
|
|
269
|
+
propsRef.current = props;
|
|
270
|
+
}, [props]);
|
|
271
|
+
|
|
272
|
+
// ✅ OPTIMALIZACE: Helper funkce pro aktualizaci aggregace notifikace s cache check
|
|
273
|
+
// Volá se BĚHEM označování s 100ms throttle pro real-time feedback
|
|
274
|
+
// STABILNÍ callback - používá pouze refs!
|
|
275
|
+
const updateAggregationNotification = React.useCallback((event) => {
|
|
276
|
+
// Pokud je notificationMode 'none', nedělat nic
|
|
277
|
+
if (notificationModeRef.current === 'none') {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Lightweight cache check PŘED voláním Aggregations()
|
|
282
|
+
const ranges = event.api.getCellRanges();
|
|
283
|
+
const currentRangeHash = hashRanges(ranges);
|
|
284
|
+
|
|
285
|
+
// Žádné ranges nebo jen jedna buňka - vyčistit vše
|
|
286
|
+
if (!ranges || ranges.length === 0 || !ranges[0]?.startRow) {
|
|
287
|
+
lastRangeHashRef.current = null;
|
|
288
|
+
|
|
289
|
+
// V 'full' módu zavřít notifikaci
|
|
290
|
+
if (activeNotificationModeRef.current === 'full') {
|
|
291
|
+
const gridId = propsRef.current.gridName || propsRef.current.id || 'default';
|
|
292
|
+
const key = `aggregation-grid-${gridId}`;
|
|
293
|
+
notification.destroy(key);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// V 'simple' módu vyčistit aggregationData
|
|
297
|
+
if (activeNotificationModeRef.current === 'simple') {
|
|
298
|
+
aggregationDataRef.current = null;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Cache hit - ranges se nezměnily, skip
|
|
305
|
+
if (currentRangeHash === lastRangeHashRef.current) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Cache miss - spočítat aggregace
|
|
310
|
+
const messageInfo = Aggregations(internalRef);
|
|
311
|
+
|
|
312
|
+
if (messageInfo.count > 1) {
|
|
313
|
+
// Uložit hash pro příští porovnání
|
|
314
|
+
lastRangeHashRef.current = currentRangeHash;
|
|
315
|
+
|
|
316
|
+
// Zavolat onAggregationChanged callback pokud existuje
|
|
317
|
+
if (propsRef.current.onAggregationChanged) {
|
|
318
|
+
propsRef.current.onAggregationChanged(messageInfo);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Podle aktivního módu zobrazit buď notifikaci nebo aktualizovat status bar
|
|
322
|
+
if (activeNotificationModeRef.current === 'full') {
|
|
323
|
+
// FULL mód - zobrazit plovoucí notifikaci s tlačítkem pro přepnutí na simple
|
|
324
|
+
const gridId = propsRef.current.gridName || propsRef.current.id || 'default';
|
|
325
|
+
const key = `aggregation-grid-${gridId}`;
|
|
326
|
+
|
|
327
|
+
const dynamicStyle = {
|
|
328
|
+
...styleRef.current,
|
|
329
|
+
pointerEvents: isSelectingRef.current ? 'none' : 'auto'
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
notification.info({
|
|
333
|
+
key,
|
|
334
|
+
message: notificationHeadRef.current(messageInfo),
|
|
335
|
+
description: createNotificationDescriptionRef.current(messageInfo, true),
|
|
336
|
+
duration: 0,
|
|
337
|
+
style: dynamicStyle,
|
|
338
|
+
placement: placementRef.current,
|
|
339
|
+
});
|
|
340
|
+
} else if (activeNotificationModeRef.current === 'simple') {
|
|
341
|
+
// SIMPLE mód - aktualizovat state pro status bar
|
|
342
|
+
aggregationDataRef.current = messageInfo;
|
|
343
|
+
}
|
|
344
|
+
} else {
|
|
345
|
+
// Jen jedna buňka - zavřít/vyčistit vše
|
|
346
|
+
lastRangeHashRef.current = null;
|
|
347
|
+
|
|
348
|
+
if (activeNotificationModeRef.current === 'full') {
|
|
349
|
+
const gridId = propsRef.current.gridName || propsRef.current.id || 'default';
|
|
350
|
+
const key = `aggregation-grid-${gridId}`;
|
|
351
|
+
notification.destroy(key);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (activeNotificationModeRef.current === 'simple') {
|
|
355
|
+
aggregationDataRef.current = null;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}, [internalRef]); // ✅ Pouze internalRef v dependencies!
|
|
359
|
+
|
|
360
|
+
// Helper funkce pro nastavení pointer-events na notifikace
|
|
361
|
+
const setNotificationPointerEvents = React.useCallback((enable) => {
|
|
362
|
+
const notifications = document.querySelectorAll('.ant-notification');
|
|
363
|
+
|
|
364
|
+
notifications.forEach((notif) => {
|
|
365
|
+
if (enable) {
|
|
366
|
+
// Obnovit pointer-events na notifikaci
|
|
367
|
+
const original = notif.dataset.originalPointerEvents || 'auto';
|
|
368
|
+
notif.style.pointerEvents = original;
|
|
369
|
+
notif.style.removeProperty('pointer-events');
|
|
370
|
+
delete notif.dataset.originalPointerEvents;
|
|
371
|
+
|
|
372
|
+
// Obnovit pointer-events na všechny child elementy
|
|
373
|
+
const allChildren = notif.querySelectorAll('*');
|
|
374
|
+
allChildren.forEach((child) => {
|
|
375
|
+
child.style.removeProperty('pointer-events');
|
|
376
|
+
delete child.dataset.originalPointerEvents;
|
|
377
|
+
});
|
|
378
|
+
} else {
|
|
379
|
+
// Zakázat pointer-events na notifikaci
|
|
380
|
+
if (!notif.dataset.originalPointerEvents) {
|
|
381
|
+
notif.dataset.originalPointerEvents = notif.style.pointerEvents || 'auto';
|
|
382
|
+
}
|
|
383
|
+
notif.style.setProperty('pointer-events', 'none', 'important');
|
|
384
|
+
|
|
385
|
+
// Zakázat pointer-events na všechny child elementy
|
|
386
|
+
const allChildren = notif.querySelectorAll('*');
|
|
387
|
+
allChildren.forEach((child) => {
|
|
388
|
+
if (!child.dataset.originalPointerEvents) {
|
|
389
|
+
child.dataset.originalPointerEvents = child.style.pointerEvents || '';
|
|
390
|
+
}
|
|
391
|
+
child.style.setProperty('pointer-events', 'none', 'important');
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
}, []);
|
|
396
|
+
|
|
397
|
+
// ✅ FIX: Helper funkce pro update isSelecting bez state změny (eliminuje rerender)
|
|
398
|
+
const updatePointerEventsForSelecting = React.useCallback((selecting) => {
|
|
399
|
+
isSelectingRef.current = selecting;
|
|
400
|
+
|
|
401
|
+
if (selecting) {
|
|
402
|
+
document.body.classList.add('ag-grid-selecting');
|
|
403
|
+
setNotificationPointerEvents(false);
|
|
404
|
+
|
|
405
|
+
// KRITICKÉ: Sledovat DOM změny - když se objeví nová notifikace během označování
|
|
406
|
+
const observer = new MutationObserver((mutations) => {
|
|
407
|
+
mutations.forEach((mutation) => {
|
|
408
|
+
mutation.addedNodes.forEach((node) => {
|
|
409
|
+
if (node.nodeType === 1) {
|
|
410
|
+
if (node.classList?.contains('ant-notification') ||
|
|
411
|
+
node.querySelector?.('.ant-notification')) {
|
|
412
|
+
setNotificationPointerEvents(false);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
observer.observe(document.body, {
|
|
420
|
+
childList: true,
|
|
421
|
+
subtree: true
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
// Uložit observer do ref pro případný cleanup
|
|
425
|
+
if (!updatePointerEventsForSelecting.observer) {
|
|
426
|
+
updatePointerEventsForSelecting.observer = observer;
|
|
427
|
+
}
|
|
428
|
+
} else {
|
|
429
|
+
document.body.classList.remove('ag-grid-selecting');
|
|
430
|
+
|
|
431
|
+
// Cleanup observer
|
|
432
|
+
if (updatePointerEventsForSelecting.observer) {
|
|
433
|
+
updatePointerEventsForSelecting.observer.disconnect();
|
|
434
|
+
updatePointerEventsForSelecting.observer = null;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
setTimeout(() => setNotificationPointerEvents(true), 100);
|
|
438
|
+
}
|
|
439
|
+
}, [setNotificationPointerEvents]);
|
|
440
|
+
|
|
441
|
+
// Detekce konce označování pomocí mouseup event
|
|
442
|
+
React.useEffect(() => {
|
|
443
|
+
const handleMouseUp = () => {
|
|
444
|
+
// ✅ FIX: Ukončit isSelecting pomocí ref (bez state update → žádný rerender)
|
|
445
|
+
setTimeout(() => {
|
|
446
|
+
updatePointerEventsForSelecting(false);
|
|
447
|
+
|
|
448
|
+
// ✅ FIX: Zkontrolovat jestli jsou stále nějaké ranges, pokud ne - vyčistit status bar
|
|
449
|
+
if (internalRef?.current?.api && activeNotificationModeRef.current === 'simple') {
|
|
450
|
+
const ranges = internalRef.current.api.getCellRanges();
|
|
451
|
+
if (!ranges || ranges.length === 0 || !ranges[0]?.startRow) {
|
|
452
|
+
aggregationDataRef.current = null;
|
|
453
|
+
// Reset na původní notificationMode když jsou všechny buňky odznačeny
|
|
454
|
+
activeNotificationModeRef.current = notificationMode;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}, 50);
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
document.addEventListener('mouseup', handleMouseUp);
|
|
461
|
+
document.addEventListener('touchend', handleMouseUp); // Pro touch zařízení
|
|
462
|
+
|
|
463
|
+
return () => {
|
|
464
|
+
document.removeEventListener('mouseup', handleMouseUp);
|
|
465
|
+
document.removeEventListener('touchend', handleMouseUp);
|
|
466
|
+
};
|
|
467
|
+
}, [notificationMode, internalRef, updatePointerEventsForSelecting]);
|
|
468
|
+
|
|
469
|
+
// Cleanup notifikací a timerů při zničení komponenty
|
|
470
|
+
React.useEffect(() => {
|
|
471
|
+
return () => {
|
|
472
|
+
// Clear throttle timer
|
|
473
|
+
if (notificationThrottleRef.current) {
|
|
474
|
+
clearTimeout(notificationThrottleRef.current);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if (notificationMode === 'full') {
|
|
478
|
+
const gridId = props.gridName || props.id || 'default';
|
|
479
|
+
const key = `aggregation-grid-${gridId}`;
|
|
480
|
+
notification.destroy(key);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Cleanup SignalR transaction callback při unmount
|
|
484
|
+
if (queryKey && window.agGridTransactionCallbacks) {
|
|
485
|
+
delete window.agGridTransactionCallbacks[queryKey];
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
}, [notificationMode, props.gridName, props.id, queryKey]);
|
|
489
|
+
|
|
490
|
+
React.useEffect(() => {
|
|
491
|
+
// VYPNUTO: Cell flash je globálně vypnutý
|
|
492
|
+
return;
|
|
493
|
+
|
|
494
|
+
if (!newRowFlash && !updatedRowFlash) return;
|
|
495
|
+
|
|
496
|
+
const previousRowData = previousRowDataRef.current;
|
|
497
|
+
const addedRows = newRowFlash ? findNewRows(previousRowData, rowData) : [];
|
|
498
|
+
const updatedRows = updatedRowFlash ? findUpdatedRows(previousRowData, rowData) : [];
|
|
499
|
+
|
|
500
|
+
if (addedRows.length > 0 || updatedRows.length > 0) {
|
|
501
|
+
setTimeout(() => {
|
|
502
|
+
try {
|
|
503
|
+
// Bezpečnostní kontrola API dostupnosti
|
|
504
|
+
if (!internalRef.current?.api || internalRef.current.api.isDestroyed?.()) {
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// Modern AG-Grid (33+): getColumnState() moved to main api
|
|
509
|
+
const columnApi = internalRef.current?.columnApi || internalRef.current?.api;
|
|
510
|
+
if (!columnApi?.getColumnState) {
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const columnStates = columnApi.getColumnState();
|
|
515
|
+
const allColumns = columnStates
|
|
516
|
+
.filter(colState => colState.colId) // Filtrujeme pouze platné colId
|
|
517
|
+
.map((colState) => colState.colId);
|
|
518
|
+
|
|
519
|
+
// Kontrola, že máme platné sloupce
|
|
520
|
+
if (allColumns.length === 0) {
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Flash efekt pro nové řádky (používá defaultní zelená barva AG Grid)
|
|
525
|
+
if (addedRows.length > 0) {
|
|
526
|
+
const newRowNodes = [];
|
|
527
|
+
internalRef.current.api.forEachNode((node) => {
|
|
528
|
+
if (addedRows.some((row) => row.id === node.data.id)) {
|
|
529
|
+
newRowNodes.push(node);
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
if (newRowNodes.length > 0) {
|
|
534
|
+
internalRef.current.api.flashCells({
|
|
535
|
+
rowNodes: newRowNodes,
|
|
536
|
+
columns: allColumns,
|
|
537
|
+
flashDelay: 0,
|
|
538
|
+
fadeDelay: 1000,
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Flash efekt pro aktualizované řádky (modrá barva)
|
|
544
|
+
if (updatedRows.length > 0) {
|
|
545
|
+
const updatedRowNodes = [];
|
|
546
|
+
internalRef.current.api.forEachNode((node) => {
|
|
547
|
+
if (updatedRows.some((row) => row.id === node.data.id)) {
|
|
548
|
+
updatedRowNodes.push(node);
|
|
549
|
+
}
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
if (updatedRowNodes.length > 0) {
|
|
553
|
+
// Použijeme vlastní CSS animaci pro modrou flash
|
|
554
|
+
updatedRowNodes.forEach(node => {
|
|
555
|
+
const rowElement = node.eGridRow || document.querySelector(`[row-id="${node.data.id}"]`);
|
|
556
|
+
if (rowElement) {
|
|
557
|
+
rowElement.classList.add('ag-row-flash-updated');
|
|
558
|
+
setTimeout(() => {
|
|
559
|
+
rowElement.classList.remove('ag-row-flash-updated');
|
|
560
|
+
}, 1000);
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
} catch (error) {
|
|
566
|
+
// Ignorujeme chybu a pokračujeme bez crash aplikace
|
|
567
|
+
}
|
|
568
|
+
}, 100); // Zvýšený timeout pro stabilizaci gridu
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
previousRowDataRef.current = rowData;
|
|
572
|
+
}, [rowData, newRowFlash, updatedRowFlash]);
|
|
573
|
+
|
|
574
|
+
// ========== PERFORMANCE FIX: Stabilní ref pattern pro callbacks ==========
|
|
575
|
+
// Refs pro aktuální hodnoty - zabraňuje re-creation RhPlusRangeSelectionChanged při změně stavu
|
|
576
|
+
const notificationModeRef = React.useRef(notificationMode);
|
|
577
|
+
const enableBulkEditRef = React.useRef(enableBulkEdit);
|
|
578
|
+
const handleRangeChangeRef = React.useRef(handleRangeChange);
|
|
579
|
+
const onRangeSelectionChangedRef = React.useRef(props.onRangeSelectionChanged);
|
|
580
|
+
const updateAggregationNotificationRef = React.useRef(updateAggregationNotification);
|
|
581
|
+
|
|
582
|
+
// ✅ FIX #12: Refs pro grid layout handlery (eliminuje rerendery při změně props)
|
|
583
|
+
const onColumnMovedRef = React.useRef(props.onColumnMoved);
|
|
584
|
+
const onDragStoppedRef = React.useRef(props.onDragStopped);
|
|
585
|
+
const onColumnVisibleRef = React.useRef(props.onColumnVisible);
|
|
586
|
+
const onColumnPinnedRef = React.useRef(props.onColumnPinned);
|
|
587
|
+
const onColumnResizedRef = React.useRef(props.onColumnResized);
|
|
588
|
+
|
|
589
|
+
// Aktualizovat refs při změně hodnot
|
|
590
|
+
React.useEffect(() => {
|
|
591
|
+
notificationModeRef.current = notificationMode;
|
|
592
|
+
}, [notificationMode]);
|
|
593
|
+
|
|
594
|
+
React.useEffect(() => {
|
|
595
|
+
enableBulkEditRef.current = enableBulkEdit;
|
|
596
|
+
}, [enableBulkEdit]);
|
|
597
|
+
|
|
598
|
+
React.useEffect(() => {
|
|
599
|
+
handleRangeChangeRef.current = handleRangeChange;
|
|
600
|
+
}, [handleRangeChange]);
|
|
601
|
+
|
|
602
|
+
React.useEffect(() => {
|
|
603
|
+
onRangeSelectionChangedRef.current = props.onRangeSelectionChanged;
|
|
604
|
+
}, [props.onRangeSelectionChanged]);
|
|
605
|
+
|
|
606
|
+
React.useEffect(() => {
|
|
607
|
+
updateAggregationNotificationRef.current = updateAggregationNotification;
|
|
608
|
+
}, [updateAggregationNotification]);
|
|
609
|
+
|
|
610
|
+
// ✅ FIX #12: Aktualizovat grid layout handler refs
|
|
611
|
+
React.useEffect(() => {
|
|
612
|
+
onColumnMovedRef.current = props.onColumnMoved;
|
|
613
|
+
}, [props.onColumnMoved]);
|
|
614
|
+
|
|
615
|
+
React.useEffect(() => {
|
|
616
|
+
onDragStoppedRef.current = props.onDragStopped;
|
|
617
|
+
}, [props.onDragStopped]);
|
|
618
|
+
|
|
619
|
+
React.useEffect(() => {
|
|
620
|
+
onColumnVisibleRef.current = props.onColumnVisible;
|
|
621
|
+
}, [props.onColumnVisible]);
|
|
622
|
+
|
|
623
|
+
React.useEffect(() => {
|
|
624
|
+
onColumnPinnedRef.current = props.onColumnPinned;
|
|
625
|
+
}, [props.onColumnPinned]);
|
|
626
|
+
|
|
627
|
+
React.useEffect(() => {
|
|
628
|
+
onColumnResizedRef.current = props.onColumnResized;
|
|
629
|
+
}, [props.onColumnResized]);
|
|
630
|
+
|
|
631
|
+
// Stabilní callback s prázdnými dependencies - používá pouze refs
|
|
632
|
+
const RhPlusRangeSelectionChanged = React.useCallback(
|
|
633
|
+
(event) => {
|
|
634
|
+
// ✅ FIX: Detekovat začátek označování pomocí ref (bez state update → žádný rerender)
|
|
635
|
+
updatePointerEventsForSelecting(true);
|
|
636
|
+
|
|
637
|
+
// 1. ✅ OPTIMALIZACE: Zobrazení notifikace BĚHEM označování s THROTTLE
|
|
638
|
+
// Simple mode: 300ms throttle (méně častá aktualizace, lepší výkon)
|
|
639
|
+
// Full mode: 100ms throttle (rychlejší feedback, plovoucí notifikace je levná)
|
|
640
|
+
if (notificationModeRef.current !== 'none') {
|
|
641
|
+
const throttleInterval = notificationModeRef.current === 'simple' ? 300 : 100;
|
|
642
|
+
const now = Date.now();
|
|
643
|
+
const timeSinceLastCall = now - notificationLastCallRef.current;
|
|
644
|
+
|
|
645
|
+
// První volání NEBO uplynul throttle interval
|
|
646
|
+
if (timeSinceLastCall >= throttleInterval) {
|
|
647
|
+
// Okamžité volání
|
|
648
|
+
notificationLastCallRef.current = now;
|
|
649
|
+
if (internalRef?.current) {
|
|
650
|
+
updateAggregationNotificationRef.current(event);
|
|
651
|
+
}
|
|
652
|
+
} else {
|
|
653
|
+
// Naplánovat volání za zbývající čas (trailing edge)
|
|
654
|
+
if (notificationThrottleRef.current) {
|
|
655
|
+
clearTimeout(notificationThrottleRef.current);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
notificationThrottleRef.current = setTimeout(() => {
|
|
659
|
+
notificationLastCallRef.current = Date.now();
|
|
660
|
+
if (internalRef?.current) {
|
|
661
|
+
updateAggregationNotificationRef.current(event);
|
|
662
|
+
}
|
|
663
|
+
}, throttleInterval - timeSinceLastCall);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// 2. ✅ OPTIMALIZACE: Bulk edit handler BEZ debounce - okamžité zobrazení ikony
|
|
668
|
+
// Lightweight validace v useBulkCellEdit zajistí okamžité zobrazení
|
|
669
|
+
// Těžká validace proběhne s 15ms debounce uvnitř useBulkCellEdit
|
|
670
|
+
if (enableBulkEditRef.current) {
|
|
671
|
+
handleRangeChangeRef.current(event);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// 3. Custom onRangeSelectionChanged callback - bez debounce
|
|
675
|
+
if (onRangeSelectionChangedRef.current) {
|
|
676
|
+
onRangeSelectionChangedRef.current(event);
|
|
677
|
+
}
|
|
678
|
+
},
|
|
679
|
+
[] // ✅ PRÁZDNÉ dependencies - stabilní reference!
|
|
680
|
+
);
|
|
681
|
+
|
|
682
|
+
// ✅ FIX #12: Stabilní wrappery pro grid layout handlery (prázdné dependencies)
|
|
683
|
+
const stableOnColumnMoved = React.useCallback(
|
|
684
|
+
(params) => {
|
|
685
|
+
if (onColumnMovedRef.current) {
|
|
686
|
+
onColumnMovedRef.current(params);
|
|
687
|
+
}
|
|
688
|
+
},
|
|
689
|
+
[]
|
|
690
|
+
);
|
|
691
|
+
|
|
692
|
+
const stableOnDragStopped = React.useCallback(
|
|
693
|
+
(params) => {
|
|
694
|
+
if (onDragStoppedRef.current) {
|
|
695
|
+
onDragStoppedRef.current(params);
|
|
696
|
+
}
|
|
697
|
+
},
|
|
698
|
+
[]
|
|
699
|
+
);
|
|
700
|
+
|
|
701
|
+
const stableOnColumnVisible = React.useCallback(
|
|
702
|
+
(params) => {
|
|
703
|
+
if (onColumnVisibleRef.current) {
|
|
704
|
+
onColumnVisibleRef.current(params);
|
|
705
|
+
}
|
|
706
|
+
},
|
|
707
|
+
[]
|
|
708
|
+
);
|
|
709
|
+
|
|
710
|
+
const stableOnColumnPinned = React.useCallback(
|
|
711
|
+
(params) => {
|
|
712
|
+
if (onColumnPinnedRef.current) {
|
|
713
|
+
onColumnPinnedRef.current(params);
|
|
714
|
+
}
|
|
715
|
+
},
|
|
716
|
+
[]
|
|
717
|
+
);
|
|
718
|
+
|
|
719
|
+
const stableOnColumnResized = React.useCallback(
|
|
720
|
+
(params) => {
|
|
721
|
+
if (onColumnResizedRef.current) {
|
|
722
|
+
onColumnResizedRef.current(params);
|
|
723
|
+
}
|
|
724
|
+
},
|
|
725
|
+
[]
|
|
726
|
+
);
|
|
727
|
+
|
|
728
|
+
const AgGridOnGridReady = (event, options) => {
|
|
729
|
+
if (onGridReady) {
|
|
730
|
+
onGridReady(event, options);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// Nejprve nastavíme API reference pro interní použití
|
|
734
|
+
if (internalRef.current) {
|
|
735
|
+
internalRef.current.api = event.api;
|
|
736
|
+
internalRef.current.columnApi = event.columnApi || event.api; // fallback for modern AG-Grid
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
// Nastavíme API ready flag pro quick filter useEffect
|
|
740
|
+
setIsApiReady(true);
|
|
741
|
+
|
|
742
|
+
// Registruj callback pro AG Grid transactions (SignalR optimalizace)
|
|
743
|
+
// Inicializace globálního registru pro více AG-Grid instancí
|
|
744
|
+
if (!window.agGridTransactionCallbacks) {
|
|
745
|
+
window.agGridTransactionCallbacks = {};
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// Registrace callbacku pro tuto konkrétní AG-Grid instanci (podle queryKey)
|
|
749
|
+
if (event.api && queryKey) {
|
|
750
|
+
window.agGridTransactionCallbacks[queryKey] = (transactionData) => {
|
|
751
|
+
try {
|
|
752
|
+
if (!event.api || event.api.isDestroyed?.()) return;
|
|
753
|
+
|
|
754
|
+
const { operation, records } = transactionData;
|
|
755
|
+
|
|
756
|
+
switch (operation) {
|
|
757
|
+
case 'add':
|
|
758
|
+
// Nastavíme flag _rh_plus_ag_grid_signal_new (NE _rh_plus_ag_grid_new_item),
|
|
759
|
+
// aby postSort přesunul řádek na začátek, ale renderery obsah nezahovaly
|
|
760
|
+
const addRecords = records.map(r => ({ ...r, _rh_plus_ag_grid_signal_new: true }));
|
|
761
|
+
event.api.applyTransaction({ add: addRecords, addIndex: 0 });
|
|
762
|
+
// Flash efekt pro nové řádky + po 5s odebrat signal flag a re-sort
|
|
763
|
+
setTimeout(() => {
|
|
764
|
+
records.forEach(record => {
|
|
765
|
+
const rowNode = event.api.getRowNode(record.id);
|
|
766
|
+
if (rowNode) {
|
|
767
|
+
if (rowNode.rowElement) {
|
|
768
|
+
rowNode.rowElement.classList.add('ag-row-flash-created');
|
|
769
|
+
setTimeout(() => {
|
|
770
|
+
rowNode.rowElement.classList.remove('ag-row-flash-created');
|
|
771
|
+
}, 1000);
|
|
772
|
+
}
|
|
773
|
+
// Po 5 sekundách odebrat signal flag a refreshnout sort
|
|
774
|
+
setTimeout(() => {
|
|
775
|
+
if (rowNode.data) {
|
|
776
|
+
delete rowNode.data._rh_plus_ag_grid_signal_new;
|
|
777
|
+
// Refresh sort, aby se řádek zařadil podle aktuálního řazení
|
|
778
|
+
if (event.api && !event.api.isDestroyed?.()) {
|
|
779
|
+
event.api.refreshClientSideRowModel('sort');
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}, 5000);
|
|
783
|
+
}
|
|
784
|
+
});
|
|
785
|
+
}, 100);
|
|
786
|
+
break;
|
|
787
|
+
|
|
788
|
+
case 'update':
|
|
789
|
+
event.api.applyTransaction({ update: records });
|
|
790
|
+
// Flash efekt pro aktualizované řádky
|
|
791
|
+
setTimeout(() => {
|
|
792
|
+
records.forEach(record => {
|
|
793
|
+
const rowNode = event.api.getRowNode(record.id);
|
|
794
|
+
if (rowNode && rowNode.rowElement) {
|
|
795
|
+
rowNode.rowElement.classList.add('ag-row-flash-updated');
|
|
796
|
+
setTimeout(() => {
|
|
797
|
+
rowNode.rowElement.classList.remove('ag-row-flash-updated');
|
|
798
|
+
}, 1000);
|
|
799
|
+
}
|
|
800
|
+
});
|
|
801
|
+
}, 100);
|
|
802
|
+
break;
|
|
803
|
+
|
|
804
|
+
case 'remove':
|
|
805
|
+
// Flash efekt před smazáním
|
|
806
|
+
records.forEach(record => {
|
|
807
|
+
const rowNode = event.api.getRowNode(record.id);
|
|
808
|
+
if (rowNode && rowNode.rowElement) {
|
|
809
|
+
rowNode.rowElement.classList.add('ag-row-flash-deleted');
|
|
810
|
+
}
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
setTimeout(() => {
|
|
814
|
+
event.api.applyTransaction({ remove: records });
|
|
815
|
+
}, 500); // Krátké zpoždění pro zobrazení flash efektu
|
|
816
|
+
break;
|
|
817
|
+
}
|
|
818
|
+
} catch (error) {
|
|
819
|
+
// Ignorujeme chyby
|
|
820
|
+
}
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// Pak zavoláme parent onGridReady handler, pokud existuje
|
|
825
|
+
// Toto je kritické pro správné fungování bit/ui/grid a GridLayout
|
|
826
|
+
if (options.onGridReady) {
|
|
827
|
+
try {
|
|
828
|
+
options.onGridReady(event);
|
|
829
|
+
} catch (error) {
|
|
830
|
+
// Error handling without console output
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
// Nakonec nastavíme grid ready state s timeout
|
|
835
|
+
setTimeout(() => {
|
|
836
|
+
setIsGridReady(true);
|
|
837
|
+
}, 1000);
|
|
838
|
+
};
|
|
839
|
+
|
|
840
|
+
const components = React.useMemo(() => {
|
|
841
|
+
return {
|
|
842
|
+
checkboxRenderer: CheckboxRenderer,
|
|
843
|
+
selectRenderer: SelectRenderer,
|
|
844
|
+
countrySelectRenderer: CountrySelectRenderer,
|
|
845
|
+
booleanRenderer: BooleanRenderer,
|
|
846
|
+
buttonRenderer: ButtonRenderer,
|
|
847
|
+
iconRenderer: IconRenderer,
|
|
848
|
+
imageRenderer: ImageRenderer,
|
|
849
|
+
stateRenderer: StateRenderer,
|
|
850
|
+
objectRenderer: ObjectRenderer,
|
|
851
|
+
linkRenderer: LinkRenderer,
|
|
852
|
+
...props.frameworkComponents,
|
|
853
|
+
};
|
|
854
|
+
}, [props.frameworkComponents]);
|
|
855
|
+
|
|
856
|
+
// ========== PERFORMANCE OPTIMIZATION: Memoizované event handlers ==========
|
|
857
|
+
const memoizedOnCellEditingStarted = React.useCallback(
|
|
858
|
+
(event) => RhPlusOnCellEditingStarted(event, props),
|
|
859
|
+
[props.onCellEditingStarted]
|
|
860
|
+
);
|
|
861
|
+
|
|
862
|
+
const memoizedOnCellDoubleClicked = React.useCallback(
|
|
863
|
+
(event) => RhPlusOnCellDoubleClicked(event, props),
|
|
864
|
+
[props.onCellDoubleClicked]
|
|
865
|
+
);
|
|
866
|
+
|
|
867
|
+
const memoizedOnCellValueChanged = React.useCallback(
|
|
868
|
+
(event) => RhPlusOnCellValueChanged(event, props),
|
|
869
|
+
[props.onCellValueChanged]
|
|
870
|
+
);
|
|
871
|
+
|
|
872
|
+
const memoizedPostSort = React.useCallback(
|
|
873
|
+
(event) => AgGridPostSort(event, props),
|
|
874
|
+
[props.postSort]
|
|
875
|
+
);
|
|
876
|
+
|
|
877
|
+
const memoizedOnGridReady = React.useCallback(
|
|
878
|
+
(event, options) => AgGridOnGridReady(event, props),
|
|
879
|
+
[onGridReady]
|
|
880
|
+
);
|
|
881
|
+
|
|
882
|
+
const memoizedOnRowDataChanged = React.useCallback(
|
|
883
|
+
(event) => AgGridOnRowDataChanged(event, props),
|
|
884
|
+
[props.onRowDataChanged]
|
|
885
|
+
);
|
|
886
|
+
|
|
887
|
+
const memoizedOnRowDataUpdated = React.useCallback(
|
|
888
|
+
(event) => AgGridOnRowDataUpdated(event, props),
|
|
889
|
+
[props.onRowDataUpdated]
|
|
890
|
+
);
|
|
891
|
+
|
|
892
|
+
// Memoizovaný context object
|
|
893
|
+
// ✅ OPTIMALIZACE: Použít pouze relevantní props pro context - neměnit při změně props
|
|
894
|
+
const memoizedContext = React.useMemo(() => ({
|
|
895
|
+
componentParent: props
|
|
896
|
+
}), [props.context, props.gridName, props.id]);
|
|
897
|
+
|
|
898
|
+
// Memoizovaný defaultColDef
|
|
899
|
+
const memoizedDefaultColDef = React.useMemo(() => ({
|
|
900
|
+
filter: 'agTextColumnFilter',
|
|
901
|
+
floatingFilter: true,
|
|
902
|
+
filterParams: {
|
|
903
|
+
defaultOption: 'contains',
|
|
904
|
+
},
|
|
905
|
+
...props.defaultColDef,
|
|
906
|
+
suppressHeaderMenuButton: true,
|
|
907
|
+
suppressHeaderFilterButton: true,
|
|
908
|
+
suppressMenu: true,
|
|
909
|
+
// ✅ FIX AG-Grid v35: Bezpečné zpracování null hodnot v Quick Filter
|
|
910
|
+
// AG-Grid v35 změnil implementaci Quick Filter - nyní volá .toString() na hodnotách buněk bez null check
|
|
911
|
+
// Tento callback zajistí že nikdy nedojde k chybě "Cannot read properties of null (reading 'toString')"
|
|
912
|
+
getQuickFilterText: (params) => {
|
|
913
|
+
const value = params.value;
|
|
914
|
+
// Null/undefined → prázdný string (bez chyby)
|
|
915
|
+
if (value == null) return '';
|
|
916
|
+
// Objekty a pole → JSON string
|
|
917
|
+
if (typeof value === 'object') {
|
|
918
|
+
try {
|
|
919
|
+
return JSON.stringify(value);
|
|
920
|
+
} catch {
|
|
921
|
+
// Cirkulární reference nebo jiná chyba při serializaci → prázdný string
|
|
922
|
+
return '';
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
// Primitivní typy → toString
|
|
926
|
+
return value.toString();
|
|
927
|
+
},
|
|
928
|
+
}), [props.defaultColDef]);
|
|
929
|
+
|
|
930
|
+
// ========== PERFORMANCE FIX: Memoizovat columnDefs ==========
|
|
931
|
+
// AgGridColumns vrací nový array při každém volání, i když jsou vstupy stejné
|
|
932
|
+
const memoizedColumnDefs = React.useMemo(() => {
|
|
933
|
+
return AgGridColumns(props.columnDefs, props);
|
|
934
|
+
}, [props.columnDefs, props.getRowId, props.frameworkComponents]);
|
|
935
|
+
|
|
936
|
+
// ========== CRITICAL FIX: Stabilní allGridProps objekt pomocí ref ==========
|
|
937
|
+
// Problém: Jakékoli použití useMemo vytváří nový objekt při změně dependencies
|
|
938
|
+
// Řešení: Použít ref pro VŽDY stejný objekt
|
|
939
|
+
// AG-Grid САМО detekuje změny rowData a columnDefs → NENÍ potřeba forceUpdate!
|
|
940
|
+
|
|
941
|
+
const allGridPropsRef = React.useRef(null);
|
|
942
|
+
|
|
943
|
+
// Inicializace objektu
|
|
944
|
+
if (!allGridPropsRef.current) {
|
|
945
|
+
allGridPropsRef.current = {};
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// ✅ Aktualizovat props v EXISTUJÍCÍM objektu (mutace)
|
|
949
|
+
// AG Grid interně detekuje změny props, nepotřebuje nový objekt
|
|
950
|
+
allGridPropsRef.current.ref = internalRef;
|
|
951
|
+
allGridPropsRef.current.rowData = props.rowData;
|
|
952
|
+
allGridPropsRef.current.getRowId = props.getRowId;
|
|
953
|
+
allGridPropsRef.current.theme = themeObject;
|
|
954
|
+
allGridPropsRef.current.columnDefs = memoizedColumnDefs;
|
|
955
|
+
allGridPropsRef.current.defaultColDef = memoizedDefaultColDef;
|
|
956
|
+
allGridPropsRef.current.onCellEditingStarted = memoizedOnCellEditingStarted;
|
|
957
|
+
allGridPropsRef.current.onCellDoubleClicked = memoizedOnCellDoubleClicked;
|
|
958
|
+
allGridPropsRef.current.onCellValueChanged = memoizedOnCellValueChanged;
|
|
959
|
+
allGridPropsRef.current.postSort = memoizedPostSort;
|
|
960
|
+
allGridPropsRef.current.onGridReady = memoizedOnGridReady;
|
|
961
|
+
allGridPropsRef.current.onRowDataChanged = memoizedOnRowDataChanged;
|
|
962
|
+
allGridPropsRef.current.onRowDataUpdated = memoizedOnRowDataUpdated;
|
|
963
|
+
allGridPropsRef.current.onRangeSelectionChanged = RhPlusRangeSelectionChanged;
|
|
964
|
+
allGridPropsRef.current.context = memoizedContext;
|
|
965
|
+
allGridPropsRef.current.components = components;
|
|
966
|
+
|
|
967
|
+
// Další AG Grid props
|
|
968
|
+
allGridPropsRef.current.rowModelType = props.rowModelType;
|
|
969
|
+
allGridPropsRef.current.rowSelection = props.rowSelection;
|
|
970
|
+
allGridPropsRef.current.enableRangeSelection = props.enableRangeSelection;
|
|
971
|
+
allGridPropsRef.current.enableRangeHandle = props.enableRangeHandle;
|
|
972
|
+
allGridPropsRef.current.enableFillHandle = props.enableFillHandle;
|
|
973
|
+
allGridPropsRef.current.suppressRowClickSelection = props.suppressRowClickSelection;
|
|
974
|
+
allGridPropsRef.current.singleClickEdit = props.singleClickEdit;
|
|
975
|
+
allGridPropsRef.current.stopEditingWhenCellsLoseFocus = props.stopEditingWhenCellsLoseFocus;
|
|
976
|
+
allGridPropsRef.current.rowClass = props.rowClass;
|
|
977
|
+
allGridPropsRef.current.rowStyle = props.rowStyle;
|
|
978
|
+
allGridPropsRef.current.getRowClass = props.getRowClass;
|
|
979
|
+
allGridPropsRef.current.getRowStyle = props.getRowStyle;
|
|
980
|
+
allGridPropsRef.current.animateRows = props.animateRows;
|
|
981
|
+
allGridPropsRef.current.suppressCellFocus = props.suppressCellFocus;
|
|
982
|
+
allGridPropsRef.current.suppressMenuHide = props.suppressMenuHide;
|
|
983
|
+
allGridPropsRef.current.enableCellTextSelection = props.enableCellTextSelection;
|
|
984
|
+
allGridPropsRef.current.ensureDomOrder = props.ensureDomOrder;
|
|
985
|
+
allGridPropsRef.current.suppressRowTransform = props.suppressRowTransform;
|
|
986
|
+
allGridPropsRef.current.suppressColumnVirtualisation = props.suppressColumnVirtualisation;
|
|
987
|
+
allGridPropsRef.current.suppressRowVirtualisation = props.suppressRowVirtualisation;
|
|
988
|
+
allGridPropsRef.current.tooltipShowDelay = props.tooltipShowDelay;
|
|
989
|
+
allGridPropsRef.current.tooltipHideDelay = props.tooltipHideDelay;
|
|
990
|
+
allGridPropsRef.current.tooltipMouseTrack = props.tooltipMouseTrack;
|
|
991
|
+
allGridPropsRef.current.gridId = props.gridId;
|
|
992
|
+
allGridPropsRef.current.id = props.id;
|
|
993
|
+
allGridPropsRef.current.gridName = props.gridName;
|
|
994
|
+
allGridPropsRef.current.getContextMenuItems = props.getContextMenuItems;
|
|
995
|
+
|
|
996
|
+
// Grid Layout event handlers - stabilní wrappery (eliminuje rerendery)
|
|
997
|
+
// ✅ FIX #12: Používáme stabilní wrappery místo props → eliminuje rerendery při změně props
|
|
998
|
+
allGridPropsRef.current.onColumnMoved = stableOnColumnMoved;
|
|
999
|
+
allGridPropsRef.current.onDragStopped = stableOnDragStopped;
|
|
1000
|
+
allGridPropsRef.current.onColumnVisible = stableOnColumnVisible;
|
|
1001
|
+
allGridPropsRef.current.onColumnPinned = stableOnColumnPinned;
|
|
1002
|
+
allGridPropsRef.current.onColumnResized = stableOnColumnResized;
|
|
1003
|
+
|
|
1004
|
+
// ✅ gridOptions support - spread additional AG-Grid props
|
|
1005
|
+
// Přidává POUZE hodnoty které ještě NEJSOU nastaveny výše (nejnižší priorita)
|
|
1006
|
+
if (props.gridOptions && typeof props.gridOptions === 'object') {
|
|
1007
|
+
for (const key in props.gridOptions) {
|
|
1008
|
+
if (props.gridOptions[key] !== undefined && allGridPropsRef.current[key] === undefined) {
|
|
1009
|
+
allGridPropsRef.current[key] = props.gridOptions[key];
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
// ✅ AG-Grid САМО detekuje změny rowData a columnDefs
|
|
1015
|
+
// NENÍ potřeba forceUpdate - AG-Grid reaguje na změny v props automaticky!
|
|
1016
|
+
|
|
1017
|
+
// ✅ Vracíme VŽDY stejný objekt (stabilní reference)
|
|
1018
|
+
const allGridProps = allGridPropsRef.current;
|
|
1019
|
+
|
|
1020
|
+
// State pro sledování, kdy je API ready
|
|
1021
|
+
const [isApiReady, setIsApiReady] = React.useState(false);
|
|
1022
|
+
|
|
1023
|
+
// Nastavení Quick Filter přes API (podle AG-Grid v33 dokumentace)
|
|
1024
|
+
// Tento useEffect se volá při změně quickFilterText NEBO když API je ready
|
|
1025
|
+
React.useEffect(() => {
|
|
1026
|
+
if (internalRef.current?.api && !internalRef.current.api.isDestroyed?.()) {
|
|
1027
|
+
// ✅ FIX AG-Grid v35: Zajistit že quickFilterText není null/undefined
|
|
1028
|
+
// AG-Grid v35 interně volá .toString() na této hodnotě bez null checku
|
|
1029
|
+
const safeQuickFilterText = quickFilterText ?? '';
|
|
1030
|
+
internalRef.current.api.setGridOption("quickFilterText", safeQuickFilterText);
|
|
1031
|
+
}
|
|
1032
|
+
}, [quickFilterText, isApiReady]);
|
|
1033
|
+
|
|
1034
|
+
// Status bar se zobrazuje pouze v simple mode
|
|
1035
|
+
const showStatusBar = notificationMode === 'simple' && aggregationDataRef.current;
|
|
1036
|
+
|
|
1037
|
+
// ✅ FIX: Rezervovat místo pro status bar pouze když má data
|
|
1038
|
+
// Grid se dynamicky rozšíří/zmenší podle přítomnosti status baru
|
|
1039
|
+
const shouldReserveSpace = showStatusBar;
|
|
1040
|
+
const gridContainerStyle = shouldReserveSpace
|
|
1041
|
+
? { height: `calc(100% - ${statusBarHeight}px)`, width: '100%', position: 'relative' }
|
|
1042
|
+
: { height: '100%', width: '100%', position: 'relative' };
|
|
1043
|
+
|
|
1044
|
+
return (
|
|
1045
|
+
<div style={{ height: '100%', width: '100%', position: 'relative' }}>
|
|
1046
|
+
{/* AG Grid - fixní výška, nikdy se nemění */}
|
|
1047
|
+
<div style={gridContainerStyle}>
|
|
1048
|
+
<AgGridReact {...allGridProps} />
|
|
1049
|
+
</div>
|
|
1050
|
+
|
|
1051
|
+
{/* Aggregation Status Bar - absolutně pozicovaný na spodku */}
|
|
1052
|
+
{shouldReserveSpace && (
|
|
1053
|
+
<div style={{
|
|
1054
|
+
position: 'absolute',
|
|
1055
|
+
bottom: 0,
|
|
1056
|
+
left: 0,
|
|
1057
|
+
right: 0,
|
|
1058
|
+
height: `${statusBarHeight}px`,
|
|
1059
|
+
opacity: showStatusBar ? 1 : 0,
|
|
1060
|
+
visibility: showStatusBar ? 'visible' : 'hidden',
|
|
1061
|
+
pointerEvents: showStatusBar ? 'auto' : 'none',
|
|
1062
|
+
zIndex: 10,
|
|
1063
|
+
// GPU acceleration pro plynulejší zobrazení
|
|
1064
|
+
transform: 'translateZ(0)',
|
|
1065
|
+
willChange: 'opacity'
|
|
1066
|
+
}}>
|
|
1067
|
+
<AggregationStatusBar
|
|
1068
|
+
data={aggregationDataRef.current || {}}
|
|
1069
|
+
metrics={statusBarMetrics}
|
|
1070
|
+
height={statusBarHeight}
|
|
1071
|
+
onSwitchToFull={handleSwitchToFull}
|
|
1072
|
+
/>
|
|
1073
|
+
</div>
|
|
1074
|
+
)}
|
|
1075
|
+
|
|
1076
|
+
{/* Bulk Edit Floating Button */}
|
|
1077
|
+
{enableBulkEdit && floatingButton.visible && (
|
|
1078
|
+
<BulkEditButton
|
|
1079
|
+
visible={floatingButton.visible}
|
|
1080
|
+
position={floatingButton.position}
|
|
1081
|
+
range={floatingButton.range}
|
|
1082
|
+
column={floatingButton.column}
|
|
1083
|
+
cellCount={floatingButton.cellCount}
|
|
1084
|
+
rowsContainer={floatingButton.rowsContainer}
|
|
1085
|
+
gridApi={internalRef.current?.api}
|
|
1086
|
+
editPopover={editPopover}
|
|
1087
|
+
onOpenPopover={handleOpenPopover}
|
|
1088
|
+
onValueChange={handleValueChange}
|
|
1089
|
+
onSubmit={handleSubmitEdit}
|
|
1090
|
+
onCancel={handleCancelEdit}
|
|
1091
|
+
/>
|
|
1092
|
+
)}
|
|
1093
|
+
</div>
|
|
1094
|
+
);
|
|
1095
|
+
});
|
|
1096
|
+
|
|
1097
|
+
// ========== PERFORMANCE OPTIMIZATION: React.memo ==========
|
|
1098
|
+
// Refactored: Používá createGridComparison utility místo manuálního deep comparison
|
|
1099
|
+
// Eliminováno ~120 řádků duplicitního kódu, zachována stejná funkcionalita
|
|
1100
|
+
// Diagnostic mode: zapnutý pouze ve development módu
|
|
1101
|
+
export default React.memo(AgGrid, createGridComparison(
|
|
1102
|
+
process.env.NODE_ENV !== 'production' // Diagnostic mode pouze ve development
|
|
1103
|
+
));
|
|
1104
|
+
|
|
1105
|
+
export {
|
|
1106
|
+
useBulkCellEdit,
|
|
1107
|
+
BulkEditButton,
|
|
1108
|
+
BulkEditPopover,
|
|
1109
|
+
BulkEditSelect,
|
|
1110
|
+
BulkEditDatePicker,
|
|
1111
|
+
BulkEditModule,
|
|
1112
|
+
BulkEditInput,
|
|
1113
|
+
BulkEditTagsSelect
|
|
1114
|
+
} from './BulkEdit';
|
|
1115
|
+
|
|
1116
|
+
export {
|
|
1117
|
+
default as CheckboxRenderer
|
|
1118
|
+
} from './Renderers/CheckboxRenderer';
|
|
1119
|
+
|
|
1120
|
+
export * from './Renderers';
|
|
1121
1121
|
export * from './Editors';
|