@bit.rhplus/ag-grid 0.0.123 → 0.0.124

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.
@@ -62,6 +62,7 @@ const ModuleLookupEditor = React.forwardRef(({
62
62
  moduleDefinition,
63
63
  allowFreeText = false,
64
64
  rowHeight = 42,
65
+ autoOpen = true,
65
66
  }, _ref) => {
66
67
 
67
68
  // ─── Hooks (vždy voláno) ──────────────────────────────────────────────────────
@@ -109,6 +110,12 @@ const ModuleLookupEditor = React.forwardRef(({
109
110
 
110
111
  const [inputText, setInputText] = useState(initialText);
111
112
  const [dropdownOpen, setDropdownOpen] = useState(false);
113
+
114
+ useEffect(() => {
115
+ if (autoOpen) {
116
+ setDropdownOpen(true);
117
+ }
118
+ }, [autoOpen]);
112
119
  const [multiMatchData, setMultiMatchData] = useState([]);
113
120
  const [multiMatchModalOpen, setMultiMatchModalOpen] = useState(false);
114
121
  const catalogModalOpenRef = useRef(false);
@@ -117,7 +124,7 @@ const ModuleLookupEditor = React.forwardRef(({
117
124
  multiMatchModalOpenRef.current = multiMatchModalOpen;
118
125
  const currentTextRef = useRef(initialText);
119
126
  const inputTextRef = useRef(initialText); // vždy aktuální inputText pro native handler
120
- const freeTextWrapperRef = useRef(null); // ref na wrapper div → querySelector('input')
127
+ const editorWrapperRef = useRef(null); // ref na wrapper div → querySelector('input')
121
128
  const isStoppedRef = useRef(false);
122
129
  const justSelectedRef = useRef(false);
123
130
  // Nastaven synchronně před každým stopEditing() voláním.
@@ -133,6 +140,7 @@ const ModuleLookupEditor = React.forwardRef(({
133
140
  // Ref na suggestions a handleAutoCompleteSelect — přístupné v capture handleru bez stale closure
134
141
  const suggestionsRef = useRef([]);
135
142
  const handleAutoCompleteSelectRef = useRef(null);
143
+ const handleCatalogSelectChangeRef = useRef(null);
136
144
 
137
145
  // ─── Lokální filtrování návrhů (bez API volání) ───────────────────────────────
138
146
  const suggestions = React.useMemo(() => {
@@ -143,12 +151,22 @@ const ModuleLookupEditor = React.forwardRef(({
143
151
  const label = moduleDefinition?.getDisplayValue?.(item) || item.code || item.name || '';
144
152
  return label.toLowerCase().includes(inputText.toLowerCase());
145
153
  });
146
- return source.slice(0, 15).map(item => ({
147
- value: String(item[vf]),
148
- label: moduleDefinition?.getDisplayValue?.(item) || item.code || item.name || '',
149
- item,
150
- }));
151
- }, [allItems, inputText, moduleDefinition]);
154
+ const result = source.map(item => {
155
+ const displayText = moduleDefinition?.getDisplayValue?.(item) || item.code || item.name || String(item[vf] || '');
156
+ const originalLabel = moduleDefinition?.renderOption ? moduleDefinition.renderOption(item, displayText) : displayText;
157
+ return {
158
+ value: String(item[vf]),
159
+ label: (
160
+ <div data-mle-value={String(item[vf])} style={{ display: 'contents' }}>
161
+ {originalLabel}
162
+ </div>
163
+ ),
164
+ displayText,
165
+ item,
166
+ };
167
+ });
168
+ return allowFreeText ? result.slice(0, 50) : result;
169
+ }, [allItems, inputText, moduleDefinition, allowFreeText]);
152
170
  suggestionsRef.current = suggestions;
153
171
 
154
172
  // ─── AG Grid useGridCellEditor ───────────────────────────────────────────────
@@ -215,6 +233,7 @@ const ModuleLookupEditor = React.forwardRef(({
215
233
  if (onValueChange) onValueChange(item);
216
234
  setTimeout(() => stopEditing(), 0);
217
235
  }, [allItems, moduleDefinition, onValueChange, stopEditing]);
236
+ handleCatalogSelectChangeRef.current = handleCatalogSelectChange;
218
237
 
219
238
  const catalogCurrentId = React.useMemo(() => {
220
239
  if (allowFreeText || !value) return null;
@@ -358,8 +377,7 @@ const ModuleLookupEditor = React.forwardRef(({
358
377
  // rc-select volá stopPropagation() na Enter v bubble fázi → náš React onKeyDown
359
378
  // na wrapperu ho nikdy nedostane. Capture-phase native listener se spustí jako první.
360
379
  useEffect(() => {
361
- if (!allowFreeText) return;
362
- const el = freeTextWrapperRef.current;
380
+ const el = editorWrapperRef.current;
363
381
  if (!el) return;
364
382
  const input = el.querySelector('input');
365
383
  if (!input) return;
@@ -383,17 +401,41 @@ const ModuleLookupEditor = React.forwardRef(({
383
401
  if (activeOptionEl) {
384
402
  e.stopImmediatePropagation();
385
403
  e.preventDefault();
386
- const labelText = activeOptionEl.querySelector('.ant-select-item-option-content')?.textContent?.trim();
387
- if (labelText) {
388
- const matched = suggestionsRef.current.find((s) => s.label === labelText);
389
- if (matched) {
390
- handleAutoCompleteSelectRef.current?.(matched.value, matched);
391
- return;
404
+ const contentEl = activeOptionEl.querySelector('.ant-select-item-option-content');
405
+ if (contentEl) {
406
+ const mleValue = contentEl.querySelector('[data-mle-value]')?.getAttribute('data-mle-value');
407
+ if (mleValue) {
408
+ const matched = suggestionsRef.current.find((s) => s.value === mleValue);
409
+ if (matched) {
410
+ if (allowFreeText) {
411
+ handleAutoCompleteSelectRef.current?.(matched.value, matched);
412
+ } else {
413
+ handleCatalogSelectChangeRef.current?.(matched.value);
414
+ }
415
+ return;
416
+ }
417
+ }
418
+
419
+ const spanText = contentEl.querySelector('span')?.textContent?.trim();
420
+ const fullText = contentEl.textContent?.trim();
421
+ const labelText = spanText || fullText;
422
+ if (labelText) {
423
+ const matched = suggestionsRef.current.find((s) => s.displayText === labelText || String(s.label) === labelText);
424
+ if (matched) {
425
+ if (allowFreeText) {
426
+ handleAutoCompleteSelectRef.current?.(matched.value, matched);
427
+ } else {
428
+ handleCatalogSelectChangeRef.current?.(matched.value);
429
+ }
430
+ return;
431
+ }
392
432
  }
393
433
  }
394
434
  return;
395
435
  }
396
436
 
437
+ if (!allowFreeText) return;
438
+
397
439
  const text = inputTextRef.current || '';
398
440
  // Prázdný input bez předchozí hodnoty → nechat AG Grid zpracovat (cancel)
399
441
  if (!text.trim() && pendingValueRef.current === null && !initialText) return;
@@ -442,27 +484,39 @@ const ModuleLookupEditor = React.forwardRef(({
442
484
  // ─── Render: catalog-only mód ─────────────────────────────────────────────────
443
485
  if (!allowFreeText) {
444
486
  const vf = moduleDefinition?.valueField || 'id';
445
- const selectOptions = allItems.map(item => ({
446
- value: item[vf],
447
- label: moduleDefinition?.getDisplayValue?.(item) || item.code || item.name || String(item[vf]),
448
- }));
487
+ const selectOptions = allItems.map(item => {
488
+ const displayText = moduleDefinition?.getDisplayValue?.(item) || item.code || item.name || String(item[vf] || '');
489
+ const originalLabel = moduleDefinition?.renderOption ? moduleDefinition.renderOption(item, displayText) : displayText;
490
+ return {
491
+ value: item[vf],
492
+ label: (
493
+ <div data-mle-value={String(item[vf])} style={{ display: 'contents' }}>
494
+ {originalLabel}
495
+ </div>
496
+ ),
497
+ displayText,
498
+ };
499
+ });
449
500
 
450
501
  return (
451
- <div style={{ width: '100%', minWidth: 250, height: rowHeight, display: 'flex', alignItems: 'stretch' }}>
502
+ <div style={{ width: '100%', minWidth: 250, height: rowHeight, display: 'flex', alignItems: 'stretch' }} ref={editorWrapperRef}>
452
503
  <div style={{ display: 'flex', flex: 1, height: '100%' }}>
453
504
  <Select
454
505
  style={{ flex: 1, height: '100%' }}
455
506
  value={catalogCurrentId}
456
507
  options={selectOptions}
508
+ optionLabelProp="displayText"
457
509
  loading={allItemsLoading}
458
510
  showSearch
459
511
  filterOption={(input, option) =>
460
- String(option?.label || '').toLowerCase().includes(input.toLowerCase())
512
+ String(option?.displayText || option?.label || '').toLowerCase().includes(input.toLowerCase())
461
513
  }
462
514
  onChange={handleCatalogSelectChange}
515
+ onSearch={handleInputChange}
463
516
  getPopupContainer={() => document.body}
464
517
  dropdownStyle={{ zIndex: 9999 }}
465
518
  placeholder={moduleDefinition?.placeholder || 'Vyberte...'}
519
+ defaultOpen={autoOpen}
466
520
  autoFocus
467
521
  />
468
522
  <Button icon={<SearchOutlined />} onMouseDown={e => e.preventDefault()} onClick={() => setCatalogModalOpen(true)} type="primary" style={{ height: '100%', borderRadius: 0 }} />
@@ -475,7 +529,7 @@ const ModuleLookupEditor = React.forwardRef(({
475
529
  // ─── Render: free-text mód ────────────────────────────────────────────────────
476
530
  return (
477
531
  <div style={{ width: '100%', minWidth: 250, height: rowHeight, display: 'flex', alignItems: 'stretch' }}>
478
- <div ref={freeTextWrapperRef} style={{ display: 'flex', flex: 1, height: '100%' }}>
532
+ <div ref={editorWrapperRef} style={{ display: 'flex', flex: 1, height: '100%' }}>
479
533
  <AutoComplete
480
534
  style={{ flex: 1 }}
481
535
  value={inputText}
@@ -0,0 +1,124 @@
1
+ /* eslint-disable */
2
+ import React, { useState, useRef, useCallback, useEffect } from 'react';
3
+ import { useGridCellEditor } from 'ag-grid-react';
4
+ import DraggableModal from '@bit.rhplus/draggable-modal';
5
+ import { useSharedGridItems } from '@bit.rhplus/shared-grid-form';
6
+
7
+ /**
8
+ * Vnitřní komponenta editoru — přístup k Jotai SharedGridForm kontextu.
9
+ */
10
+ const RouteEditorInner = ({
11
+ instanceId,
12
+ initialData,
13
+ gridComponent: GridComponent,
14
+ onCancel,
15
+ onSave,
16
+ }) => {
17
+ const { effectiveItems, setItems } = useSharedGridItems(instanceId);
18
+ const distanceKmRef = useRef(null);
19
+
20
+ // Inicializace dat při mountu
21
+ useEffect(() => {
22
+ if (Array.isArray(initialData)) {
23
+ setItems(initialData);
24
+ }
25
+ }, []); // Pouze při mountu
26
+
27
+ const handleRouteCalculated = useCallback((info) => {
28
+ if (info?.distance != null && info.distance > 0) {
29
+ distanceKmRef.current = Math.round(info.distance / 1000);
30
+ }
31
+ }, []);
32
+
33
+ const handleOk = useCallback(() => {
34
+ onSave(effectiveItems, distanceKmRef.current);
35
+ }, [effectiveItems, onSave]);
36
+
37
+ return (
38
+ <DraggableModal
39
+ title="Editace trasy"
40
+ open={true}
41
+ onOk={handleOk}
42
+ onCancel={onCancel}
43
+ width={1100}
44
+ height="80vh"
45
+ resizable={true}
46
+ getContainer={() => document.body}
47
+ destroyOnClose
48
+ styles={{ body: { height: '100%', overflow: 'hidden', padding: 0 } }}
49
+ centered
50
+ maskClosable={false}
51
+ >
52
+ <div style={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
53
+ <GridComponent instanceId={instanceId} showAddButton={true} onRouteCalculated={handleRouteCalculated} />
54
+ </div>
55
+ </DraggableModal>
56
+ );
57
+ };
58
+
59
+ /**
60
+ * RouteCellEditor — AG Grid 35 Cell Editor pro správu tras.
61
+ *
62
+ * Používá useGridCellEditor hook (povinné v AG Grid 31+).
63
+ * Props jsou předány přímo z AG Grid (value, onValueChange, stopEditing + cellEditorParams).
64
+ */
65
+ /**
66
+ * RouteCellEditor — AG Grid 35 Cell Editor pro správu tras.
67
+ *
68
+ * Používá useGridCellEditor hook (povinné v AG Grid 31+).
69
+ * Props jsou předány přímo z AG Grid (value, onValueChange, stopEditing + cellEditorParams).
70
+ *
71
+ * @param {Function} transformValue - volitelná funkce z cellEditorParams pro transformaci
72
+ * effectiveItems před uložením (např. pro převod temp ID na null a type string na transportTypeId).
73
+ * Pokud není zadána, data se uloží bez transformace.
74
+ */
75
+ const RouteCellEditor = ({ value, onValueChange, stopEditing, data, gridComponent: GridComponent, transformValue }) => {
76
+ const [visible, setVisible] = useState(true);
77
+
78
+ // Ref pro synchronní přístup z getValue() — AG Grid 35 volá getValue() při stopEditing()
79
+ const valueRef = useRef(value);
80
+
81
+ // AG Grid 35: useGridCellEditor je povinné pro správné getValue() při stopEditing()
82
+ useGridCellEditor({
83
+ getValue: () => valueRef.current,
84
+ });
85
+
86
+ // Stabilní instanceId pro celý editační proces
87
+ const [instanceId] = useState(() => `route-editor-${data?.id || Date.now()}`);
88
+
89
+ const handleSave = useCallback((items, distanceKm) => {
90
+ // Transformace dat před uložením — připraví formát kompatibilní s backendem
91
+ const prepared = transformValue ? transformValue(items) : items;
92
+ const payload = distanceKm != null ? { routes: prepared, destination: distanceKm } : prepared;
93
+ valueRef.current = payload;
94
+ if (onValueChange) onValueChange(payload);
95
+ setVisible(false);
96
+ stopEditing();
97
+ }, [transformValue, onValueChange, stopEditing]);
98
+
99
+ const handleCancel = useCallback(() => {
100
+ setVisible(false);
101
+ setTimeout(() => stopEditing(true), 0); // true = cancel, necommitovat
102
+ }, [stopEditing]);
103
+
104
+ if (!visible) return null;
105
+
106
+ if (!GridComponent) {
107
+ console.error('RouteCellEditor: gridComponent missing in cellEditorParams');
108
+ return null;
109
+ }
110
+
111
+ return (
112
+ <RouteEditorInner
113
+ instanceId={instanceId}
114
+ initialData={value}
115
+ gridComponent={GridComponent}
116
+ onSave={handleSave}
117
+ onCancel={handleCancel}
118
+ />
119
+ );
120
+ };
121
+
122
+ RouteCellEditor.displayName = 'RouteCellEditor';
123
+
124
+ export default RouteCellEditor;
package/Editors/index.jsx CHANGED
@@ -1,3 +1,4 @@
1
1
  export {default as DatePickerEditor} from './DatePickerEditor';
2
2
  export {default as ModuleLookupEditor} from './ModuleLookupEditor';
3
3
  export {default as AddressLookupEditor} from './AddressLookupEditor';
4
+ export {default as RouteCellEditor} from './RouteCellEditor';
@@ -1,5 +1,4 @@
1
-
2
- /* eslint-disable no-param-reassign */
1
+ /* eslint-disable */
3
2
  export const RhPlusOnCellValueChanged = (event, options) =>
4
3
  {
5
4
  console.log("onCellValueChanged");
@@ -0,0 +1,76 @@
1
+ /* eslint-disable */
2
+ import React from 'react';
3
+ import { MapPin, Flag, ChevronRight } from 'lucide-react';
4
+
5
+ /**
6
+ * RouteCellRenderer — zobrazuje trasu se šipkami a ikonami pro nakládku/vykládku.
7
+ * Bucovice => slavkov u Brna => brno => praha
8
+ */
9
+ const RouteCellRenderer = (params) => {
10
+ const routes = params.value || params.data?.routes || [];
11
+ if (!Array.isArray(routes) || routes.length === 0) {
12
+ return <span style={{ color: '#bfbfbf', fontStyle: 'italic' }}>Nezadána</span>;
13
+ }
14
+
15
+
16
+ return (
17
+ <div style={{
18
+ display: 'flex',
19
+ alignItems: 'center',
20
+ gap: '4px',
21
+ overflow: 'hidden',
22
+ height: '100%',
23
+ padding: '0 4px'
24
+ }}>
25
+ {routes.map((route, index) => {
26
+ const isLast = index === routes.length - 1;
27
+ const isFirst = index === 0;
28
+
29
+ // Typ: 1 = loading (nakládka), 2 = unloading (vykládka)
30
+ const transportTypeId = route.transportTypeId || (route.type === 'loading' ? 1 : 2);
31
+
32
+ const isLoading = transportTypeId === 1;
33
+ const color = isLoading ? '#1890ff' : '#52c41a';
34
+ const Icon = isLoading ? MapPin : Flag;
35
+
36
+ const name = route.project?.name || route.name || route.address || '???';
37
+
38
+ return (
39
+ <React.Fragment key={route.id || index}>
40
+ <div style={{
41
+ display: 'flex',
42
+ alignItems: 'center',
43
+ gap: '2px',
44
+ flexShrink: 0,
45
+ maxWidth: '120px'
46
+ }}>
47
+ <Icon
48
+ size={14}
49
+ color={color}
50
+ fill={isFirst || isLast ? `${color}33` : 'none'}
51
+ style={{ flexShrink: 0 }}
52
+ />
53
+ <span style={{
54
+ fontSize: '12px',
55
+ fontWeight: isFirst || isLast ? '500' : '400',
56
+ overflow: 'hidden',
57
+ textOverflow: 'ellipsis',
58
+ whiteSpace: 'nowrap',
59
+ color: '#262626'
60
+ }}>
61
+ {name}
62
+ </span>
63
+ </div>
64
+ {!isLast && (
65
+ <ChevronRight size={12} color="#bfbfbf" style={{ flexShrink: 0 }} />
66
+ )}
67
+ </React.Fragment>
68
+ );
69
+ })}
70
+ </div>
71
+ );
72
+ };
73
+
74
+ RouteCellRenderer.displayName = 'RouteCellRenderer';
75
+
76
+ export default RouteCellRenderer;
@@ -2,16 +2,37 @@
2
2
  import React from 'react';
3
3
  import * as LucideIcons from 'lucide-react';
4
4
 
5
- export default ({value, displayField = 'name', imageField = null}) => {
5
+ export default (params) => {
6
+ const { value, data, displayField = 'name', imageField = null, icon, iconColor } = params;
6
7
  if (value == null) return null;
8
+
7
9
  const displayText = typeof value === 'object'
8
10
  ? (value[displayField] ?? value.name ?? '')
9
11
  : String(value);
10
- const icon = value?.icon;
12
+
11
13
  const imageUrl = imageField && typeof value === 'object' ? value[imageField] : null;
12
14
 
13
- // Přímý přístup ke komponentě ikony
14
- const Icon = icon && LucideIcons[icon] ? LucideIcons[icon] : null;
15
+ // Lucide ikona z hodnoty (hodnota má pole icon s názvem Lucide komponenty)
16
+ const lucideIconName = value?.icon;
17
+ const LucideIcon = !imageUrl && !icon && lucideIconName && LucideIcons[lucideIconName]
18
+ ? LucideIcons[lucideIconName]
19
+ : null;
20
+
21
+ // Custom ikona předaná přes cellRendererParams.icon — může být element, funkce nebo komponenta
22
+ let iconElement = null;
23
+ if (!imageUrl && icon) {
24
+ const resolvedColor = typeof iconColor === 'function' ? iconColor({ data, value }) : iconColor;
25
+ const colorStyle = resolvedColor ? { color: resolvedColor } : {};
26
+
27
+ if (React.isValidElement(icon)) {
28
+ iconElement = React.cloneElement(icon, { style: { ...colorStyle, ...icon.props.style } });
29
+ } else if (typeof icon === 'function') {
30
+ const rendered = icon({ data, value });
31
+ iconElement = resolvedColor && React.isValidElement(rendered)
32
+ ? React.cloneElement(rendered, { style: { ...colorStyle, ...rendered.props?.style } })
33
+ : rendered;
34
+ }
35
+ }
15
36
 
16
37
  return (
17
38
  <div style={{
@@ -29,14 +50,19 @@ export default ({value, displayField = 'name', imageField = null}) => {
29
50
  onError={(e) => { e.currentTarget.style.display = 'none'; }}
30
51
  />
31
52
  )}
32
- {!imageUrl && Icon && (
53
+ {iconElement && (
54
+ <span style={{ flexShrink: 0, display: 'flex', alignItems: 'center' }}>
55
+ {iconElement}
56
+ </span>
57
+ )}
58
+ {!iconElement && LucideIcon && (
33
59
  <span style={{ flexShrink: 0, display: 'flex', alignItems: 'center' }}>
34
- <Icon size={16} />
60
+ <LucideIcon size={16} />
35
61
  </span>
36
62
  )}
37
63
  <span style={{ overflow: 'hidden', textOverflow: 'ellipsis' }}>
38
64
  {displayText}
39
65
  </span>
40
66
  </div>
41
- )
67
+ );
42
68
  }
@@ -2,3 +2,4 @@ export {default as SelectCellRenderer} from './SelectCellRenderer';
2
2
  export {default as ModuleLookupRenderer} from './ModuleLookupRenderer';
3
3
  export {default as AddressLookupRenderer} from './AddressLookupRenderer';
4
4
  export {default as NumberedListRenderer} from './NumberedListRenderer';
5
+ export {default as RouteCellRenderer} from './RouteCellRenderer';
@@ -53,7 +53,7 @@ document.addEventListener('focusin', (e) => {
53
53
  * 1 shoda → auto-výběr + ikona 🔗
54
54
  * 2+ shody → multi-match modal → uživatel vybere → 🔗
55
55
  */
56
- const ModuleLookupEditor = React.forwardRef(({ value, onValueChange, stopEditing, moduleDefinition, allowFreeText = false, rowHeight = 42, }, _ref) => {
56
+ const ModuleLookupEditor = React.forwardRef(({ value, onValueChange, stopEditing, moduleDefinition, allowFreeText = false, rowHeight = 42, autoOpen = true, }, _ref) => {
57
57
  // ─── Hooks (vždy voláno) ──────────────────────────────────────────────────────
58
58
  const { fetchDataUIAsync } = useData();
59
59
  const { accessToken } = useOidcAccessToken();
@@ -97,6 +97,11 @@ const ModuleLookupEditor = React.forwardRef(({ value, onValueChange, stopEditing
97
97
  }, []); // jen při mount
98
98
  const [inputText, setInputText] = useState(initialText);
99
99
  const [dropdownOpen, setDropdownOpen] = useState(false);
100
+ useEffect(() => {
101
+ if (autoOpen) {
102
+ setDropdownOpen(true);
103
+ }
104
+ }, [autoOpen]);
100
105
  const [multiMatchData, setMultiMatchData] = useState([]);
101
106
  const [multiMatchModalOpen, setMultiMatchModalOpen] = useState(false);
102
107
  const catalogModalOpenRef = useRef(false);
@@ -105,7 +110,7 @@ const ModuleLookupEditor = React.forwardRef(({ value, onValueChange, stopEditing
105
110
  multiMatchModalOpenRef.current = multiMatchModalOpen;
106
111
  const currentTextRef = useRef(initialText);
107
112
  const inputTextRef = useRef(initialText); // vždy aktuální inputText pro native handler
108
- const freeTextWrapperRef = useRef(null); // ref na wrapper div → querySelector('input')
113
+ const editorWrapperRef = useRef(null); // ref na wrapper div → querySelector('input')
109
114
  const isStoppedRef = useRef(false);
110
115
  const justSelectedRef = useRef(false);
111
116
  // Nastaven synchronně před každým stopEditing() voláním.
@@ -119,6 +124,7 @@ const ModuleLookupEditor = React.forwardRef(({ value, onValueChange, stopEditing
119
124
  // Ref na suggestions a handleAutoCompleteSelect — přístupné v capture handleru bez stale closure
120
125
  const suggestionsRef = useRef([]);
121
126
  const handleAutoCompleteSelectRef = useRef(null);
127
+ const handleCatalogSelectChangeRef = useRef(null);
122
128
  // ─── Lokální filtrování návrhů (bez API volání) ───────────────────────────────
123
129
  const suggestions = React.useMemo(() => {
124
130
  const vf = moduleDefinition?.valueField || 'id';
@@ -128,12 +134,18 @@ const ModuleLookupEditor = React.forwardRef(({ value, onValueChange, stopEditing
128
134
  const label = moduleDefinition?.getDisplayValue?.(item) || item.code || item.name || '';
129
135
  return label.toLowerCase().includes(inputText.toLowerCase());
130
136
  });
131
- return source.slice(0, 15).map(item => ({
132
- value: String(item[vf]),
133
- label: moduleDefinition?.getDisplayValue?.(item) || item.code || item.name || '',
134
- item,
135
- }));
136
- }, [allItems, inputText, moduleDefinition]);
137
+ const result = source.map(item => {
138
+ const displayText = moduleDefinition?.getDisplayValue?.(item) || item.code || item.name || String(item[vf] || '');
139
+ const originalLabel = moduleDefinition?.renderOption ? moduleDefinition.renderOption(item, displayText) : displayText;
140
+ return {
141
+ value: String(item[vf]),
142
+ label: (_jsx("div", { "data-mle-value": String(item[vf]), style: { display: 'contents' }, children: originalLabel })),
143
+ displayText,
144
+ item,
145
+ };
146
+ });
147
+ return allowFreeText ? result.slice(0, 50) : result;
148
+ }, [allItems, inputText, moduleDefinition, allowFreeText]);
137
149
  suggestionsRef.current = suggestions;
138
150
  // ─── AG Grid useGridCellEditor ───────────────────────────────────────────────
139
151
  // AG Grid 35 vyžaduje getValue() pro získání výsledné hodnoty při stopEditing().
@@ -195,6 +207,7 @@ const ModuleLookupEditor = React.forwardRef(({ value, onValueChange, stopEditing
195
207
  onValueChange(item);
196
208
  setTimeout(() => stopEditing(), 0);
197
209
  }, [allItems, moduleDefinition, onValueChange, stopEditing]);
210
+ handleCatalogSelectChangeRef.current = handleCatalogSelectChange;
198
211
  const catalogCurrentId = React.useMemo(() => {
199
212
  if (allowFreeText || !value)
200
213
  return null;
@@ -343,9 +356,7 @@ const ModuleLookupEditor = React.forwardRef(({ value, onValueChange, stopEditing
343
356
  // rc-select volá stopPropagation() na Enter v bubble fázi → náš React onKeyDown
344
357
  // na wrapperu ho nikdy nedostane. Capture-phase native listener se spustí jako první.
345
358
  useEffect(() => {
346
- if (!allowFreeText)
347
- return;
348
- const el = freeTextWrapperRef.current;
359
+ const el = editorWrapperRef.current;
349
360
  if (!el)
350
361
  return;
351
362
  const input = el.querySelector('input');
@@ -369,16 +380,41 @@ const ModuleLookupEditor = React.forwardRef(({ value, onValueChange, stopEditing
369
380
  if (activeOptionEl) {
370
381
  e.stopImmediatePropagation();
371
382
  e.preventDefault();
372
- const labelText = activeOptionEl.querySelector('.ant-select-item-option-content')?.textContent?.trim();
373
- if (labelText) {
374
- const matched = suggestionsRef.current.find((s) => s.label === labelText);
375
- if (matched) {
376
- handleAutoCompleteSelectRef.current?.(matched.value, matched);
377
- return;
383
+ const contentEl = activeOptionEl.querySelector('.ant-select-item-option-content');
384
+ if (contentEl) {
385
+ const mleValue = contentEl.querySelector('[data-mle-value]')?.getAttribute('data-mle-value');
386
+ if (mleValue) {
387
+ const matched = suggestionsRef.current.find((s) => s.value === mleValue);
388
+ if (matched) {
389
+ if (allowFreeText) {
390
+ handleAutoCompleteSelectRef.current?.(matched.value, matched);
391
+ }
392
+ else {
393
+ handleCatalogSelectChangeRef.current?.(matched.value);
394
+ }
395
+ return;
396
+ }
397
+ }
398
+ const spanText = contentEl.querySelector('span')?.textContent?.trim();
399
+ const fullText = contentEl.textContent?.trim();
400
+ const labelText = spanText || fullText;
401
+ if (labelText) {
402
+ const matched = suggestionsRef.current.find((s) => s.displayText === labelText || String(s.label) === labelText);
403
+ if (matched) {
404
+ if (allowFreeText) {
405
+ handleAutoCompleteSelectRef.current?.(matched.value, matched);
406
+ }
407
+ else {
408
+ handleCatalogSelectChangeRef.current?.(matched.value);
409
+ }
410
+ return;
411
+ }
378
412
  }
379
413
  }
380
414
  return;
381
415
  }
416
+ if (!allowFreeText)
417
+ return;
382
418
  const text = inputTextRef.current || '';
383
419
  // Prázdný input bez předchozí hodnoty → nechat AG Grid zpracovat (cancel)
384
420
  if (!text.trim() && pendingValueRef.current === null && !initialText)
@@ -402,14 +438,19 @@ const ModuleLookupEditor = React.forwardRef(({ value, onValueChange, stopEditing
402
438
  // ─── Render: catalog-only mód ─────────────────────────────────────────────────
403
439
  if (!allowFreeText) {
404
440
  const vf = moduleDefinition?.valueField || 'id';
405
- const selectOptions = allItems.map(item => ({
406
- value: item[vf],
407
- label: moduleDefinition?.getDisplayValue?.(item) || item.code || item.name || String(item[vf]),
408
- }));
409
- return (_jsxs("div", { style: { width: '100%', minWidth: 250, height: rowHeight, display: 'flex', alignItems: 'stretch' }, children: [_jsxs("div", { style: { display: 'flex', flex: 1, height: '100%' }, children: [_jsx(Select, { style: { flex: 1, height: '100%' }, value: catalogCurrentId, options: selectOptions, loading: allItemsLoading, showSearch: true, filterOption: (input, option) => String(option?.label || '').toLowerCase().includes(input.toLowerCase()), onChange: handleCatalogSelectChange, getPopupContainer: () => document.body, dropdownStyle: { zIndex: 9999 }, placeholder: moduleDefinition?.placeholder || 'Vyberte...', autoFocus: true }), _jsx(Button, { icon: _jsx(SearchOutlined, {}), onMouseDown: e => e.preventDefault(), onClick: () => setCatalogModalOpen(true), type: "primary", style: { height: '100%', borderRadius: 0 } })] }), catalogModalContent] }));
441
+ const selectOptions = allItems.map(item => {
442
+ const displayText = moduleDefinition?.getDisplayValue?.(item) || item.code || item.name || String(item[vf] || '');
443
+ const originalLabel = moduleDefinition?.renderOption ? moduleDefinition.renderOption(item, displayText) : displayText;
444
+ return {
445
+ value: item[vf],
446
+ label: (_jsx("div", { "data-mle-value": String(item[vf]), style: { display: 'contents' }, children: originalLabel })),
447
+ displayText,
448
+ };
449
+ });
450
+ return (_jsxs("div", { style: { width: '100%', minWidth: 250, height: rowHeight, display: 'flex', alignItems: 'stretch' }, ref: editorWrapperRef, children: [_jsxs("div", { style: { display: 'flex', flex: 1, height: '100%' }, children: [_jsx(Select, { style: { flex: 1, height: '100%' }, value: catalogCurrentId, options: selectOptions, optionLabelProp: "displayText", loading: allItemsLoading, showSearch: true, filterOption: (input, option) => String(option?.displayText || option?.label || '').toLowerCase().includes(input.toLowerCase()), onChange: handleCatalogSelectChange, onSearch: handleInputChange, getPopupContainer: () => document.body, dropdownStyle: { zIndex: 9999 }, placeholder: moduleDefinition?.placeholder || 'Vyberte...', defaultOpen: autoOpen, autoFocus: true }), _jsx(Button, { icon: _jsx(SearchOutlined, {}), onMouseDown: e => e.preventDefault(), onClick: () => setCatalogModalOpen(true), type: "primary", style: { height: '100%', borderRadius: 0 } })] }), catalogModalContent] }));
410
451
  }
411
452
  // ─── Render: free-text mód ────────────────────────────────────────────────────
412
- return (_jsxs("div", { style: { width: '100%', minWidth: 250, height: rowHeight, display: 'flex', alignItems: 'stretch' }, children: [_jsxs("div", { ref: freeTextWrapperRef, style: { display: 'flex', flex: 1, height: '100%' }, children: [_jsx(AutoComplete, { style: { flex: 1 }, value: inputText, options: suggestions, open: dropdownOpen && suggestions.length > 0, defaultActiveFirstOption: false, allowClear: true, onChange: handleInputChange, onClear: handleClear, onSelect: handleAutoCompleteSelect, onFocus: () => setDropdownOpen(true), onBlur: () => {
453
+ return (_jsxs("div", { style: { width: '100%', minWidth: 250, height: rowHeight, display: 'flex', alignItems: 'stretch' }, children: [_jsxs("div", { ref: editorWrapperRef, style: { display: 'flex', flex: 1, height: '100%' }, children: [_jsx(AutoComplete, { style: { flex: 1 }, value: inputText, options: suggestions, open: dropdownOpen && suggestions.length > 0, defaultActiveFirstOption: false, allowClear: true, onChange: handleInputChange, onClear: handleClear, onSelect: handleAutoCompleteSelect, onFocus: () => setDropdownOpen(true), onBlur: () => {
413
454
  setTimeout(() => setDropdownOpen(false), 150);
414
455
  handleBlur();
415
456
  }, notFoundContent: allItemsLoading ? 'Načítání...' : null, placeholder: moduleDefinition?.placeholder || 'Zadejte nebo vyberte...', autoFocus: true }), _jsx(Button, { icon: _jsx(SearchOutlined, {}), onMouseDown: e => e.preventDefault(), onClick: () => {