@bit.rhplus/ag-grid 0.0.50 → 0.0.51

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.
Files changed (41) hide show
  1. package/BulkEdit/BulkEditSelect.jsx +23 -7
  2. package/BulkEdit/utils.js +54 -7
  3. package/Renderers/BooleanRenderer.jsx +53 -38
  4. package/Renderers/ButtonRenderer.jsx +61 -44
  5. package/Renderers/CheckboxRenderer.jsx +16 -8
  6. package/Renderers/CountrySelectRenderer.jsx +19 -12
  7. package/Renderers/IconRenderer.jsx +147 -117
  8. package/Renderers/ImageRenderer.jsx +31 -40
  9. package/Renderers/SelectRenderer.jsx +4 -2
  10. package/Renderers/StateRenderer.jsx +54 -52
  11. package/dist/BulkEdit/BulkEditSelect.d.ts +2 -1
  12. package/dist/BulkEdit/BulkEditSelect.js +16 -6
  13. package/dist/BulkEdit/BulkEditSelect.js.map +1 -1
  14. package/dist/BulkEdit/utils.js +47 -2
  15. package/dist/BulkEdit/utils.js.map +1 -1
  16. package/dist/Renderers/BooleanRenderer.d.ts +4 -1
  17. package/dist/Renderers/BooleanRenderer.js +36 -31
  18. package/dist/Renderers/BooleanRenderer.js.map +1 -1
  19. package/dist/Renderers/ButtonRenderer.d.ts +3 -1
  20. package/dist/Renderers/ButtonRenderer.js +35 -25
  21. package/dist/Renderers/ButtonRenderer.js.map +1 -1
  22. package/dist/Renderers/CheckboxRenderer.d.ts +4 -1
  23. package/dist/Renderers/CheckboxRenderer.js +11 -8
  24. package/dist/Renderers/CheckboxRenderer.js.map +1 -1
  25. package/dist/Renderers/CountrySelectRenderer.d.ts +4 -1
  26. package/dist/Renderers/CountrySelectRenderer.js +15 -9
  27. package/dist/Renderers/CountrySelectRenderer.js.map +1 -1
  28. package/dist/Renderers/IconRenderer.d.ts +4 -1
  29. package/dist/Renderers/IconRenderer.js +114 -94
  30. package/dist/Renderers/IconRenderer.js.map +1 -1
  31. package/dist/Renderers/ImageRenderer.d.ts +4 -1
  32. package/dist/Renderers/ImageRenderer.js +24 -32
  33. package/dist/Renderers/ImageRenderer.js.map +1 -1
  34. package/dist/Renderers/SelectRenderer.d.ts +4 -1
  35. package/dist/Renderers/SelectRenderer.js +2 -1
  36. package/dist/Renderers/SelectRenderer.js.map +1 -1
  37. package/dist/Renderers/StateRenderer.d.ts +4 -1
  38. package/dist/Renderers/StateRenderer.js +38 -37
  39. package/dist/Renderers/StateRenderer.js.map +1 -1
  40. package/package.json +5 -5
  41. /package/dist/{preview-1761316095439.js → preview-1761923870469.js} +0 -0
@@ -1,6 +1,7 @@
1
1
  /* eslint-disable */
2
2
  import React, { useRef, useEffect } from 'react';
3
3
  import { Select, Button, Space } from 'antd';
4
+ import * as LucideIcons from 'lucide-react';
4
5
 
5
6
  const BulkEditSelect = ({
6
7
  value,
@@ -13,6 +14,7 @@ const BulkEditSelect = ({
13
14
  allowClear = true,
14
15
  fieldLabel = 'value',
15
16
  fieldValue = 'id',
17
+ fieldIcon = null,
16
18
  showSearch = false,
17
19
  }) => {
18
20
  const selectRef = useRef(null);
@@ -47,13 +49,27 @@ const BulkEditSelect = ({
47
49
  disabled={loading}
48
50
  allowClear={allowClear}
49
51
  showSearch={showSearch}
50
- filterOption={showSearch ? (input, option) =>
51
- (option?.label ?? '').toLowerCase().includes(input.toLowerCase())
52
- : false}
53
- options={options.map(item => ({
54
- label: item[fieldLabel],
55
- value: item[fieldValue]
56
- }))}
52
+ filterOption={showSearch ? (input, option) => {
53
+ // Najdi původní item podle value pro filtrování
54
+ const originalItem = options.find(item => item[fieldValue] === option.value);
55
+ const searchText = originalItem?.[fieldLabel] || '';
56
+ return searchText.toLowerCase().includes(input.toLowerCase());
57
+ } : false}
58
+ options={options.map(item => {
59
+ const iconName = fieldIcon ? item[fieldIcon] : null;
60
+ const LucideIcon = iconName ? LucideIcons[iconName] : null;
61
+
62
+ return {
63
+ label: (
64
+ <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
65
+ {LucideIcon && <LucideIcon size={16} />}
66
+ <span>{item[fieldLabel]}</span>
67
+ </div>
68
+ ),
69
+ value: item[fieldValue],
70
+ searchText: item[fieldLabel] // Pro lepší filtrování
71
+ };
72
+ })}
57
73
  onKeyDown={handleKeyDown}
58
74
  onClick={(e) => e.stopPropagation()}
59
75
  getPopupContainer={(trigger) => trigger.parentNode}
package/BulkEdit/utils.js CHANGED
@@ -453,17 +453,22 @@ export const applyBulkChangesWithApi = async (
453
453
  } else if (bulkEditApi.lookup) {
454
454
  // Lookup pattern - najít objekt v lookupData a aktualizovat rows
455
455
  let selectedItem = null;
456
-
456
+
457
457
  // Pokud je newValue prázdné ("" nebo null), nastavit selectedItem na null
458
458
  if (newValue !== "" && newValue !== null && newValue !== undefined) {
459
- selectedItem = bulkEditApi.lookup.data.find(item => item.id === newValue);
460
-
461
- if (!selectedItem) {
459
+ const foundItem = bulkEditApi.lookup.data.find(item => item.id === newValue);
460
+
461
+ if (!foundItem) {
462
462
  console.warn(`⚠️ ${bulkEditApi.lookup.objectField} nenalezen pro ID:`, newValue);
463
+ } else {
464
+ // ✅ Vytvoř deep copy objektu včetně všech nested properties (např. icon)
465
+ // Tím zajistíme, že AG Grid detekuje změnu a IconRenderer dostane nová data
466
+ selectedItem = JSON.parse(JSON.stringify(foundItem));
463
467
  }
464
468
  }
465
-
469
+
466
470
  // Aktualizovat rows - i když je selectedItem null (= clear hodnoty)
471
+ // ✅ Vytvoř nový objekt row s novým nested objektem (např. accountingType)
467
472
  updatedData = rows.map(row => {
468
473
  if (ids.includes(row.id)) {
469
474
  return {
@@ -502,8 +507,50 @@ export const applyBulkChangesWithApi = async (
502
507
  // Aplikovat změny do gridu
503
508
  if (updatedData && updatedData.length > 0) {
504
509
  gridApi.applyTransaction({ update: updatedData });
510
+
511
+ // ✅ DŮLEŽITÉ: Force refresh buněk po update transaction
512
+ // AG Grid nemusí detekovat změny v nested objektech (např. accountingType)
513
+ // refreshCells zajistí, že cell renderery dostanou nová data
514
+ const updatedNodes = [];
515
+ ids.forEach(id => {
516
+ let node = gridApi.getRowNode(id);
517
+ if (!node) {
518
+ gridApi.forEachNode(n => {
519
+ if (n.data?.id === id) {
520
+ node = n;
521
+ }
522
+ });
523
+ }
524
+ if (node) {
525
+ updatedNodes.push(node);
526
+ console.log('🔄 Bulk Edit - Updated node data:', {
527
+ id: node.data?.id,
528
+ field: field,
529
+ objectField: bulkEditApi.lookup?.objectField,
530
+ objectValue: node.data?.[bulkEditApi.lookup?.objectField],
531
+ column: column.getColId()
532
+ });
533
+ }
534
+ });
535
+
536
+ if (updatedNodes.length > 0) {
537
+ console.log('🔄 Bulk Edit - Refreshing cells:', {
538
+ nodesCount: updatedNodes.length,
539
+ column: column?.getColId(),
540
+ refreshingAllCells: true
541
+ });
542
+
543
+ // ✅ KLÍČOVÉ: Refresh VŠECH buněk v řádku, ne jen editovaného sloupce
544
+ // Důvod: Pokud se změní nested objekt (např. accountingType.icon),
545
+ // musíme refreshnout všechny sloupce které z něj čtou (včetně iconField)
546
+ gridApi.refreshCells({
547
+ rowNodes: updatedNodes,
548
+ // columns: [column.getColId()], // ← Odstraněno - refresh všech sloupců
549
+ force: true
550
+ });
551
+ }
505
552
  }
506
-
553
+
507
554
  // Flash efekt
508
555
  setTimeout(() => {
509
556
  const updatedNodes = [];
@@ -522,7 +569,7 @@ export const applyBulkChangesWithApi = async (
522
569
  updatedNodes.push(node);
523
570
  }
524
571
  });
525
-
572
+
526
573
  if (updatedNodes.length > 0 && column) {
527
574
  gridApi.flashCells({
528
575
  rowNodes: updatedNodes,
@@ -1,13 +1,36 @@
1
+ /* eslint-disable */
1
2
  import * as React from 'react';
2
3
  import { CircleHelp, Check, X } from 'lucide-react';
3
4
 
4
- export default function BooleanRenderer(props) {
5
- const { data, value, colDef: { booleanRendererParams = {} } = {} } = props;
5
+ // Memoized Icon component
6
+ const Icon = React.memo(({ innerValue, size, colorTrue, colorFalse, visibleTrue, visibleFalse, defaultIcon, defaultIconColor, iconStyle }) => {
7
+ if (
8
+ innerValue === undefined ||
9
+ (!visibleFalse && !innerValue) ||
10
+ (!visibleTrue && innerValue)
11
+ ) {
12
+ if (defaultIcon)
13
+ return <CircleHelp size={size} color={defaultIconColor} style={iconStyle} />;
14
+ return <div />;
15
+ }
16
+
17
+ if (innerValue)
18
+ return <Check size={size} color={colorTrue} style={iconStyle} />;
19
+ return <X size={size} color={colorFalse} style={iconStyle} />;
20
+ });
21
+
22
+ Icon.displayName = 'BooleanIcon';
6
23
 
7
- // Remove or comment out console statement to fix eslint warning
8
- // console.log("🚀 ~ BooleanRenderer ~ value:", props, value);
24
+ // Icon style constant
25
+ const ICON_STYLE = {
26
+ display: 'inline-block',
27
+ height: '100%',
28
+ };
29
+
30
+ function BooleanRenderer(props) {
31
+ const { data, value, colDef: { booleanRendererParams = {} } = {} } = props;
9
32
 
10
- if (!booleanRendererParams) return null; // Return null instead of undefined to match React's expected output
33
+ if (!booleanRendererParams) return null;
11
34
 
12
35
  const {
13
36
  cellAlign,
@@ -22,50 +45,42 @@ export default function BooleanRenderer(props) {
22
45
  colorFalse = 'red',
23
46
  } = booleanRendererParams;
24
47
 
25
- const visibleResult = visibleGetter ? visibleGetter(data) : true;
26
-
27
- const Icon = ({ innerValue, ...restProps }) => {
28
- if (
29
- innerValue === undefined ||
30
- (!visibleFalse && !innerValue) ||
31
- (!visibleTrue && innerValue)
32
- ) {
33
- if (defaultIcon)
34
- return <CircleHelp size={size} color={defaultIconColor} />;
35
- return <div />;
36
- }
37
-
38
- if (innerValue)
39
- return <Check size={size} color={colorTrue} {...restProps} />;
40
- return <X size={size} color={colorFalse} {...restProps} />;
41
- };
48
+ const visibleResult = React.useMemo(() =>
49
+ visibleGetter ? visibleGetter(data) : true,
50
+ [visibleGetter, data]
51
+ );
42
52
 
43
- const showCondition = () => {
53
+ const showCondition = React.useMemo(() => {
44
54
  const newItem = (data && data._rh_plus_ag_grid_new_item) || false;
45
55
  return !newItem && (showOnGroup || !!data) && visibleResult;
46
- };
56
+ }, [data, showOnGroup, visibleResult]);
47
57
 
48
- if (!showCondition()) return null; // Return null instead of false to fix eslint error
58
+ const containerStyle = React.useMemo(() => ({
59
+ width: '100%',
60
+ display: 'flex',
61
+ justifyContent: cellAlign ?? 'center',
62
+ alignItems: 'center',
63
+ height: '100%',
64
+ }), [cellAlign]);
65
+
66
+ if (!showCondition) return null;
49
67
 
50
68
  return (
51
- <span
52
- style={{
53
- width: '100%',
54
- display: 'flex',
55
- justifyContent: cellAlign ?? 'center',
56
- alignItems: 'center',
57
- height: '100%',
58
- }}
59
- >
69
+ <span style={containerStyle}>
60
70
  <Icon
61
- style={{
62
- display: 'inline-block',
63
- height: '100%',
64
- }}
71
+ style={ICON_STYLE}
65
72
  innerValue={value}
73
+ size={size}
74
+ colorTrue={colorTrue}
75
+ colorFalse={colorFalse}
66
76
  visibleTrue={visibleTrue}
67
77
  visibleFalse={visibleFalse}
78
+ defaultIcon={defaultIcon}
79
+ defaultIconColor={defaultIconColor}
80
+ iconStyle={ICON_STYLE}
68
81
  />
69
82
  </span>
70
83
  );
71
84
  }
85
+
86
+ export default React.memo(BooleanRenderer);
@@ -1,38 +1,52 @@
1
- import React, { useState } from 'react';
1
+ /* eslint-disable */
2
+ import React, { useState, useCallback, useMemo } from 'react';
2
3
  import Button from 'antd/es/button';
3
4
 
4
- const defaultStyle = {
5
+ const DEFAULT_STYLE = {
5
6
  fontSize: '12px'
6
- }
7
+ };
7
8
 
8
- const LargeButton = ({ button, onClick, props }) => {
9
+ const SPAN_STYLE = { flexGrow: 1 };
10
+ const LARGE_CONTAINER_STYLE = { display: 'flex', gap: '8px', width: '100%' };
11
+ const SMALL_CONTAINER_STYLE = { display: 'flex', gap: '8px' };
12
+
13
+ const LargeButton = React.memo(({ button, onClick, props }) => {
9
14
  const { id, label, largeLabel, style, className, icon } = button;
10
15
  const largeLabelValue = largeLabel instanceof Function ? largeLabel(props) : largeLabel;
11
16
  const labelValue = label instanceof Function ? label(props) : label;
17
+ const buttonStyle = useMemo(() => ({...DEFAULT_STYLE, ...style}), [style]);
18
+
19
+ const handleClick = useCallback((e) => onClick(e, id), [onClick, id]);
20
+
12
21
  return (
13
22
  <Button
14
23
  key={id}
15
24
  type="primary"
16
25
  size="small"
17
- onClick={(e) => onClick(e, id)}
18
- style={{...defaultStyle, ...style}}
26
+ onClick={handleClick}
27
+ style={buttonStyle}
19
28
  className={className}
20
29
  icon={icon}
21
30
  >
22
31
  {largeLabelValue || labelValue}
23
32
  </Button>
24
33
  );
25
- };
34
+ });
35
+
36
+ LargeButton.displayName = 'LargeButton';
26
37
 
27
- const SmallButton = ({ button, onClick, props }) => {
38
+ const SmallButton = React.memo(({ button, onClick, props }) => {
28
39
  const { id, label, style, className, icon } = button;
29
40
  const labelValue = label instanceof Function ? label(props) : label;
41
+
42
+ const handleClick = useCallback((e) => onClick(e, id), [onClick, id]);
43
+
30
44
  return (
31
45
  <Button
32
46
  key={id}
33
47
  type="primary"
34
48
  size="small"
35
- onClick={(e) => onClick(e, id)}
49
+ onClick={handleClick}
36
50
  style={style}
37
51
  className={className}
38
52
  icon={icon}
@@ -40,9 +54,11 @@ const SmallButton = ({ button, onClick, props }) => {
40
54
  {labelValue}
41
55
  </Button>
42
56
  );
43
- };
57
+ });
58
+
59
+ SmallButton.displayName = 'SmallButton';
44
60
 
45
- const ButtonRenderer = (props) => {
61
+ function ButtonRenderer(props) {
46
62
  const [hovered, setHovered] = useState(false);
47
63
  const { value, colDef: { buttonRendererParams = {} } = {} } = props;
48
64
 
@@ -50,56 +66,57 @@ const ButtonRenderer = (props) => {
50
66
 
51
67
  const { visibleGetter, buttons = [] } = buttonRendererParams;
52
68
 
53
- const onMouseEnter = () => {
69
+ const onMouseEnter = useCallback(() => {
54
70
  setHovered(true);
55
- };
71
+ }, []);
56
72
 
57
- const onMouseLeave = () => {
73
+ const onMouseLeave = useCallback(() => {
58
74
  setHovered(false);
59
- };
75
+ }, []);
60
76
 
61
- const onClick = (e, id) => {
77
+ const onClick = useCallback((e, id) => {
62
78
  e.preventDefault();
63
79
  const { context: { componentParent: { onButtonClick } = {} } = {} } = props || {};
64
80
  if (onButtonClick) onButtonClick(props, id);
65
- };
81
+ }, [props]);
66
82
 
67
83
  const isEmptyValue = value === null || value === '' || value === undefined;
68
84
 
69
- const visible = visibleGetter(props);
85
+ const visible = useMemo(() => visibleGetter(props), [visibleGetter, props]);
86
+
87
+ const containerStyle = useMemo(() => ({
88
+ display: 'flex',
89
+ alignItems: 'center',
90
+ justifyContent: isEmptyValue ? 'center' : 'space-between',
91
+ height: '100%',
92
+ width: '100%',
93
+ }), [isEmptyValue]);
70
94
 
71
95
  return (
72
96
  <div
73
97
  className="custom-cell-renderer"
74
98
  onMouseEnter={onMouseEnter}
75
99
  onMouseLeave={onMouseLeave}
76
- style={{
77
- display: 'flex',
78
- alignItems: 'center',
79
- justifyContent: isEmptyValue ? 'center' : 'space-between',
80
- height: '100%',
81
- width: '100%',
82
- }}
100
+ style={containerStyle}
83
101
  >
84
- {!isEmptyValue && <span style={{ flexGrow: 1 }}>{value}</span>}
85
- {visible && (
86
- hovered && buttons.length > 0 && (
87
- isEmptyValue ? (
88
- <div style={{ display: 'flex', gap: '8px', width: '100%' }}>
89
- {buttons.map((button, index) => (
90
- <LargeButton key={index} button={button} onClick={onClick} props={props} />
91
- ))}
92
- </div>
93
- ) : (
94
- <div style={{ display: 'flex', gap: '8px' }}>
95
- {buttons.map((button, index) => (
96
- <SmallButton key={index} button={button} onClick={onClick} props={props} />
97
- ))}
98
- </div>
99
- )
100
- ))}
102
+ {!isEmptyValue && <span style={SPAN_STYLE}>{value}</span>}
103
+ {visible && hovered && buttons.length > 0 && (
104
+ isEmptyValue ? (
105
+ <div style={LARGE_CONTAINER_STYLE}>
106
+ {buttons.map((button, index) => (
107
+ <LargeButton key={index} button={button} onClick={onClick} props={props} />
108
+ ))}
109
+ </div>
110
+ ) : (
111
+ <div style={SMALL_CONTAINER_STYLE}>
112
+ {buttons.map((button, index) => (
113
+ <SmallButton key={index} button={button} onClick={onClick} props={props} />
114
+ ))}
115
+ </div>
116
+ )
117
+ )}
101
118
  </div>
102
119
  );
103
- };
120
+ }
104
121
 
105
- export default ButtonRenderer;
122
+ export default React.memo(ButtonRenderer);
@@ -1,35 +1,43 @@
1
1
  import * as React from 'react';
2
2
 
3
- export default function CheckboxRenderer(props) {
3
+ // Style constant
4
+ const CONTAINER_STYLE = { textAlign: "center" };
5
+
6
+ function CheckboxRenderer(props) {
4
7
  const {
5
8
  value,
6
9
  data,
7
10
  column: { colDef: { cellTypeParams: { showOnGroup, condition } = {} } = {} } = {},
8
11
  } = props;
9
12
 
10
- const onChange = e => {
13
+ const onChange = React.useCallback((e) => {
11
14
  e.preventDefault();
12
15
  const {context: { componentParent: {onCheckboxChange} = {}} = {}} = props || {};
13
16
  if (onCheckboxChange)
14
17
  onCheckboxChange(props);
15
- }
18
+ }, [props]);
16
19
 
17
- const visibleCondition = condition ? condition(data) : true;
20
+ const visibleCondition = React.useMemo(() =>
21
+ condition ? condition(data) : true,
22
+ [condition, data]
23
+ );
18
24
 
19
- const showCondition = () => {
25
+ const showCondition = React.useMemo(() => {
20
26
  const newItem = (data && data._rh_plus_ag_grid_new_item) || false;
21
27
  return !newItem && (showOnGroup || !!data) && visibleCondition;
22
- };
28
+ }, [data, showOnGroup, visibleCondition]);
23
29
 
24
- if (!showCondition()) return <div />;
30
+ if (!showCondition) return <div />;
25
31
 
26
32
  if (value === undefined) {
27
33
  return <div />
28
34
  }
29
35
 
30
36
  return (
31
- <div style={{ textAlign: "center" }}>
37
+ <div style={CONTAINER_STYLE}>
32
38
  <input type="checkbox" checked={value} onChange={onChange} />
33
39
  </div>
34
40
  );
35
41
  }
42
+
43
+ export default React.memo(CheckboxRenderer);
@@ -1,37 +1,44 @@
1
1
  import * as React from 'react';
2
2
 
3
- export default function SimpleCountryCellRenderer(params) {
3
+ // Style constants
4
+ const SPAN_STYLE = { marginLeft: 10, marginRight: 10 };
5
+ const IMG_STYLE = { marginTop: -5 };
6
+
7
+ function SimpleCountryCellRenderer(params) {
4
8
  const {
5
9
  value = {},
6
10
  data,
7
11
  column: {
8
12
  colDef: {
9
- cellTypeParams: { showOnGroup, condition },
10
- },
13
+ cellTypeParams: { showOnGroup, condition } = {},
14
+ } = {},
11
15
  } = {},
12
16
  } = params;
13
17
  const {id, name, code} = value;
14
18
 
15
19
  const visibleCondition = condition ? condition(data) : true;
16
20
 
17
- const showCondition = () => {
21
+ const showCondition = React.useMemo(() => {
18
22
  const newItem = (data && data._rh_plus_ag_grid_new_item) || false;
19
23
  return !newItem && (showOnGroup || !!data) && visibleCondition;
20
- };
24
+ }, [data, showOnGroup, visibleCondition]);
21
25
 
22
- if (!showCondition()) return <div />;
23
- if (!id || !code) return <div />;
26
+ const imageUrl = React.useMemo(() => {
27
+ if (!code) return "";
28
+ return `https://rhplus.blob.core.windows.net/countries/${code.toUpperCase()}.png`;
29
+ }, [code]);
24
30
 
25
- let imageUrl = "";
26
- if (code)
27
- imageUrl = `https://rhplus.blob.core.windows.net/countries/${code.toUpperCase()}.png`;
31
+ if (!showCondition) return <div />;
32
+ if (!id || !code) return <div />;
28
33
 
29
34
  return (
30
35
  <div>
31
- <span style={{ marginLeft: 10, marginRight: 10 }}>
32
- <img alt="img" style={{ marginTop: -5 }} src={imageUrl} />
36
+ <span style={SPAN_STYLE}>
37
+ <img alt="img" style={IMG_STYLE} src={imageUrl} />
33
38
  </span>
34
39
  {name}
35
40
  </div>
36
41
  );
37
42
  }
43
+
44
+ export default React.memo(SimpleCountryCellRenderer);