@bit.rhplus/ui2.module-dropdown-list 0.1.110 → 0.1.112
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/dist/index.d.ts +14 -1
- package/dist/index.js +403 -121
- package/dist/index.js.map +1 -1
- package/index.jsx +653 -351
- package/package.json +7 -8
- package/ModuleInput.css +0 -259
- package/dist/ModuleInput.css +0 -259
- package/dist/store/recentItemsStore.d.ts +0 -11
- package/dist/store/recentItemsStore.js +0 -29
- package/dist/store/recentItemsStore.js.map +0 -1
- package/dist/useModuleDropdownList.d.ts +0 -26
- package/dist/useModuleDropdownList.js +0 -171
- package/dist/useModuleDropdownList.js.map +0 -1
- package/store/recentItemsStore.js +0 -37
- package/useModuleDropdownList.js +0 -198
- /package/dist/{preview-1773828129212.js → preview-1774279607741.js} +0 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,15 @@
|
|
|
1
1
|
export default ModuleDropdownList;
|
|
2
|
-
declare function ModuleDropdownList(
|
|
2
|
+
declare function ModuleDropdownList({ value, onChange, onFreeTextCommit, placeholder, disabled, style, allowFreeText, createNewOnMismatch, showArrow, showAddButton, getContainer, moduleDefinition, }: {
|
|
3
|
+
value: any;
|
|
4
|
+
onChange: any;
|
|
5
|
+
onFreeTextCommit: any;
|
|
6
|
+
placeholder?: string;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
style?: {};
|
|
9
|
+
allowFreeText?: boolean;
|
|
10
|
+
createNewOnMismatch?: boolean;
|
|
11
|
+
showArrow?: boolean;
|
|
12
|
+
showAddButton?: boolean;
|
|
13
|
+
getContainer?: () => HTMLElement;
|
|
14
|
+
moduleDefinition: any;
|
|
15
|
+
}): import("react/jsx-runtime").JSX.Element;
|
package/dist/index.js
CHANGED
|
@@ -1,140 +1,422 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
/* eslint-disable */
|
|
3
|
-
import React, { useEffect, useRef, useCallback } from 'react';
|
|
4
|
-
import
|
|
3
|
+
import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
|
4
|
+
import AutoComplete from 'antd/es/auto-complete';
|
|
5
5
|
import Select from 'antd/es/select';
|
|
6
6
|
import Button from 'antd/es/button';
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
7
|
+
import Input from 'antd/es/input';
|
|
8
|
+
import { SearchOutlined, LinkOutlined, EditOutlined, DownOutlined, PlusOutlined } from '@ant-design/icons';
|
|
9
|
+
import { AgGridReact } from 'ag-grid-react';
|
|
10
10
|
import DraggableModal from '@bit.rhplus/draggable-modal';
|
|
11
|
-
import
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
11
|
+
import useData from '@bit.rhplus/data';
|
|
12
|
+
import { useOidcAccessToken } from '@axa-fr/react-oidc';
|
|
13
|
+
// Session-level cache — data načtena jednou per moduleName per session
|
|
14
|
+
const _cache = {};
|
|
15
|
+
// ─── Detail panel subkomponenta ───────────────────────────────────────────────
|
|
16
|
+
const DetailPanel = ({ definition, item }) => {
|
|
17
|
+
if (!definition || !item)
|
|
18
|
+
return null;
|
|
19
|
+
if (typeof definition.render === 'function')
|
|
20
|
+
return definition.render(item);
|
|
21
|
+
if (!Array.isArray(definition.fields))
|
|
22
|
+
return null;
|
|
23
|
+
if (!definition.fields.length)
|
|
24
|
+
return null;
|
|
25
|
+
return (_jsx("div", { style: {
|
|
26
|
+
background: '#fafafa',
|
|
27
|
+
border: '1px solid #d9d9d9',
|
|
28
|
+
borderRadius: 6,
|
|
29
|
+
padding: '8px 12px',
|
|
30
|
+
display: 'grid',
|
|
31
|
+
gridTemplateColumns: '1fr 1fr',
|
|
32
|
+
gap: '4px 16px',
|
|
33
|
+
fontSize: 12,
|
|
34
|
+
}, children: definition.fields.map((f) => (_jsxs("div", { style: { display: 'flex', gap: 4, minWidth: 0 }, children: [_jsxs("span", { style: { color: '#8c8c8c', flexShrink: 0 }, children: [f.label, ":"] }), _jsx("span", { style: {
|
|
35
|
+
fontWeight: 500,
|
|
36
|
+
overflow: 'hidden',
|
|
37
|
+
textOverflow: 'ellipsis',
|
|
38
|
+
whiteSpace: 'nowrap',
|
|
39
|
+
color: item[f.key] ? 'inherit' : '#bfbfbf',
|
|
40
|
+
}, children: item[f.key] ?? '—' })] }, f.key))) }));
|
|
41
|
+
};
|
|
42
|
+
const ModuleDropdownList = ({ value, onChange, onFreeTextCommit, placeholder = 'Vyberte', disabled = false, style = {}, allowFreeText = true, createNewOnMismatch = false, showArrow = true, showAddButton = true, getContainer = () => document.body, moduleDefinition, }) => {
|
|
43
|
+
const moduleName = moduleDefinition?.moduleName;
|
|
44
|
+
const { fetchDataUIAsync } = useData();
|
|
45
|
+
const { accessToken } = useOidcAccessToken();
|
|
46
|
+
// ─── Data a načítání ──────────────────────────────────────────────────────────
|
|
47
|
+
const [allItems, setAllItems] = useState(() => _cache[moduleName] || []);
|
|
48
|
+
const [allItemsLoading, setAllItemsLoading] = useState(!_cache[moduleName]);
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
if (!moduleName || !moduleDefinition)
|
|
51
|
+
return;
|
|
52
|
+
if (_cache[moduleName]) {
|
|
53
|
+
setAllItems(_cache[moduleName]);
|
|
54
|
+
setAllItemsLoading(false);
|
|
34
55
|
return;
|
|
35
|
-
// Vytvoření nového seznamu bez aktuální položky (pokud existuje)
|
|
36
|
-
const filteredItems = recentItems.filter((existingItem) => existingItem[moduleDefinition.valueField || 'id'] !==
|
|
37
|
-
item[moduleDefinition.valueField || 'id']);
|
|
38
|
-
// Přidání nové položky na začátek a omezení počtu položek
|
|
39
|
-
const updatedItems = [item, ...filteredItems].slice(0, maxRecentItems);
|
|
40
|
-
setRecentItems(updatedItems);
|
|
41
|
-
}, [recentItems, moduleDefinition, maxRecentItems, setRecentItems]);
|
|
42
|
-
// Použití custom hooku pro veškerou logiku ModuleInput
|
|
43
|
-
const { modalOpen, searchQuery, dropdownSearchQuery, rowData, fullListData, isLoading, isFullListLoading, selectedItem, isDetailLoading, moduleDetailQuery, openModal, closeModal, handleSearchChange, handleDropdownSearchChange, handleItemSelect, clearSelection, getDisplayValue, } = useModuleDropdownList({ moduleDefinition, value, displayMode });
|
|
44
|
-
const selectItem = (item) => {
|
|
45
|
-
const selectedItemData = handleItemSelect(item);
|
|
46
|
-
// Uložení vybrané položky do nedávných položek
|
|
47
|
-
if (selectedItemData) {
|
|
48
|
-
addToRecentItems(selectedItemData);
|
|
49
|
-
}
|
|
50
|
-
// Přímé volání onChange s novou hodnotou a celým objektem
|
|
51
|
-
if (typeof onChange === 'function' && moduleDefinition) {
|
|
52
|
-
const id = selectedItemData[moduleDefinition.valueField || 'id'];
|
|
53
|
-
onChange(id, selectedItemData);
|
|
54
56
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
+
setAllItemsLoading(true);
|
|
58
|
+
moduleDefinition
|
|
59
|
+
.fetchData('', fetchDataUIAsync, accessToken)
|
|
60
|
+
.then((data) => {
|
|
61
|
+
const items = Array.isArray(data) ? data : [];
|
|
62
|
+
_cache[moduleName] = items;
|
|
63
|
+
setAllItems(items);
|
|
64
|
+
})
|
|
65
|
+
.catch(() => setAllItems([]))
|
|
66
|
+
.finally(() => setAllItemsLoading(false));
|
|
67
|
+
}, []); // pouze při mount
|
|
68
|
+
// ─── Free-text stav ──────────────────────────────────────────────────────────
|
|
69
|
+
const initialText = useMemo(() => {
|
|
70
|
+
if (!allowFreeText)
|
|
71
|
+
return '';
|
|
72
|
+
if (typeof value === 'string')
|
|
73
|
+
return value;
|
|
74
|
+
return '';
|
|
75
|
+
}, []); // pouze při mount
|
|
76
|
+
const [inputText, setInputText] = useState(initialText);
|
|
77
|
+
const [dropdownOpen, setDropdownOpen] = useState(false);
|
|
78
|
+
// null = prázdno/psaní, true = katalog (🔗), false = volný text (✏️)
|
|
79
|
+
const [isLinked, setIsLinked] = useState(null);
|
|
80
|
+
const [selectedItem, setSelectedItem] = useState(null);
|
|
81
|
+
const [detailOpen, setDetailOpen] = useState(false);
|
|
82
|
+
const justSelectedRef = useRef(false);
|
|
83
|
+
const freeTextWrapperRef = useRef(null);
|
|
84
|
+
const inputTextRef = useRef(initialText);
|
|
85
|
+
inputTextRef.current = inputText;
|
|
86
|
+
const isLinkedRef = useRef(null);
|
|
87
|
+
isLinkedRef.current = isLinked;
|
|
88
|
+
// Ref na aktuální suggestions — přístupný v capture handleru bez stale closure
|
|
89
|
+
const suggestionsRef = useRef([]);
|
|
90
|
+
// Ref na handleAutoCompleteSelect — přístupný v capture handleru bez stale closure
|
|
91
|
+
const handleAutoCompleteSelectRef = useRef(null);
|
|
92
|
+
// ─── Sync externího value (načtení formuláře) ────────────────────────────────
|
|
93
|
+
// Když se value změní zvenku (form load), najde položku v katalogu a zobrazí ji.
|
|
94
|
+
// Pokud položka není v allItems, zkusí fetchById fallback.
|
|
57
95
|
useEffect(() => {
|
|
58
|
-
|
|
59
|
-
|
|
96
|
+
console.log('[ModuleDropdownList] sync value:', { value, allItemsCount: allItems.length, allItemsLoading, moduleName });
|
|
97
|
+
if (!value)
|
|
98
|
+
return;
|
|
99
|
+
// Pokud allItems ještě loading, počkáme na další run
|
|
100
|
+
if (allItemsLoading)
|
|
101
|
+
return;
|
|
102
|
+
const vf = moduleDefinition?.valueField || 'id';
|
|
103
|
+
const showItem = (item) => {
|
|
104
|
+
console.log('[ModuleDropdownList] showItem:', { id: item?.[vf], displayValue: moduleDefinition?.getDisplayValue?.(item) });
|
|
105
|
+
const displayText = moduleDefinition?.getDisplayValue?.(item) || item.code || item.name || '';
|
|
106
|
+
setInputText(displayText);
|
|
107
|
+
setIsLinked(true);
|
|
108
|
+
setSelectedItem(item);
|
|
109
|
+
};
|
|
110
|
+
if (allItems.length) {
|
|
111
|
+
const item = allItems.find((i) => String(i[vf]) === String(value));
|
|
112
|
+
if (item) {
|
|
113
|
+
showItem(item);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Fallback: fetchById pro položky, které nejsou v seznamu
|
|
118
|
+
if (moduleDefinition?.fetchById) {
|
|
119
|
+
moduleDefinition.fetchById(value, fetchDataUIAsync, accessToken).then((item) => {
|
|
120
|
+
if (item) {
|
|
121
|
+
showItem(item);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}, [value, allItems, allItemsLoading]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
126
|
+
// ─── Create-new modal ─────────────────────────────────────────────────────────
|
|
127
|
+
const [createNewModalOpen, setCreateNewModalOpen] = useState(false);
|
|
128
|
+
const [createNewInitialName, setCreateNewInitialName] = useState('');
|
|
129
|
+
// ─── Modaly ───────────────────────────────────────────────────────────────────
|
|
130
|
+
const [catalogFilter, setCatalogFilter] = useState('');
|
|
131
|
+
const [catalogModalOpen, setCatalogModalOpen] = useState(false);
|
|
132
|
+
const [multiMatchData, setMultiMatchData] = useState([]);
|
|
133
|
+
const [multiMatchModalOpen, setMultiMatchModalOpen] = useState(false);
|
|
134
|
+
const catalogModalOpenRef = useRef(false);
|
|
135
|
+
catalogModalOpenRef.current = catalogModalOpen;
|
|
136
|
+
const multiMatchModalOpenRef = useRef(false);
|
|
137
|
+
multiMatchModalOpenRef.current = multiMatchModalOpen;
|
|
138
|
+
// ─── Lokální filtrování návrhů ────────────────────────────────────────────────
|
|
139
|
+
const suggestions = useMemo(() => {
|
|
140
|
+
const vf = moduleDefinition?.valueField || 'id';
|
|
141
|
+
const source = !inputText || !inputText.trim()
|
|
142
|
+
? allItems
|
|
143
|
+
: allItems.filter((item) => {
|
|
144
|
+
const label = moduleDefinition?.getDisplayValue?.(item) ||
|
|
145
|
+
item.code ||
|
|
146
|
+
item.name ||
|
|
147
|
+
'';
|
|
148
|
+
return label.toLowerCase().includes(inputText.toLowerCase());
|
|
149
|
+
});
|
|
150
|
+
return source.slice(0, 15).map((item) => ({
|
|
151
|
+
value: String(item[vf]),
|
|
152
|
+
label: moduleDefinition?.getDisplayValue?.(item) ||
|
|
153
|
+
item.code ||
|
|
154
|
+
item.name ||
|
|
155
|
+
'',
|
|
156
|
+
item,
|
|
157
|
+
}));
|
|
158
|
+
}, [allItems, inputText, moduleDefinition]);
|
|
159
|
+
suggestionsRef.current = suggestions;
|
|
160
|
+
// Catalog-only: options pro Select
|
|
161
|
+
const catalogOptions = useMemo(() => {
|
|
162
|
+
const vf = moduleDefinition?.valueField || 'id';
|
|
163
|
+
return allItems.map((item) => ({
|
|
164
|
+
value: item[vf],
|
|
165
|
+
label: moduleDefinition?.getDisplayValue?.(item) ||
|
|
166
|
+
item.code ||
|
|
167
|
+
item.name ||
|
|
168
|
+
String(item[vf]),
|
|
169
|
+
}));
|
|
170
|
+
}, [allItems, moduleDefinition]);
|
|
171
|
+
// ─── Výběr z katalogu (shared) ────────────────────────────────────────────────
|
|
172
|
+
const commitCatalogItem = useCallback((item) => {
|
|
173
|
+
if (!item)
|
|
174
|
+
return;
|
|
175
|
+
const vf = moduleDefinition?.valueField || 'id';
|
|
176
|
+
const id = item[vf];
|
|
177
|
+
setSelectedItem(item);
|
|
178
|
+
if (typeof onChange === 'function') {
|
|
179
|
+
onChange(id, item);
|
|
60
180
|
}
|
|
61
|
-
}, [
|
|
62
|
-
|
|
63
|
-
|
|
181
|
+
}, [moduleDefinition, onChange]);
|
|
182
|
+
const handleCreateSuccess = useCallback((newItem) => {
|
|
183
|
+
if (newItem) {
|
|
184
|
+
_cache[moduleName] = [...(_cache[moduleName] || []), newItem];
|
|
185
|
+
setAllItems((prev) => [...prev, newItem]);
|
|
186
|
+
const displayText = moduleDefinition?.getDisplayValue?.(newItem) || newItem.name || '';
|
|
187
|
+
setInputText(displayText);
|
|
188
|
+
setIsLinked(true);
|
|
189
|
+
commitCatalogItem(newItem);
|
|
190
|
+
}
|
|
191
|
+
setCreateNewModalOpen(false);
|
|
192
|
+
}, [moduleName, moduleDefinition, commitCatalogItem]);
|
|
193
|
+
// ─── Catalog-only: výběr ze Select ───────────────────────────────────────────
|
|
194
|
+
const handleCatalogSelectChange = useCallback((selectedId) => {
|
|
64
195
|
if (!selectedId) {
|
|
65
|
-
|
|
66
|
-
if (typeof onChange === 'function') {
|
|
196
|
+
if (typeof onChange === 'function')
|
|
67
197
|
onChange(null, null);
|
|
68
|
-
|
|
198
|
+
return;
|
|
69
199
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
200
|
+
const vf = moduleDefinition?.valueField || 'id';
|
|
201
|
+
const item = allItems.find((i) => String(i[vf]) === String(selectedId));
|
|
202
|
+
if (item)
|
|
203
|
+
commitCatalogItem(item);
|
|
204
|
+
}, [allItems, moduleDefinition, commitCatalogItem, onChange]);
|
|
205
|
+
// ─── Výběr z catalog modalu ───────────────────────────────────────────────────
|
|
206
|
+
const handleCatalogModalSelect = useCallback((params) => {
|
|
207
|
+
if (!params.data)
|
|
208
|
+
return;
|
|
209
|
+
const item = params.data;
|
|
210
|
+
if (allowFreeText) {
|
|
211
|
+
const displayText = moduleDefinition?.getDisplayValue?.(item) ||
|
|
212
|
+
item.code ||
|
|
213
|
+
item.name ||
|
|
214
|
+
'';
|
|
215
|
+
setInputText(displayText);
|
|
216
|
+
}
|
|
217
|
+
isLinkedRef.current = true; // synchronní update před zavřením modalu (onCancel race condition)
|
|
218
|
+
setIsLinked(true);
|
|
219
|
+
commitCatalogItem(item);
|
|
220
|
+
setCatalogModalOpen(false);
|
|
221
|
+
}, [allowFreeText, moduleDefinition, commitCatalogItem]);
|
|
222
|
+
// ─── Free-text: výběr návrhu z AutoComplete ───────────────────────────────────
|
|
223
|
+
const handleAutoCompleteSelect = useCallback((optionValue, option) => {
|
|
224
|
+
justSelectedRef.current = true;
|
|
225
|
+
const item = option.item;
|
|
226
|
+
const displayText = moduleDefinition?.getDisplayValue?.(item) ||
|
|
227
|
+
item.code ||
|
|
228
|
+
item.name ||
|
|
229
|
+
'';
|
|
230
|
+
setInputText(displayText);
|
|
231
|
+
setDropdownOpen(false);
|
|
232
|
+
setIsLinked(true);
|
|
233
|
+
commitCatalogItem(item);
|
|
234
|
+
// Ztratit focus z inputu po výběru
|
|
235
|
+
freeTextWrapperRef.current?.querySelector('input')?.blur();
|
|
236
|
+
}, [moduleDefinition, commitCatalogItem]);
|
|
237
|
+
handleAutoCompleteSelectRef.current = handleAutoCompleteSelect;
|
|
238
|
+
// ─── Free-text: validace po Enter / blur ──────────────────────────────────────
|
|
239
|
+
const handleFreeTextCommit = useCallback((text) => {
|
|
240
|
+
if (!text || !text.trim()) {
|
|
241
|
+
if (typeof onChange === 'function')
|
|
242
|
+
onChange(null, null);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
const searchLower = text.trim().toLowerCase();
|
|
246
|
+
const vf = moduleDefinition?.valueField || 'id';
|
|
247
|
+
const localMatches = allItems.filter((item) => {
|
|
248
|
+
const label = moduleDefinition?.getDisplayValue?.(item) ||
|
|
249
|
+
item.code ||
|
|
250
|
+
item.name ||
|
|
251
|
+
'';
|
|
252
|
+
return label.toLowerCase() === searchLower;
|
|
253
|
+
});
|
|
254
|
+
if (localMatches.length === 0) {
|
|
255
|
+
if (createNewOnMismatch && moduleDefinition?.createFormComponent) {
|
|
256
|
+
// Create-new mód — otevřít formulář pro vytvoření nového záznamu
|
|
257
|
+
setCreateNewInitialName(text.trim());
|
|
258
|
+
setCreateNewModalOpen(true);
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
// Volný text — žádná shoda v katalogu
|
|
262
|
+
setIsLinked(false);
|
|
263
|
+
setSelectedItem(null);
|
|
264
|
+
setDetailOpen(false);
|
|
265
|
+
if (typeof onFreeTextCommit === 'function') {
|
|
266
|
+
onFreeTextCommit(text.trim());
|
|
79
267
|
}
|
|
268
|
+
if (typeof onChange === 'function')
|
|
269
|
+
onChange(null, null);
|
|
270
|
+
freeTextWrapperRef.current?.querySelector('input')?.blur();
|
|
80
271
|
}
|
|
81
272
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
name: moduleDefinition?.getDisplayValue?.(item) || item.name || item.id,
|
|
93
|
-
fullItem: item,
|
|
94
|
-
}));
|
|
95
|
-
// Výběr správného seznamu podle displayMode
|
|
96
|
-
const selectOptions = displayMode === 'full' ? fullListOptions : recentItemsOptions;
|
|
97
|
-
// Pokud nemáme definici modulu, zobrazíme prázdné pole
|
|
98
|
-
if (!moduleDefinition)
|
|
99
|
-
return _jsx("div", {});
|
|
100
|
-
// Funkce pro výběr položky a zavření modálu
|
|
101
|
-
const handleModalItemSelect = (event) => {
|
|
102
|
-
selectItem(event.data);
|
|
103
|
-
closeModal();
|
|
104
|
-
};
|
|
105
|
-
// Získání aktuální zobrazované hodnoty pro Select
|
|
106
|
-
const displayValueText = getDisplayValue();
|
|
107
|
-
// Funkce pro vykreslení obsahu Option při detailním načítání
|
|
108
|
-
const renderDetailOption = () => {
|
|
109
|
-
if (isDetailLoading) {
|
|
110
|
-
return 'Načítání...';
|
|
273
|
+
else if (localMatches.length === 1) {
|
|
274
|
+
// Přesná jedna shoda → auto-výběr
|
|
275
|
+
const item = localMatches[0];
|
|
276
|
+
const displayText = moduleDefinition?.getDisplayValue?.(item) ||
|
|
277
|
+
item.code ||
|
|
278
|
+
item.name ||
|
|
279
|
+
text.trim();
|
|
280
|
+
setInputText(displayText);
|
|
281
|
+
setIsLinked(true);
|
|
282
|
+
commitCatalogItem(item);
|
|
111
283
|
}
|
|
112
|
-
|
|
113
|
-
|
|
284
|
+
else {
|
|
285
|
+
// Více shod → multi-match modal
|
|
286
|
+
setMultiMatchData(localMatches);
|
|
287
|
+
setMultiMatchModalOpen(true);
|
|
288
|
+
}
|
|
289
|
+
}, [allItems, moduleDefinition, onChange, onFreeTextCommit, commitCatalogItem]);
|
|
290
|
+
// ─── Free-text: blur ──────────────────────────────────────────────────────────
|
|
291
|
+
const handleBlur = useCallback(() => {
|
|
292
|
+
if (justSelectedRef.current) {
|
|
293
|
+
justSelectedRef.current = false;
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
// Pokud už byla hodnota commitnuta (katalog / autocomplete), neoverwritovat
|
|
297
|
+
if (isLinkedRef.current === true)
|
|
298
|
+
return;
|
|
299
|
+
// Refy místo state — vždy aktuální hodnota, bez stale closure problémů
|
|
300
|
+
if (inputTextRef.current && !multiMatchModalOpenRef.current && !catalogModalOpenRef.current) {
|
|
301
|
+
handleFreeTextCommit(inputTextRef.current);
|
|
114
302
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
303
|
+
}, [handleFreeTextCommit]);
|
|
304
|
+
// ─── Free-text: výběr z multi-match modalu ────────────────────────────────────
|
|
305
|
+
const handleMultiMatchSelect = useCallback((params) => {
|
|
306
|
+
if (!params.data)
|
|
307
|
+
return;
|
|
308
|
+
const item = params.data;
|
|
309
|
+
const displayText = moduleDefinition?.getDisplayValue?.(item) ||
|
|
310
|
+
item.code ||
|
|
311
|
+
item.name ||
|
|
312
|
+
'';
|
|
313
|
+
setInputText(displayText);
|
|
314
|
+
isLinkedRef.current = true; // synchronní update před zavřením modalu (onCancel race condition)
|
|
315
|
+
setIsLinked(true);
|
|
316
|
+
setMultiMatchModalOpen(false);
|
|
317
|
+
commitCatalogItem(item);
|
|
318
|
+
}, [moduleDefinition, commitCatalogItem]);
|
|
319
|
+
// ─── Native capture Enter handler (rc-select stopPropagation obrana) ─────────
|
|
320
|
+
useEffect(() => {
|
|
321
|
+
if (!allowFreeText)
|
|
322
|
+
return;
|
|
323
|
+
const el = freeTextWrapperRef.current;
|
|
324
|
+
if (!el)
|
|
325
|
+
return;
|
|
326
|
+
const input = el.querySelector('input');
|
|
327
|
+
if (!input)
|
|
328
|
+
return;
|
|
329
|
+
const handler = (e) => {
|
|
330
|
+
if (e.key !== 'Enter')
|
|
331
|
+
return;
|
|
332
|
+
// Pokud je v dropdown označená položka šipkou, ručně ji vybereme
|
|
333
|
+
const activeOptionEl = document.querySelector('.ant-select-item-option-active');
|
|
334
|
+
if (activeOptionEl) {
|
|
335
|
+
e.stopImmediatePropagation();
|
|
336
|
+
e.preventDefault();
|
|
337
|
+
const labelText = activeOptionEl.querySelector('.ant-select-item-option-content')?.textContent?.trim();
|
|
338
|
+
if (labelText) {
|
|
339
|
+
const matched = suggestionsRef.current.find((s) => s.label === labelText);
|
|
340
|
+
if (matched) {
|
|
341
|
+
handleAutoCompleteSelectRef.current(matched.value, matched);
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
// Žádná aktivní položka → volný text
|
|
348
|
+
const text = inputTextRef.current || '';
|
|
349
|
+
if (!text.trim())
|
|
350
|
+
return;
|
|
351
|
+
e.stopImmediatePropagation();
|
|
352
|
+
e.preventDefault();
|
|
353
|
+
handleFreeTextCommit(text);
|
|
354
|
+
};
|
|
355
|
+
input.addEventListener('keydown', handler, true);
|
|
356
|
+
return () => input.removeEventListener('keydown', handler, true);
|
|
357
|
+
}, [allowFreeText, handleFreeTextCommit]);
|
|
358
|
+
// ─── Shared: create-new modal ────────────────────────────────────────────────
|
|
359
|
+
const createNewModalContent = moduleDefinition?.createFormComponent ? (_jsx(DraggableModal, { open: createNewModalOpen, title: moduleDefinition.createModalTitle || 'Nový záznam', width: moduleDefinition.createModalWidth || 600, onCancel: () => setCreateNewModalOpen(false), maskClosable: false, getContainer: getContainer, zIndex: 99999999, footer: null, children: React.createElement(moduleDefinition.createFormComponent, {
|
|
360
|
+
initialName: createNewInitialName,
|
|
361
|
+
onSuccess: handleCreateSuccess,
|
|
362
|
+
onCancel: () => setCreateNewModalOpen(false),
|
|
363
|
+
fetchDataUIAsync,
|
|
364
|
+
accessToken,
|
|
365
|
+
createRecord: moduleDefinition.createRecord,
|
|
366
|
+
}) })) : null;
|
|
367
|
+
const addButton = showAddButton && moduleDefinition?.createFormComponent ? (_jsx(Button, { icon: _jsx(PlusOutlined, {}), onMouseDown: (e) => e.preventDefault(), onClick: () => {
|
|
368
|
+
setCreateNewInitialName('');
|
|
369
|
+
setCreateNewModalOpen(true);
|
|
370
|
+
}, disabled: disabled })) : null;
|
|
371
|
+
const searchButton = (onClickFn) => (_jsx(Button, { icon: _jsx(SearchOutlined, {}), onMouseDown: (e) => e.preventDefault(), onClick: onClickFn, disabled: disabled }));
|
|
372
|
+
// ─── Shared: catalog modal ────────────────────────────────────────────────────
|
|
373
|
+
const catalogModalContent = (_jsx(DraggableModal, { open: catalogModalOpen, title: moduleDefinition?.modalTitle ||
|
|
374
|
+
`Vybrat z modulu: ${moduleDefinition?.moduleName}`, width: moduleDefinition?.modalWidth || 800, onCancel: () => {
|
|
375
|
+
setCatalogModalOpen(false);
|
|
376
|
+
setCatalogFilter('');
|
|
377
|
+
// Pokud uživatel zavřel modal bez výběru a text není katalogově propojený, commitni ho
|
|
378
|
+
if (allowFreeText && inputTextRef.current && isLinkedRef.current !== true) {
|
|
379
|
+
setTimeout(() => handleFreeTextCommit(inputTextRef.current), 0);
|
|
380
|
+
}
|
|
381
|
+
}, maskClosable: false, getContainer: getContainer, zIndex: 99999999, footer: null, children: allItemsLoading ? (_jsx("div", { style: { padding: 24, textAlign: 'center' }, children: "Na\u010D\u00EDt\u00E1n\u00ED..." })) : (_jsxs(_Fragment, { children: [_jsx(Input, { prefix: _jsx(SearchOutlined, { style: { color: '#bfbfbf' } }), placeholder: "Hledat...", value: catalogFilter, onChange: (e) => setCatalogFilter(e.target.value), allowClear: true, style: { marginBottom: 8 } }), _jsx("div", { className: "ag-theme-alpine", style: { height: 400, width: '100%' }, children: _jsx(AgGridReact, { columnDefs: moduleDefinition?.columnDefs || [], rowData: allItems, quickFilterText: catalogFilter, onRowDoubleClicked: (params) => {
|
|
382
|
+
handleCatalogModalSelect(params);
|
|
383
|
+
setCatalogFilter('');
|
|
384
|
+
} }) })] })) }));
|
|
385
|
+
// ─── Render: catalog-only mód ─────────────────────────────────────────────────
|
|
386
|
+
if (!allowFreeText) {
|
|
387
|
+
const vf = moduleDefinition?.valueField || 'id';
|
|
388
|
+
const currentId = value && typeof value === 'object' ? value[vf] : value;
|
|
389
|
+
return (_jsxs("div", { style: { display: 'flex', gap: 4, ...style }, children: [_jsx(Select, { style: { flex: 1 }, value: currentId, options: catalogOptions, loading: allItemsLoading, showSearch: true, filterOption: (input, option) => String(option?.label || '')
|
|
390
|
+
.toLowerCase()
|
|
391
|
+
.includes(input.toLowerCase()), onChange: handleCatalogSelectChange, getPopupContainer: () => document.body, dropdownStyle: { zIndex: 9999 }, placeholder: placeholder, disabled: disabled }), addButton, searchButton(() => {
|
|
392
|
+
catalogModalOpenRef.current = true;
|
|
393
|
+
setCatalogModalOpen(true);
|
|
394
|
+
}), catalogModalContent, createNewModalContent] }));
|
|
395
|
+
}
|
|
396
|
+
// ─── Render: free-text mód ────────────────────────────────────────────────────
|
|
397
|
+
return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 4 }, children: [_jsxs("div", { style: { display: 'flex', gap: 4, alignItems: 'center', ...style }, children: [_jsxs("div", { ref: freeTextWrapperRef, style: { flex: 1, position: 'relative' }, children: [moduleDefinition?.detailPanel && showArrow && (_jsx(DownOutlined, { onMouseDown: (e) => e.preventDefault(), onClick: () => selectedItem && setDetailOpen((v) => !v), style: {
|
|
398
|
+
position: 'absolute',
|
|
399
|
+
left: -18,
|
|
400
|
+
top: '50%',
|
|
401
|
+
transform: `translateY(-50%) rotate(${detailOpen ? 180 : 0}deg)`,
|
|
402
|
+
transition: 'transform 0.2s',
|
|
403
|
+
fontSize: 11,
|
|
404
|
+
cursor: selectedItem ? 'pointer' : 'default',
|
|
405
|
+
color: selectedItem ? 'rgba(0,0,0,0.45)' : '#d9d9d9',
|
|
406
|
+
zIndex: 1,
|
|
407
|
+
pointerEvents: selectedItem ? 'auto' : 'none',
|
|
408
|
+
} })), _jsx(AutoComplete, { style: { width: '100%' }, value: inputText, options: suggestions, open: dropdownOpen && suggestions.length > 0, defaultActiveFirstOption: false, allowClear: true, onChange: (text) => {
|
|
409
|
+
setInputText(text || '');
|
|
410
|
+
setIsLinked(null);
|
|
411
|
+
setSelectedItem(null);
|
|
412
|
+
setDetailOpen(false);
|
|
413
|
+
}, onSelect: handleAutoCompleteSelect, onFocus: () => setDropdownOpen(true), onBlur: () => {
|
|
414
|
+
setTimeout(() => setDropdownOpen(false), 150);
|
|
415
|
+
handleBlur();
|
|
416
|
+
}, notFoundContent: allItemsLoading ? 'Načítání...' : null, placeholder: placeholder, disabled: disabled, getPopupContainer: () => document.body, dropdownStyle: { zIndex: 9999 } })] }), !createNewOnMismatch && isLinked === true && (_jsx(LinkOutlined, { style: { color: '#52c41a', alignSelf: 'center', fontSize: 14, flexShrink: 0 } })), !createNewOnMismatch && isLinked === false && inputText && (_jsx(EditOutlined, { style: { color: '#faad14', alignSelf: 'center', fontSize: 14, flexShrink: 0 } })), addButton, searchButton(() => {
|
|
417
|
+
catalogModalOpenRef.current = true;
|
|
418
|
+
setCatalogModalOpen(true);
|
|
419
|
+
})] }), moduleDefinition?.detailPanel && detailOpen && selectedItem && (_jsx(DetailPanel, { definition: moduleDefinition.detailPanel, item: selectedItem })), catalogModalContent, createNewModalContent, _jsx(DraggableModal, { open: multiMatchModalOpen, title: "Nalezeno v\u00EDce shod", width: moduleDefinition?.modalWidth || 800, onCancel: () => setMultiMatchModalOpen(false), maskClosable: false, getContainer: getContainer, zIndex: 99999999, footer: null, children: _jsx("div", { className: "ag-theme-alpine", style: { height: 400, width: '100%' }, children: _jsx(AgGridReact, { columnDefs: moduleDefinition?.columnDefs || [], rowData: multiMatchData, onRowDoubleClicked: handleMultiMatchSelect }) }) })] }));
|
|
138
420
|
};
|
|
139
421
|
export default ModuleDropdownList;
|
|
140
422
|
//# sourceMappingURL=index.js.map
|