@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 CHANGED
@@ -1,2 +1,15 @@
1
1
  export default ModuleDropdownList;
2
- declare function ModuleDropdownList(props: any): import("react/jsx-runtime").JSX.Element;
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, jsxs as _jsxs } from "react/jsx-runtime";
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 Input from 'antd/es/input';
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 { SearchOutlined } from '@ant-design/icons';
8
- import AgGrid from '@bit.rhplus/ag-grid';
9
- import { useAtom } from 'jotai';
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 { recentItemsAtomFamily } from './store/recentItemsStore';
12
- import { useModuleDropdownList } from './useModuleDropdownList';
13
- import './ModuleInput.css';
14
- const { Option } = Select;
15
- const ModuleDropdownList = (props) => {
16
- const { value, onChange, placeholder = 'Vyberte', disabled = false, readOnly = false, style = {}, width, maxRecentItems = 5, moduleDefinition, displayMode = 'recent', getContainer = () => document.body, // Vlastnost pro nastavení kontejneru modálu
17
- } = props;
18
- // zmena
19
- // Interní stavy pro Select
20
- const containerRef = useRef(null);
21
- const processedStyle = typeof style === 'string' ? { width: style } : { ...style };
22
- // Priorita nastavení šířky: 1. prop width, 2. style.width, 3. defaultní 100%
23
- if (width) {
24
- processedStyle.width = width;
25
- }
26
- else if (!processedStyle.width) {
27
- processedStyle.width = '100%';
28
- }
29
- // Použití Jotai atom pro poslední vybrané položky daného typu modulu
30
- const [recentItems, setRecentItems] = useAtom(recentItemsAtomFamily(moduleDefinition?.moduleName || ''));
31
- // Přidání položky do seznamu nedávných položek - obaleno v useCallback
32
- const addToRecentItems = useCallback((item) => {
33
- if (!item || !moduleDefinition)
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
- // Přidání vybrané položky do nedávných položek při načtení
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
- if (selectedItem && moduleDefinition) {
59
- addToRecentItems(selectedItem);
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
- }, [selectedItem, moduleDefinition, addToRecentItems]);
62
- // Výběr položky přímo ze Select dropdown
63
- const handleSelectChange = (selectedId) => {
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
- clearSelection();
66
- if (typeof onChange === 'function') {
196
+ if (typeof onChange === 'function')
67
197
  onChange(null, null);
68
- }
198
+ return;
69
199
  }
70
- else {
71
- // Pokud je v nedávných položkách nebo fullListData, nastavte ji jako vybranou
72
- const allOptions = displayMode === 'full' ? fullListData : recentItems;
73
- const selectedItem = allOptions.find((item) => item[moduleDefinition?.valueField || 'id'] === selectedId);
74
- if (selectedItem) {
75
- handleItemSelect(selectedItem);
76
- // Přímé volání onChange s ID a celým objektem
77
- if (typeof onChange === 'function') {
78
- onChange(selectedId, selectedItem);
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
- // Formátování nedávných položek pro zobrazení v Select
84
- const recentItemsOptions = recentItems.map((item) => ({
85
- id: item[moduleDefinition.valueField || 'id'],
86
- name: moduleDefinition?.getDisplayValue?.(item) || item.name || item.id,
87
- fullItem: item, // Uchování celé položky pro snadné získání později
88
- }));
89
- // Formátování kompletního seznamu pro zobrazení v Select (full mode)
90
- const fullListOptions = fullListData.map((item) => ({
91
- id: item[moduleDefinition.valueField || 'id'],
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
- if (moduleDetailQuery?.data) {
113
- return (_jsxs("div", { className: "option-item", children: [_jsx("div", { className: "option-avatar", children: displayValueText.charAt(0) }), _jsxs("div", { className: "option-content", children: [_jsx("div", { className: "option-name", children: displayValueText }), _jsx("div", { className: "option-email", children: moduleDetailQuery.data.email || '-' })] })] }));
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
- return `${value} (nenačteno)`;
116
- };
117
- return (_jsxs("div", { className: "module-input-container", style: processedStyle, ref: containerRef, children: [_jsxs("div", { className: "simple-select-container", children: [_jsxs(Select, { value: value, onChange: handleSelectChange, className: "simple-select", placeholder: placeholder, disabled: disabled || readOnly, dropdownMatchSelectWidth: false, getPopupContainer: () => document.body, dropdownStyle: { zIndex: 9999 }, loading: displayMode === 'full' && isFullListLoading, style: { width: '100%', fontSize: '13px' }, maxTagTextLength: 20,
118
- // Vlastní tučná šipka s rotací
119
- suffixIcon: _jsx("svg", { className: "select-arrow-icon", width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: _jsx("path", { d: "M2 4L6 8L10 4", stroke: "#bfbfbf", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round" }) }),
120
- // Zde definujeme, jak bude vypadat vybraná hodnota v selectu (pouze název)
121
- optionLabelProp: "label", listItemHeight: 32, listHeight: 256, dropdownRender: (menu) => (_jsxs("div", { className: "select-dropdown-container", children: [displayMode === 'full' && (_jsx("div", { className: "search-box", children: _jsx(Input, { placeholder: "Search", value: dropdownSearchQuery, onChange: (e) => handleDropdownSearchChange(e.target.value), prefix: _jsx(SearchOutlined, {}), className: "dropdown-search-input" }) })), menu, _jsxs("div", { className: "new-customer-item", children: [_jsx("div", { className: "plus-icon", children: "+" }), _jsx("span", { children: "New" })] })] })), children: [selectOptions.map((option) => {
122
- const nameStr = String(option.name || '');
123
- const firstChar = nameStr.charAt(0) || '?';
124
- return (_jsx(Option, { value: option.id,
125
- // Zde přidáváme label pro zobrazení jen názvu při vybrání položky
126
- label: option.name, children: option.name }, option.id));
127
- }), value &&
128
- !selectOptions.some((option) => option.id === value) && (_jsx(Option, { value: value, label: displayValueText, children: renderDetailOption() }, value))] }), _jsx(Button, { icon: _jsx(SearchOutlined, {}), className: "search-button", onClick: openModal, type: "primary" })] }), _jsxs(DraggableModal, { width: moduleDefinition.modalWidth || 800, open: modalOpen, getContainer: getContainer, onCancel: closeModal, maskClosable: false, zIndex: 99999999, title: moduleDefinition.modalTitle ||
129
- `Vybrat z modulu: ${moduleDefinition.moduleName}`, footer: () => {
130
- return [
131
- _jsx(Button, { onClick: closeModal, style: { marginRight: '10px', width: 'auto' }, children: "Zav\u0159\u00EDt" }, "cancel"),
132
- ];
133
- }, children: [_jsx(Input, { placeholder: "Filtrovat...", value: searchQuery, onChange: (e) => handleSearchChange(e.target.value), prefix: _jsx(SearchOutlined, {}), allowClear: true }), isLoading ? (_jsx("div", { className: "text-align-center padding", children: "Na\u010D\u00EDt\u00E1n\u00ED..." })) : (_jsx("div", { style: { height: 400, width: '100%' }, children: _jsx(AgGrid, { theme: "themeAlpine", rowData: rowData, columnDefs: moduleDefinition.columnDefs, defaultColDef: {
134
- sortable: true,
135
- filter: true,
136
- resizable: true,
137
- }, onCellDoubleClicked: handleModalItemSelect, rowSelection: "single", enableCellChangeFlash: false }) }))] })] }));
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