@bit.rhplus/ui.grid 0.0.98 → 0.0.100

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