@bit.rhplus/ui2.module-dropdown-list 0.1.111 → 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,171 +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';
5
- import Select from 'antd/es/select';
3
+ import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
6
4
  import AutoComplete from 'antd/es/auto-complete';
5
+ import Select from 'antd/es/select';
7
6
  import Button from 'antd/es/button';
8
- import { SearchOutlined } from '@ant-design/icons';
9
- import AgGrid from '@bit.rhplus/ag-grid';
10
- 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';
11
10
  import DraggableModal from '@bit.rhplus/draggable-modal';
12
- import { recentItemsAtomFamily } from './store/recentItemsStore';
13
- import { useModuleDropdownList } from './useModuleDropdownList';
14
- import './ModuleInput.css';
15
- const { Option } = Select;
16
- const ModuleDropdownList = (props) => {
17
- 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
18
- // Free-text mód
19
- allowFreeText = false, onFreeTextCommit, // (text: string) => void — voláno při Enter nebo blur bez výběru
20
- onSearchChange, // (text: string) => void — sleduje aktuální text v inputu
21
- initialSearchValue = '', // Počáteční text pro předvyplnění search inputu
22
- } = props;
23
- // Interní stavy pro Select
24
- const containerRef = useRef(null);
25
- // Free-text mód — text který uživatel napsal do search inputu
26
- const [freeTextInput, setFreeTextInput] = React.useState(initialSearchValue || '');
27
- // Zabrání dvojímu spuštění onFreeTextCommit při výběru z AutoComplete dropdown
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);
55
+ return;
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);
28
82
  const justSelectedRef = useRef(false);
29
- const processedStyle = typeof style === 'string' ? { width: style } : { ...style };
30
- // Priorita nastavení šířky: 1. prop width, 2. style.width, 3. defaultní 100%
31
- if (width) {
32
- processedStyle.width = width;
33
- }
34
- else if (!processedStyle.width) {
35
- processedStyle.width = '100%';
36
- }
37
- // Použití Jotai atom pro poslední vybrané položky daného typu modulu
38
- const [recentItems, setRecentItems] = useAtom(recentItemsAtomFamily(moduleDefinition?.moduleName || ''));
39
- // Přidání položky do seznamu nedávných položek - obaleno v useCallback
40
- const addToRecentItems = useCallback((item) => {
41
- if (!item || !moduleDefinition)
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.
95
+ useEffect(() => {
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)
42
101
  return;
43
- // Vytvoření nového seznamu bez aktuální položky (pokud existuje)
44
- const filteredItems = recentItems.filter((existingItem) => existingItem[moduleDefinition.valueField || 'id'] !==
45
- item[moduleDefinition.valueField || 'id']);
46
- // Přidání nové položky na začátek a omezení počtu položek
47
- const updatedItems = [item, ...filteredItems].slice(0, maxRecentItems);
48
- setRecentItems(updatedItems);
49
- }, [recentItems, moduleDefinition, maxRecentItems, setRecentItems]);
50
- // Použití custom hooku pro veškerou logiku ModuleInput
51
- const { modalOpen, searchQuery, dropdownSearchQuery, rowData, fullListData, isLoading, isFullListLoading, selectedItem, isDetailLoading, moduleDetailQuery, openModal, closeModal, handleSearchChange, handleDropdownSearchChange, handleItemSelect, clearSelection, getDisplayValue, } = useModuleDropdownList({ moduleDefinition, value, displayMode });
52
- const selectItem = (item) => {
53
- const selectedItemData = handleItemSelect(item);
54
- // Uložení vybrané položky do nedávných položek
55
- if (selectedItemData) {
56
- addToRecentItems(selectedItemData);
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
+ }
57
116
  }
58
- // Přímé volání onChange s novou hodnotou a celým objektem
59
- if (typeof onChange === 'function' && moduleDefinition) {
60
- const id = selectedItemData[moduleDefinition.valueField || 'id'];
61
- onChange(id, selectedItemData);
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
+ });
62
124
  }
63
- };
64
- // Přidání vybrané položky do nedávných položek při načtení
65
- useEffect(() => {
66
- if (selectedItem && moduleDefinition) {
67
- addToRecentItems(selectedItem);
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);
180
+ }
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);
68
190
  }
69
- }, [selectedItem, moduleDefinition, addToRecentItems]);
70
- // Výběr položky přímo ze Select dropdown
71
- const handleSelectChange = (selectedId) => {
191
+ setCreateNewModalOpen(false);
192
+ }, [moduleName, moduleDefinition, commitCatalogItem]);
193
+ // ─── Catalog-only: výběr ze Select ───────────────────────────────────────────
194
+ const handleCatalogSelectChange = useCallback((selectedId) => {
72
195
  if (!selectedId) {
73
- clearSelection();
74
- if (typeof onChange === 'function') {
196
+ if (typeof onChange === 'function')
75
197
  onChange(null, null);
76
- }
198
+ return;
77
199
  }
78
- else {
79
- // Pokud je v nedávných položkách nebo fullListData, nastavte ji jako vybranou
80
- const allOptions = displayMode === 'full' ? fullListData : recentItems;
81
- const selectedItem = allOptions.find((item) => item[moduleDefinition?.valueField || 'id'] === selectedId);
82
- if (selectedItem) {
83
- handleItemSelect(selectedItem);
84
- // Přímé volání onChange s ID a celým objektem
85
- if (typeof onChange === 'function') {
86
- 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());
87
267
  }
268
+ if (typeof onChange === 'function')
269
+ onChange(null, null);
270
+ freeTextWrapperRef.current?.querySelector('input')?.blur();
88
271
  }
89
272
  }
90
- };
91
- // Formátování nedávných položek pro zobrazení v Select
92
- const recentItemsOptions = recentItems.map((item) => ({
93
- id: item[moduleDefinition.valueField || 'id'],
94
- name: moduleDefinition?.getDisplayValue?.(item) || item.name || item.id,
95
- fullItem: item, // Uchování celé položky pro snadné získání později
96
- }));
97
- // Formátování kompletního seznamu pro zobrazení v Select (full mode)
98
- const fullListOptions = fullListData.map((item) => ({
99
- id: item[moduleDefinition.valueField || 'id'],
100
- name: moduleDefinition?.getDisplayValue?.(item) || item.name || item.id,
101
- fullItem: item,
102
- }));
103
- // Výběr správného seznamu podle displayMode
104
- const selectOptions = displayMode === 'full' ? fullListOptions : recentItemsOptions;
105
- // AutoComplete options pro free-text mód
106
- const autoCompleteOptions = selectOptions.map((option) => ({
107
- value: String(option.id),
108
- label: String(option.name || ''),
109
- }));
110
- // Pokud nemáme definici modulu, zobrazíme prázdné pole
111
- if (!moduleDefinition)
112
- return _jsx("div", {});
113
- // Funkce pro výběr položky a zavření modálu
114
- const handleModalItemSelect = (event) => {
115
- selectItem(event.data);
116
- closeModal();
117
- };
118
- // Získání aktuální zobrazované hodnoty pro Select
119
- const displayValueText = getDisplayValue();
120
- // Funkce pro vykreslení obsahu Option při detailním načítání
121
- const renderDetailOption = () => {
122
- if (isDetailLoading) {
123
- 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);
283
+ }
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;
124
295
  }
125
- if (moduleDetailQuery?.data) {
126
- 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 || '-' })] })] }));
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);
127
302
  }
128
- return `${value} (nenačteno)`;
129
- };
130
- return (_jsxs("div", { className: "module-input-container", style: processedStyle, ref: containerRef, children: [_jsxs("div", { className: "simple-select-container", children: [allowFreeText ? (_jsx(AutoComplete, { value: freeTextInput, options: autoCompleteOptions, style: { width: '100%', fontSize: '13px' }, placeholder: placeholder, disabled: disabled || readOnly, getPopupContainer: () => document.body, dropdownStyle: { zIndex: 9999 }, filterOption: (input, option) => String(option?.label || '').toLowerCase().includes(input.toLowerCase()), onChange: (text) => {
131
- setFreeTextInput(text);
132
- onSearchChange?.(text);
133
- }, onSelect: (optionValue) => {
134
- justSelectedRef.current = true;
135
- const opt = selectOptions.find((o) => String(o.id) === optionValue);
136
- if (opt) {
137
- addToRecentItems(opt.fullItem);
138
- if (typeof onChange === 'function') {
139
- onChange(opt.id, opt.fullItem);
140
- }
141
- }
142
- }, onKeyDown: (e) => {
143
- if (e.key === 'Enter') {
144
- e.preventDefault();
145
- e.stopPropagation();
146
- if (freeTextInput)
147
- onFreeTextCommit?.(freeTextInput);
148
- }
149
- }, onBlur: () => {
150
- if (justSelectedRef.current) {
151
- justSelectedRef.current = false;
152
- return;
153
- }
154
- if (freeTextInput && !modalOpen) {
155
- onFreeTextCommit?.(freeTextInput);
156
- }
157
- } })) : (_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, filterOption: false,
158
- // Vlastní tučná šipka s rotací
159
- 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" }) }), 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) => (_jsx(Option, { value: option.id, label: option.name, children: option.name }, option.id))), value && !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 ||
160
- `Vybrat z modulu: ${moduleDefinition.moduleName}`, footer: () => {
161
- return [
162
- _jsx(Button, { onClick: closeModal, style: { marginRight: '10px', width: 'auto' }, children: "Zav\u0159\u00EDt" }, "cancel"),
163
- ];
164
- }, 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: {
165
- sortable: true,
166
- filter: true,
167
- resizable: true,
168
- }, 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 }) }) })] }));
169
420
  };
170
421
  export default ModuleDropdownList;
171
422
  //# sourceMappingURL=index.js.map