@bit.rhplus/ag-grid 0.0.55 → 0.0.57

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 CHANGED
@@ -1,657 +1,670 @@
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 IconRenderer from './Renderers/IconRenderer';
14
- import ImageRenderer from './Renderers/ImageRenderer';
15
- import StateRenderer from './Renderers/StateRenderer';
16
- import SelectRenderer from './Renderers/SelectRenderer';
17
- import ButtonRenderer from './Renderers/ButtonRenderer';
18
- import CountrySelectRenderer from './Renderers/CountrySelectRenderer';
19
- import NotificationOptionsInit from "./NotificationOptions";
20
- import { notification } from "antd";
21
- import Aggregations, { hashRanges } from "./Aggregations";
22
- import { useBulkCellEdit, BulkEditButton } from './BulkEdit';
23
- import {
24
- ModuleRegistry,
25
- themeAlpine,
26
- themeBalham,
27
- themeMaterial,
28
- themeQuartz,
29
- } from "ag-grid-community";
30
-
31
- const themes = [
32
- { id: "themeQuartz", theme: themeQuartz },
33
- { id: "themeBalham", theme: themeBalham },
34
- { id: "themeMaterial", theme: themeMaterial },
35
- { id: "themeAlpine", theme: themeAlpine },
36
- ];
37
-
38
- const AgGrid = React.forwardRef((props, ref) => {
39
- const internalRef = React.useRef();
40
- const {
41
- theme = "themeAlpine", // Default theme
42
- rowData = [],
43
- newRowFlash = true,
44
- updatedRowFlash = false,
45
- onGridReady,
46
- enableNotification,
47
- notificationOptions: {
48
- notificationHead = NotificationOptionsInit.head,
49
- notificationBody = NotificationOptionsInit.body,
50
- style = NotificationOptionsInit.style,
51
- placement = NotificationOptionsInit.placement,
52
- } = {},
53
- // Bulk Edit props
54
- enableBulkEdit = false,
55
- bulkEditOptions = {},
56
- bulkEditAccessToken,
57
- onBulkEditStart,
58
- onBulkEditComplete,
59
- } = props;
60
-
61
- // Najít theme objekt podle názvu z props
62
- const themeObject = React.useMemo(() => {
63
- const foundTheme = themes.find(t => t.id === theme);
64
- return foundTheme ? foundTheme.theme : themeQuartz; // Fallback na themeQuartz
65
- }, [theme]);
66
- const [, setIsGridReady] = React.useState(false);
67
- const [isSelecting, setIsSelecting] = React.useState(false);
68
- const previousRowDataRef = React.useRef(rowData);
69
-
70
- // Bulk Edit hook
71
- const {
72
- floatingButton,
73
- editPopover,
74
- handleRangeChange,
75
- handleOpenPopover,
76
- handleSubmitEdit,
77
- handleCancelEdit,
78
- handleValueChange,
79
- } = useBulkCellEdit(internalRef, {
80
- enabled: enableBulkEdit,
81
- accessToken: bulkEditAccessToken,
82
- onBulkEditStart,
83
- onBulkEditComplete,
84
- ...bulkEditOptions,
85
- });
86
-
87
-
88
- // ========== PERFORMANCE OPTIMIZATION: Shallow comparison helper ==========
89
- // Shallow porovnání objektů - 100x rychlejší než JSON.stringify
90
- const shallowEqual = React.useCallback((obj1, obj2) => {
91
- if (obj1 === obj2) return true;
92
- if (!obj1 || !obj2) return false;
93
-
94
- const keys1 = Object.keys(obj1);
95
- const keys2 = Object.keys(obj2);
96
-
97
- if (keys1.length !== keys2.length) return false;
98
-
99
- for (let key of keys1) {
100
- if (obj1[key] !== obj2[key]) return false;
101
- }
102
-
103
- return true;
104
- }, []);
105
-
106
- // Memoizované funkce pro detekci změn v rowData
107
- const findNewRows = React.useCallback((oldData, newData) => {
108
- const oldIds = new Set(oldData.map((row) => row.id));
109
- return newData.filter((row) => !oldIds.has(row.id));
110
- }, []);
111
-
112
- const findUpdatedRows = React.useCallback((oldData, newData) => {
113
- const oldDataMap = new Map(oldData.map((row) => [row.id, row]));
114
- return newData.filter((newRow) => {
115
- const oldRow = oldDataMap.get(newRow.id);
116
- if (!oldRow) return false; // Nový řádek, ne aktualizovaný
117
-
118
- // OPTIMALIZACE: Shallow comparison místo JSON.stringify (100x rychlejší!)
119
- return !shallowEqual(oldRow, newRow);
120
- });
121
- }, [shallowEqual]);
122
-
123
- React.useImperativeHandle(ref, () => internalRef.current, [internalRef]);
124
-
125
- // Throttle timer pro notifikace - zobrazuje se BĚHEM označování s 100ms throttle
126
- const notificationThrottleRef = React.useRef(null);
127
- const notificationLastCallRef = React.useRef(0);
128
- const lastRangeHashRef = React.useRef(null);
129
-
130
- // OPTIMALIZACE: Helper funkce pro aktualizaci aggregace notifikace s cache check
131
- // Volá se BĚHEM označování s 100ms throttle pro real-time feedback
132
- const updateAggregationNotification = React.useCallback((event) => {
133
- // Lightweight cache check PŘED voláním Aggregations()
134
- const ranges = event.api.getCellRanges();
135
- const currentRangeHash = hashRanges(ranges);
136
-
137
- // Žádné ranges nebo jen jedna buňka - zavřít notifikaci
138
- if (!ranges || ranges.length === 0 || !ranges[0]?.startRow) {
139
- const gridId = props.gridName || props.id || 'default';
140
- const key = `aggregation-grid-${gridId}`;
141
- lastRangeHashRef.current = null;
142
- notification.destroy(key);
143
- return;
144
- }
145
-
146
- // Cache hit - ranges se nezměnily, skip
147
- if (currentRangeHash === lastRangeHashRef.current) {
148
- return;
149
- }
150
-
151
- // Cache miss - spočítat aggregace
152
- const messageInfo = Aggregations(internalRef);
153
-
154
- const gridId = props.gridName || props.id || 'default';
155
- const key = `aggregation-grid-${gridId}`;
156
-
157
- if (messageInfo.count > 1) {
158
- // Uložit hash pro příští porovnání
159
- lastRangeHashRef.current = currentRangeHash;
160
-
161
- if (props.onAggregationChanged) {
162
- props.onAggregationChanged(messageInfo);
163
- }
164
-
165
- // Dynamický style s pointer-events podle stavu označování
166
- const dynamicStyle = {
167
- ...style,
168
- pointerEvents: isSelecting ? 'none' : 'auto'
169
- };
170
-
171
- // Aktualizace existující notifikace nebo vytvoření nové
172
- notification.info({
173
- key,
174
- message: notificationHead(messageInfo),
175
- description: notificationBody(messageInfo),
176
- duration: 0,
177
- style: dynamicStyle,
178
- placement,
179
- });
180
- } else {
181
- // Jen jedna buňka - zavřít notifikaci
182
- lastRangeHashRef.current = null;
183
- notification.destroy(key);
184
- }
185
- }, [internalRef, props, notificationHead, notificationBody, style, placement, isSelecting]);
186
-
187
- // Detekce konce označování pomocí mouseup event
188
- React.useEffect(() => {
189
- const handleMouseUp = () => {
190
- // OPTIMALIZACE: Notifikace se už zobrazuje BĚHEM označování (s debounce),
191
- // takže tady jen ukončíme isSelecting stav
192
- setTimeout(() => {
193
- setIsSelecting(false);
194
- }, 50);
195
- };
196
-
197
- document.addEventListener('mouseup', handleMouseUp);
198
- document.addEventListener('touchend', handleMouseUp); // Pro touch zařízení
199
-
200
- return () => {
201
- document.removeEventListener('mouseup', handleMouseUp);
202
- document.removeEventListener('touchend', handleMouseUp);
203
- };
204
- }, []);
205
-
206
- // Helper funkce pro nastavení pointer-events na notifikace
207
- const setNotificationPointerEvents = React.useCallback((enable) => {
208
- const notifications = document.querySelectorAll('.ant-notification');
209
-
210
- notifications.forEach((notif) => {
211
- if (enable) {
212
- // Obnovit pointer-events na notifikaci
213
- const original = notif.dataset.originalPointerEvents || 'auto';
214
- notif.style.pointerEvents = original;
215
- notif.style.removeProperty('pointer-events');
216
- delete notif.dataset.originalPointerEvents;
217
-
218
- // Obnovit pointer-events na všechny child elementy
219
- const allChildren = notif.querySelectorAll('*');
220
- allChildren.forEach((child) => {
221
- child.style.removeProperty('pointer-events');
222
- delete child.dataset.originalPointerEvents;
223
- });
224
- } else {
225
- // Zakázat pointer-events na notifikaci
226
- if (!notif.dataset.originalPointerEvents) {
227
- notif.dataset.originalPointerEvents = notif.style.pointerEvents || 'auto';
228
- }
229
- notif.style.setProperty('pointer-events', 'none', 'important');
230
-
231
- // Zakázat pointer-events na všechny child elementy
232
- const allChildren = notif.querySelectorAll('*');
233
- allChildren.forEach((child) => {
234
- if (!child.dataset.originalPointerEvents) {
235
- child.dataset.originalPointerEvents = child.style.pointerEvents || '';
236
- }
237
- child.style.setProperty('pointer-events', 'none', 'important');
238
- });
239
- }
240
- });
241
- }, []);
242
-
243
- // Dynamicky přidávat/odebírat pointer-events na notifikace podle isSelecting stavu
244
- React.useEffect(() => {
245
- if (isSelecting) {
246
- document.body.classList.add('ag-grid-selecting');
247
-
248
- // Nastavit pointer-events na existující notifikace
249
- setNotificationPointerEvents(false);
250
-
251
- // KRITICKÉ: Sledovat DOM změny - když se objeví nová notifikace během označování
252
- const observer = new MutationObserver((mutations) => {
253
- mutations.forEach((mutation) => {
254
- mutation.addedNodes.forEach((node) => {
255
- if (node.nodeType === 1) { // Element node
256
- // Zkontrolovat jestli je to notifikace nebo obsahuje notifikaci
257
- if (node.classList?.contains('ant-notification') ||
258
- node.querySelector?.('.ant-notification')) {
259
- setNotificationPointerEvents(false);
260
- }
261
- }
262
- });
263
- });
264
- });
265
-
266
- // Sledovat změny v document.body
267
- observer.observe(document.body, {
268
- childList: true,
269
- subtree: true
270
- });
271
-
272
- // Uložit observer pro cleanup
273
- return () => {
274
- observer.disconnect();
275
- };
276
- } else {
277
- document.body.classList.remove('ag-grid-selecting');
278
-
279
- // Malé zpoždění před obnovením pointer-events (100ms)
280
- // Zajistí že notifikace jsou interaktivní až když skutečně skončí označování
281
- setTimeout(() => {
282
- setNotificationPointerEvents(true);
283
- }, 100);
284
- }
285
- }, [isSelecting, setNotificationPointerEvents]);
286
-
287
- // Cleanup notifikací a timerů při zničení komponenty
288
- React.useEffect(() => {
289
- return () => {
290
- // Clear throttle timer
291
- if (notificationThrottleRef.current) {
292
- clearTimeout(notificationThrottleRef.current);
293
- }
294
-
295
- if (enableNotification) {
296
- const gridId = props.gridName || props.id || 'default';
297
- const key = `aggregation-grid-${gridId}`;
298
- notification.destroy(key);
299
- }
300
- };
301
- }, [enableNotification, props.gridName, props.id]);
302
-
303
- React.useEffect(() => {
304
- // VYPNUTO: Cell flash je globálně vypnutý
305
- return;
306
-
307
- if (!newRowFlash && !updatedRowFlash) return;
308
-
309
- const previousRowData = previousRowDataRef.current;
310
- const addedRows = newRowFlash ? findNewRows(previousRowData, rowData) : [];
311
- const updatedRows = updatedRowFlash ? findUpdatedRows(previousRowData, rowData) : [];
312
-
313
- if (addedRows.length > 0 || updatedRows.length > 0) {
314
- setTimeout(() => {
315
- try {
316
- // Bezpečnostní kontrola API dostupnosti
317
- if (!internalRef.current?.api || internalRef.current.api.isDestroyed?.()) {
318
- return;
319
- }
320
-
321
- // Modern AG-Grid (33+): getColumnState() moved to main api
322
- const columnApi = internalRef.current?.columnApi || internalRef.current?.api;
323
- if (!columnApi?.getColumnState) {
324
- return;
325
- }
326
-
327
- const columnStates = columnApi.getColumnState();
328
- const allColumns = columnStates
329
- .filter(colState => colState.colId) // Filtrujeme pouze platné colId
330
- .map((colState) => colState.colId);
331
-
332
- // Kontrola, že máme platné sloupce
333
- if (allColumns.length === 0) {
334
- return;
335
- }
336
-
337
- // Flash efekt pro nové řádky (používá defaultní zelená barva AG Grid)
338
- if (addedRows.length > 0) {
339
- const newRowNodes = [];
340
- internalRef.current.api.forEachNode((node) => {
341
- if (addedRows.some((row) => row.id === node.data.id)) {
342
- newRowNodes.push(node);
343
- }
344
- });
345
-
346
- if (newRowNodes.length > 0) {
347
- internalRef.current.api.flashCells({
348
- rowNodes: newRowNodes,
349
- columns: allColumns,
350
- flashDelay: 0,
351
- fadeDelay: 1000,
352
- });
353
- }
354
- }
355
-
356
- // Flash efekt pro aktualizované řádky (modrá barva)
357
- if (updatedRows.length > 0) {
358
- const updatedRowNodes = [];
359
- internalRef.current.api.forEachNode((node) => {
360
- if (updatedRows.some((row) => row.id === node.data.id)) {
361
- updatedRowNodes.push(node);
362
- }
363
- });
364
-
365
- if (updatedRowNodes.length > 0) {
366
- // Použijeme vlastní CSS animaci pro modrou flash
367
- updatedRowNodes.forEach(node => {
368
- const rowElement = node.eGridRow || document.querySelector(`[row-id="${node.data.id}"]`);
369
- if (rowElement) {
370
- rowElement.classList.add('ag-row-flash-updated');
371
- setTimeout(() => {
372
- rowElement.classList.remove('ag-row-flash-updated');
373
- }, 1000);
374
- }
375
- });
376
- }
377
- }
378
- } catch (error) {
379
- // Ignorujeme chybu a pokračujeme bez crash aplikace
380
- }
381
- }, 100); // Zvýšený timeout pro stabilizaci gridu
382
- }
383
-
384
- previousRowDataRef.current = rowData;
385
- }, [rowData, newRowFlash, updatedRowFlash]);
386
-
387
- const RhPlusRangeSelectionChanged = React.useCallback(
388
- (event) => {
389
- // Detekovat začátek označování - nastavit isSelecting na true
390
- setIsSelecting(true);
391
-
392
- // 1. OPTIMALIZACE: Zobrazení notifikace BĚHEM označování s THROTTLE (100ms)
393
- // Throttle = spustí okamžitě první event, pak každých 100ms během označování
394
- // Cache check v updateAggregationNotification zajistí, že se agregace nepočítají znovu pokud se range nezměnil
395
- if (enableNotification) {
396
- const now = Date.now();
397
- const timeSinceLastCall = now - notificationLastCallRef.current;
398
-
399
- // První volání NEBO uplynulo 100ms od posledního volání
400
- if (timeSinceLastCall >= 100) {
401
- // Okamžité volání
402
- notificationLastCallRef.current = now;
403
- if (internalRef?.current) {
404
- updateAggregationNotification(event);
405
- }
406
- } else {
407
- // Naplánovat volání za zbývající čas (trailing edge)
408
- if (notificationThrottleRef.current) {
409
- clearTimeout(notificationThrottleRef.current);
410
- }
411
-
412
- notificationThrottleRef.current = setTimeout(() => {
413
- notificationLastCallRef.current = Date.now();
414
- if (internalRef?.current) {
415
- updateAggregationNotification(event);
416
- }
417
- }, 100 - timeSinceLastCall);
418
- }
419
- }
420
-
421
- // 2. ✅ OPTIMALIZACE: Bulk edit handler BEZ debounce - okamžité zobrazení ikony
422
- // Lightweight validace v useBulkCellEdit zajistí okamžité zobrazení
423
- // Těžká validace proběhne s 15ms debounce uvnitř useBulkCellEdit
424
- if (enableBulkEdit) {
425
- handleRangeChange(event);
426
- }
427
-
428
- // 3. Custom onRangeSelectionChanged callback - bez debounce
429
- if (props.onRangeSelectionChanged) props.onRangeSelectionChanged(event);
430
- },
431
- [enableNotification, enableBulkEdit, handleRangeChange, props.onRangeSelectionChanged, updateAggregationNotification]
432
- );
433
-
434
- const AgGridOnGridReady = (event, options) => {
435
- if (onGridReady) {
436
- onGridReady(event, options);
437
- }
438
-
439
- // Nejprve nastavíme API reference pro interní použití
440
- if (internalRef.current) {
441
- internalRef.current.api = event.api;
442
- internalRef.current.columnApi = event.columnApi || event.api; // fallback for modern AG-Grid
443
- }
444
-
445
- // Registruj callback pro AG Grid transactions (SignalR optimalizace)
446
- if (event.api && !window.agGridTransactionCallback) {
447
- window.agGridTransactionCallback = (transactionData) => {
448
- try {
449
- if (!event.api || event.api.isDestroyed?.()) return;
450
-
451
- const { operation, records } = transactionData;
452
-
453
- switch (operation) {
454
- case 'add':
455
- event.api.applyTransaction({ add: records });
456
- // Flash efekt pro nové řádky
457
- setTimeout(() => {
458
- records.forEach(record => {
459
- const rowNode = event.api.getRowNode(record.id);
460
- if (rowNode && rowNode.rowElement) {
461
- rowNode.rowElement.classList.add('ag-row-flash-created');
462
- setTimeout(() => {
463
- rowNode.rowElement.classList.remove('ag-row-flash-created');
464
- }, 1000);
465
- }
466
- });
467
- }, 100);
468
- break;
469
-
470
- case 'update':
471
- event.api.applyTransaction({ update: records });
472
- // Flash efekt pro aktualizované řádky
473
- setTimeout(() => {
474
- records.forEach(record => {
475
- const rowNode = event.api.getRowNode(record.id);
476
- if (rowNode && rowNode.rowElement) {
477
- rowNode.rowElement.classList.add('ag-row-flash-updated');
478
- setTimeout(() => {
479
- rowNode.rowElement.classList.remove('ag-row-flash-updated');
480
- }, 1000);
481
- }
482
- });
483
- }, 100);
484
- break;
485
-
486
- case 'remove':
487
- // Flash efekt před smazáním
488
- records.forEach(record => {
489
- const rowNode = event.api.getRowNode(record.id);
490
- if (rowNode && rowNode.rowElement) {
491
- rowNode.rowElement.classList.add('ag-row-flash-deleted');
492
- }
493
- });
494
-
495
- setTimeout(() => {
496
- event.api.applyTransaction({ remove: records });
497
- }, 500); // Krátké zpoždění pro zobrazení flash efektu
498
- break;
499
- }
500
- } catch (error) {
501
- // Ignorujeme chyby
502
- }
503
- };
504
- }
505
-
506
- // Pak zavoláme parent onGridReady handler, pokud existuje
507
- // Toto je kritické pro správné fungování bit/ui/grid a GridLayout
508
- if (options.onGridReady) {
509
- try {
510
- options.onGridReady(event);
511
- } catch (error) {
512
- // Error handling without console output
513
- }
514
- }
515
-
516
- // Nakonec nastavíme grid ready state s timeout
517
- setTimeout(() => {
518
- setIsGridReady(true);
519
- }, 1000);
520
- };
521
-
522
- const components = React.useMemo(() => {
523
- return {
524
- checkboxRenderer: CheckboxRenderer,
525
- selectRenderer: SelectRenderer,
526
- countrySelectRenderer: CountrySelectRenderer,
527
- booleanRenderer: BooleanRenderer,
528
- buttonRenderer: ButtonRenderer,
529
- iconRenderer: IconRenderer,
530
- imageRenderer: ImageRenderer,
531
- stateRenderer: StateRenderer,
532
- ...props.frameworkComponents,
533
- };
534
- }, [props.frameworkComponents]);
535
-
536
- // ========== PERFORMANCE OPTIMIZATION: Memoizované event handlers ==========
537
- const memoizedOnCellEditingStarted = React.useCallback(
538
- (event) => RhPlusOnCellEditingStarted(event, props),
539
- [props.onCellEditingStarted]
540
- );
541
-
542
- const memoizedOnCellDoubleClicked = React.useCallback(
543
- (event) => RhPlusOnCellDoubleClicked(event, props),
544
- [props.onCellDoubleClicked]
545
- );
546
-
547
- const memoizedOnCellValueChanged = React.useCallback(
548
- (event) => RhPlusOnCellValueChanged(event, props),
549
- [props.onCellValueChanged]
550
- );
551
-
552
- const memoizedPostSort = React.useCallback(
553
- (event) => AgGridPostSort(event, props),
554
- [props.postSort]
555
- );
556
-
557
- const memoizedOnGridReady = React.useCallback(
558
- (event, options) => AgGridOnGridReady(event, props),
559
- [onGridReady]
560
- );
561
-
562
- const memoizedOnRowDataChanged = React.useCallback(
563
- (event) => AgGridOnRowDataChanged(event, props),
564
- [props.onRowDataChanged]
565
- );
566
-
567
- const memoizedOnRowDataUpdated = React.useCallback(
568
- (event) => AgGridOnRowDataUpdated(event, props),
569
- [props.onRowDataUpdated]
570
- );
571
-
572
- // Memoizovaný context object
573
- const memoizedContext = React.useMemo(() => ({
574
- componentParent: { ...props }
575
- }), [props.context, props.gridName, props.id]);
576
-
577
- // Memoizovaný defaultColDef
578
- const memoizedDefaultColDef = React.useMemo(() => ({
579
- ...props.defaultColDef,
580
- suppressHeaderMenuButton: true,
581
- suppressHeaderFilterButton: true,
582
- suppressMenu: true,
583
- }), [props.defaultColDef]);
584
-
585
- // ========== PERFORMANCE OPTIMIZATION: Memoizovaný allGridProps ==========
586
- const allGridProps = React.useMemo(() => ({
587
- ref: internalRef,
588
- ...props,
589
- theme: themeObject,
590
- columnDefs: AgGridColumns(props.columnDefs, props),
591
- defaultColDef: memoizedDefaultColDef,
592
- onCellEditingStarted: memoizedOnCellEditingStarted,
593
- onCellDoubleClicked: memoizedOnCellDoubleClicked,
594
- onCellValueChanged: memoizedOnCellValueChanged,
595
- postSort: memoizedPostSort,
596
- onGridReady: memoizedOnGridReady,
597
- onRowDataChanged: memoizedOnRowDataChanged,
598
- onRowDataUpdated: memoizedOnRowDataUpdated,
599
- onRangeSelectionChanged: RhPlusRangeSelectionChanged,
600
- context: memoizedContext,
601
- components,
602
- }), [
603
- internalRef,
604
- themeObject,
605
- props.columnDefs,
606
- memoizedDefaultColDef,
607
- memoizedOnCellEditingStarted,
608
- memoizedOnCellDoubleClicked,
609
- memoizedOnCellValueChanged,
610
- memoizedPostSort,
611
- memoizedOnGridReady,
612
- memoizedOnRowDataChanged,
613
- memoizedOnRowDataUpdated,
614
- RhPlusRangeSelectionChanged,
615
- memoizedContext,
616
- components,
617
- ]);
618
-
619
- return (
620
- <>
621
- <AgGridReact {...allGridProps} />
622
-
623
- {/* Bulk Edit Floating Button */}
624
- {enableBulkEdit && floatingButton.visible && (
625
- <BulkEditButton
626
- visible={floatingButton.visible}
627
- position={floatingButton.position}
628
- range={floatingButton.range}
629
- column={floatingButton.column}
630
- cellCount={floatingButton.cellCount}
631
- rowsContainer={floatingButton.rowsContainer}
632
- editPopover={editPopover}
633
- onOpenPopover={handleOpenPopover}
634
- onValueChange={handleValueChange}
635
- onSubmit={handleSubmitEdit}
636
- onCancel={handleCancelEdit}
637
- />
638
- )}
639
- </>
640
- );
641
- });
642
-
643
- export default AgGrid;
644
-
645
- export {
646
- useBulkCellEdit,
647
- BulkEditButton,
648
- BulkEditPopover,
649
- BulkEditSelect,
650
- BulkEditDatePicker,
651
- BulkEditModule,
652
- BulkEditInput
653
- } from './BulkEdit';
654
-
655
- export {
656
- default as CheckboxRenderer
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 IconRenderer from './Renderers/IconRenderer';
14
+ import ImageRenderer from './Renderers/ImageRenderer';
15
+ import StateRenderer from './Renderers/StateRenderer';
16
+ import SelectRenderer from './Renderers/SelectRenderer';
17
+ import ButtonRenderer from './Renderers/ButtonRenderer';
18
+ import CountrySelectRenderer from './Renderers/CountrySelectRenderer';
19
+ import NotificationOptionsInit from "./NotificationOptions";
20
+ import { notification } from "antd";
21
+ import Aggregations, { hashRanges } from "./Aggregations";
22
+ import { useBulkCellEdit, BulkEditButton } from './BulkEdit';
23
+ import {
24
+ ModuleRegistry,
25
+ themeAlpine,
26
+ themeBalham,
27
+ themeMaterial,
28
+ themeQuartz,
29
+ } from "ag-grid-community";
30
+
31
+ const themes = [
32
+ { id: "themeQuartz", theme: themeQuartz },
33
+ { id: "themeBalham", theme: themeBalham },
34
+ { id: "themeMaterial", theme: themeMaterial },
35
+ { id: "themeAlpine", theme: themeAlpine },
36
+ ];
37
+
38
+ const AgGrid = React.forwardRef((props, ref) => {
39
+ const internalRef = React.useRef();
40
+ const {
41
+ theme = "themeAlpine", // Default theme
42
+ rowData = [],
43
+ newRowFlash = true,
44
+ updatedRowFlash = false,
45
+ onGridReady,
46
+ enableNotification,
47
+ notificationOptions: {
48
+ notificationHead = NotificationOptionsInit.head,
49
+ notificationBody = NotificationOptionsInit.body,
50
+ style = NotificationOptionsInit.style,
51
+ placement = NotificationOptionsInit.placement,
52
+ } = {},
53
+ // Bulk Edit props
54
+ enableBulkEdit = false,
55
+ bulkEditOptions = {},
56
+ bulkEditAccessToken,
57
+ onBulkEditStart,
58
+ onBulkEditComplete,
59
+ // SignalR transaction support
60
+ queryKey, // Identifikátor pro SignalR transactions
61
+ } = props;
62
+
63
+ // Najít theme objekt podle názvu z props
64
+ const themeObject = React.useMemo(() => {
65
+ const foundTheme = themes.find(t => t.id === theme);
66
+ return foundTheme ? foundTheme.theme : themeQuartz; // Fallback na themeQuartz
67
+ }, [theme]);
68
+ const [, setIsGridReady] = React.useState(false);
69
+ const [isSelecting, setIsSelecting] = React.useState(false);
70
+ const previousRowDataRef = React.useRef(rowData);
71
+
72
+ // Bulk Edit hook
73
+ const {
74
+ floatingButton,
75
+ editPopover,
76
+ handleRangeChange,
77
+ handleOpenPopover,
78
+ handleSubmitEdit,
79
+ handleCancelEdit,
80
+ handleValueChange,
81
+ } = useBulkCellEdit(internalRef, {
82
+ enabled: enableBulkEdit,
83
+ accessToken: bulkEditAccessToken,
84
+ onBulkEditStart,
85
+ onBulkEditComplete,
86
+ ...bulkEditOptions,
87
+ });
88
+
89
+
90
+ // ========== PERFORMANCE OPTIMIZATION: Shallow comparison helper ==========
91
+ // Shallow porovnání objektů - 100x rychlejší než JSON.stringify
92
+ const shallowEqual = React.useCallback((obj1, obj2) => {
93
+ if (obj1 === obj2) return true;
94
+ if (!obj1 || !obj2) return false;
95
+
96
+ const keys1 = Object.keys(obj1);
97
+ const keys2 = Object.keys(obj2);
98
+
99
+ if (keys1.length !== keys2.length) return false;
100
+
101
+ for (let key of keys1) {
102
+ if (obj1[key] !== obj2[key]) return false;
103
+ }
104
+
105
+ return true;
106
+ }, []);
107
+
108
+ // Memoizované funkce pro detekci změn v rowData
109
+ const findNewRows = React.useCallback((oldData, newData) => {
110
+ const oldIds = new Set(oldData.map((row) => row.id));
111
+ return newData.filter((row) => !oldIds.has(row.id));
112
+ }, []);
113
+
114
+ const findUpdatedRows = React.useCallback((oldData, newData) => {
115
+ const oldDataMap = new Map(oldData.map((row) => [row.id, row]));
116
+ return newData.filter((newRow) => {
117
+ const oldRow = oldDataMap.get(newRow.id);
118
+ if (!oldRow) return false; // Nový řádek, ne aktualizovaný
119
+
120
+ // ✅ OPTIMALIZACE: Shallow comparison místo JSON.stringify (100x rychlejší!)
121
+ return !shallowEqual(oldRow, newRow);
122
+ });
123
+ }, [shallowEqual]);
124
+
125
+ React.useImperativeHandle(ref, () => internalRef.current, [internalRef]);
126
+
127
+ // Throttle timer pro notifikace - zobrazuje se BĚHEM označování s 100ms throttle
128
+ const notificationThrottleRef = React.useRef(null);
129
+ const notificationLastCallRef = React.useRef(0);
130
+ const lastRangeHashRef = React.useRef(null);
131
+
132
+ // OPTIMALIZACE: Helper funkce pro aktualizaci aggregace notifikace s cache check
133
+ // Volá se BĚHEM označování s 100ms throttle pro real-time feedback
134
+ const updateAggregationNotification = React.useCallback((event) => {
135
+ // Lightweight cache check PŘED voláním Aggregations()
136
+ const ranges = event.api.getCellRanges();
137
+ const currentRangeHash = hashRanges(ranges);
138
+
139
+ // Žádné ranges nebo jen jedna buňka - zavřít notifikaci
140
+ if (!ranges || ranges.length === 0 || !ranges[0]?.startRow) {
141
+ const gridId = props.gridName || props.id || 'default';
142
+ const key = `aggregation-grid-${gridId}`;
143
+ lastRangeHashRef.current = null;
144
+ notification.destroy(key);
145
+ return;
146
+ }
147
+
148
+ // Cache hit - ranges se nezměnily, skip
149
+ if (currentRangeHash === lastRangeHashRef.current) {
150
+ return;
151
+ }
152
+
153
+ // Cache miss - spočítat aggregace
154
+ const messageInfo = Aggregations(internalRef);
155
+
156
+ const gridId = props.gridName || props.id || 'default';
157
+ const key = `aggregation-grid-${gridId}`;
158
+
159
+ if (messageInfo.count > 1) {
160
+ // Uložit hash pro příští porovnání
161
+ lastRangeHashRef.current = currentRangeHash;
162
+
163
+ if (props.onAggregationChanged) {
164
+ props.onAggregationChanged(messageInfo);
165
+ }
166
+
167
+ // Dynamický style s pointer-events podle stavu označování
168
+ const dynamicStyle = {
169
+ ...style,
170
+ pointerEvents: isSelecting ? 'none' : 'auto'
171
+ };
172
+
173
+ // Aktualizace existující notifikace nebo vytvoření nové
174
+ notification.info({
175
+ key,
176
+ message: notificationHead(messageInfo),
177
+ description: notificationBody(messageInfo),
178
+ duration: 0,
179
+ style: dynamicStyle,
180
+ placement,
181
+ });
182
+ } else {
183
+ // Jen jedna buňka - zavřít notifikaci
184
+ lastRangeHashRef.current = null;
185
+ notification.destroy(key);
186
+ }
187
+ }, [internalRef, props, notificationHead, notificationBody, style, placement, isSelecting]);
188
+
189
+ // Detekce konce označování pomocí mouseup event
190
+ React.useEffect(() => {
191
+ const handleMouseUp = () => {
192
+ // ✅ OPTIMALIZACE: Notifikace se už zobrazuje BĚHEM označování (s debounce),
193
+ // takže tady jen ukončíme isSelecting stav
194
+ setTimeout(() => {
195
+ setIsSelecting(false);
196
+ }, 50);
197
+ };
198
+
199
+ document.addEventListener('mouseup', handleMouseUp);
200
+ document.addEventListener('touchend', handleMouseUp); // Pro touch zařízení
201
+
202
+ return () => {
203
+ document.removeEventListener('mouseup', handleMouseUp);
204
+ document.removeEventListener('touchend', handleMouseUp);
205
+ };
206
+ }, []);
207
+
208
+ // Helper funkce pro nastavení pointer-events na notifikace
209
+ const setNotificationPointerEvents = React.useCallback((enable) => {
210
+ const notifications = document.querySelectorAll('.ant-notification');
211
+
212
+ notifications.forEach((notif) => {
213
+ if (enable) {
214
+ // Obnovit pointer-events na notifikaci
215
+ const original = notif.dataset.originalPointerEvents || 'auto';
216
+ notif.style.pointerEvents = original;
217
+ notif.style.removeProperty('pointer-events');
218
+ delete notif.dataset.originalPointerEvents;
219
+
220
+ // Obnovit pointer-events na všechny child elementy
221
+ const allChildren = notif.querySelectorAll('*');
222
+ allChildren.forEach((child) => {
223
+ child.style.removeProperty('pointer-events');
224
+ delete child.dataset.originalPointerEvents;
225
+ });
226
+ } else {
227
+ // Zakázat pointer-events na notifikaci
228
+ if (!notif.dataset.originalPointerEvents) {
229
+ notif.dataset.originalPointerEvents = notif.style.pointerEvents || 'auto';
230
+ }
231
+ notif.style.setProperty('pointer-events', 'none', 'important');
232
+
233
+ // Zakázat pointer-events na všechny child elementy
234
+ const allChildren = notif.querySelectorAll('*');
235
+ allChildren.forEach((child) => {
236
+ if (!child.dataset.originalPointerEvents) {
237
+ child.dataset.originalPointerEvents = child.style.pointerEvents || '';
238
+ }
239
+ child.style.setProperty('pointer-events', 'none', 'important');
240
+ });
241
+ }
242
+ });
243
+ }, []);
244
+
245
+ // Dynamicky přidávat/odebírat pointer-events na notifikace podle isSelecting stavu
246
+ React.useEffect(() => {
247
+ if (isSelecting) {
248
+ document.body.classList.add('ag-grid-selecting');
249
+
250
+ // Nastavit pointer-events na existující notifikace
251
+ setNotificationPointerEvents(false);
252
+
253
+ // KRITICKÉ: Sledovat DOM změny - když se objeví nová notifikace během označování
254
+ const observer = new MutationObserver((mutations) => {
255
+ mutations.forEach((mutation) => {
256
+ mutation.addedNodes.forEach((node) => {
257
+ if (node.nodeType === 1) { // Element node
258
+ // Zkontrolovat jestli je to notifikace nebo obsahuje notifikaci
259
+ if (node.classList?.contains('ant-notification') ||
260
+ node.querySelector?.('.ant-notification')) {
261
+ setNotificationPointerEvents(false);
262
+ }
263
+ }
264
+ });
265
+ });
266
+ });
267
+
268
+ // Sledovat změny v document.body
269
+ observer.observe(document.body, {
270
+ childList: true,
271
+ subtree: true
272
+ });
273
+
274
+ // Uložit observer pro cleanup
275
+ return () => {
276
+ observer.disconnect();
277
+ };
278
+ } else {
279
+ document.body.classList.remove('ag-grid-selecting');
280
+
281
+ // Malé zpoždění před obnovením pointer-events (100ms)
282
+ // Zajistí že notifikace jsou interaktivní až když skutečně skončí označování
283
+ setTimeout(() => {
284
+ setNotificationPointerEvents(true);
285
+ }, 100);
286
+ }
287
+ }, [isSelecting, setNotificationPointerEvents]);
288
+
289
+ // Cleanup notifikací a timerů při zničení komponenty
290
+ React.useEffect(() => {
291
+ return () => {
292
+ // Clear throttle timer
293
+ if (notificationThrottleRef.current) {
294
+ clearTimeout(notificationThrottleRef.current);
295
+ }
296
+
297
+ if (enableNotification) {
298
+ const gridId = props.gridName || props.id || 'default';
299
+ const key = `aggregation-grid-${gridId}`;
300
+ notification.destroy(key);
301
+ }
302
+
303
+ // Cleanup SignalR transaction callback při unmount
304
+ if (queryKey && window.agGridTransactionCallbacks) {
305
+ delete window.agGridTransactionCallbacks[queryKey];
306
+ }
307
+ };
308
+ }, [enableNotification, props.gridName, props.id, queryKey]);
309
+
310
+ React.useEffect(() => {
311
+ // VYPNUTO: Cell flash je globálně vypnutý
312
+ return;
313
+
314
+ if (!newRowFlash && !updatedRowFlash) return;
315
+
316
+ const previousRowData = previousRowDataRef.current;
317
+ const addedRows = newRowFlash ? findNewRows(previousRowData, rowData) : [];
318
+ const updatedRows = updatedRowFlash ? findUpdatedRows(previousRowData, rowData) : [];
319
+
320
+ if (addedRows.length > 0 || updatedRows.length > 0) {
321
+ setTimeout(() => {
322
+ try {
323
+ // Bezpečnostní kontrola API dostupnosti
324
+ if (!internalRef.current?.api || internalRef.current.api.isDestroyed?.()) {
325
+ return;
326
+ }
327
+
328
+ // Modern AG-Grid (33+): getColumnState() moved to main api
329
+ const columnApi = internalRef.current?.columnApi || internalRef.current?.api;
330
+ if (!columnApi?.getColumnState) {
331
+ return;
332
+ }
333
+
334
+ const columnStates = columnApi.getColumnState();
335
+ const allColumns = columnStates
336
+ .filter(colState => colState.colId) // Filtrujeme pouze platné colId
337
+ .map((colState) => colState.colId);
338
+
339
+ // Kontrola, že máme platné sloupce
340
+ if (allColumns.length === 0) {
341
+ return;
342
+ }
343
+
344
+ // Flash efekt pro nové řádky (používá defaultní zelená barva AG Grid)
345
+ if (addedRows.length > 0) {
346
+ const newRowNodes = [];
347
+ internalRef.current.api.forEachNode((node) => {
348
+ if (addedRows.some((row) => row.id === node.data.id)) {
349
+ newRowNodes.push(node);
350
+ }
351
+ });
352
+
353
+ if (newRowNodes.length > 0) {
354
+ internalRef.current.api.flashCells({
355
+ rowNodes: newRowNodes,
356
+ columns: allColumns,
357
+ flashDelay: 0,
358
+ fadeDelay: 1000,
359
+ });
360
+ }
361
+ }
362
+
363
+ // Flash efekt pro aktualizované řádky (modrá barva)
364
+ if (updatedRows.length > 0) {
365
+ const updatedRowNodes = [];
366
+ internalRef.current.api.forEachNode((node) => {
367
+ if (updatedRows.some((row) => row.id === node.data.id)) {
368
+ updatedRowNodes.push(node);
369
+ }
370
+ });
371
+
372
+ if (updatedRowNodes.length > 0) {
373
+ // Použijeme vlastní CSS animaci pro modrou flash
374
+ updatedRowNodes.forEach(node => {
375
+ const rowElement = node.eGridRow || document.querySelector(`[row-id="${node.data.id}"]`);
376
+ if (rowElement) {
377
+ rowElement.classList.add('ag-row-flash-updated');
378
+ setTimeout(() => {
379
+ rowElement.classList.remove('ag-row-flash-updated');
380
+ }, 1000);
381
+ }
382
+ });
383
+ }
384
+ }
385
+ } catch (error) {
386
+ // Ignorujeme chybu a pokračujeme bez crash aplikace
387
+ }
388
+ }, 100); // Zvýšený timeout pro stabilizaci gridu
389
+ }
390
+
391
+ previousRowDataRef.current = rowData;
392
+ }, [rowData, newRowFlash, updatedRowFlash]);
393
+
394
+ const RhPlusRangeSelectionChanged = React.useCallback(
395
+ (event) => {
396
+ // Detekovat začátek označování - nastavit isSelecting na true
397
+ setIsSelecting(true);
398
+
399
+ // 1. OPTIMALIZACE: Zobrazení notifikace BĚHEM označování s THROTTLE (100ms)
400
+ // Throttle = spustí okamžitě první event, pak každých 100ms během označování
401
+ // Cache check v updateAggregationNotification zajistí, že se agregace nepočítají znovu pokud se range nezměnil
402
+ if (enableNotification) {
403
+ const now = Date.now();
404
+ const timeSinceLastCall = now - notificationLastCallRef.current;
405
+
406
+ // První volání NEBO uplynulo 100ms od posledního volání
407
+ if (timeSinceLastCall >= 100) {
408
+ // Okamžité volání
409
+ notificationLastCallRef.current = now;
410
+ if (internalRef?.current) {
411
+ updateAggregationNotification(event);
412
+ }
413
+ } else {
414
+ // Naplánovat volání za zbývající čas (trailing edge)
415
+ if (notificationThrottleRef.current) {
416
+ clearTimeout(notificationThrottleRef.current);
417
+ }
418
+
419
+ notificationThrottleRef.current = setTimeout(() => {
420
+ notificationLastCallRef.current = Date.now();
421
+ if (internalRef?.current) {
422
+ updateAggregationNotification(event);
423
+ }
424
+ }, 100 - timeSinceLastCall);
425
+ }
426
+ }
427
+
428
+ // 2. OPTIMALIZACE: Bulk edit handler BEZ debounce - okamžité zobrazení ikony
429
+ // Lightweight validace v useBulkCellEdit zajistí okamžité zobrazení
430
+ // Těžká validace proběhne s 15ms debounce uvnitř useBulkCellEdit
431
+ if (enableBulkEdit) {
432
+ handleRangeChange(event);
433
+ }
434
+
435
+ // 3. Custom onRangeSelectionChanged callback - bez debounce
436
+ if (props.onRangeSelectionChanged) props.onRangeSelectionChanged(event);
437
+ },
438
+ [enableNotification, enableBulkEdit, handleRangeChange, props.onRangeSelectionChanged, updateAggregationNotification]
439
+ );
440
+
441
+ const AgGridOnGridReady = (event, options) => {
442
+ if (onGridReady) {
443
+ onGridReady(event, options);
444
+ }
445
+
446
+ // Nejprve nastavíme API reference pro interní použití
447
+ if (internalRef.current) {
448
+ internalRef.current.api = event.api;
449
+ internalRef.current.columnApi = event.columnApi || event.api; // fallback for modern AG-Grid
450
+ }
451
+
452
+ // Registruj callback pro AG Grid transactions (SignalR optimalizace)
453
+ // Inicializace globálního registru pro více AG-Grid instancí
454
+ if (!window.agGridTransactionCallbacks) {
455
+ window.agGridTransactionCallbacks = {};
456
+ }
457
+
458
+ // Registrace callbacku pro tuto konkrétní AG-Grid instanci (podle queryKey)
459
+ if (event.api && queryKey) {
460
+ window.agGridTransactionCallbacks[queryKey] = (transactionData) => {
461
+ try {
462
+ if (!event.api || event.api.isDestroyed?.()) return;
463
+
464
+ const { operation, records } = transactionData;
465
+
466
+ switch (operation) {
467
+ case 'add':
468
+ event.api.applyTransaction({ add: records });
469
+ // Flash efekt pro nové řádky
470
+ setTimeout(() => {
471
+ records.forEach(record => {
472
+ const rowNode = event.api.getRowNode(record.id);
473
+ if (rowNode && rowNode.rowElement) {
474
+ rowNode.rowElement.classList.add('ag-row-flash-created');
475
+ setTimeout(() => {
476
+ rowNode.rowElement.classList.remove('ag-row-flash-created');
477
+ }, 1000);
478
+ }
479
+ });
480
+ }, 100);
481
+ break;
482
+
483
+ case 'update':
484
+ event.api.applyTransaction({ update: records });
485
+ // Flash efekt pro aktualizované řádky
486
+ setTimeout(() => {
487
+ records.forEach(record => {
488
+ const rowNode = event.api.getRowNode(record.id);
489
+ if (rowNode && rowNode.rowElement) {
490
+ rowNode.rowElement.classList.add('ag-row-flash-updated');
491
+ setTimeout(() => {
492
+ rowNode.rowElement.classList.remove('ag-row-flash-updated');
493
+ }, 1000);
494
+ }
495
+ });
496
+ }, 100);
497
+ break;
498
+
499
+ case 'remove':
500
+ // Flash efekt před smazáním
501
+ records.forEach(record => {
502
+ const rowNode = event.api.getRowNode(record.id);
503
+ if (rowNode && rowNode.rowElement) {
504
+ rowNode.rowElement.classList.add('ag-row-flash-deleted');
505
+ }
506
+ });
507
+
508
+ setTimeout(() => {
509
+ event.api.applyTransaction({ remove: records });
510
+ }, 500); // Krátké zpoždění pro zobrazení flash efektu
511
+ break;
512
+ }
513
+ } catch (error) {
514
+ // Ignorujeme chyby
515
+ }
516
+ };
517
+ }
518
+
519
+ // Pak zavoláme parent onGridReady handler, pokud existuje
520
+ // Toto je kritické pro správné fungování bit/ui/grid a GridLayout
521
+ if (options.onGridReady) {
522
+ try {
523
+ options.onGridReady(event);
524
+ } catch (error) {
525
+ // Error handling without console output
526
+ }
527
+ }
528
+
529
+ // Nakonec nastavíme grid ready state s timeout
530
+ setTimeout(() => {
531
+ setIsGridReady(true);
532
+ }, 1000);
533
+ };
534
+
535
+ const components = React.useMemo(() => {
536
+ return {
537
+ checkboxRenderer: CheckboxRenderer,
538
+ selectRenderer: SelectRenderer,
539
+ countrySelectRenderer: CountrySelectRenderer,
540
+ booleanRenderer: BooleanRenderer,
541
+ buttonRenderer: ButtonRenderer,
542
+ iconRenderer: IconRenderer,
543
+ imageRenderer: ImageRenderer,
544
+ stateRenderer: StateRenderer,
545
+ ...props.frameworkComponents,
546
+ };
547
+ }, [props.frameworkComponents]);
548
+
549
+ // ========== PERFORMANCE OPTIMIZATION: Memoizované event handlers ==========
550
+ const memoizedOnCellEditingStarted = React.useCallback(
551
+ (event) => RhPlusOnCellEditingStarted(event, props),
552
+ [props.onCellEditingStarted]
553
+ );
554
+
555
+ const memoizedOnCellDoubleClicked = React.useCallback(
556
+ (event) => RhPlusOnCellDoubleClicked(event, props),
557
+ [props.onCellDoubleClicked]
558
+ );
559
+
560
+ const memoizedOnCellValueChanged = React.useCallback(
561
+ (event) => RhPlusOnCellValueChanged(event, props),
562
+ [props.onCellValueChanged]
563
+ );
564
+
565
+ const memoizedPostSort = React.useCallback(
566
+ (event) => AgGridPostSort(event, props),
567
+ [props.postSort]
568
+ );
569
+
570
+ const memoizedOnGridReady = React.useCallback(
571
+ (event, options) => AgGridOnGridReady(event, props),
572
+ [onGridReady]
573
+ );
574
+
575
+ const memoizedOnRowDataChanged = React.useCallback(
576
+ (event) => AgGridOnRowDataChanged(event, props),
577
+ [props.onRowDataChanged]
578
+ );
579
+
580
+ const memoizedOnRowDataUpdated = React.useCallback(
581
+ (event) => AgGridOnRowDataUpdated(event, props),
582
+ [props.onRowDataUpdated]
583
+ );
584
+
585
+ // Memoizovaný context object
586
+ const memoizedContext = React.useMemo(() => ({
587
+ componentParent: { ...props }
588
+ }), [props.context, props.gridName, props.id]);
589
+
590
+ // Memoizovaný defaultColDef
591
+ const memoizedDefaultColDef = React.useMemo(() => ({
592
+ ...props.defaultColDef,
593
+ suppressHeaderMenuButton: true,
594
+ suppressHeaderFilterButton: true,
595
+ suppressMenu: true,
596
+ }), [props.defaultColDef]);
597
+
598
+ // ========== PERFORMANCE OPTIMIZATION: Memoizovaný allGridProps ==========
599
+ const allGridProps = React.useMemo(() => ({
600
+ ref: internalRef,
601
+ ...props,
602
+ theme: themeObject,
603
+ columnDefs: AgGridColumns(props.columnDefs, props),
604
+ defaultColDef: memoizedDefaultColDef,
605
+ onCellEditingStarted: memoizedOnCellEditingStarted,
606
+ onCellDoubleClicked: memoizedOnCellDoubleClicked,
607
+ onCellValueChanged: memoizedOnCellValueChanged,
608
+ postSort: memoizedPostSort,
609
+ onGridReady: memoizedOnGridReady,
610
+ onRowDataChanged: memoizedOnRowDataChanged,
611
+ onRowDataUpdated: memoizedOnRowDataUpdated,
612
+ onRangeSelectionChanged: RhPlusRangeSelectionChanged,
613
+ context: memoizedContext,
614
+ components,
615
+ }), [
616
+ internalRef,
617
+ themeObject,
618
+ props.columnDefs,
619
+ memoizedDefaultColDef,
620
+ memoizedOnCellEditingStarted,
621
+ memoizedOnCellDoubleClicked,
622
+ memoizedOnCellValueChanged,
623
+ memoizedPostSort,
624
+ memoizedOnGridReady,
625
+ memoizedOnRowDataChanged,
626
+ memoizedOnRowDataUpdated,
627
+ RhPlusRangeSelectionChanged,
628
+ memoizedContext,
629
+ components,
630
+ ]);
631
+
632
+ return (
633
+ <>
634
+ <AgGridReact {...allGridProps} />
635
+
636
+ {/* Bulk Edit Floating Button */}
637
+ {enableBulkEdit && floatingButton.visible && (
638
+ <BulkEditButton
639
+ visible={floatingButton.visible}
640
+ position={floatingButton.position}
641
+ range={floatingButton.range}
642
+ column={floatingButton.column}
643
+ cellCount={floatingButton.cellCount}
644
+ rowsContainer={floatingButton.rowsContainer}
645
+ editPopover={editPopover}
646
+ onOpenPopover={handleOpenPopover}
647
+ onValueChange={handleValueChange}
648
+ onSubmit={handleSubmitEdit}
649
+ onCancel={handleCancelEdit}
650
+ />
651
+ )}
652
+ </>
653
+ );
654
+ });
655
+
656
+ export default AgGrid;
657
+
658
+ export {
659
+ useBulkCellEdit,
660
+ BulkEditButton,
661
+ BulkEditPopover,
662
+ BulkEditSelect,
663
+ BulkEditDatePicker,
664
+ BulkEditModule,
665
+ BulkEditInput
666
+ } from './BulkEdit';
667
+
668
+ export {
669
+ default as CheckboxRenderer
657
670
  } from './Renderers/CheckboxRenderer'