@bit.rhplus/ag-grid 0.0.119 → 0.0.121

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 (45) hide show
  1. package/AgGridColumn.js +9 -0
  2. package/BulkEdit/BulkEditButton.jsx +9 -3
  3. package/BulkEdit/BulkEditModule.jsx +14 -12
  4. package/BulkEdit/useBulkCellEdit.js +2 -0
  5. package/BulkEdit/utils.js +6 -1
  6. package/Editors/AddressLookupEditor.jsx +529 -0
  7. package/Editors/index.jsx +1 -0
  8. package/Renderers/AddressLookupRenderer.jsx +71 -0
  9. package/Renderers/NumberedListRenderer.jsx +75 -0
  10. package/Renderers/ObjectRenderer.jsx +9 -6
  11. package/Renderers/SelectCellRenderer.jsx +16 -4
  12. package/Renderers/index.jsx +2 -0
  13. package/dist/AgGridColumn.js +8 -0
  14. package/dist/AgGridColumn.js.map +1 -1
  15. package/dist/BulkEdit/BulkEditButton.js +7 -3
  16. package/dist/BulkEdit/BulkEditButton.js.map +1 -1
  17. package/dist/BulkEdit/BulkEditModule.js +2 -2
  18. package/dist/BulkEdit/BulkEditModule.js.map +1 -1
  19. package/dist/BulkEdit/useBulkCellEdit.js +2 -0
  20. package/dist/BulkEdit/useBulkCellEdit.js.map +1 -1
  21. package/dist/BulkEdit/utils.js +4 -0
  22. package/dist/BulkEdit/utils.js.map +1 -1
  23. package/dist/Editors/AddressLookupEditor.d.ts +11 -0
  24. package/dist/Editors/AddressLookupEditor.js +400 -0
  25. package/dist/Editors/AddressLookupEditor.js.map +1 -0
  26. package/dist/Editors/index.js +1 -0
  27. package/dist/Editors/index.js.map +1 -1
  28. package/dist/Renderers/AddressLookupRenderer.d.ts +3 -0
  29. package/dist/Renderers/AddressLookupRenderer.js +56 -0
  30. package/dist/Renderers/AddressLookupRenderer.js.map +1 -0
  31. package/dist/Renderers/NumberedListRenderer.d.ts +3 -0
  32. package/dist/Renderers/NumberedListRenderer.js +43 -0
  33. package/dist/Renderers/NumberedListRenderer.js.map +1 -0
  34. package/dist/Renderers/ObjectRenderer.js +9 -6
  35. package/dist/Renderers/ObjectRenderer.js.map +1 -1
  36. package/dist/Renderers/SelectCellRenderer.d.ts +3 -1
  37. package/dist/Renderers/SelectCellRenderer.js +7 -3
  38. package/dist/Renderers/SelectCellRenderer.js.map +1 -1
  39. package/dist/Renderers/index.js +2 -0
  40. package/dist/Renderers/index.js.map +1 -1
  41. package/dist/index.js +10 -1
  42. package/dist/index.js.map +1 -1
  43. package/index.jsx +11 -0
  44. package/package.json +5 -5
  45. /package/dist/{preview-1774365222649.js → preview-1775124667740.js} +0 -0
package/AgGridColumn.js CHANGED
@@ -57,6 +57,15 @@ export const AgGridColumn = (column, options) => {
57
57
  };
58
58
  }
59
59
 
60
+ if (column.numberedListRenderer) {
61
+ column.cellRenderer = "numberedListRenderer";
62
+ // AG-Grid deferRender: odkládá rendering během scrollování pro lepší výkon
63
+ column.cellRendererParams = {
64
+ ...column.numberedListRendererParams,
65
+ deferRender: false
66
+ };
67
+ }
68
+
60
69
  if (column.buttonRenderer) {
61
70
  column.cellRenderer = "buttonRenderer";
62
71
  // AG-Grid deferRender: odkládá rendering během scrollování pro lepší výkon
@@ -104,6 +104,12 @@ const BulkEditButton = ({
104
104
  * Handler pro kliknutí mimo popover (close)
105
105
  */
106
106
  const handleClickOutside = (e) => {
107
+ // Ignoruj kliknutí do Ant Design portálovaných elementů (dropdown, modal, tooltip, picker)
108
+ const isAntdPortal = e.target.closest(
109
+ '.ant-select-dropdown, .ant-picker-dropdown, .ant-modal-wrap, .ant-modal-root, .ant-tooltip'
110
+ );
111
+ if (isAntdPortal) return;
112
+
107
113
  if (
108
114
  popoverVisible &&
109
115
  popoverRef.current &&
@@ -148,7 +154,7 @@ const BulkEditButton = ({
148
154
  position: 'absolute',
149
155
  top: position?.y || 0,
150
156
  left: position?.x || 0,
151
- zIndex: 9999,
157
+ zIndex: 1050,
152
158
  pointerEvents: 'auto',
153
159
  }}
154
160
  >
@@ -156,7 +162,7 @@ const BulkEditButton = ({
156
162
  title={tooltipTitle}
157
163
  placement="top"
158
164
  getPopupContainer={() => document.body}
159
- zIndex={10001}
165
+ zIndex={1101}
160
166
  >
161
167
  <Button
162
168
  type="primary"
@@ -180,7 +186,7 @@ const BulkEditButton = ({
180
186
  position: 'fixed',
181
187
  top: popoverPosition?.top ?? 0,
182
188
  left: popoverPosition?.left ?? 0,
183
- zIndex: 99999,
189
+ zIndex: 1100,
184
190
  pointerEvents: 'auto',
185
191
  opacity: popoverPosition ? 1 : 0,
186
192
  transition: 'opacity 0.1s ease-in-out',
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable */
2
2
  import React, { useRef, useEffect } from 'react';
3
3
  import { createMemoComparison } from '@bit.rhplus/react-memo';
4
- import { Button, Space } from 'antd';
4
+ import { Button, Space, ConfigProvider } from 'antd';
5
5
  import ModuleDropdownList from '@bit.rhplus/ui2.module-dropdown-list';
6
6
 
7
7
  const BulkEditModule = ({
@@ -36,17 +36,19 @@ const BulkEditModule = ({
36
36
  return (
37
37
  <>
38
38
  <div style={{ marginBottom: '12px' }} onClick={(e) => e.stopPropagation()}>
39
- <ModuleDropdownList
40
- ref={moduleRef}
41
- value={value}
42
- onChange={onChange}
43
- moduleDefinition={moduleDefinition}
44
- placeholder={placeholder}
45
- disabled={loading}
46
- style={{ width: '100%' }}
47
- onKeyDown={handleKeyDown}
48
- displayMode="full"
49
- />
39
+ <ConfigProvider theme={{ token: { zIndexPopupBase: 100000 } }}>
40
+ <ModuleDropdownList
41
+ ref={moduleRef}
42
+ value={value}
43
+ onChange={onChange}
44
+ moduleDefinition={moduleDefinition}
45
+ placeholder={placeholder}
46
+ disabled={loading}
47
+ style={{ width: '100%' }}
48
+ onKeyDown={handleKeyDown}
49
+ displayMode="full"
50
+ />
51
+ </ConfigProvider>
50
52
  </div>
51
53
 
52
54
  <Space style={{ width: '100%', justifyContent: 'flex-end' }}>
@@ -350,6 +350,7 @@ export const useBulkCellEdit = (gridRef, options = {}) => {
350
350
  range: floatingButton.range,
351
351
  column: floatingButton.column,
352
352
  newValue,
353
+ gridApi: gridRef.current?.api,
353
354
  });
354
355
  }
355
356
  }
@@ -367,6 +368,7 @@ export const useBulkCellEdit = (gridRef, options = {}) => {
367
368
  range: floatingButton.range,
368
369
  column: floatingButton.column,
369
370
  newValue,
371
+ gridApi: gridRef.current?.api,
370
372
  });
371
373
  }
372
374
  }
package/BulkEdit/utils.js CHANGED
@@ -53,9 +53,14 @@ export const validateRangeEditable = (range, gridApi) => {
53
53
  if (!range || !range.columns || range.columns.length === 0) {
54
54
  return false;
55
55
  }
56
-
56
+
57
57
  const column = range.columns[0];
58
58
  const colDef = column.getColDef();
59
+
60
+ // Sloupce s bulkEditApi nebo bulkEditPopover jsou vždy validní pro bulk edit
61
+ if (colDef?.bulkEditApi || colDef?.bulkEditPopover) {
62
+ return true;
63
+ }
59
64
 
60
65
  // Pokud je editable funkce, musíme zkontrolovat každý řádek v range
61
66
  if (typeof colDef.editable === 'function') {
@@ -0,0 +1,529 @@
1
+ /* eslint-disable */
2
+ import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
3
+ import { useGridCellEditor } from 'ag-grid-react';
4
+ import { AgGridReact } from 'ag-grid-react';
5
+ import AutoComplete from 'antd/es/auto-complete';
6
+ import Button from 'antd/es/button';
7
+ import Segmented from 'antd/es/segmented';
8
+ import { SearchOutlined, PlusOutlined } from '@ant-design/icons';
9
+ import DraggableModal from '@bit.rhplus/draggable-modal';
10
+ import useData from '@bit.rhplus/data';
11
+ import { useOidcAccessToken } from '@axa-fr/react-oidc';
12
+
13
+ // Sdílená cache s ModuleLookupEditor — data se načtou jednou per moduleName
14
+ const _lookupCache = {};
15
+
16
+ // ─── Module-level ochrana před AG Grid outside-click detekcí ─────────────────
17
+ let _editorMounted = false;
18
+ let _editorModalOpen = false;
19
+
20
+ document.addEventListener('mousedown', (e) => {
21
+ if (!_editorMounted) return;
22
+ if (e.target.closest?.('.ant-select-dropdown')) {
23
+ e.preventDefault();
24
+ e.stopImmediatePropagation();
25
+ return;
26
+ }
27
+ if (_editorModalOpen && e.target.closest?.('.ant-modal-wrap')) {
28
+ e.stopImmediatePropagation();
29
+ }
30
+ }, true);
31
+
32
+ document.addEventListener('focusin', (e) => {
33
+ if (!_editorMounted) return;
34
+ const target = e.target;
35
+ if (target.closest?.('.ant-select-dropdown') ||
36
+ (_editorModalOpen && target.closest?.('.ant-modal-wrap'))) {
37
+ e.stopImmediatePropagation();
38
+ }
39
+ }, true);
40
+
41
+ const renderContractOption = (label, subLabel) => (
42
+ <div style={{ lineHeight: 1.4 }}>
43
+ <div style={{ fontWeight: 400 }}>{label}</div>
44
+ {subLabel && <div style={{ fontSize: 12, color: '#8c8c8c' }}>{subLabel}</div>}
45
+ </div>
46
+ );
47
+
48
+ const renderGoogleOption = (name, description) => (
49
+ <div style={{ lineHeight: 1.4 }}>
50
+ <div style={{ fontWeight: 400 }}>{name}</div>
51
+ {description && <div style={{ fontSize: 12, color: '#8c8c8c' }}>{description}</div>}
52
+ </div>
53
+ );
54
+
55
+ /**
56
+ * AddressLookupEditor — AG Grid cell editor
57
+ *
58
+ * Kombinuje výběr z katalogu kontraktů (přes moduleDefinition) s vyhledáváním Google Places.
59
+ * Přepínání probíhá přes Segmented switcher.
60
+ *
61
+ * Hodnota: { id: string|null, text: string, source: 'contract'|'google'|'free' }
62
+ */
63
+ const AddressLookupEditor = React.forwardRef(({
64
+ value,
65
+ onValueChange,
66
+ stopEditing,
67
+ moduleDefinition,
68
+ column,
69
+ }, _ref) => {
70
+ const MIN_WIDTH = 300;
71
+ const editorWidth = Math.max(MIN_WIDTH, column?.getActualWidth?.() || 0);
72
+
73
+ // ─── Hooks ───────────────────────────────────────────────────────────────────
74
+ const { fetchDataUIAsync } = useData();
75
+ const { accessToken } = useOidcAccessToken();
76
+
77
+ // ─── Inicializace textu z hodnoty ─────────────────────────────────────────────
78
+ const initialText = useMemo(() => {
79
+ if (!value) return '';
80
+ if (typeof value === 'object' && 'text' in value) return value.text || '';
81
+ if (typeof value === 'string') return value;
82
+ return '';
83
+ }, []); // jen při mount
84
+
85
+ // ─── Stav ────────────────────────────────────────────────────────────────────
86
+ const [mode, setMode] = useState(() => value?.id ? 'contracts' : 'google');
87
+ const [inputText, setInputText] = useState(initialText);
88
+ const [catalogModalOpen, setCatalogModalOpen] = useState(false);
89
+ const [createNewModalOpen, setCreateNewModalOpen] = useState(false);
90
+ const [createNewInitialName, setCreateNewInitialName] = useState('');
91
+
92
+ // Contracts state
93
+ const moduleName = moduleDefinition?.moduleName;
94
+ const [allItems, setAllItems] = useState(() => _lookupCache[moduleName] || []);
95
+ const [allItemsLoading, setAllItemsLoading] = useState(!_lookupCache[moduleName]);
96
+
97
+ // Google state
98
+ const [googleOptions, setGoogleOptions] = useState([]);
99
+ const [googleLoading, setGoogleLoading] = useState(false);
100
+ const placesServiceRef = useRef(null);
101
+ const googleDebounceRef = useRef(null);
102
+
103
+ // ─── Refy pro handlery ────────────────────────────────────────────────────────
104
+ const catalogModalOpenRef = useRef(false);
105
+ catalogModalOpenRef.current = catalogModalOpen;
106
+ const inputTextRef = useRef(initialText);
107
+ inputTextRef.current = inputText;
108
+ const currentTextRef = useRef(initialText);
109
+ const isStoppedRef = useRef(false);
110
+ const justSelectedRef = useRef(false);
111
+ const pendingValueRef = useRef(null);
112
+ const modeRef = useRef('contracts');
113
+ modeRef.current = mode;
114
+ const onValueChangeRef = useRef(onValueChange);
115
+ useEffect(() => { onValueChangeRef.current = onValueChange; });
116
+
117
+ // Uchování selekce kontraktu nezávisle na Google záložce (přetrvává při přepínání)
118
+ const contractValueRef = useRef(
119
+ value?.source === 'contract' ? value : null
120
+ );
121
+ // Flag pro zabránění handleBlur zavřít editor při kliknutí na Segmented
122
+ const isSwitchingModeRef = useRef(false);
123
+ // Samostatný input text pro Google záložku — inicializace pro všechny hodnoty bez id
124
+ const googleInputTextRef = useRef(!value?.id ? (value?.text || '') : '');
125
+
126
+ // ─── Načtení kontraktů (jednou při mount) ─────────────────────────────────────
127
+ useEffect(() => {
128
+ if (!moduleName || !moduleDefinition) return;
129
+ if (_lookupCache[moduleName]) {
130
+ setAllItems(_lookupCache[moduleName]);
131
+ setAllItemsLoading(false);
132
+ return;
133
+ }
134
+ setAllItemsLoading(true);
135
+ moduleDefinition.fetchData('', fetchDataUIAsync, accessToken)
136
+ .then((data) => {
137
+ const items = Array.isArray(data) ? data : [];
138
+ _lookupCache[moduleName] = items;
139
+ setAllItems(items);
140
+ })
141
+ .catch(() => setAllItems([]))
142
+ .finally(() => setAllItemsLoading(false));
143
+ }, []); // jen při mount
144
+
145
+ // ─── AG Grid isCancelAfterEnd ──────────────────────────────────────────────────
146
+ useGridCellEditor({
147
+ isCancelAfterEnd: () => {
148
+ const currentText = currentTextRef.current ?? '';
149
+ const originalText = typeof value === 'object' ? (value?.text || '') : (value || '');
150
+ return pendingValueRef.current !== null ? false : currentText === originalText;
151
+ },
152
+ });
153
+
154
+ // ─── Sync module-level flags ───────────────────────────────────────────────────
155
+ useEffect(() => {
156
+ _editorMounted = true;
157
+ return () => { _editorMounted = false; };
158
+ }, []);
159
+
160
+ // ─── Pokud se editor otevře v Google módu s textem, spustit search ─────────────
161
+ useEffect(() => {
162
+ if (!value?.id && initialText) {
163
+ searchGooglePlaces(initialText);
164
+ }
165
+ }, []); // jen při mount
166
+
167
+ useEffect(() => {
168
+ _editorModalOpen = catalogModalOpen || createNewModalOpen;
169
+ }, [catalogModalOpen, createNewModalOpen]);
170
+
171
+ // ─── Google Places: inicializace ─────────────────────────────────────────────
172
+ const initGooglePlaces = useCallback(async () => {
173
+ if (placesServiceRef.current) return true;
174
+ if (!window.google?.maps) return false;
175
+ if (!window.google.maps.places) {
176
+ try {
177
+ if (window.google.maps.importLibrary) {
178
+ await window.google.maps.importLibrary('places');
179
+ } else {
180
+ await new Promise((resolve, reject) => {
181
+ const script = document.createElement('script');
182
+ script.src = `https://maps.googleapis.com/maps/api/js?key=${process.env.REACT_APP_GOOGLE_MAPS_API_KEY}&libraries=places&v=weekly`;
183
+ script.onload = resolve;
184
+ script.onerror = reject;
185
+ document.head.appendChild(script);
186
+ });
187
+ }
188
+ } catch {
189
+ return false;
190
+ }
191
+ }
192
+ if (window.google?.maps?.places) {
193
+ placesServiceRef.current = new window.google.maps.places.AutocompleteService();
194
+ return true;
195
+ }
196
+ return false;
197
+ }, []);
198
+
199
+ // ─── Google Places: vyhledávání ───────────────────────────────────────────────
200
+ const searchGooglePlaces = useCallback(async (query) => {
201
+ if (!query?.trim()) {
202
+ setGoogleOptions([]);
203
+ return;
204
+ }
205
+ const trimmed = query.trim();
206
+ const exactItem = { name: trimmed, address: trimmed, description: 'Přesný text', source: 'free' };
207
+ const exactOpt = {
208
+ value: `__exact__${trimmed}`,
209
+ label: renderGoogleOption(trimmed, 'Přesný text'),
210
+ item: exactItem,
211
+ };
212
+
213
+ if (!(await initGooglePlaces())) {
214
+ setGoogleOptions([exactOpt]);
215
+ return;
216
+ }
217
+
218
+ setGoogleLoading(true);
219
+ try {
220
+ placesServiceRef.current.getPlacePredictions(
221
+ { input: trimmed, types: ['establishment', 'geocode'] },
222
+ (predictions, status) => {
223
+ setGoogleLoading(false);
224
+ if (status === window.google.maps.places.PlacesServiceStatus.OK && predictions?.length) {
225
+ const opts = predictions.slice(0, 9).map((p) => {
226
+ const item = {
227
+ name: p.structured_formatting?.main_text || p.description,
228
+ address: p.description,
229
+ description: p.structured_formatting?.secondary_text || 'Google Maps',
230
+ source: 'google',
231
+ place_id: p.place_id,
232
+ };
233
+ return { value: p.place_id, label: renderGoogleOption(item.name, item.description), item };
234
+ });
235
+ setGoogleOptions([exactOpt, ...opts]);
236
+ } else {
237
+ setGoogleOptions([exactOpt]);
238
+ }
239
+ }
240
+ );
241
+ } catch {
242
+ setGoogleLoading(false);
243
+ setGoogleOptions([exactOpt]);
244
+ }
245
+ }, [initGooglePlaces]);
246
+
247
+ // ─── Options pro contracts ────────────────────────────────────────────────────
248
+ const contractOptions = useMemo(() => {
249
+ const vf = moduleDefinition?.valueField || 'id';
250
+ const text = (inputText || '').toLowerCase();
251
+ const source = text
252
+ ? allItems.filter((item) => {
253
+ const label = moduleDefinition?.getDisplayValue?.(item) || item.code || item.name || '';
254
+ return label.toLowerCase().includes(text);
255
+ })
256
+ : allItems;
257
+ return source.slice(0, 15).map((item) => {
258
+ const label = moduleDefinition?.getDisplayValue?.(item) || item.code || item.name || '';
259
+ const subLabel = item.city || item.address || '';
260
+ return {
261
+ value: String(item[vf]),
262
+ label: renderContractOption(label, subLabel),
263
+ item,
264
+ };
265
+ });
266
+ }, [allItems, inputText, moduleDefinition]);
267
+
268
+ const options = mode === 'contracts' ? contractOptions : googleOptions;
269
+
270
+ // ─── Input change ──────────────────────────────────────────────────────────────
271
+ const handleInputChange = useCallback((text) => {
272
+ setInputText(text || '');
273
+ currentTextRef.current = text || '';
274
+ if (modeRef.current === 'google') {
275
+ googleInputTextRef.current = text || '';
276
+ clearTimeout(googleDebounceRef.current);
277
+ googleDebounceRef.current = setTimeout(() => searchGooglePlaces(text), 300);
278
+ }
279
+ }, [searchGooglePlaces]);
280
+
281
+ // ─── Výběr z autocomplete ─────────────────────────────────────────────────────
282
+ const handleSelect = useCallback((_, option) => {
283
+ justSelectedRef.current = true;
284
+ const item = option.item;
285
+ const vf = moduleDefinition?.valueField || 'id';
286
+ let selected;
287
+ if (item.source === 'google' || item.source === 'free') {
288
+ selected = { id: null, text: item.name || item.address || '', source: item.source, address: item.address };
289
+ } else {
290
+ const displayText = moduleDefinition?.getDisplayValue?.(item) || item.code || item.name || '';
291
+ selected = { id: item[vf], text: displayText, source: 'contract', _catalogItem: item };
292
+ contractValueRef.current = selected;
293
+ }
294
+ pendingValueRef.current = selected;
295
+ if (onValueChange) onValueChange(selected);
296
+ setTimeout(() => stopEditing(), 0);
297
+ }, [moduleDefinition, onValueChange, stopEditing]);
298
+
299
+ // ─── Výběr z catalog modalu ───────────────────────────────────────────────────
300
+ const handleCatalogModalSelect = useCallback((params) => {
301
+ if (!params.data) return;
302
+ const item = params.data;
303
+ const vf = moduleDefinition?.valueField || 'id';
304
+ const displayText = moduleDefinition?.getDisplayValue?.(item) || item.code || item.name || '';
305
+ justSelectedRef.current = true;
306
+ const selected = { id: item[vf], text: displayText, source: 'contract', _catalogItem: item };
307
+ contractValueRef.current = selected;
308
+ pendingValueRef.current = selected;
309
+ if (onValueChange) onValueChange(selected);
310
+ setCatalogModalOpen(false);
311
+ setTimeout(() => stopEditing(), 0);
312
+ }, [moduleDefinition, onValueChange, stopEditing]);
313
+
314
+ // ─── Vytvoření nového kontraktu ───────────────────────────────────────────────
315
+ const handleCreateSuccess = useCallback((newItem) => {
316
+ const updated = [...allItems, newItem];
317
+ _lookupCache[moduleName] = updated;
318
+ setAllItems(updated);
319
+ setCreateNewModalOpen(false);
320
+
321
+ const vf = moduleDefinition?.valueField || 'id';
322
+ const displayText = moduleDefinition?.getDisplayValue?.(newItem) || newItem.code || newItem.name || '';
323
+ const selected = { id: newItem[vf], text: displayText, source: 'contract', _catalogItem: newItem };
324
+ contractValueRef.current = selected;
325
+ pendingValueRef.current = selected;
326
+ if (onValueChange) onValueChange(selected);
327
+ setTimeout(() => stopEditing(), 0);
328
+ }, [allItems, moduleName, moduleDefinition, onValueChange, stopEditing]);
329
+
330
+ // ─── Blur ──────────────────────────────────────────────────────────────────────
331
+ const handleBlur = useCallback(() => {
332
+ if (isSwitchingModeRef.current) {
333
+ isSwitchingModeRef.current = false;
334
+ return; // Přepínání záložek — editor nezavírat
335
+ }
336
+ if (justSelectedRef.current) {
337
+ justSelectedRef.current = false;
338
+ return;
339
+ }
340
+ if (pendingValueRef.current !== null) return;
341
+ if (currentTextRef.current && !catalogModalOpenRef.current) {
342
+ const text = currentTextRef.current.trim();
343
+ if (text) {
344
+ const selected = { id: null, text, source: 'free' };
345
+ pendingValueRef.current = selected;
346
+ if (onValueChange) onValueChange(selected);
347
+ }
348
+ if (!isStoppedRef.current) stopEditing();
349
+ }
350
+ }, [onValueChange, stopEditing]);
351
+
352
+ // ─── Přepnutí módu ────────────────────────────────────────────────────────────
353
+ const handleModeChange = useCallback((newMode) => {
354
+ setMode(newMode);
355
+ if (newMode === 'google') {
356
+ setGoogleOptions([]);
357
+ // Obnovit vlastní Google input (první přepnutí = prázdné)
358
+ const googleText = googleInputTextRef.current;
359
+ setInputText(googleText);
360
+ currentTextRef.current = googleText;
361
+ if (googleText) {
362
+ searchGooglePlaces(googleText);
363
+ }
364
+ } else if (newMode === 'contracts') {
365
+ const cv = contractValueRef.current;
366
+ if (cv) {
367
+ // Obnovit vybraný kontrakt
368
+ setInputText(cv.text || '');
369
+ currentTextRef.current = cv.text || '';
370
+ inputTextRef.current = cv.text || '';
371
+ pendingValueRef.current = cv;
372
+ onValueChangeRef.current?.(cv);
373
+ } else {
374
+ setInputText('');
375
+ currentTextRef.current = '';
376
+ }
377
+ }
378
+ }, [searchGooglePlaces]);
379
+
380
+ // ─── Cleanup ──────────────────────────────────────────────────────────────────
381
+ useEffect(() => {
382
+ return () => {
383
+ isStoppedRef.current = true;
384
+ clearTimeout(googleDebounceRef.current);
385
+ };
386
+ }, []);
387
+
388
+ // ─── Render ───────────────────────────────────────────────────────────────────
389
+ return (
390
+ <div style={{ width: editorWidth, minWidth: MIN_WIDTH, padding: 4, borderRadius: 0, background: '#fff', boxShadow: '0 2px 8px rgba(0,0,0,0.15)' }}>
391
+ {/* Segmented switcher */}
392
+ <div
393
+ style={{ paddingBottom: 6, borderBottom: '1px solid #f0f0f0', marginBottom: 6 }}
394
+ onMouseDown={() => { isSwitchingModeRef.current = true; }}
395
+ >
396
+ <Segmented
397
+ size="small"
398
+ value={mode}
399
+ onChange={handleModeChange}
400
+ options={[
401
+ { label: 'Kontrakty', value: 'contracts' },
402
+ { label: 'Google', value: 'google' },
403
+ ]}
404
+ />
405
+ </div>
406
+
407
+ {/* AutoComplete + Search button */}
408
+ <div
409
+ style={{ display: 'flex', gap: 4 }}
410
+ onKeyDownCapture={(e) => {
411
+ if (e.key !== 'Enter' || modeRef.current !== 'google') return;
412
+ // Capture fáze — náš handler poběží před Ant Design interním handlerem
413
+ e.stopPropagation(); // AG Grid nedostane Enter
414
+ e.preventDefault(); // AutoComplete nedostane Enter (žádný dropdown výběr klávesnicí)
415
+ const text = (inputTextRef.current || '').trim();
416
+ if (text) {
417
+ const selected = { id: null, text, source: 'free' };
418
+ pendingValueRef.current = selected;
419
+ if (onValueChange) onValueChange(selected);
420
+ stopEditing();
421
+ }
422
+ }}
423
+ >
424
+ <AutoComplete
425
+ key={mode}
426
+ style={{ flex: 1 }}
427
+ value={inputText}
428
+ options={options}
429
+ open={options.length > 0}
430
+ defaultActiveFirstOption={false}
431
+ allowClear
432
+ onChange={handleInputChange}
433
+ onSelect={handleSelect}
434
+ onBlur={() => {
435
+ setTimeout(() => handleBlur(), 150);
436
+ }}
437
+ notFoundContent={
438
+ (mode === 'contracts' && allItemsLoading) || (mode === 'google' && googleLoading)
439
+ ? 'Načítání...'
440
+ : null
441
+ }
442
+ placeholder={
443
+ mode === 'contracts'
444
+ ? (moduleDefinition?.placeholder || 'Vyberte kontrakt...')
445
+ : 'Zadejte adresu pro vyhledání...'
446
+ }
447
+ autoFocus
448
+ getPopupContainer={() => document.body}
449
+ dropdownStyle={{ zIndex: 9999, minWidth: 360 }}
450
+ />
451
+ <Button
452
+ icon={<SearchOutlined />}
453
+ onMouseDown={(e) => e.preventDefault()}
454
+ onClick={() => {
455
+ if (mode === 'contracts') {
456
+ catalogModalOpenRef.current = true;
457
+ setCatalogModalOpen(true);
458
+ }
459
+ }}
460
+ disabled={mode === 'google'}
461
+ type="primary"
462
+ />
463
+ {mode === 'contracts' && moduleDefinition?.createFormComponent && (
464
+ <Button
465
+ icon={<PlusOutlined />}
466
+ onMouseDown={(e) => e.preventDefault()}
467
+ onClick={() => {
468
+ setCreateNewInitialName('');
469
+ setCreateNewModalOpen(true);
470
+ }}
471
+ />
472
+ )}
473
+ </div>
474
+
475
+ {/* Catalog modal */}
476
+ <DraggableModal
477
+ open={catalogModalOpen}
478
+ title={moduleDefinition?.modalTitle || 'Vybrat z katalogu'}
479
+ width={moduleDefinition?.modalWidth || 800}
480
+ onCancel={() => setCatalogModalOpen(false)}
481
+ maskClosable={false}
482
+ getContainer={() => document.body}
483
+ zIndex={99999999}
484
+ footer={null}
485
+ >
486
+ {allItemsLoading ? (
487
+ <div style={{ padding: 24, textAlign: 'center' }}>Načítání...</div>
488
+ ) : (
489
+ <div className="ag-theme-alpine" style={{ height: 400, width: '100%' }}>
490
+ <AgGridReact
491
+ columnDefs={moduleDefinition?.columnDefs || []}
492
+ rowData={allItems}
493
+ onRowDoubleClicked={handleCatalogModalSelect}
494
+ />
495
+ </div>
496
+ )}
497
+ </DraggableModal>
498
+
499
+ {/* Create-new modal */}
500
+ {moduleDefinition?.createFormComponent && (
501
+ <DraggableModal
502
+ open={createNewModalOpen}
503
+ title={moduleDefinition.createModalTitle || 'Nový kontrakt'}
504
+ width={moduleDefinition.createModalWidth || 800}
505
+ onCancel={() => setCreateNewModalOpen(false)}
506
+ maskClosable={false}
507
+ getContainer={() => document.body}
508
+ zIndex={99999999}
509
+ footer={null}
510
+ >
511
+ {React.createElement(moduleDefinition.createFormComponent, {
512
+ initialName: createNewInitialName,
513
+ onSuccess: handleCreateSuccess,
514
+ onCancel: () => setCreateNewModalOpen(false),
515
+ fetchDataUIAsync,
516
+ accessToken,
517
+ createRecord: moduleDefinition.createRecord,
518
+ ...(moduleDefinition.createFormComponentProps || {}),
519
+ })}
520
+ </DraggableModal>
521
+ )}
522
+ </div>
523
+ );
524
+ });
525
+
526
+ AddressLookupEditor.displayName = 'AddressLookupEditor';
527
+
528
+ export default AddressLookupEditor;
529
+ /* eslint-enable */
package/Editors/index.jsx CHANGED
@@ -1,2 +1,3 @@
1
1
  export {default as DatePickerEditor} from './DatePickerEditor';
2
2
  export {default as ModuleLookupEditor} from './ModuleLookupEditor';
3
+ export {default as AddressLookupEditor} from './AddressLookupEditor';