@bit.rhplus/ui.grid 0.0.52 → 0.0.53

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/ColumnBuilder.jsx CHANGED
@@ -1,1280 +1,1294 @@
1
- /* eslint-disable */
2
- import * as React from 'react';
3
- import Menu from 'antd/es/menu';
4
- import Dropdown from 'antd/es/dropdown';
5
- import Button from 'antd/es/button';
6
- import Divider from 'antd/es/divider';
7
- import { prepareCellStyle, prepareColDef, getValueByPath } from './functions';
8
- import { currencySymbols } from './enums';
9
- import { ActionIcon } from './Icons';
10
- import { CircleHelp, Check, X } from 'lucide-react';
11
-
12
- /**
13
- * Custom React hook pro zpracování interakcí s link buňkou v AG Grid.
14
- * Obsluhuje hover efekty, kliky a přepínání overview módu.
15
- * @param {Object} params - AG Grid parametry buňky
16
- * @param {Function} onClick - Callback funkce při kliku na link
17
- * @param {Object|Function} linkStyle - CSS styl pro link nebo funkce vracející styl
18
- * @param {Object|Function} hoverStyle - CSS styl pro hover stav nebo funkce vracející styl
19
- * @param {boolean} overviewToggle - Příznak zda aktivovat overview toggle funkčnost
20
- * @returns {Object} Objekt s handlry a styly pro link buňku
21
- */
22
- const useLinkCellRenderer = (
23
- params,
24
- onClick,
25
- linkStyle,
26
- hoverStyle,
27
- overviewToggle
28
- ) => {
29
- const [isHovered, setIsHovered] = React.useState(false);
30
-
31
- const handleMouseEnter = React.useCallback(() => {
32
- setIsHovered(true);
33
- }, []);
34
-
35
- const handleMouseLeave = React.useCallback(() => {
36
- setIsHovered(false);
37
- }, []);
38
-
39
- const handleClick = React.useCallback(
40
- (event) => {
41
- event.stopPropagation();
42
-
43
- if (overviewToggle && params.api) {
44
- params.api.dispatchEvent({
45
- type: 'overviewToggle',
46
- data: params.data,
47
- params,
48
- });
49
- }
50
-
51
- if (onClick) {
52
- onClick(params);
53
- }
54
- },
55
- [onClick, overviewToggle, params]
56
- );
57
-
58
- // Přesunuto do useMemo, aby se vytvořilo pouze jednou
59
- const defaultLinkStyle = React.useMemo(
60
- () => ({
61
- color: '#1a73e8',
62
- textDecoration: 'none',
63
- cursor: 'pointer',
64
- }),
65
- []
66
- );
67
-
68
- // Přesunuto do useMemo, aby se vytvořilo pouze jednou
69
- const defaultHoverStyle = React.useMemo(
70
- () => ({
71
- textDecoration: 'underline',
72
- }),
73
- []
74
- );
75
-
76
- const computedLinkStyle = React.useMemo(
77
- () => ({
78
- ...defaultLinkStyle,
79
- ...(typeof linkStyle === 'function'
80
- ? linkStyle(params)
81
- : linkStyle || {}),
82
- }),
83
- [linkStyle, params, defaultLinkStyle]
84
- );
85
-
86
- const computedHoverStyle = React.useMemo(
87
- () => ({
88
- ...defaultLinkStyle,
89
- ...computedLinkStyle,
90
- ...defaultHoverStyle,
91
- ...(typeof hoverStyle === 'function'
92
- ? hoverStyle(params)
93
- : hoverStyle || {}),
94
- }),
95
- [computedLinkStyle, hoverStyle, params, defaultLinkStyle, defaultHoverStyle]
96
- );
97
-
98
- return {
99
- isHovered,
100
- handleMouseEnter,
101
- handleMouseLeave,
102
- handleClick,
103
- computedLinkStyle,
104
- computedHoverStyle,
105
- };
106
- };
107
-
108
- /**
109
- * Custom React hook pro zpracování interakcí s object buňkou v AG Grid.
110
- * Poskytuje funkcionalitu pro editovatelné objekty s hover efekty a edit tlačítkem.
111
- * @param {Object} params - AG Grid parametry buňky
112
- * @param {boolean} editable - Příznak zda je buňka editovatelná
113
- * @param {Function} onEditClick - Callback funkce při kliku na edit tlačítko
114
- * @returns {Object} Objekt s handlry pro object buňku
115
- */
116
- const useObjectCellRenderer = (params, editable, onEditClick) => {
117
- const [isHovered, setIsHovered] = React.useState(false);
118
-
119
- const handleMouseEnter = React.useCallback(() => {
120
- setIsHovered(true);
121
- }, []);
122
-
123
- const handleMouseLeave = React.useCallback(() => {
124
- setIsHovered(false);
125
- }, []);
126
-
127
- const handleEditClick = React.useCallback(
128
- (event) => {
129
- event.stopPropagation();
130
- if (onEditClick) {
131
- onEditClick(params);
132
- }
133
- },
134
- [onEditClick, params]
135
- );
136
-
137
- return {
138
- isHovered,
139
- handleMouseEnter,
140
- handleMouseLeave,
141
- handleEditClick,
142
- };
143
- };
144
-
145
- /**
146
- * Custom React hook pro detekci hover stavu řádku v AG Grid.
147
- * Používá MutationObserver pro sledování CSS tříd řádků a detekci hover stavu.
148
- * Tento hook se používá pro akční tlačítka, která se zobrazují pouze při hoveru nad řádkem.
149
- * @param {Object} params - AG Grid parametry buňky obsahující informace o řádku
150
- * @returns {Object} Objekt s boolean příznakem isRowHovered
151
- */
152
- const useRowHoverActionRenderer = (params) => {
153
- const [isRowHovered, setIsRowHovered] = React.useState(false);
154
- const rowRef = React.useRef(null);
155
-
156
- React.useEffect(() => {
157
- if (!params.node) return undefined;
158
-
159
- // Funkce pro kontrolu, zda je řádek v hover stavu
160
- const checkRowHoverState = () => {
161
- // Najdeme DOM element řádku pomocí rowIndex
162
- const rowElement = document.querySelector(
163
- `.ag-row[row-index="${params.rowIndex}"]`
164
- );
165
- if (rowElement) {
166
- rowRef.current = rowElement;
167
- // AG-Grid automaticky přidává třídu ag-row-hover k řádku při najetí myší
168
- const isHovered = rowElement.classList.contains('ag-row-hover');
169
- setIsRowHovered(isHovered);
170
- }
171
- };
172
-
173
- // Vytvoříme MutationObserver pro sledování změn tříd na řádku
174
- const observer = new MutationObserver((mutations) => {
175
- mutations.forEach((mutation) => {
176
- if (
177
- mutation.type === 'attributes' &&
178
- mutation.attributeName === 'class'
179
- ) {
180
- checkRowHoverState();
181
- }
182
- });
183
- });
184
-
185
- // Inicializujeme stav
186
- checkRowHoverState();
187
-
188
- // Přidáme observer k element řádku, pokud existuje
189
- if (rowRef.current) {
190
- observer.observe(rowRef.current, { attributes: true });
191
- }
192
-
193
- // Přidáme event listeners na grid container pro sledování pohybu myši
194
- const gridContainer = document.querySelector('.ag-root-wrapper');
195
- if (gridContainer) {
196
- gridContainer.addEventListener('mouseover', checkRowHoverState);
197
- gridContainer.addEventListener('mouseout', checkRowHoverState);
198
- }
199
-
200
- // Cleanup při odmontování komponenty
201
- return () => {
202
- if (rowRef.current) {
203
- observer.disconnect();
204
- }
205
- if (gridContainer) {
206
- gridContainer.removeEventListener('mouseover', checkRowHoverState);
207
- gridContainer.removeEventListener('mouseout', checkRowHoverState);
208
- }
209
- };
210
- }, [params.node, params.rowIndex]);
211
-
212
- return {
213
- isRowHovered,
214
- };
215
- };
216
-
217
- /**
218
- * ColumnBuilder třída pro builder pattern vytváření sloupců AG Grid.
219
- * Poskytuje fluent API pro snadné a čitelné definování různých typů sloupců.
220
- * Podporuje lokalizaci přes react-intl a automatickou konfiguraci sloupců.
221
- */
222
- class ColumnBuilder {
223
- /**
224
- * Privátní metoda pro přidání připraveného sloupce do kolekce.
225
- * Aplikuje standardní konfiguraci sloupce včetně lokalizace.
226
- * @param {Object} props - Konfigurace sloupce
227
- */
228
- #addPreparedColumn(props) {
229
- this.columns.push(prepareColDef(this.intl, props));
230
- }
231
-
232
- /**
233
- * Konstruktor ColumnBuilder třídy.
234
- * @param {Object} intl - React-intl objekt pro lokalizaci
235
- */
236
- constructor(intl) {
237
- this.columns = [];
238
- this.intl = intl;
239
- }
240
-
241
- /**
242
- * Přidá základní sloupec do AG Grid s podporou barevného pole.
243
- * Umožňuje dynamické obarvení textu buňky na základě hodnoty z jiného pole.
244
- * @param {Object} config - Konfigurace sloupce
245
- * @param {string} config.colorField - Název pole obsahujícího barvu pro text buňky
246
- * @param {Object} ...props - Další AG Grid vlastnosti sloupce
247
- * @returns {ColumnBuilder} Instance pro chaining
248
- */
249
- addColumn({ colorField, ...props }) {
250
- // Získáme původní cellStyle, pokud existuje
251
- const originalCellStyle = props.cellStyle;
252
- const { cellAlign, ...restProps } = props;
253
-
254
- // Pokud je zadáno colorField, vytvoříme novou funkci pro cellStyle
255
- if (colorField) {
256
- const overrideCellStyle = (params) => {
257
- // Získáme základní styl
258
- let baseStyle = {};
259
- if (originalCellStyle) {
260
- if (typeof originalCellStyle === 'function') {
261
- baseStyle = originalCellStyle(params);
262
- } else {
263
- baseStyle = originalCellStyle;
264
- }
265
- }
266
-
267
- // Získáme hodnotu barvy z dat
268
- const colorValue =
269
- params.data && getValueByPath(params.data, colorField);
270
- const restStyle = prepareCellStyle(props);
271
- // Vrátíme kombinovaný styl
272
- return colorValue
273
- ? { ...baseStyle, ...restStyle, color: colorValue }
274
- : { ...baseStyle, ...restStyle };
275
- };
276
-
277
- // Předáme upravené props do addPreparedColumn
278
- this.#addPreparedColumn({ ...restProps, overrideCellStyle });
279
- } else {
280
- // Pokud není colorField zadán, použijeme původní implementaci
281
- this.#addPreparedColumn(props);
282
- }
283
-
284
- return this;
285
- }
286
-
287
- /**
288
- * Přidá sloupec s kartou faktury optimalizovaný pro overview mód.
289
- * Zobrazuje fakturační informace v kartovém formátu s podporou checkboxu.
290
- * @param {Object} config - Konfigurace sloupce
291
- * @param {string} [config.field='invoice'] - Název datového pole
292
- * @param {string} [config.headerName=''] - Název hlavičky sloupce
293
- * @param {number} [config.width=340] - Šířka sloupce
294
- * @param {Function} config.onCheckboxChange - Callback při změně stavu checkboxu
295
- * @param {Object} ...restProps - Další AG Grid vlastnosti sloupce
296
- * @returns {ColumnBuilder} Instance pro chaining
297
- */
298
- addInvoiceCardColumn({
299
- field = 'invoice',
300
- headerName = '',
301
- width = 340,
302
- onCheckboxChange,
303
- ...restProps
304
- } = {}) {
305
- const cellRenderer = 'invoiceCardRenderer';
306
-
307
- this.#addPreparedColumn({
308
- headerName,
309
- field,
310
- width,
311
- cellRenderer,
312
- suppressSizeToFit: true,
313
- cellRendererParams: {
314
- onCheckboxChange:
315
- onCheckboxChange ||
316
- (() => {
317
- // Funkce bez console.log
318
- }),
319
- },
320
- ...restProps,
321
- });
322
-
323
- return this;
324
- }
325
-
326
- /**
327
- * Přidá sloupec pro zobrazení měny s podporou různých formátů a pozicování symbolu.
328
- * Automaticky formátuje hodnoty jako měnu s dvěma desetinnými místy.
329
- * @param {Object} config - Konfigurace měnového sloupce
330
- * @param {string|Function} config.currency - Kód měny nebo funkce vracející kód měny
331
- * @param {string} [config.position] - Pozice symbolu měny ('left' nebo 'right')
332
- * @param {Object|Function} config.currencyStyle - CSS styl pro symbol měny
333
- * @param {string} [config.cellAlign='right'] - Zarovnání obsahu buňky
334
- * @param {boolean} config.editable - Zda je sloupec editovatelný
335
- * @param {Object} ...restProps - Další AG Grid vlastnosti sloupce
336
- * @returns {ColumnBuilder} Instance pro chaining
337
- */
338
- addCurrencyColumn({
339
- currency,
340
- position,
341
- currencyStyle,
342
- cellAlign = 'right',
343
- editable,
344
- ...restProps
345
- }) {
346
- const cellRenderer = (params) => {
347
- const currencyValue =
348
- typeof currency === 'function' ? currency(params) : currency;
349
- const symbol = currencySymbols[currencyValue] || currencyValue;
350
-
351
- // Použití Number.isNaN místo globální isNaN
352
- const value =
353
- params.value != null && !Number.isNaN(Number(params.value))
354
- ? params.value.toFixed(2)
355
- : '0.00';
356
-
357
- const style =
358
- typeof currencyStyle === 'function'
359
- ? currencyStyle(params)
360
- : currencyStyle;
361
-
362
- return (
363
- <span>
364
- {position === 'left' ? (
365
- <>
366
- <span style={style}>{symbol}</span> {value}
367
- </>
368
- ) : (
369
- <>
370
- {value} <span style={style}>{symbol}</span>
371
- </>
372
- )}
373
- </span>
374
- );
375
- };
376
-
377
- this.#addPreparedColumn({
378
- cellAlign,
379
- cellRenderer,
380
- editable: this.#resolveEditable(editable),
381
- ...restProps,
382
- });
383
- return this;
384
- }
385
-
386
- /**
387
- * Přidá sloupec pro zobrazení data s možností specifikace formátu.
388
- * Používá standardní AG Grid date renderer s českým formátováním.
389
- * @param {Object} config - Konfigurace datového sloupce
390
- * @param {string} [config.dateFormat='DD.MM.YYYY'] - Formát zobrazení data
391
- * @param {boolean} config.editable - Zda je sloupec editovatelný
392
- * @param {Object} ...restProps - Další AG Grid vlastnosti sloupce
393
- * @returns {ColumnBuilder} Instance pro chaining
394
- */
395
- addDateColumn({ dateFormat = 'DD.MM.YYYY', editable, ...restProps }) {
396
- this.#addPreparedColumn({
397
- dateRenderer: dateFormat,
398
- editable: this.#resolveEditable(editable),
399
- ...restProps,
400
- });
401
- return this;
402
- }
403
-
404
- /**
405
- * Přidá sloupec s klikatelným linkem podporujícím hover efekty.
406
- * Link může spouštět akce nebo přepínat overview mód v gridu.
407
- * @param {Object} config - Konfigurace linkového sloupce
408
- * @param {Function} config.onClick - Callback funkce při kliku na link
409
- * @param {Object|Function} config.linkStyle - CSS styl pro link nebo funkce vracející styl
410
- * @param {Object|Function} config.hoverStyle - CSS styl pro hover stav
411
- * @param {string} [config.cellAlign='left'] - Zarovnání obsahu buňky
412
- * @param {boolean} config.editable - Zda je sloupec editovatelný
413
- * @param {boolean} [config.overviewToggle=false] - Zda aktivovat overview toggle
414
- * @param {Object} ...restProps - Další AG Grid vlastnosti sloupce
415
- * @returns {ColumnBuilder} Instance pro chaining
416
- */
417
- addLinkColumn({
418
- onClick,
419
- linkStyle,
420
- hoverStyle,
421
- cellAlign = 'left',
422
- editable,
423
- overviewToggle = false, // Přidán výchozí parametr pro overview
424
- ...restProps
425
- }) {
426
- // Vytvořím komponentu pro cell renderer, která používá hook
427
- const LinkCellRenderer = React.memo((params) => {
428
- const {
429
- isHovered,
430
- handleMouseEnter,
431
- handleMouseLeave,
432
- handleClick,
433
- computedLinkStyle,
434
- computedHoverStyle,
435
- } = useLinkCellRenderer(
436
- params,
437
- onClick,
438
- linkStyle,
439
- hoverStyle,
440
- overviewToggle
441
- );
442
-
443
- return (
444
- <div
445
- className="link-cell-container"
446
- style={{
447
- width: '100%',
448
- height: '100%',
449
- display: 'flex',
450
- alignItems: 'center',
451
- }}
452
- >
453
- <span
454
- onClick={handleClick}
455
- onMouseEnter={handleMouseEnter}
456
- onMouseLeave={handleMouseLeave}
457
- style={isHovered ? computedHoverStyle : computedLinkStyle}
458
- role="button"
459
- tabIndex={0}
460
- onKeyPress={(event) => {
461
- if (event.key === 'Enter' || event.key === ' ') {
462
- handleClick(event);
463
- }
464
- }}
465
- >
466
- {params.value}
467
- </span>
468
- </div>
469
- );
470
- });
471
-
472
- // Používáme funkci, která vrátí React komponentu
473
- const cellRenderer = (params) => {
474
- return <LinkCellRenderer {...params} />;
475
- };
476
-
477
- this.#addPreparedColumn({
478
- cellAlign,
479
- cellRenderer,
480
- editable: this.#resolveEditable(editable),
481
- overviewToggle, // Předání příznaku dál
482
- ...restProps,
483
- });
484
- return this;
485
- }
486
-
487
- /**
488
- * Helper metoda pro přidání overview link sloupce s přednastavenými hodnotami.
489
- * Specializovaný link sloupec pro přepínání overview módu v gridu.
490
- * @param {Object} config - Konfigurace overview link sloupce
491
- * @param {string} [config.field='documentNumber'] - Název datového pole
492
- * @param {string} [config.headerName=''] - Název hlavičky sloupce
493
- * @param {number} [config.width=130] - Šířka sloupce
494
- * @param {string} [config.cellAlign='left'] - Zarovnání obsahu buňky
495
- * @param {Object|Function} config.linkStyle - CSS styl pro link
496
- * @param {Object|Function} config.hoverStyle - CSS styl pro hover stav
497
- * @param {boolean} [config.editable=false] - Zda je sloupec editovatelný
498
- * @param {Object} ...rest - Další AG Grid vlastnosti sloupce včetně intlId
499
- * @returns {ColumnBuilder} Instance pro chaining
500
- */
501
- addOverviewLinkColumn({
502
- field = 'documentNumber',
503
- headerName = '',
504
- width = 130,
505
- cellAlign = 'left',
506
- linkStyle,
507
- hoverStyle,
508
- editable = false,
509
- ...rest
510
- } = {}) {
511
- return this.addLinkColumn({
512
- field,
513
- headerName: this?.intl?.formatMessage({ id: rest?.intlId }) || headerName,
514
- width,
515
- cellAlign,
516
- linkStyle,
517
- hoverStyle,
518
- editable,
519
- overviewToggle: true,
520
- ...rest,
521
- });
522
- }
523
-
524
- /**
525
- * Přidá sloupec pro zobrazení objektů s možností editace a tooltip podporou.
526
- * Při hoveru zobrazuje edit tlačítko pro editovatelné objekty.
527
- * @param {Object} config - Konfigurace objektového sloupce
528
- * @param {boolean} config.contentTooltip - Zda zobrazovat tooltip s obsahem
529
- * @param {string} config.tooltipField - Pole obsahující text pro tooltip
530
- * @param {boolean} config.tooltipInteraction - Zda povolit interakci s tooltipem
531
- * @param {number} [config.tooltipShowDelay=100] - Zpoždění zobrazení tooltipu v ms
532
- * @param {boolean} config.editable - Zda je objekt editovatelný
533
- * @param {Function} config.onEditClick - Callback při kliku na edit tlačítko
534
- * @param {Object} ...restProps - Další AG Grid vlastnosti sloupce
535
- * @returns {ColumnBuilder} Instance pro chaining
536
- */
537
- addObjectColumn({
538
- contentTooltip,
539
- tooltipField,
540
- tooltipInteraction,
541
- tooltipShowDelay = 100,
542
- editable,
543
- onEditClick,
544
- ...restProps
545
- }) {
546
- // Vytvořím komponentu pro cell renderer, která používá hook
547
- const ObjectCellRenderer = React.memo((params) => {
548
- const { isHovered, handleMouseEnter, handleMouseLeave, handleEditClick } =
549
- useObjectCellRenderer(params, editable, onEditClick);
550
-
551
- return (
552
- <div
553
- className="object-cell-container"
554
- style={{
555
- position: 'relative',
556
- width: '100%',
557
- height: '100%',
558
- display: 'flex',
559
- alignItems: 'center',
560
- }}
561
- onMouseEnter={handleMouseEnter}
562
- onMouseLeave={handleMouseLeave}
563
- >
564
- <div style={{ flexGrow: 1 }}>{params.value}</div>
565
- {isHovered && editable && (
566
- <button
567
- className="edit-object-button"
568
- style={{
569
- position: 'absolute',
570
- right: '4px',
571
- background: 'transparent',
572
- border: 'none',
573
- cursor: 'pointer',
574
- padding: '4px',
575
- display: 'flex',
576
- alignItems: 'center',
577
- justifyContent: 'center',
578
- }}
579
- onClick={handleEditClick}
580
- title="Upravit"
581
- >
582
- <span style={{ fontSize: '16px' }}>⋮</span>
583
- </button>
584
- )}
585
- </div>
586
- );
587
- });
588
-
589
- // Používáme funkci, která vrátí React komponentu
590
- const cellRenderer = (params) => {
591
- return <ObjectCellRenderer {...params} />;
592
- };
593
-
594
- this.#addPreparedColumn({
595
- contentTooltip,
596
- tooltipField,
597
- tooltipInteraction,
598
- tooltipShowDelay,
599
- cellRenderer,
600
- editable: this.#resolveEditable(editable),
601
- ...restProps,
602
- });
603
- return this;
604
- }
605
-
606
- /**
607
- * Přidá sloupec pro zobrazení ikon s podporou badge (číselného označení).
608
- * Umožňuje podmíněné zobrazování ikon a jejich stylování.
609
- * @param {Object} config - Konfigurace ikonového sloupce
610
- * @param {Function} [config.visibleGetter=() => true] - Funkce určující viditelnost ikony
611
- * @param {string} config.cellAlign - Zarovnání ikony v buňce
612
- * @param {string|Function} config.icon - Název ikony nebo funkce vracející ikonu
613
- * @param {string|Function} [config.iconColor] - Barva ikony nebo funkce vracející barvu podle parametrů
614
- * @param {number} [config.size=16] - Velikost ikony v pixelech
615
- * @param {boolean} [config.iconLeft=false] - Zda zobrazit ikonu vlevo od textu
616
- * @param {boolean} [config.iconRight=false] - Zda zobrazit ikonu vpravo od textu
617
- * @param {boolean} [config.showText=true] - Zda zobrazit text (hodnotu pole)
618
- * @param {number} [config.textGap=5] - Mezera mezi ikonou a textem v pixelech
619
- * @param {boolean} [config.showOnGroup=false] - Zda zobrazit ikonu na skupinových řádcích
620
- * @param {string|number|Function} config.badge - Text/číslo badge nebo funkce vracející badge
621
- * @param {boolean} config.showZero - Zda zobrazit badge s hodnotou 0
622
- * @param {string} [config.badgeColor='#dc3545'] - Barva pozadí badge
623
- * @param {string} [config.badgeTextColor='white'] - Barva textu badge
624
- * @param {string} [config.badgePosition='top-right'] - Pozice badge ('top-right', 'top-left', 'bottom-right', 'bottom-left')
625
- * @param {Object} ...restProps - Další AG Grid vlastnosti sloupce
626
- * @returns {ColumnBuilder} Instance pro chaining
627
- */
628
- addIconColumn({
629
- visibleGetter = () => true,
630
- cellAlign,
631
- icon,
632
- iconColor,
633
- size = 16,
634
- iconLeft = false,
635
- iconRight = false,
636
- showText = true,
637
- textGap = 5,
638
- showOnGroup = false,
639
- badge,
640
- showZero,
641
- badgeColor = '#dc3545',
642
- badgeTextColor = 'white',
643
- badgePosition = 'top-right',
644
- ...restProps
645
- }) {
646
- const iconRendererParams = {
647
- cellAlign,
648
- visibleGetter,
649
- icon,
650
- iconColor,
651
- size,
652
- iconLeft,
653
- iconRight,
654
- showText,
655
- textGap,
656
- showOnGroup,
657
- badge,
658
- showZero,
659
- badgeColor,
660
- badgeTextColor,
661
- badgePosition,
662
- };
663
- this.#addPreparedColumn({
664
- iconRenderer: true,
665
- iconRendererParams,
666
- ...restProps,
667
- });
668
- return this;
669
- }
670
-
671
- /**
672
- * Přidá sloupec pro zobrazování stavů s kruhovým pozadím.
673
- * Zobrazuje text stavu v kruhovém pozadí s definovanou barvou.
674
- * @param {Object} config - Konfigurace stavového sloupce
675
- * @param {Function} [config.visibleGetter=() => true] - Funkce určující viditelnost
676
- * @param {string} [config.cellAlign='center'] - Zarovnání v buňce
677
- * @param {string|Function} [config.bgColor] - Barva pozadí nebo funkce vracející barvu podle parametrů
678
- * @param {string|Function} [config.textColor='white'] - Barva textu nebo funkce vracející barvu
679
- * @param {string|Function} [config.displayField] - Pole pro zobrazení textu (pokud se liší od field)
680
- * @param {number} [config.minWidth=80] - Minimální šířka kruhového pozadí
681
- * @param {number} [config.fontSize=12] - Velikost fontu
682
- * @param {boolean} [config.showOnGroup=false] - Zda zobrazit na skupinových řádcích
683
- * @param {Object} ...restProps - Další AG Grid vlastnosti sloupce
684
- * @returns {ColumnBuilder} Instance pro chaining
685
- */
686
- addStateColumn({
687
- visibleGetter = () => true,
688
- cellAlign = 'center',
689
- bgColor,
690
- textColor = 'white',
691
- displayField,
692
- minWidth = 80,
693
- fontSize = 12,
694
- showOnGroup = false,
695
- ...restProps
696
- }) {
697
- const stateRendererParams = {
698
- visibleGetter,
699
- cellAlign,
700
- bgColor,
701
- textColor,
702
- displayField,
703
- minWidth,
704
- fontSize,
705
- showOnGroup,
706
- };
707
- this.#addPreparedColumn({
708
- stateRenderer: true,
709
- stateRendererParams,
710
- ...restProps,
711
- });
712
- return this;
713
- }
714
-
715
- /**
716
- * Přidá sloupec pro zobrazení boolean hodnot pomocí ikon (zaškrtnutí/křížek).
717
- * Podporuje různé ikony pro true/false stav a podmíněnou viditelnost.
718
- * @param {Object} config - Konfigurace boolean sloupce
719
- * @param {boolean} [config.visibleFalse=true] - Zda zobrazit ikonu pro false hodnotu
720
- * @param {boolean} [config.visibleTrue=true] - Zda zobrazit ikonu pro true hodnotu
721
- * @param {string} [config.cellAlign='center'] - Zarovnání ikony v buňce
722
- * @param {Function} [config.visibleGetter=() => true] - Funkce určující viditelnost ikony
723
- * @param {boolean} [config.defaultIcon=true] - Zda zobrazit výchozí ikonu pro undefined hodnoty
724
- * @param {string} [config.defaultIconColor='#898989'] - Barva výchozí ikony
725
- * @param {number} [config.size=16] - Velikost ikony v pixelech
726
- * @param {string} [config.colorTrue='green'] - Barva ikony pro true hodnotu
727
- * @param {string} [config.colorFalse='red'] - Barva ikony pro false hodnotu
728
- * @param {boolean} [config.showOnGroup=false] - Zda zobrazit ikonu na skupinových řádcích
729
- * @param {boolean} config.editable - Zda je sloupec editovatelný
730
- * @param {Object} ...restProps - Další AG Grid vlastnosti sloupce
731
- * @returns {ColumnBuilder} Instance pro chaining
732
- */
733
- addBooleanColumn({
734
- visibleFalse = true,
735
- visibleTrue = true,
736
- cellAlign = 'center',
737
- visibleGetter = () => true,
738
- defaultIcon = true,
739
- defaultIconColor = '#898989',
740
- size = 16,
741
- colorTrue = 'green',
742
- colorFalse = 'red',
743
- showOnGroup = false,
744
- editable,
745
- ...restProps
746
- }) {
747
- // Create a React component for boolean cell renderer
748
- const BooleanCellRenderer = React.memo((params) => {
749
- const { data, value } = params;
750
-
751
- const visibleResult = visibleGetter ? visibleGetter(data) : true;
752
-
753
- const Icon = ({ innerValue, ...iconProps }) => {
754
- if (
755
- innerValue === undefined ||
756
- (!visibleFalse && !innerValue) ||
757
- (!visibleTrue && innerValue)
758
- ) {
759
- if (defaultIcon)
760
- return <CircleHelp size={size} color={defaultIconColor} {...iconProps} />;
761
- return <div />;
762
- }
763
-
764
- if (innerValue)
765
- return <Check size={size} color={colorTrue} {...iconProps} />;
766
- return <X size={size} color={colorFalse} {...iconProps} />;
767
- };
768
-
769
- const showCondition = () => {
770
- const newItem = (data && data._rh_plus_ag_grid_new_item) || false;
771
- return !newItem && (showOnGroup || !!data) && visibleResult;
772
- };
773
-
774
- if (!showCondition()) return null;
775
-
776
- return (
777
- <span
778
- style={{
779
- width: '100%',
780
- display: 'flex',
781
- justifyContent: cellAlign,
782
- alignItems: 'center',
783
- height: '100%',
784
- }}
785
- >
786
- <Icon
787
- style={{
788
- display: 'inline-block',
789
- height: '100%',
790
- }}
791
- innerValue={value}
792
- visibleTrue={visibleTrue}
793
- visibleFalse={visibleFalse}
794
- />
795
- </span>
796
- );
797
- });
798
-
799
- // Use the function that returns React component
800
- const cellRenderer = (params) => {
801
- return <BooleanCellRenderer {...params} />;
802
- };
803
-
804
- this.#addPreparedColumn({
805
- cellRenderer,
806
- editable: this.#resolveEditable(editable),
807
- ...restProps,
808
- });
809
- return this;
810
- }
811
-
812
- /**
813
- * Přidá připnutý akční sloupec s dropdown menu, který se zobrazuje pouze při hoveru nad řádkem.
814
- * Používa pokročilou detekci hover stavu s MutationObserver pro každou instanci gridu zvlášť.
815
- * Sloupec je automaticky připnut vpravo a obsahuje akční tlačítko s kontextovým menu.
816
- * @param {Object} config - Konfigurace akčního sloupce
817
- * @param {Array} [config.menuItems=[]] - Array položek menu s key, label, disable vlastnostmi
818
- * @param {Function} config.onActionClick - Callback při kliku na položku menu (key, params, item)
819
- * @param {Function} config.onBeforeShow - Async callback před zobrazením menu pro dynamické položky
820
- * @param {Object} ...restProps - Další AG Grid vlastnosti sloupce
821
- * @returns {ColumnBuilder} Instance pro chaining
822
- */
823
- addPinnedActionButton({
824
- menuItems = [],
825
- onActionClick,
826
- onBeforeShow,
827
- ...restProps
828
- } = {}) {
829
- const ActionDropdownRenderer = (params) => {
830
- const [isRowHovered, setIsRowHovered] = React.useState(false);
831
- const [isDropdownVisible, setIsDropdownVisible] = React.useState(false);
832
- const [currentMenuItems, setCurrentMenuItems] = React.useState(menuItems);
833
-
834
- const isMountedRef = React.useRef(true);
835
- const rowIndexRef = React.useRef(null);
836
- const observerRef = React.useRef(null);
837
- const gridContainerRef = React.useRef(null);
838
- const currentHoveredRowRef = React.useRef(null);
839
- const mouseListenersSetupRef = React.useRef(false);
840
-
841
- React.useEffect(() => {
842
- return () => {
843
- isMountedRef.current = false;
844
- cleanup();
845
- };
846
- }, []);
847
-
848
- // Cleanup funkce
849
- const cleanup = () => {
850
- if (observerRef.current) {
851
- observerRef.current.disconnect();
852
- observerRef.current = null;
853
- }
854
-
855
- if (gridContainerRef.current && mouseListenersSetupRef.current) {
856
- gridContainerRef.current.removeEventListener(
857
- 'mousemove',
858
- handleMouseMove
859
- );
860
- gridContainerRef.current.removeEventListener(
861
- 'mouseleave',
862
- handleMouseLeave
863
- );
864
- // gridContainerRef.current.removeEventListener(
865
- // 'mouseenter',
866
- // handleMouseEnter
867
- // );
868
- mouseListenersSetupRef.current = false;
869
- }
870
- };
871
-
872
- // Získáme row index z params
873
- React.useEffect(() => {
874
- if (params.node && params.node.rowIndex !== undefined) {
875
- rowIndexRef.current = params.node.rowIndex.toString();
876
- } else if (params.rowIndex !== undefined) {
877
- rowIndexRef.current = params.rowIndex.toString();
878
- }
879
- }, [params.node, params.rowIndex]);
880
-
881
- // Mouse event handlers
882
- const handleMouseMove = React.useCallback((event) => {
883
- if (!isMountedRef.current) return;
884
-
885
- const target = event.target;
886
- if (!target) return;
887
-
888
- // Najdeme nejbližší AG-Grid row element
889
- let rowElement = target.closest('.ag-row');
890
-
891
- if (rowElement) {
892
- // Získáme row index z AG-Grid attributů
893
- const rowIndex =
894
- rowElement.getAttribute('row-index') ||
895
- rowElement.getAttribute('aria-rowindex') ||
896
- rowElement.dataset.rowIndex ||
897
- rowElement.getAttribute('data-ag-row-index');
898
-
899
- if (rowIndex !== null) {
900
- const normalizedRowIndex = rowIndex.toString();
901
-
902
- // KLÍČOVÁ ZMĚNA: Explicitně resetujeme předchozí řádek při změně
903
- if (normalizedRowIndex !== currentHoveredRowRef.current) {
904
- // Pokud jsme měli předchozí řádek, nastavíme mu hover false
905
- if (currentHoveredRowRef.current !== null) {
906
- notifyRowHoverChange(currentHoveredRowRef.current, false);
907
- }
908
-
909
- // Nastavíme nový řádek jako hovered
910
- currentHoveredRowRef.current = normalizedRowIndex;
911
- notifyRowHoverChange(normalizedRowIndex, true);
912
- }
913
- }
914
- } else {
915
- // Myš není nad žádným řádkem - resetujeme všechny
916
- if (currentHoveredRowRef.current !== null) {
917
- notifyRowHoverChange(currentHoveredRowRef.current, false);
918
- currentHoveredRowRef.current = null;
919
- }
920
- }
921
- }, []);
922
-
923
- const handleMouseLeave = React.useCallback((event) => {
924
- if (!isMountedRef.current) return;
925
-
926
- // Lepší detekce opuštění gridu
927
- const relatedTarget = event.relatedTarget;
928
-
929
- // Pokud myš opustí celý grid container NEBO jde mimo dokument
930
- if (
931
- gridContainerRef.current &&
932
- (!relatedTarget || !gridContainerRef.current.contains(relatedTarget))
933
- ) {
934
- if (currentHoveredRowRef.current !== null) {
935
- notifyRowHoverChange(currentHoveredRowRef.current, false);
936
- currentHoveredRowRef.current = null;
937
- }
938
- }
939
- }, []);
940
-
941
- // Notifikace o změně hover stavu pro tento specifický grid
942
- const notifyRowHoverChange = (rowIndex, isHovered) => {
943
- if (!isMountedRef.current) return;
944
-
945
- // EXPLICITNÍ ŘÍZENÍ: Pokud nastavujeme hover false, resetujeme stav pro všechny řádky
946
- if (!isHovered && rowIndexRef.current) {
947
- setIsRowHovered(false);
948
- }
949
-
950
- // Pokud je toto náš řádek a nastavujeme hover true, aktualizujeme stav
951
- if (isHovered && rowIndexRef.current === rowIndex) {
952
- setIsRowHovered(true);
953
- }
954
- };
955
-
956
- // Nastavení observer pro tento konkrétní grid
957
- const setupGridObserver = React.useCallback(() => {
958
- if (!params.api || observerRef.current || !isMountedRef.current) return;
959
-
960
- // Najdeme grid container pro tento konkrétní grid instance
961
- const findThisGridContainer = () => {
962
- if (params.eGridCell) {
963
- let current = params.eGridCell;
964
- while (current && current !== document.body) {
965
- if (
966
- current.classList &&
967
- (current.classList.contains('ag-root-wrapper') ||
968
- current.classList.contains('ag-theme-alpine') ||
969
- current.classList.contains('ag-theme-balham') ||
970
- current.classList.contains('ag-theme-material') ||
971
- current.classList.contains('ag-theme-fresh') ||
972
- current.classList.contains('ag-theme-dark') ||
973
- current.classList.contains('ag-theme-blue') ||
974
- current.classList.contains('ag-theme-bootstrap'))
975
- ) {
976
- return current;
977
- }
978
- current = current.parentElement;
979
- }
980
- }
981
- return null;
982
- };
983
-
984
- const gridContainer = findThisGridContainer();
985
- if (!gridContainer) {
986
- // Zkusíme znovu za chvíli
987
- setTimeout(setupGridObserver, 100);
988
- return;
989
- }
990
-
991
- gridContainerRef.current = gridContainer;
992
-
993
- // Přidáme mouse event listenery pouze pokud ještě nejsou
994
- if (!mouseListenersSetupRef.current) {
995
- gridContainer.addEventListener('mousemove', handleMouseMove, {
996
- passive: true,
997
- });
998
- gridContainer.addEventListener('mouseleave', handleMouseLeave, {
999
- passive: true,
1000
- });
1001
-
1002
- // DŮLEŽITÉ: Přidáme také mouseenter pro reset při vstupu do gridu
1003
- const handleMouseEnter = () => {
1004
- if (isMountedRef.current) {
1005
- // Reset stavu při vstupu do gridu
1006
- currentHoveredRowRef.current = null;
1007
- setIsRowHovered(false);
1008
- }
1009
- };
1010
- gridContainer.addEventListener('mouseenter', handleMouseEnter, {
1011
- passive: true,
1012
- });
1013
-
1014
- mouseListenersSetupRef.current = true;
1015
- }
1016
-
1017
- // Nastavíme MutationObserver pro tento grid
1018
- observerRef.current = new MutationObserver((mutations) => {
1019
- if (!isMountedRef.current) return;
1020
-
1021
- let shouldRecalculate = false;
1022
-
1023
- mutations.forEach((mutation) => {
1024
- if (mutation.type === 'childList') {
1025
- const addedRows = Array.from(mutation.addedNodes).some(
1026
- (node) =>
1027
- node.nodeType === Node.ELEMENT_NODE &&
1028
- (node.classList?.contains('ag-row') ||
1029
- node.querySelector?.('.ag-row'))
1030
- );
1031
-
1032
- const removedRows = Array.from(mutation.removedNodes).some(
1033
- (node) =>
1034
- node.nodeType === Node.ELEMENT_NODE &&
1035
- (node.classList?.contains('ag-row') ||
1036
- node.querySelector?.('.ag-row'))
1037
- );
1038
-
1039
- if (addedRows || removedRows) {
1040
- shouldRecalculate = true;
1041
- }
1042
- }
1043
- });
1044
-
1045
- if (shouldRecalculate) {
1046
- // Reset hover stavu
1047
- currentHoveredRowRef.current = null;
1048
- setIsRowHovered(false);
1049
-
1050
- // Znovu detekujeme aktuální pozici myši po krátké době
1051
- setTimeout(() => {
1052
- if (isMountedRef.current && gridContainerRef.current) {
1053
- const rect = gridContainerRef.current.getBoundingClientRect();
1054
- const mouseEvent = new MouseEvent('mousemove', {
1055
- clientX: window.lastMouseX || rect.left + rect.width / 2,
1056
- clientY: window.lastMouseY || rect.top + rect.height / 2,
1057
- bubbles: true,
1058
- });
1059
- gridContainerRef.current.dispatchEvent(mouseEvent);
1060
- }
1061
- }, 100);
1062
- }
1063
- });
1064
-
1065
- // Spustíme observer na grid container
1066
- observerRef.current.observe(gridContainer, {
1067
- childList: true,
1068
- subtree: true,
1069
- attributes: false,
1070
- });
1071
-
1072
- // Sledujeme pozici myši globálně pro tento observer
1073
- const trackMouse = (e) => {
1074
- window.lastMouseX = e.clientX;
1075
- window.lastMouseY = e.clientY;
1076
- };
1077
-
1078
- document.addEventListener('mousemove', trackMouse, { passive: true });
1079
-
1080
- // Cleanup tracking při unmount
1081
- return () => {
1082
- document.removeEventListener('mousemove', trackMouse);
1083
- };
1084
- }, [params.api, params.eGridCell, handleMouseMove, handleMouseLeave]);
1085
-
1086
- // Spustíme setup při mount a při změně params
1087
- React.useEffect(() => {
1088
- setupGridObserver();
1089
- }, [setupGridObserver]);
1090
-
1091
- const handleVisibleChange = React.useCallback(
1092
- async (visible) => {
1093
- if (!isMountedRef.current) return;
1094
-
1095
- if (visible && onBeforeShow) {
1096
- try {
1097
- const updatedMenuItems = await onBeforeShow(params, menuItems);
1098
- if (
1099
- updatedMenuItems &&
1100
- Array.isArray(updatedMenuItems) &&
1101
- isMountedRef.current
1102
- ) {
1103
- setCurrentMenuItems(updatedMenuItems);
1104
- }
1105
- } catch (error) {
1106
- // Removed console.error for production
1107
- if (isMountedRef.current) {
1108
- setCurrentMenuItems(menuItems);
1109
- }
1110
- }
1111
- }
1112
-
1113
- if (isMountedRef.current) {
1114
- setIsDropdownVisible(visible);
1115
- }
1116
- },
1117
- [params, menuItems, onBeforeShow]
1118
- );
1119
-
1120
- React.useEffect(() => {
1121
- if (isMountedRef.current) {
1122
- setCurrentMenuItems(menuItems);
1123
- }
1124
- }, [menuItems]);
1125
-
1126
- const menu = React.useMemo(
1127
- () => (
1128
- <Menu
1129
- onClick={(e) => {
1130
- if (onActionClick) {
1131
- const item = currentMenuItems.filter(f => f.key === e.key)[0];
1132
- onActionClick(e.key, params, item);
1133
- }
1134
- setIsDropdownVisible(false);
1135
- }}
1136
- >
1137
- {currentMenuItems.map((item) => {
1138
- if (item.type === 'divider') {
1139
- return <Divider key={item.key} style={{ margin: '4px 0' }} />;
1140
- }
1141
-
1142
- let isDisabled = false;
1143
- if (typeof item.disable === 'function') {
1144
- isDisabled = item.disable(params);
1145
- } else if (typeof item.disable === 'boolean') {
1146
- isDisabled = item.disable;
1147
- } else if (item.disabled !== undefined) {
1148
- isDisabled = item.disabled;
1149
- }
1150
-
1151
- return (
1152
- <Menu.Item key={item.key} disabled={isDisabled}>
1153
- {item.label}
1154
- </Menu.Item>
1155
- );
1156
- })}
1157
- </Menu>
1158
- ),
1159
- [currentMenuItems, onActionClick, params]
1160
- );
1161
-
1162
- const containerStyle = {
1163
- width: '32px',
1164
- height: '32px',
1165
- display: 'flex',
1166
- alignItems: 'center',
1167
- justifyContent: 'center',
1168
- position: 'relative',
1169
- };
1170
-
1171
- const buttonStyle = {
1172
- minWidth: 'auto',
1173
- width: '32px',
1174
- height: '32px',
1175
- padding: '0',
1176
- background: 'transparent',
1177
- border: 'none',
1178
- cursor: 'pointer',
1179
- display: 'flex',
1180
- alignItems: 'center',
1181
- justifyContent: 'center',
1182
- opacity: isRowHovered || isDropdownVisible ? 1 : 0,
1183
- visibility: isRowHovered || isDropdownVisible ? 'visible' : 'hidden',
1184
- transition: 'opacity 0.15s ease-in-out, visibility 0.15s ease-in-out',
1185
- pointerEvents: isRowHovered || isDropdownVisible ? 'auto' : 'none',
1186
- };
1187
-
1188
- return (
1189
- <div style={containerStyle}>
1190
- <Dropdown
1191
- overlayStyle={{ zIndex: 5000 }}
1192
- overlay={menu}
1193
- trigger={['click']}
1194
- visible={isDropdownVisible}
1195
- onVisibleChange={handleVisibleChange}
1196
- getPopupContainer={() => document.body}
1197
- >
1198
- <div
1199
- style={buttonStyle}
1200
- onClick={(e) => {
1201
- e.stopPropagation();
1202
- }}
1203
- >
1204
- <ActionIcon />
1205
- </div>
1206
- </Dropdown>
1207
- </div>
1208
- );
1209
- };
1210
-
1211
- this.#addPreparedColumn({
1212
- headerName: '',
1213
- pinned: 'right',
1214
- maxWidth: 40,
1215
- minWidth: 40,
1216
- suppressSizeToFit: true,
1217
- suppressMenu: true,
1218
- sortable: false,
1219
- filter: false,
1220
- resizable: false,
1221
- cellRenderer: ActionDropdownRenderer,
1222
- cellClass: 'action-button-cell-observer',
1223
- ...restProps,
1224
- });
1225
-
1226
- return this;
1227
- }
1228
-
1229
- /**
1230
- * Přidá sloupec s custom tlačítky s podmíněnou viditelností.
1231
- * Každé tlačítko může mít vlastní styl, akci a podmínku viditelnosti.
1232
- * @param {Object} config - Konfigurace tlačítkového sloupce
1233
- * @param {Array} config.buttons - Array definic tlačítek s vlastnostmi jako text, onClick, style
1234
- * @param {Function} [config.visibleGetter=() => true] - Funkce určující viditelnost tlačítek
1235
- * @param {boolean} config.editable - Zda je sloupec editovatelný
1236
- * @param {Object} ...restProps - Další AG Grid vlastnosti sloupce
1237
- * @returns {ColumnBuilder} Instance pro chaining
1238
- */
1239
- addButtonColumn({
1240
- buttons,
1241
- visibleGetter = () => true,
1242
- editable,
1243
- ...restProps
1244
- }) {
1245
- const buttonRendererParams = {
1246
- buttons,
1247
- visibleGetter,
1248
- };
1249
- this.#addPreparedColumn({
1250
- buttonRenderer: true,
1251
- buttonRendererParams,
1252
- editable: this.#resolveEditable(editable),
1253
- ...restProps,
1254
- });
1255
- return this;
1256
- }
1257
-
1258
- /**
1259
- * Privátní pomocná metoda pro správné nastavení editable vlastnosti sloupce.
1260
- * Zajišťuje konzistentní zpracování boolean hodnot pro editovatelnost.
1261
- * @param {boolean|Function} editable - Editovatelnost sloupce nebo funkce vracející boolean
1262
- * @returns {boolean|Function} Upravená hodnota editovatelnosti
1263
- */
1264
- #resolveEditable(editable) {
1265
- if (!editable) return false;
1266
- return editable;
1267
- }
1268
-
1269
- /**
1270
- * Finální metoda pro sestavení pole sloupců AG Grid.
1271
- * Vrací kompletní konfiguraci všech přidaných sloupců připravených pro AG Grid.
1272
- * @returns {Array} Array připravených definic sloupců pro AG Grid
1273
- */
1274
- build() {
1275
- return this.columns;
1276
- }
1277
- }
1278
-
1279
- export default ColumnBuilder;
1
+ /* eslint-disable */
2
+ import * as React from 'react';
3
+ import Menu from 'antd/es/menu';
4
+ import Dropdown from 'antd/es/dropdown';
5
+ import Button from 'antd/es/button';
6
+ import Divider from 'antd/es/divider';
7
+ import { prepareCellStyle, prepareColDef, getValueByPath } from './functions';
8
+ import { currencySymbols } from './enums';
9
+ import { ActionIcon } from './Icons';
10
+ import { CircleHelp, Check, X } from 'lucide-react';
11
+
12
+ /**
13
+ * Custom React hook pro zpracování interakcí s link buňkou v AG Grid.
14
+ * Obsluhuje hover efekty, kliky a přepínání overview módu.
15
+ * @param {Object} params - AG Grid parametry buňky
16
+ * @param {Function} onClick - Callback funkce při kliku na link
17
+ * @param {Object|Function} linkStyle - CSS styl pro link nebo funkce vracející styl
18
+ * @param {Object|Function} hoverStyle - CSS styl pro hover stav nebo funkce vracející styl
19
+ * @param {boolean} overviewToggle - Příznak zda aktivovat overview toggle funkčnost
20
+ * @returns {Object} Objekt s handlry a styly pro link buňku
21
+ */
22
+ const useLinkCellRenderer = (
23
+ params,
24
+ onClick,
25
+ linkStyle,
26
+ hoverStyle,
27
+ overviewToggle
28
+ ) => {
29
+ const [isHovered, setIsHovered] = React.useState(false);
30
+
31
+ const handleMouseEnter = React.useCallback(() => {
32
+ setIsHovered(true);
33
+ }, []);
34
+
35
+ const handleMouseLeave = React.useCallback(() => {
36
+ setIsHovered(false);
37
+ }, []);
38
+
39
+ const handleClick = React.useCallback(
40
+ (event) => {
41
+ event.stopPropagation();
42
+
43
+ if (overviewToggle && params.api) {
44
+ params.api.dispatchEvent({
45
+ type: 'overviewToggle',
46
+ data: params.data,
47
+ params,
48
+ });
49
+ }
50
+
51
+ if (onClick) {
52
+ onClick(params);
53
+ }
54
+ },
55
+ [onClick, overviewToggle, params]
56
+ );
57
+
58
+ // Přesunuto do useMemo, aby se vytvořilo pouze jednou
59
+ const defaultLinkStyle = React.useMemo(
60
+ () => ({
61
+ color: '#1a73e8',
62
+ textDecoration: 'none',
63
+ cursor: 'pointer',
64
+ }),
65
+ []
66
+ );
67
+
68
+ // Přesunuto do useMemo, aby se vytvořilo pouze jednou
69
+ const defaultHoverStyle = React.useMemo(
70
+ () => ({
71
+ textDecoration: 'underline',
72
+ }),
73
+ []
74
+ );
75
+
76
+ const computedLinkStyle = React.useMemo(
77
+ () => ({
78
+ ...defaultLinkStyle,
79
+ ...(typeof linkStyle === 'function'
80
+ ? linkStyle(params)
81
+ : linkStyle || {}),
82
+ }),
83
+ [linkStyle, params, defaultLinkStyle]
84
+ );
85
+
86
+ const computedHoverStyle = React.useMemo(
87
+ () => ({
88
+ ...defaultLinkStyle,
89
+ ...computedLinkStyle,
90
+ ...defaultHoverStyle,
91
+ ...(typeof hoverStyle === 'function'
92
+ ? hoverStyle(params)
93
+ : hoverStyle || {}),
94
+ }),
95
+ [computedLinkStyle, hoverStyle, params, defaultLinkStyle, defaultHoverStyle]
96
+ );
97
+
98
+ return {
99
+ isHovered,
100
+ handleMouseEnter,
101
+ handleMouseLeave,
102
+ handleClick,
103
+ computedLinkStyle,
104
+ computedHoverStyle,
105
+ };
106
+ };
107
+
108
+ /**
109
+ * Custom React hook pro zpracování interakcí s object buňkou v AG Grid.
110
+ * Poskytuje funkcionalitu pro editovatelné objekty s hover efekty a edit tlačítkem.
111
+ * @param {Object} params - AG Grid parametry buňky
112
+ * @param {boolean} editable - Příznak zda je buňka editovatelná
113
+ * @param {Function} onEditClick - Callback funkce při kliku na edit tlačítko
114
+ * @returns {Object} Objekt s handlry pro object buňku
115
+ */
116
+ const useObjectCellRenderer = (params, editable, onEditClick) => {
117
+ const [isHovered, setIsHovered] = React.useState(false);
118
+
119
+ const handleMouseEnter = React.useCallback(() => {
120
+ setIsHovered(true);
121
+ }, []);
122
+
123
+ const handleMouseLeave = React.useCallback(() => {
124
+ setIsHovered(false);
125
+ }, []);
126
+
127
+ const handleEditClick = React.useCallback(
128
+ (event) => {
129
+ event.stopPropagation();
130
+ if (onEditClick) {
131
+ onEditClick(params);
132
+ }
133
+ },
134
+ [onEditClick, params]
135
+ );
136
+
137
+ return {
138
+ isHovered,
139
+ handleMouseEnter,
140
+ handleMouseLeave,
141
+ handleEditClick,
142
+ };
143
+ };
144
+
145
+ /**
146
+ * Custom React hook pro detekci hover stavu řádku v AG Grid.
147
+ * Používá MutationObserver pro sledování CSS tříd řádků a detekci hover stavu.
148
+ * Tento hook se používá pro akční tlačítka, která se zobrazují pouze při hoveru nad řádkem.
149
+ * @param {Object} params - AG Grid parametry buňky obsahující informace o řádku
150
+ * @returns {Object} Objekt s boolean příznakem isRowHovered
151
+ */
152
+ const useRowHoverActionRenderer = (params) => {
153
+ const [isRowHovered, setIsRowHovered] = React.useState(false);
154
+ const rowRef = React.useRef(null);
155
+
156
+ React.useEffect(() => {
157
+ if (!params.node) return undefined;
158
+
159
+ // Funkce pro kontrolu, zda je řádek v hover stavu
160
+ const checkRowHoverState = () => {
161
+ // Najdeme DOM element řádku pomocí rowIndex
162
+ const rowElement = document.querySelector(
163
+ `.ag-row[row-index="${params.rowIndex}"]`
164
+ );
165
+ if (rowElement) {
166
+ rowRef.current = rowElement;
167
+ // AG-Grid automaticky přidává třídu ag-row-hover k řádku při najetí myší
168
+ const isHovered = rowElement.classList.contains('ag-row-hover');
169
+ setIsRowHovered(isHovered);
170
+ }
171
+ };
172
+
173
+ // Vytvoříme MutationObserver pro sledování změn tříd na řádku
174
+ const observer = new MutationObserver((mutations) => {
175
+ mutations.forEach((mutation) => {
176
+ if (
177
+ mutation.type === 'attributes' &&
178
+ mutation.attributeName === 'class'
179
+ ) {
180
+ checkRowHoverState();
181
+ }
182
+ });
183
+ });
184
+
185
+ // Inicializujeme stav
186
+ checkRowHoverState();
187
+
188
+ // Přidáme observer k element řádku, pokud existuje
189
+ if (rowRef.current) {
190
+ observer.observe(rowRef.current, { attributes: true });
191
+ }
192
+
193
+ // Přidáme event listeners na grid container pro sledování pohybu myši
194
+ const gridContainer = document.querySelector('.ag-root-wrapper');
195
+ if (gridContainer) {
196
+ gridContainer.addEventListener('mouseover', checkRowHoverState);
197
+ gridContainer.addEventListener('mouseout', checkRowHoverState);
198
+ }
199
+
200
+ // Cleanup při odmontování komponenty
201
+ return () => {
202
+ if (rowRef.current) {
203
+ observer.disconnect();
204
+ }
205
+ if (gridContainer) {
206
+ gridContainer.removeEventListener('mouseover', checkRowHoverState);
207
+ gridContainer.removeEventListener('mouseout', checkRowHoverState);
208
+ }
209
+ };
210
+ }, [params.node, params.rowIndex]);
211
+
212
+ return {
213
+ isRowHovered,
214
+ };
215
+ };
216
+
217
+ /**
218
+ * ColumnBuilder třída pro builder pattern vytváření sloupců AG Grid.
219
+ * Poskytuje fluent API pro snadné a čitelné definování různých typů sloupců.
220
+ * Podporuje lokalizaci přes react-intl a automatickou konfiguraci sloupců.
221
+ */
222
+ class ColumnBuilder {
223
+ /**
224
+ * Privátní metoda pro přidání připraveného sloupce do kolekce.
225
+ * Aplikuje standardní konfiguraci sloupce včetně lokalizace.
226
+ * @param {Object} props - Konfigurace sloupce
227
+ */
228
+ #addPreparedColumn(props) {
229
+ this.columns.push(prepareColDef(this.intl, props));
230
+ }
231
+
232
+ /**
233
+ * Konstruktor ColumnBuilder třídy.
234
+ * @param {Object} intl - React-intl objekt pro lokalizaci
235
+ */
236
+ constructor(intl) {
237
+ this.columns = [];
238
+ this.intl = intl;
239
+ }
240
+
241
+ /**
242
+ * Přidá základní sloupec do AG Grid s podporou barevného pole.
243
+ * Umožňuje dynamické obarvení textu buňky na základě hodnoty z jiného pole.
244
+ * @param {Object} config - Konfigurace sloupce
245
+ * @param {string} config.colorField - Název pole obsahujícího barvu pro text buňky
246
+ * @param {string} config.bgColorField - Název pole obsahujícího barvu pozadí buňky
247
+ * @param {Object} ...props - Další AG Grid vlastnosti sloupce
248
+ * @returns {ColumnBuilder} Instance pro chaining
249
+ */
250
+ addColumn({ colorField, bgColorField, ...props }) {
251
+ // Získáme původní cellStyle, pokud existuje
252
+ const originalCellStyle = props.cellStyle;
253
+ const { cellAlign, ...restProps } = props;
254
+
255
+ // Pokud je zadáno colorField nebo bgColorField, vytvoříme novou funkci pro cellStyle
256
+ if (colorField || bgColorField) {
257
+ const overrideCellStyle = (params) => {
258
+ // Získáme základní styl
259
+ let baseStyle = {};
260
+ if (originalCellStyle) {
261
+ if (typeof originalCellStyle === 'function') {
262
+ baseStyle = originalCellStyle(params);
263
+ } else {
264
+ baseStyle = originalCellStyle;
265
+ }
266
+ }
267
+
268
+ // Získáme hodnotu barvy textu z dat
269
+ const colorValue =
270
+ colorField && params.data ? getValueByPath(params.data, colorField) : null;
271
+
272
+ // Získáme hodnotu barvy pozadí z dat
273
+ const bgColorValue =
274
+ bgColorField && params.data ? getValueByPath(params.data, bgColorField) : null;
275
+
276
+ const restStyle = prepareCellStyle(props);
277
+
278
+ // Vrátíme kombinovaný styl s barvou textu i pozadí
279
+ const newStyle = { ...baseStyle, ...restStyle };
280
+ if (colorValue) {
281
+ newStyle.color = colorValue;
282
+ }
283
+ if (bgColorValue) {
284
+ newStyle.backgroundColor = bgColorValue;
285
+ }
286
+
287
+ return newStyle;
288
+ };
289
+
290
+ // Předáme upravené props do addPreparedColumn
291
+ this.#addPreparedColumn({ ...restProps, overrideCellStyle });
292
+ } else {
293
+ // Pokud není colorField ani bgColorField zadán, použijeme původní implementaci
294
+ this.#addPreparedColumn(props);
295
+ }
296
+
297
+ return this;
298
+ }
299
+
300
+ /**
301
+ * Přidá sloupec s kartou faktury optimalizovaný pro overview mód.
302
+ * Zobrazuje fakturační informace v kartovém formátu s podporou checkboxu.
303
+ * @param {Object} config - Konfigurace sloupce
304
+ * @param {string} [config.field='invoice'] - Název datového pole
305
+ * @param {string} [config.headerName=''] - Název hlavičky sloupce
306
+ * @param {number} [config.width=340] - Šířka sloupce
307
+ * @param {Function} config.onCheckboxChange - Callback při změně stavu checkboxu
308
+ * @param {Object} ...restProps - Další AG Grid vlastnosti sloupce
309
+ * @returns {ColumnBuilder} Instance pro chaining
310
+ */
311
+ addInvoiceCardColumn({
312
+ field = 'invoice',
313
+ headerName = '',
314
+ width = 340,
315
+ onCheckboxChange,
316
+ ...restProps
317
+ } = {}) {
318
+ const cellRenderer = 'invoiceCardRenderer';
319
+
320
+ this.#addPreparedColumn({
321
+ headerName,
322
+ field,
323
+ width,
324
+ cellRenderer,
325
+ suppressSizeToFit: true,
326
+ cellRendererParams: {
327
+ onCheckboxChange:
328
+ onCheckboxChange ||
329
+ (() => {
330
+ // Funkce bez console.log
331
+ }),
332
+ },
333
+ ...restProps,
334
+ });
335
+
336
+ return this;
337
+ }
338
+
339
+ /**
340
+ * Přidá sloupec pro zobrazení měny s podporou různých formátů a pozicování symbolu.
341
+ * Automaticky formátuje hodnoty jako měnu s dvěma desetinnými místy.
342
+ * @param {Object} config - Konfigurace měnového sloupce
343
+ * @param {string|Function} config.currency - Kód měny nebo funkce vracející kód měny
344
+ * @param {string} [config.position] - Pozice symbolu měny ('left' nebo 'right')
345
+ * @param {Object|Function} config.currencyStyle - CSS styl pro symbol měny
346
+ * @param {string} [config.cellAlign='right'] - Zarovnání obsahu buňky
347
+ * @param {boolean} config.editable - Zda je sloupec editovatelný
348
+ * @param {Object} ...restProps - Další AG Grid vlastnosti sloupce
349
+ * @returns {ColumnBuilder} Instance pro chaining
350
+ */
351
+ addCurrencyColumn({
352
+ currency,
353
+ position,
354
+ currencyStyle,
355
+ cellAlign = 'right',
356
+ editable,
357
+ ...restProps
358
+ }) {
359
+ const cellRenderer = (params) => {
360
+ const currencyValue =
361
+ typeof currency === 'function' ? currency(params) : currency;
362
+ const symbol = currencySymbols[currencyValue] || currencyValue;
363
+
364
+ // Použití Number.isNaN místo globální isNaN
365
+ const value =
366
+ params.value != null && !Number.isNaN(Number(params.value))
367
+ ? params.value.toFixed(2)
368
+ : '0.00';
369
+
370
+ const style =
371
+ typeof currencyStyle === 'function'
372
+ ? currencyStyle(params)
373
+ : currencyStyle;
374
+
375
+ return (
376
+ <span>
377
+ {position === 'left' ? (
378
+ <>
379
+ <span style={style}>{symbol}</span> {value}
380
+ </>
381
+ ) : (
382
+ <>
383
+ {value} <span style={style}>{symbol}</span>
384
+ </>
385
+ )}
386
+ </span>
387
+ );
388
+ };
389
+
390
+ this.#addPreparedColumn({
391
+ cellAlign,
392
+ cellRenderer,
393
+ currency,
394
+ editable: this.#resolveEditable(editable),
395
+ ...restProps,
396
+ });
397
+ return this;
398
+ }
399
+
400
+ /**
401
+ * Přidá sloupec pro zobrazení data s možností specifikace formátu.
402
+ * Používá standardní AG Grid date renderer s českým formátováním.
403
+ * @param {Object} config - Konfigurace datového sloupce
404
+ * @param {string} [config.dateFormat='DD.MM.YYYY'] - Formát zobrazení data
405
+ * @param {boolean} config.editable - Zda je sloupec editovatelný
406
+ * @param {Object} ...restProps - Další AG Grid vlastnosti sloupce
407
+ * @returns {ColumnBuilder} Instance pro chaining
408
+ */
409
+ addDateColumn({ dateFormat = 'DD.MM.YYYY', editable, ...restProps }) {
410
+ this.#addPreparedColumn({
411
+ dateRenderer: dateFormat,
412
+ editable: this.#resolveEditable(editable),
413
+ ...restProps,
414
+ });
415
+ return this;
416
+ }
417
+
418
+ /**
419
+ * Přidá sloupec s klikatelným linkem podporujícím hover efekty.
420
+ * Link může spouštět akce nebo přepínat overview mód v gridu.
421
+ * @param {Object} config - Konfigurace linkového sloupce
422
+ * @param {Function} config.onClick - Callback funkce při kliku na link
423
+ * @param {Object|Function} config.linkStyle - CSS styl pro link nebo funkce vracející styl
424
+ * @param {Object|Function} config.hoverStyle - CSS styl pro hover stav
425
+ * @param {string} [config.cellAlign='left'] - Zarovnání obsahu buňky
426
+ * @param {boolean} config.editable - Zda je sloupec editovatelný
427
+ * @param {boolean} [config.overviewToggle=false] - Zda aktivovat overview toggle
428
+ * @param {Object} ...restProps - Další AG Grid vlastnosti sloupce
429
+ * @returns {ColumnBuilder} Instance pro chaining
430
+ */
431
+ addLinkColumn({
432
+ onClick,
433
+ linkStyle,
434
+ hoverStyle,
435
+ cellAlign = 'left',
436
+ editable,
437
+ overviewToggle = false, // Přidán výchozí parametr pro overview
438
+ ...restProps
439
+ }) {
440
+ // Vytvořím komponentu pro cell renderer, která používá hook
441
+ const LinkCellRenderer = React.memo((params) => {
442
+ const {
443
+ isHovered,
444
+ handleMouseEnter,
445
+ handleMouseLeave,
446
+ handleClick,
447
+ computedLinkStyle,
448
+ computedHoverStyle,
449
+ } = useLinkCellRenderer(
450
+ params,
451
+ onClick,
452
+ linkStyle,
453
+ hoverStyle,
454
+ overviewToggle
455
+ );
456
+
457
+ return (
458
+ <div
459
+ className="link-cell-container"
460
+ style={{
461
+ width: '100%',
462
+ height: '100%',
463
+ display: 'flex',
464
+ alignItems: 'center',
465
+ }}
466
+ >
467
+ <span
468
+ onClick={handleClick}
469
+ onMouseEnter={handleMouseEnter}
470
+ onMouseLeave={handleMouseLeave}
471
+ style={isHovered ? computedHoverStyle : computedLinkStyle}
472
+ role="button"
473
+ tabIndex={0}
474
+ onKeyPress={(event) => {
475
+ if (event.key === 'Enter' || event.key === ' ') {
476
+ handleClick(event);
477
+ }
478
+ }}
479
+ >
480
+ {params.value}
481
+ </span>
482
+ </div>
483
+ );
484
+ });
485
+
486
+ // Používáme funkci, která vrátí React komponentu
487
+ const cellRenderer = (params) => {
488
+ return <LinkCellRenderer {...params} />;
489
+ };
490
+
491
+ this.#addPreparedColumn({
492
+ cellAlign,
493
+ cellRenderer,
494
+ editable: this.#resolveEditable(editable),
495
+ overviewToggle, // Předání příznaku dál
496
+ ...restProps,
497
+ });
498
+ return this;
499
+ }
500
+
501
+ /**
502
+ * Helper metoda pro přidání overview link sloupce s přednastavenými hodnotami.
503
+ * Specializovaný link sloupec pro přepínání overview módu v gridu.
504
+ * @param {Object} config - Konfigurace overview link sloupce
505
+ * @param {string} [config.field='documentNumber'] - Název datového pole
506
+ * @param {string} [config.headerName=''] - Název hlavičky sloupce
507
+ * @param {number} [config.width=130] - Šířka sloupce
508
+ * @param {string} [config.cellAlign='left'] - Zarovnání obsahu buňky
509
+ * @param {Object|Function} config.linkStyle - CSS styl pro link
510
+ * @param {Object|Function} config.hoverStyle - CSS styl pro hover stav
511
+ * @param {boolean} [config.editable=false] - Zda je sloupec editovatelný
512
+ * @param {Object} ...rest - Další AG Grid vlastnosti sloupce včetně intlId
513
+ * @returns {ColumnBuilder} Instance pro chaining
514
+ */
515
+ addOverviewLinkColumn({
516
+ field = 'documentNumber',
517
+ headerName = '',
518
+ width = 130,
519
+ cellAlign = 'left',
520
+ linkStyle,
521
+ hoverStyle,
522
+ editable = false,
523
+ ...rest
524
+ } = {}) {
525
+ return this.addLinkColumn({
526
+ field,
527
+ headerName: this?.intl?.formatMessage({ id: rest?.intlId }) || headerName,
528
+ width,
529
+ cellAlign,
530
+ linkStyle,
531
+ hoverStyle,
532
+ editable,
533
+ overviewToggle: true,
534
+ ...rest,
535
+ });
536
+ }
537
+
538
+ /**
539
+ * Přidá sloupec pro zobrazení objektů s možností editace a tooltip podporou.
540
+ * Při hoveru zobrazuje edit tlačítko pro editovatelné objekty.
541
+ * @param {Object} config - Konfigurace objektového sloupce
542
+ * @param {boolean} config.contentTooltip - Zda zobrazovat tooltip s obsahem
543
+ * @param {string} config.tooltipField - Pole obsahující text pro tooltip
544
+ * @param {boolean} config.tooltipInteraction - Zda povolit interakci s tooltipem
545
+ * @param {number} [config.tooltipShowDelay=100] - Zpoždění zobrazení tooltipu v ms
546
+ * @param {boolean} config.editable - Zda je objekt editovatelný
547
+ * @param {Function} config.onEditClick - Callback při kliku na edit tlačítko
548
+ * @param {Object} ...restProps - Další AG Grid vlastnosti sloupce
549
+ * @returns {ColumnBuilder} Instance pro chaining
550
+ */
551
+ addObjectColumn({
552
+ contentTooltip,
553
+ tooltipField,
554
+ tooltipInteraction,
555
+ tooltipShowDelay = 100,
556
+ editable,
557
+ onEditClick,
558
+ ...restProps
559
+ }) {
560
+ // Vytvořím komponentu pro cell renderer, která používá hook
561
+ const ObjectCellRenderer = React.memo((params) => {
562
+ const { isHovered, handleMouseEnter, handleMouseLeave, handleEditClick } =
563
+ useObjectCellRenderer(params, editable, onEditClick);
564
+
565
+ return (
566
+ <div
567
+ className="object-cell-container"
568
+ style={{
569
+ position: 'relative',
570
+ width: '100%',
571
+ height: '100%',
572
+ display: 'flex',
573
+ alignItems: 'center',
574
+ }}
575
+ onMouseEnter={handleMouseEnter}
576
+ onMouseLeave={handleMouseLeave}
577
+ >
578
+ <div style={{ flexGrow: 1 }}>{params.value}</div>
579
+ {isHovered && editable && (
580
+ <button
581
+ className="edit-object-button"
582
+ style={{
583
+ position: 'absolute',
584
+ right: '4px',
585
+ background: 'transparent',
586
+ border: 'none',
587
+ cursor: 'pointer',
588
+ padding: '4px',
589
+ display: 'flex',
590
+ alignItems: 'center',
591
+ justifyContent: 'center',
592
+ }}
593
+ onClick={handleEditClick}
594
+ title="Upravit"
595
+ >
596
+ <span style={{ fontSize: '16px' }}>⋮</span>
597
+ </button>
598
+ )}
599
+ </div>
600
+ );
601
+ });
602
+
603
+ // Používáme funkci, která vrátí React komponentu
604
+ const cellRenderer = (params) => {
605
+ return <ObjectCellRenderer {...params} />;
606
+ };
607
+
608
+ this.#addPreparedColumn({
609
+ contentTooltip,
610
+ tooltipField,
611
+ tooltipInteraction,
612
+ tooltipShowDelay,
613
+ cellRenderer,
614
+ editable: this.#resolveEditable(editable),
615
+ ...restProps,
616
+ });
617
+ return this;
618
+ }
619
+
620
+ /**
621
+ * Přidá sloupec pro zobrazení ikon s podporou badge (číselného označení).
622
+ * Umožňuje podmíněné zobrazování ikon a jejich stylování.
623
+ * @param {Object} config - Konfigurace ikonového sloupce
624
+ * @param {Function} [config.visibleGetter=() => true] - Funkce určující viditelnost ikony
625
+ * @param {string} config.cellAlign - Zarovnání ikony v buňce
626
+ * @param {string|Function} config.icon - Název ikony nebo funkce vracející ikonu
627
+ * @param {string|Function} [config.iconColor] - Barva ikony nebo funkce vracející barvu podle parametrů
628
+ * @param {number} [config.size=16] - Velikost ikony v pixelech
629
+ * @param {boolean} [config.iconLeft=false] - Zda zobrazit ikonu vlevo od textu
630
+ * @param {boolean} [config.iconRight=false] - Zda zobrazit ikonu vpravo od textu
631
+ * @param {boolean} [config.showText=true] - Zda zobrazit text (hodnotu pole)
632
+ * @param {number} [config.textGap=5] - Mezera mezi ikonou a textem v pixelech
633
+ * @param {boolean} [config.showOnGroup=false] - Zda zobrazit ikonu na skupinových řádcích
634
+ * @param {string|number|Function} config.badge - Text/číslo badge nebo funkce vracející badge
635
+ * @param {boolean} config.showZero - Zda zobrazit badge s hodnotou 0
636
+ * @param {string} [config.badgeColor='#dc3545'] - Barva pozadí badge
637
+ * @param {string} [config.badgeTextColor='white'] - Barva textu badge
638
+ * @param {string} [config.badgePosition='top-right'] - Pozice badge ('top-right', 'top-left', 'bottom-right', 'bottom-left')
639
+ * @param {Object} ...restProps - Další AG Grid vlastnosti sloupce
640
+ * @returns {ColumnBuilder} Instance pro chaining
641
+ */
642
+ addIconColumn({
643
+ visibleGetter = () => true,
644
+ cellAlign,
645
+ icon,
646
+ iconColor,
647
+ size = 16,
648
+ iconLeft = false,
649
+ iconRight = false,
650
+ showText = true,
651
+ textGap = 5,
652
+ showOnGroup = false,
653
+ badge,
654
+ showZero,
655
+ badgeColor = '#dc3545',
656
+ badgeTextColor = 'white',
657
+ badgePosition = 'top-right',
658
+ ...restProps
659
+ }) {
660
+ const iconRendererParams = {
661
+ cellAlign,
662
+ visibleGetter,
663
+ icon,
664
+ iconColor,
665
+ size,
666
+ iconLeft,
667
+ iconRight,
668
+ showText,
669
+ textGap,
670
+ showOnGroup,
671
+ badge,
672
+ showZero,
673
+ badgeColor,
674
+ badgeTextColor,
675
+ badgePosition,
676
+ };
677
+ this.#addPreparedColumn({
678
+ iconRenderer: true,
679
+ iconRendererParams,
680
+ ...restProps,
681
+ });
682
+ return this;
683
+ }
684
+
685
+ /**
686
+ * Přidá sloupec pro zobrazování stavů s kruhovým pozadím.
687
+ * Zobrazuje text stavu v kruhovém pozadí s definovanou barvou.
688
+ * @param {Object} config - Konfigurace stavového sloupce
689
+ * @param {Function} [config.visibleGetter=() => true] - Funkce určující viditelnost
690
+ * @param {string} [config.cellAlign='center'] - Zarovnání v buňce
691
+ * @param {string|Function} [config.bgColor] - Barva pozadí nebo funkce vracející barvu podle parametrů
692
+ * @param {string|Function} [config.textColor='white'] - Barva textu nebo funkce vracející barvu
693
+ * @param {string|Function} [config.displayField] - Pole pro zobrazení textu (pokud se liší od field)
694
+ * @param {number} [config.minWidth=80] - Minimální šířka kruhového pozadí
695
+ * @param {number} [config.fontSize=12] - Velikost fontu
696
+ * @param {boolean} [config.showOnGroup=false] - Zda zobrazit na skupinových řádcích
697
+ * @param {Object} ...restProps - Další AG Grid vlastnosti sloupce
698
+ * @returns {ColumnBuilder} Instance pro chaining
699
+ */
700
+ addStateColumn({
701
+ visibleGetter = () => true,
702
+ cellAlign = 'center',
703
+ bgColor,
704
+ textColor = 'white',
705
+ displayField,
706
+ minWidth = 80,
707
+ fontSize = 12,
708
+ showOnGroup = false,
709
+ ...restProps
710
+ }) {
711
+ const stateRendererParams = {
712
+ visibleGetter,
713
+ cellAlign,
714
+ bgColor,
715
+ textColor,
716
+ displayField,
717
+ minWidth,
718
+ fontSize,
719
+ showOnGroup,
720
+ };
721
+ this.#addPreparedColumn({
722
+ stateRenderer: true,
723
+ stateRendererParams,
724
+ ...restProps,
725
+ });
726
+ return this;
727
+ }
728
+
729
+ /**
730
+ * Přidá sloupec pro zobrazení boolean hodnot pomocí ikon (zaškrtnutí/křížek).
731
+ * Podporuje různé ikony pro true/false stav a podmíněnou viditelnost.
732
+ * @param {Object} config - Konfigurace boolean sloupce
733
+ * @param {boolean} [config.visibleFalse=true] - Zda zobrazit ikonu pro false hodnotu
734
+ * @param {boolean} [config.visibleTrue=true] - Zda zobrazit ikonu pro true hodnotu
735
+ * @param {string} [config.cellAlign='center'] - Zarovnání ikony v buňce
736
+ * @param {Function} [config.visibleGetter=() => true] - Funkce určující viditelnost ikony
737
+ * @param {boolean} [config.defaultIcon=true] - Zda zobrazit výchozí ikonu pro undefined hodnoty
738
+ * @param {string} [config.defaultIconColor='#898989'] - Barva výchozí ikony
739
+ * @param {number} [config.size=16] - Velikost ikony v pixelech
740
+ * @param {string} [config.colorTrue='green'] - Barva ikony pro true hodnotu
741
+ * @param {string} [config.colorFalse='red'] - Barva ikony pro false hodnotu
742
+ * @param {boolean} [config.showOnGroup=false] - Zda zobrazit ikonu na skupinových řádcích
743
+ * @param {boolean} config.editable - Zda je sloupec editovatelný
744
+ * @param {Object} ...restProps - Další AG Grid vlastnosti sloupce
745
+ * @returns {ColumnBuilder} Instance pro chaining
746
+ */
747
+ addBooleanColumn({
748
+ visibleFalse = true,
749
+ visibleTrue = true,
750
+ cellAlign = 'center',
751
+ visibleGetter = () => true,
752
+ defaultIcon = true,
753
+ defaultIconColor = '#898989',
754
+ size = 16,
755
+ colorTrue = 'green',
756
+ colorFalse = 'red',
757
+ showOnGroup = false,
758
+ editable,
759
+ ...restProps
760
+ }) {
761
+ // Create a React component for boolean cell renderer
762
+ const BooleanCellRenderer = React.memo((params) => {
763
+ const { data, value } = params;
764
+
765
+ const visibleResult = visibleGetter ? visibleGetter(data) : true;
766
+
767
+ const Icon = ({ innerValue, ...iconProps }) => {
768
+ if (
769
+ innerValue === undefined ||
770
+ (!visibleFalse && !innerValue) ||
771
+ (!visibleTrue && innerValue)
772
+ ) {
773
+ if (defaultIcon)
774
+ return <CircleHelp size={size} color={defaultIconColor} {...iconProps} />;
775
+ return <div />;
776
+ }
777
+
778
+ if (innerValue)
779
+ return <Check size={size} color={colorTrue} {...iconProps} />;
780
+ return <X size={size} color={colorFalse} {...iconProps} />;
781
+ };
782
+
783
+ const showCondition = () => {
784
+ const newItem = (data && data._rh_plus_ag_grid_new_item) || false;
785
+ return !newItem && (showOnGroup || !!data) && visibleResult;
786
+ };
787
+
788
+ if (!showCondition()) return null;
789
+
790
+ return (
791
+ <span
792
+ style={{
793
+ width: '100%',
794
+ display: 'flex',
795
+ justifyContent: cellAlign,
796
+ alignItems: 'center',
797
+ height: '100%',
798
+ }}
799
+ >
800
+ <Icon
801
+ style={{
802
+ display: 'inline-block',
803
+ height: '100%',
804
+ }}
805
+ innerValue={value}
806
+ visibleTrue={visibleTrue}
807
+ visibleFalse={visibleFalse}
808
+ />
809
+ </span>
810
+ );
811
+ });
812
+
813
+ // Use the function that returns React component
814
+ const cellRenderer = (params) => {
815
+ return <BooleanCellRenderer {...params} />;
816
+ };
817
+
818
+ this.#addPreparedColumn({
819
+ cellRenderer,
820
+ editable: this.#resolveEditable(editable),
821
+ ...restProps,
822
+ });
823
+ return this;
824
+ }
825
+
826
+ /**
827
+ * Přidá připnutý akční sloupec s dropdown menu, který se zobrazuje pouze při hoveru nad řádkem.
828
+ * Používa pokročilou detekci hover stavu s MutationObserver pro každou instanci gridu zvlášť.
829
+ * Sloupec je automaticky připnut vpravo a obsahuje akční tlačítko s kontextovým menu.
830
+ * @param {Object} config - Konfigurace akčního sloupce
831
+ * @param {Array} [config.menuItems=[]] - Array položek menu s key, label, disable vlastnostmi
832
+ * @param {Function} config.onActionClick - Callback při kliku na položku menu (key, params, item)
833
+ * @param {Function} config.onBeforeShow - Async callback před zobrazením menu pro dynamické položky
834
+ * @param {Object} ...restProps - Další AG Grid vlastnosti sloupce
835
+ * @returns {ColumnBuilder} Instance pro chaining
836
+ */
837
+ addPinnedActionButton({
838
+ menuItems = [],
839
+ onActionClick,
840
+ onBeforeShow,
841
+ ...restProps
842
+ } = {}) {
843
+ const ActionDropdownRenderer = (params) => {
844
+ const [isRowHovered, setIsRowHovered] = React.useState(false);
845
+ const [isDropdownVisible, setIsDropdownVisible] = React.useState(false);
846
+ const [currentMenuItems, setCurrentMenuItems] = React.useState(menuItems);
847
+
848
+ const isMountedRef = React.useRef(true);
849
+ const rowIndexRef = React.useRef(null);
850
+ const observerRef = React.useRef(null);
851
+ const gridContainerRef = React.useRef(null);
852
+ const currentHoveredRowRef = React.useRef(null);
853
+ const mouseListenersSetupRef = React.useRef(false);
854
+
855
+ React.useEffect(() => {
856
+ return () => {
857
+ isMountedRef.current = false;
858
+ cleanup();
859
+ };
860
+ }, []);
861
+
862
+ // Cleanup funkce
863
+ const cleanup = () => {
864
+ if (observerRef.current) {
865
+ observerRef.current.disconnect();
866
+ observerRef.current = null;
867
+ }
868
+
869
+ if (gridContainerRef.current && mouseListenersSetupRef.current) {
870
+ gridContainerRef.current.removeEventListener(
871
+ 'mousemove',
872
+ handleMouseMove
873
+ );
874
+ gridContainerRef.current.removeEventListener(
875
+ 'mouseleave',
876
+ handleMouseLeave
877
+ );
878
+ // gridContainerRef.current.removeEventListener(
879
+ // 'mouseenter',
880
+ // handleMouseEnter
881
+ // );
882
+ mouseListenersSetupRef.current = false;
883
+ }
884
+ };
885
+
886
+ // Získáme row index z params
887
+ React.useEffect(() => {
888
+ if (params.node && params.node.rowIndex !== undefined) {
889
+ rowIndexRef.current = params.node.rowIndex.toString();
890
+ } else if (params.rowIndex !== undefined) {
891
+ rowIndexRef.current = params.rowIndex.toString();
892
+ }
893
+ }, [params.node, params.rowIndex]);
894
+
895
+ // Mouse event handlers
896
+ const handleMouseMove = React.useCallback((event) => {
897
+ if (!isMountedRef.current) return;
898
+
899
+ const target = event.target;
900
+ if (!target) return;
901
+
902
+ // Najdeme nejbližší AG-Grid row element
903
+ let rowElement = target.closest('.ag-row');
904
+
905
+ if (rowElement) {
906
+ // Získáme row index z AG-Grid attributů
907
+ const rowIndex =
908
+ rowElement.getAttribute('row-index') ||
909
+ rowElement.getAttribute('aria-rowindex') ||
910
+ rowElement.dataset.rowIndex ||
911
+ rowElement.getAttribute('data-ag-row-index');
912
+
913
+ if (rowIndex !== null) {
914
+ const normalizedRowIndex = rowIndex.toString();
915
+
916
+ // KLÍČOVÁ ZMĚNA: Explicitně resetujeme předchozí řádek při změně
917
+ if (normalizedRowIndex !== currentHoveredRowRef.current) {
918
+ // Pokud jsme měli předchozí řádek, nastavíme mu hover false
919
+ if (currentHoveredRowRef.current !== null) {
920
+ notifyRowHoverChange(currentHoveredRowRef.current, false);
921
+ }
922
+
923
+ // Nastavíme nový řádek jako hovered
924
+ currentHoveredRowRef.current = normalizedRowIndex;
925
+ notifyRowHoverChange(normalizedRowIndex, true);
926
+ }
927
+ }
928
+ } else {
929
+ // Myš není nad žádným řádkem - resetujeme všechny
930
+ if (currentHoveredRowRef.current !== null) {
931
+ notifyRowHoverChange(currentHoveredRowRef.current, false);
932
+ currentHoveredRowRef.current = null;
933
+ }
934
+ }
935
+ }, []);
936
+
937
+ const handleMouseLeave = React.useCallback((event) => {
938
+ if (!isMountedRef.current) return;
939
+
940
+ // Lepší detekce opuštění gridu
941
+ const relatedTarget = event.relatedTarget;
942
+
943
+ // Pokud myš opustí celý grid container NEBO jde mimo dokument
944
+ if (
945
+ gridContainerRef.current &&
946
+ (!relatedTarget || !gridContainerRef.current.contains(relatedTarget))
947
+ ) {
948
+ if (currentHoveredRowRef.current !== null) {
949
+ notifyRowHoverChange(currentHoveredRowRef.current, false);
950
+ currentHoveredRowRef.current = null;
951
+ }
952
+ }
953
+ }, []);
954
+
955
+ // Notifikace o změně hover stavu pro tento specifický grid
956
+ const notifyRowHoverChange = (rowIndex, isHovered) => {
957
+ if (!isMountedRef.current) return;
958
+
959
+ // EXPLICITNÍ ŘÍZENÍ: Pokud nastavujeme hover false, resetujeme stav pro všechny řádky
960
+ if (!isHovered && rowIndexRef.current) {
961
+ setIsRowHovered(false);
962
+ }
963
+
964
+ // Pokud je toto náš řádek a nastavujeme hover true, aktualizujeme stav
965
+ if (isHovered && rowIndexRef.current === rowIndex) {
966
+ setIsRowHovered(true);
967
+ }
968
+ };
969
+
970
+ // Nastavení observer pro tento konkrétní grid
971
+ const setupGridObserver = React.useCallback(() => {
972
+ if (!params.api || observerRef.current || !isMountedRef.current) return;
973
+
974
+ // Najdeme grid container pro tento konkrétní grid instance
975
+ const findThisGridContainer = () => {
976
+ if (params.eGridCell) {
977
+ let current = params.eGridCell;
978
+ while (current && current !== document.body) {
979
+ if (
980
+ current.classList &&
981
+ (current.classList.contains('ag-root-wrapper') ||
982
+ current.classList.contains('ag-theme-alpine') ||
983
+ current.classList.contains('ag-theme-balham') ||
984
+ current.classList.contains('ag-theme-material') ||
985
+ current.classList.contains('ag-theme-fresh') ||
986
+ current.classList.contains('ag-theme-dark') ||
987
+ current.classList.contains('ag-theme-blue') ||
988
+ current.classList.contains('ag-theme-bootstrap'))
989
+ ) {
990
+ return current;
991
+ }
992
+ current = current.parentElement;
993
+ }
994
+ }
995
+ return null;
996
+ };
997
+
998
+ const gridContainer = findThisGridContainer();
999
+ if (!gridContainer) {
1000
+ // Zkusíme znovu za chvíli
1001
+ setTimeout(setupGridObserver, 100);
1002
+ return;
1003
+ }
1004
+
1005
+ gridContainerRef.current = gridContainer;
1006
+
1007
+ // Přidáme mouse event listenery pouze pokud ještě nejsou
1008
+ if (!mouseListenersSetupRef.current) {
1009
+ gridContainer.addEventListener('mousemove', handleMouseMove, {
1010
+ passive: true,
1011
+ });
1012
+ gridContainer.addEventListener('mouseleave', handleMouseLeave, {
1013
+ passive: true,
1014
+ });
1015
+
1016
+ // DŮLEŽITÉ: Přidáme také mouseenter pro reset při vstupu do gridu
1017
+ const handleMouseEnter = () => {
1018
+ if (isMountedRef.current) {
1019
+ // Reset stavu při vstupu do gridu
1020
+ currentHoveredRowRef.current = null;
1021
+ setIsRowHovered(false);
1022
+ }
1023
+ };
1024
+ gridContainer.addEventListener('mouseenter', handleMouseEnter, {
1025
+ passive: true,
1026
+ });
1027
+
1028
+ mouseListenersSetupRef.current = true;
1029
+ }
1030
+
1031
+ // Nastavíme MutationObserver pro tento grid
1032
+ observerRef.current = new MutationObserver((mutations) => {
1033
+ if (!isMountedRef.current) return;
1034
+
1035
+ let shouldRecalculate = false;
1036
+
1037
+ mutations.forEach((mutation) => {
1038
+ if (mutation.type === 'childList') {
1039
+ const addedRows = Array.from(mutation.addedNodes).some(
1040
+ (node) =>
1041
+ node.nodeType === Node.ELEMENT_NODE &&
1042
+ (node.classList?.contains('ag-row') ||
1043
+ node.querySelector?.('.ag-row'))
1044
+ );
1045
+
1046
+ const removedRows = Array.from(mutation.removedNodes).some(
1047
+ (node) =>
1048
+ node.nodeType === Node.ELEMENT_NODE &&
1049
+ (node.classList?.contains('ag-row') ||
1050
+ node.querySelector?.('.ag-row'))
1051
+ );
1052
+
1053
+ if (addedRows || removedRows) {
1054
+ shouldRecalculate = true;
1055
+ }
1056
+ }
1057
+ });
1058
+
1059
+ if (shouldRecalculate) {
1060
+ // Reset hover stavu
1061
+ currentHoveredRowRef.current = null;
1062
+ setIsRowHovered(false);
1063
+
1064
+ // Znovu detekujeme aktuální pozici myši po krátké době
1065
+ setTimeout(() => {
1066
+ if (isMountedRef.current && gridContainerRef.current) {
1067
+ const rect = gridContainerRef.current.getBoundingClientRect();
1068
+ const mouseEvent = new MouseEvent('mousemove', {
1069
+ clientX: window.lastMouseX || rect.left + rect.width / 2,
1070
+ clientY: window.lastMouseY || rect.top + rect.height / 2,
1071
+ bubbles: true,
1072
+ });
1073
+ gridContainerRef.current.dispatchEvent(mouseEvent);
1074
+ }
1075
+ }, 100);
1076
+ }
1077
+ });
1078
+
1079
+ // Spustíme observer na grid container
1080
+ observerRef.current.observe(gridContainer, {
1081
+ childList: true,
1082
+ subtree: true,
1083
+ attributes: false,
1084
+ });
1085
+
1086
+ // Sledujeme pozici myši globálně pro tento observer
1087
+ const trackMouse = (e) => {
1088
+ window.lastMouseX = e.clientX;
1089
+ window.lastMouseY = e.clientY;
1090
+ };
1091
+
1092
+ document.addEventListener('mousemove', trackMouse, { passive: true });
1093
+
1094
+ // Cleanup tracking při unmount
1095
+ return () => {
1096
+ document.removeEventListener('mousemove', trackMouse);
1097
+ };
1098
+ }, [params.api, params.eGridCell, handleMouseMove, handleMouseLeave]);
1099
+
1100
+ // Spustíme setup při mount a při změně params
1101
+ React.useEffect(() => {
1102
+ setupGridObserver();
1103
+ }, [setupGridObserver]);
1104
+
1105
+ const handleVisibleChange = React.useCallback(
1106
+ async (visible) => {
1107
+ if (!isMountedRef.current) return;
1108
+
1109
+ if (visible && onBeforeShow) {
1110
+ try {
1111
+ const updatedMenuItems = await onBeforeShow(params, menuItems);
1112
+ if (
1113
+ updatedMenuItems &&
1114
+ Array.isArray(updatedMenuItems) &&
1115
+ isMountedRef.current
1116
+ ) {
1117
+ setCurrentMenuItems(updatedMenuItems);
1118
+ }
1119
+ } catch (error) {
1120
+ // Removed console.error for production
1121
+ if (isMountedRef.current) {
1122
+ setCurrentMenuItems(menuItems);
1123
+ }
1124
+ }
1125
+ }
1126
+
1127
+ if (isMountedRef.current) {
1128
+ setIsDropdownVisible(visible);
1129
+ }
1130
+ },
1131
+ [params, menuItems, onBeforeShow]
1132
+ );
1133
+
1134
+ React.useEffect(() => {
1135
+ if (isMountedRef.current) {
1136
+ setCurrentMenuItems(menuItems);
1137
+ }
1138
+ }, [menuItems]);
1139
+
1140
+ const menu = React.useMemo(
1141
+ () => (
1142
+ <Menu
1143
+ onClick={(e) => {
1144
+ if (onActionClick) {
1145
+ const item = currentMenuItems.filter(f => f.key === e.key)[0];
1146
+ onActionClick(e.key, params, item);
1147
+ }
1148
+ setIsDropdownVisible(false);
1149
+ }}
1150
+ >
1151
+ {currentMenuItems.map((item) => {
1152
+ if (item.type === 'divider') {
1153
+ return <Divider key={item.key} style={{ margin: '4px 0' }} />;
1154
+ }
1155
+
1156
+ let isDisabled = false;
1157
+ if (typeof item.disable === 'function') {
1158
+ isDisabled = item.disable(params);
1159
+ } else if (typeof item.disable === 'boolean') {
1160
+ isDisabled = item.disable;
1161
+ } else if (item.disabled !== undefined) {
1162
+ isDisabled = item.disabled;
1163
+ }
1164
+
1165
+ return (
1166
+ <Menu.Item key={item.key} disabled={isDisabled}>
1167
+ {item.label}
1168
+ </Menu.Item>
1169
+ );
1170
+ })}
1171
+ </Menu>
1172
+ ),
1173
+ [currentMenuItems, onActionClick, params]
1174
+ );
1175
+
1176
+ const containerStyle = {
1177
+ width: '32px',
1178
+ height: '32px',
1179
+ display: 'flex',
1180
+ alignItems: 'center',
1181
+ justifyContent: 'center',
1182
+ position: 'relative',
1183
+ };
1184
+
1185
+ const buttonStyle = {
1186
+ minWidth: 'auto',
1187
+ width: '32px',
1188
+ height: '32px',
1189
+ padding: '0',
1190
+ background: 'transparent',
1191
+ border: 'none',
1192
+ cursor: 'pointer',
1193
+ display: 'flex',
1194
+ alignItems: 'center',
1195
+ justifyContent: 'center',
1196
+ opacity: isRowHovered || isDropdownVisible ? 1 : 0,
1197
+ visibility: isRowHovered || isDropdownVisible ? 'visible' : 'hidden',
1198
+ transition: 'opacity 0.15s ease-in-out, visibility 0.15s ease-in-out',
1199
+ pointerEvents: isRowHovered || isDropdownVisible ? 'auto' : 'none',
1200
+ };
1201
+
1202
+ return (
1203
+ <div style={containerStyle}>
1204
+ <Dropdown
1205
+ overlayStyle={{ zIndex: 5000 }}
1206
+ overlay={menu}
1207
+ trigger={['click']}
1208
+ visible={isDropdownVisible}
1209
+ onVisibleChange={handleVisibleChange}
1210
+ getPopupContainer={() => document.body}
1211
+ >
1212
+ <div
1213
+ style={buttonStyle}
1214
+ onClick={(e) => {
1215
+ e.stopPropagation();
1216
+ }}
1217
+ >
1218
+ <ActionIcon />
1219
+ </div>
1220
+ </Dropdown>
1221
+ </div>
1222
+ );
1223
+ };
1224
+
1225
+ this.#addPreparedColumn({
1226
+ headerName: '',
1227
+ pinned: 'right',
1228
+ maxWidth: 40,
1229
+ minWidth: 40,
1230
+ suppressSizeToFit: true,
1231
+ suppressMenu: true,
1232
+ sortable: false,
1233
+ filter: false,
1234
+ resizable: false,
1235
+ cellRenderer: ActionDropdownRenderer,
1236
+ cellClass: 'action-button-cell-observer',
1237
+ ...restProps,
1238
+ });
1239
+
1240
+ return this;
1241
+ }
1242
+
1243
+ /**
1244
+ * Přidá sloupec s custom tlačítky s podmíněnou viditelností.
1245
+ * Každé tlačítko může mít vlastní styl, akci a podmínku viditelnosti.
1246
+ * @param {Object} config - Konfigurace tlačítkového sloupce
1247
+ * @param {Array} config.buttons - Array definic tlačítek s vlastnostmi jako text, onClick, style
1248
+ * @param {Function} [config.visibleGetter=() => true] - Funkce určující viditelnost tlačítek
1249
+ * @param {boolean} config.editable - Zda je sloupec editovatelný
1250
+ * @param {Object} ...restProps - Další AG Grid vlastnosti sloupce
1251
+ * @returns {ColumnBuilder} Instance pro chaining
1252
+ */
1253
+ addButtonColumn({
1254
+ buttons,
1255
+ visibleGetter = () => true,
1256
+ editable,
1257
+ ...restProps
1258
+ }) {
1259
+ const buttonRendererParams = {
1260
+ buttons,
1261
+ visibleGetter,
1262
+ };
1263
+ this.#addPreparedColumn({
1264
+ buttonRenderer: true,
1265
+ buttonRendererParams,
1266
+ editable: this.#resolveEditable(editable),
1267
+ ...restProps,
1268
+ });
1269
+ return this;
1270
+ }
1271
+
1272
+ /**
1273
+ * Privátní pomocná metoda pro správné nastavení editable vlastnosti sloupce.
1274
+ * Zajišťuje konzistentní zpracování boolean hodnot pro editovatelnost.
1275
+ * @param {boolean|Function} editable - Editovatelnost sloupce nebo funkce vracející boolean
1276
+ * @returns {boolean|Function} Upravená hodnota editovatelnosti
1277
+ */
1278
+ #resolveEditable(editable) {
1279
+ if (!editable) return false;
1280
+ return editable;
1281
+ }
1282
+
1283
+ /**
1284
+ * Finální metoda pro sestavení pole sloupců AG Grid.
1285
+ * Vrací kompletní konfiguraci všech přidaných sloupců připravených pro AG Grid.
1286
+ * @returns {Array} Array připravených definic sloupců pro AG Grid
1287
+ */
1288
+ build() {
1289
+ return this.columns;
1290
+ }
1291
+ }
1292
+
1293
+ export default ColumnBuilder;
1280
1294
  /* eslint-enable */