@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 +1497 -1497
- package/PERFORMANCE-OPTIMIZATION.md +259 -259
- package/dist/PERFORMANCE-OPTIMIZATION.md +259 -259
- package/package.json +3 -3
- /package/dist/{preview-1768405724660.js → preview-1768559621799.js} +0 -0
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 */
|