@bit.rhplus/ag-grid 0.0.120 → 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.
- package/AgGridColumn.js +9 -0
- package/BulkEdit/BulkEditButton.jsx +9 -3
- package/BulkEdit/BulkEditModule.jsx +14 -12
- package/BulkEdit/useBulkCellEdit.js +2 -0
- package/BulkEdit/utils.js +6 -1
- package/Editors/AddressLookupEditor.jsx +529 -0
- package/Editors/index.jsx +1 -0
- package/Renderers/AddressLookupRenderer.jsx +71 -0
- package/Renderers/NumberedListRenderer.jsx +75 -0
- package/Renderers/ObjectRenderer.jsx +9 -6
- package/Renderers/SelectCellRenderer.jsx +16 -4
- package/Renderers/index.jsx +2 -0
- package/dist/AgGridColumn.js +8 -0
- package/dist/AgGridColumn.js.map +1 -1
- package/dist/BulkEdit/BulkEditButton.js +7 -3
- package/dist/BulkEdit/BulkEditButton.js.map +1 -1
- package/dist/BulkEdit/BulkEditModule.js +2 -2
- package/dist/BulkEdit/BulkEditModule.js.map +1 -1
- package/dist/BulkEdit/useBulkCellEdit.js +2 -0
- package/dist/BulkEdit/useBulkCellEdit.js.map +1 -1
- package/dist/BulkEdit/utils.js +4 -0
- package/dist/BulkEdit/utils.js.map +1 -1
- package/dist/Editors/AddressLookupEditor.d.ts +11 -0
- package/dist/Editors/AddressLookupEditor.js +400 -0
- package/dist/Editors/AddressLookupEditor.js.map +1 -0
- package/dist/Editors/index.js +1 -0
- package/dist/Editors/index.js.map +1 -1
- package/dist/Renderers/AddressLookupRenderer.d.ts +3 -0
- package/dist/Renderers/AddressLookupRenderer.js +56 -0
- package/dist/Renderers/AddressLookupRenderer.js.map +1 -0
- package/dist/Renderers/NumberedListRenderer.d.ts +3 -0
- package/dist/Renderers/NumberedListRenderer.js +43 -0
- package/dist/Renderers/NumberedListRenderer.js.map +1 -0
- package/dist/Renderers/ObjectRenderer.js +9 -6
- package/dist/Renderers/ObjectRenderer.js.map +1 -1
- package/dist/Renderers/SelectCellRenderer.d.ts +3 -1
- package/dist/Renderers/SelectCellRenderer.js +7 -3
- package/dist/Renderers/SelectCellRenderer.js.map +1 -1
- package/dist/Renderers/index.js +2 -0
- package/dist/Renderers/index.js.map +1 -1
- package/dist/index.js +10 -1
- package/dist/index.js.map +1 -1
- package/index.jsx +11 -0
- package/package.json +5 -5
- /package/dist/{preview-1774528082650.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:
|
|
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={
|
|
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:
|
|
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
|
-
<
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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