@bit.rhplus/ag-grid 0.0.122 → 0.0.124
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/Aggregations.js +1 -1
- package/BulkEdit/useBulkCellEdit.js +7 -4
- package/Editors/ModuleLookupEditor.jsx +123 -34
- package/Editors/RouteCellEditor.jsx +124 -0
- package/Editors/index.jsx +1 -0
- package/OnCellValueChanged.js +2 -2
- package/Renderers/RouteCellRenderer.jsx +76 -0
- package/Renderers/SelectCellRenderer.jsx +33 -7
- package/Renderers/index.jsx +1 -0
- package/dist/Aggregations.js +1 -1
- package/dist/Aggregations.js.map +1 -1
- package/dist/BulkEdit/useBulkCellEdit.js +7 -5
- package/dist/BulkEdit/useBulkCellEdit.js.map +1 -1
- package/dist/Editors/ModuleLookupEditor.js +103 -32
- package/dist/Editors/ModuleLookupEditor.js.map +1 -1
- package/dist/Editors/RouteCellEditor.d.ts +21 -0
- package/dist/Editors/RouteCellEditor.js +79 -0
- package/dist/Editors/RouteCellEditor.js.map +1 -0
- package/dist/Editors/index.js +1 -0
- package/dist/Editors/index.js.map +1 -1
- package/dist/OnCellValueChanged.js +2 -1
- package/dist/OnCellValueChanged.js.map +1 -1
- package/dist/Renderers/RouteCellRenderer.d.ts +9 -0
- package/dist/Renderers/RouteCellRenderer.js +48 -0
- package/dist/Renderers/RouteCellRenderer.js.map +1 -0
- package/dist/Renderers/SelectCellRenderer.d.ts +1 -5
- package/dist/Renderers/SelectCellRenderer.js +23 -5
- package/dist/Renderers/SelectCellRenderer.js.map +1 -1
- package/dist/Renderers/index.js +1 -0
- package/dist/Renderers/index.js.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/index.jsx +8 -0
- package/package.json +6 -5
- /package/dist/{preview-1775220690959.js → preview-1775722983848.js} +0 -0
package/Aggregations.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* eslint-disable */
|
|
2
2
|
// ✅ OPTIMALIZACE: Enumerable odstraněn - nahrazen native JS metodami
|
|
3
3
|
import moment from 'moment';
|
|
4
|
-
import { currencySymbols } from '@bit.rhplus/ui.grid
|
|
4
|
+
import { currencySymbols } from '@bit.rhplus/ui.grid';
|
|
5
5
|
|
|
6
6
|
const getColumnOrderDirection = (range) => (range.columns[0].colId === range.startColumn.colId) ? 1 : -1;
|
|
7
7
|
|
|
@@ -17,6 +17,7 @@ export const useBulkCellEdit = (gridRef, options = {}) => {
|
|
|
17
17
|
const {
|
|
18
18
|
enabled = false,
|
|
19
19
|
minCells = 2,
|
|
20
|
+
showOnSingleCell = false, // Pokud true, ikona se zobrazí již při výběru 1 buňky (přepíše minCells na 1)
|
|
20
21
|
allowMultiColumn = false,
|
|
21
22
|
buttonPosition = 'bottom-right',
|
|
22
23
|
buttonOffset = { x: 5, y: 5 },
|
|
@@ -25,6 +26,8 @@ export const useBulkCellEdit = (gridRef, options = {}) => {
|
|
|
25
26
|
onBulkEditComplete,
|
|
26
27
|
} = options;
|
|
27
28
|
|
|
29
|
+
const effectiveMinCells = showOnSingleCell ? 1 : minCells;
|
|
30
|
+
|
|
28
31
|
const { fetchDataUIAsync } = useData();
|
|
29
32
|
|
|
30
33
|
const [floatingButton, setFloatingButton] = useState({
|
|
@@ -114,7 +117,7 @@ export const useBulkCellEdit = (gridRef, options = {}) => {
|
|
|
114
117
|
if (allowMultiColumn || range.columns.length === 1) {
|
|
115
118
|
const cellCount = Math.abs(range.endRow.rowIndex - range.startRow.rowIndex) + 1;
|
|
116
119
|
|
|
117
|
-
if (cellCount >=
|
|
120
|
+
if (cellCount >= effectiveMinCells) {
|
|
118
121
|
// Kontrola zda má sloupec bulkEditApi (rychlá kontrola)
|
|
119
122
|
const column = range.columns[0];
|
|
120
123
|
const colDef = column?.getColDef();
|
|
@@ -241,7 +244,7 @@ export const useBulkCellEdit = (gridRef, options = {}) => {
|
|
|
241
244
|
}
|
|
242
245
|
}, 50);
|
|
243
246
|
},
|
|
244
|
-
[enabled, allowMultiColumn,
|
|
247
|
+
[enabled, allowMultiColumn, effectiveMinCells, calculateButtonPosition]
|
|
245
248
|
);
|
|
246
249
|
|
|
247
250
|
/**
|
|
@@ -277,7 +280,7 @@ export const useBulkCellEdit = (gridRef, options = {}) => {
|
|
|
277
280
|
Math.abs(range.endRow.rowIndex - range.startRow.rowIndex) + 1;
|
|
278
281
|
|
|
279
282
|
// Kontrola min počtu buněk
|
|
280
|
-
if (cellCount <
|
|
283
|
+
if (cellCount < effectiveMinCells) {
|
|
281
284
|
pendingFloatingButtonRef.current = null;
|
|
282
285
|
setFloatingButton({ visible: false });
|
|
283
286
|
return;
|
|
@@ -294,7 +297,7 @@ export const useBulkCellEdit = (gridRef, options = {}) => {
|
|
|
294
297
|
// Jediný setFloatingButton call → eliminuje rerender #2
|
|
295
298
|
setFloatingButton(pendingFloatingButtonRef.current);
|
|
296
299
|
},
|
|
297
|
-
[
|
|
300
|
+
[effectiveMinCells, allowMultiColumn, buttonPosition, buttonOffset, calculateButtonPosition, gridRef]
|
|
298
301
|
);
|
|
299
302
|
|
|
300
303
|
/**
|
|
@@ -61,6 +61,8 @@ const ModuleLookupEditor = React.forwardRef(({
|
|
|
61
61
|
stopEditing,
|
|
62
62
|
moduleDefinition,
|
|
63
63
|
allowFreeText = false,
|
|
64
|
+
rowHeight = 42,
|
|
65
|
+
autoOpen = true,
|
|
64
66
|
}, _ref) => {
|
|
65
67
|
|
|
66
68
|
// ─── Hooks (vždy voláno) ──────────────────────────────────────────────────────
|
|
@@ -95,6 +97,8 @@ const ModuleLookupEditor = React.forwardRef(({
|
|
|
95
97
|
|
|
96
98
|
// ─── Catalog-only stav ───────────────────────────────────────────────────────
|
|
97
99
|
const [catalogSelected, setCatalogSelected] = useState(allowFreeText ? null : value);
|
|
100
|
+
// Ref pro synchronní přístup v isCancelAfterEnd (state je stale v closure při stopEditing)
|
|
101
|
+
const catalogSelectedRef = useRef(allowFreeText ? null : value);
|
|
98
102
|
|
|
99
103
|
// ─── Free-text stav ──────────────────────────────────────────────────────────
|
|
100
104
|
const initialText = React.useMemo(() => {
|
|
@@ -106,6 +110,12 @@ const ModuleLookupEditor = React.forwardRef(({
|
|
|
106
110
|
|
|
107
111
|
const [inputText, setInputText] = useState(initialText);
|
|
108
112
|
const [dropdownOpen, setDropdownOpen] = useState(false);
|
|
113
|
+
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
if (autoOpen) {
|
|
116
|
+
setDropdownOpen(true);
|
|
117
|
+
}
|
|
118
|
+
}, [autoOpen]);
|
|
109
119
|
const [multiMatchData, setMultiMatchData] = useState([]);
|
|
110
120
|
const [multiMatchModalOpen, setMultiMatchModalOpen] = useState(false);
|
|
111
121
|
const catalogModalOpenRef = useRef(false);
|
|
@@ -114,7 +124,7 @@ const ModuleLookupEditor = React.forwardRef(({
|
|
|
114
124
|
multiMatchModalOpenRef.current = multiMatchModalOpen;
|
|
115
125
|
const currentTextRef = useRef(initialText);
|
|
116
126
|
const inputTextRef = useRef(initialText); // vždy aktuální inputText pro native handler
|
|
117
|
-
const
|
|
127
|
+
const editorWrapperRef = useRef(null); // ref na wrapper div → querySelector('input')
|
|
118
128
|
const isStoppedRef = useRef(false);
|
|
119
129
|
const justSelectedRef = useRef(false);
|
|
120
130
|
// Nastaven synchronně před každým stopEditing() voláním.
|
|
@@ -130,6 +140,7 @@ const ModuleLookupEditor = React.forwardRef(({
|
|
|
130
140
|
// Ref na suggestions a handleAutoCompleteSelect — přístupné v capture handleru bez stale closure
|
|
131
141
|
const suggestionsRef = useRef([]);
|
|
132
142
|
const handleAutoCompleteSelectRef = useRef(null);
|
|
143
|
+
const handleCatalogSelectChangeRef = useRef(null);
|
|
133
144
|
|
|
134
145
|
// ─── Lokální filtrování návrhů (bez API volání) ───────────────────────────────
|
|
135
146
|
const suggestions = React.useMemo(() => {
|
|
@@ -140,27 +151,64 @@ const ModuleLookupEditor = React.forwardRef(({
|
|
|
140
151
|
const label = moduleDefinition?.getDisplayValue?.(item) || item.code || item.name || '';
|
|
141
152
|
return label.toLowerCase().includes(inputText.toLowerCase());
|
|
142
153
|
});
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
154
|
+
const result = source.map(item => {
|
|
155
|
+
const displayText = moduleDefinition?.getDisplayValue?.(item) || item.code || item.name || String(item[vf] || '');
|
|
156
|
+
const originalLabel = moduleDefinition?.renderOption ? moduleDefinition.renderOption(item, displayText) : displayText;
|
|
157
|
+
return {
|
|
158
|
+
value: String(item[vf]),
|
|
159
|
+
label: (
|
|
160
|
+
<div data-mle-value={String(item[vf])} style={{ display: 'contents' }}>
|
|
161
|
+
{originalLabel}
|
|
162
|
+
</div>
|
|
163
|
+
),
|
|
164
|
+
displayText,
|
|
165
|
+
item,
|
|
166
|
+
};
|
|
167
|
+
});
|
|
168
|
+
return allowFreeText ? result.slice(0, 50) : result;
|
|
169
|
+
}, [allItems, inputText, moduleDefinition, allowFreeText]);
|
|
149
170
|
suggestionsRef.current = suggestions;
|
|
150
171
|
|
|
151
|
-
// ─── AG Grid
|
|
152
|
-
//
|
|
153
|
-
//
|
|
172
|
+
// ─── AG Grid useGridCellEditor ───────────────────────────────────────────────
|
|
173
|
+
// AG Grid 35 vyžaduje getValue() pro získání výsledné hodnoty při stopEditing().
|
|
174
|
+
// Bez getValue() může grid ignorovat změnu, i když byl volán onValueChange.
|
|
175
|
+
const initialValueRef = useRef(value);
|
|
176
|
+
|
|
154
177
|
useGridCellEditor({
|
|
178
|
+
getValue: () => {
|
|
179
|
+
// Pokud máme pendingValue (zrovna vybráno), použijeme ji
|
|
180
|
+
if (pendingValueRef.current !== null) return pendingValueRef.current;
|
|
181
|
+
|
|
182
|
+
// Catalog-only mód: catalogSelectedRef je vždy aktuální
|
|
183
|
+
if (!allowFreeText) return catalogSelectedRef.current;
|
|
184
|
+
|
|
185
|
+
// Fallback na aktuální stav textu pro free-text
|
|
186
|
+
return { id: null, text: currentTextRef.current || '' };
|
|
187
|
+
},
|
|
155
188
|
isCancelAfterEnd: () => {
|
|
189
|
+
// Pokud existuje pendingValue, uživatel explicitně něco vybral/potvrdil -> NErušit.
|
|
190
|
+
if (pendingValueRef.current !== null) {
|
|
191
|
+
console.log('[MLE] isCancelAfterEnd: false (has pending value)');
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
|
|
156
195
|
if (!allowFreeText) {
|
|
157
|
-
|
|
196
|
+
// Porovnání pomocí ID proti INITIAL hodnotě (prop value se může v čase měnit)
|
|
197
|
+
const vf = moduleDefinition?.valueField || 'id';
|
|
198
|
+
const isSame = catalogSelectedRef.current?.[vf] === initialValueRef.current?.[vf];
|
|
199
|
+
console.log('[MLE] isCancelAfterEnd (catalog):', isSame, {
|
|
200
|
+
new: catalogSelectedRef.current?.[vf],
|
|
201
|
+
old: initialValueRef.current?.[vf]
|
|
202
|
+
});
|
|
203
|
+
return isSame;
|
|
158
204
|
}
|
|
205
|
+
|
|
206
|
+
// Free-text porovnání proti INITIAL hodnotě
|
|
159
207
|
const currentText = currentTextRef.current ?? '';
|
|
160
|
-
const originalText = typeof
|
|
161
|
-
const
|
|
162
|
-
console.log('[MLE] isCancelAfterEnd
|
|
163
|
-
return
|
|
208
|
+
const originalText = typeof initialValueRef.current === 'object' ? (initialValueRef.current?.text || '') : (initialValueRef.current || '');
|
|
209
|
+
const isSame = currentText === originalText;
|
|
210
|
+
console.log('[MLE] isCancelAfterEnd (free-text):', isSame);
|
|
211
|
+
return isSame;
|
|
164
212
|
},
|
|
165
213
|
});
|
|
166
214
|
|
|
@@ -179,10 +227,13 @@ const ModuleLookupEditor = React.forwardRef(({
|
|
|
179
227
|
const vf = moduleDefinition?.valueField || 'id';
|
|
180
228
|
const item = allItems.find(i => String(i[vf]) === String(selectedId));
|
|
181
229
|
if (!item) return;
|
|
230
|
+
catalogSelectedRef.current = item;
|
|
231
|
+
pendingValueRef.current = item; // Synchronní příznak pro isCancelAfterEnd
|
|
182
232
|
setCatalogSelected(item);
|
|
183
233
|
if (onValueChange) onValueChange(item);
|
|
184
234
|
setTimeout(() => stopEditing(), 0);
|
|
185
235
|
}, [allItems, moduleDefinition, onValueChange, stopEditing]);
|
|
236
|
+
handleCatalogSelectChangeRef.current = handleCatalogSelectChange;
|
|
186
237
|
|
|
187
238
|
const catalogCurrentId = React.useMemo(() => {
|
|
188
239
|
if (allowFreeText || !value) return null;
|
|
@@ -204,6 +255,8 @@ const ModuleLookupEditor = React.forwardRef(({
|
|
|
204
255
|
pendingValueRef.current = selected;
|
|
205
256
|
if (onValueChange) onValueChange(selected);
|
|
206
257
|
} else {
|
|
258
|
+
catalogSelectedRef.current = item;
|
|
259
|
+
pendingValueRef.current = item; // Synchronní příznak pro isCancelAfterEnd
|
|
207
260
|
setCatalogSelected(item);
|
|
208
261
|
if (onValueChange) onValueChange(item);
|
|
209
262
|
}
|
|
@@ -324,8 +377,7 @@ const ModuleLookupEditor = React.forwardRef(({
|
|
|
324
377
|
// rc-select volá stopPropagation() na Enter v bubble fázi → náš React onKeyDown
|
|
325
378
|
// na wrapperu ho nikdy nedostane. Capture-phase native listener se spustí jako první.
|
|
326
379
|
useEffect(() => {
|
|
327
|
-
|
|
328
|
-
const el = freeTextWrapperRef.current;
|
|
380
|
+
const el = editorWrapperRef.current;
|
|
329
381
|
if (!el) return;
|
|
330
382
|
const input = el.querySelector('input');
|
|
331
383
|
if (!input) return;
|
|
@@ -349,17 +401,41 @@ const ModuleLookupEditor = React.forwardRef(({
|
|
|
349
401
|
if (activeOptionEl) {
|
|
350
402
|
e.stopImmediatePropagation();
|
|
351
403
|
e.preventDefault();
|
|
352
|
-
const
|
|
353
|
-
if (
|
|
354
|
-
const
|
|
355
|
-
if (
|
|
356
|
-
|
|
357
|
-
|
|
404
|
+
const contentEl = activeOptionEl.querySelector('.ant-select-item-option-content');
|
|
405
|
+
if (contentEl) {
|
|
406
|
+
const mleValue = contentEl.querySelector('[data-mle-value]')?.getAttribute('data-mle-value');
|
|
407
|
+
if (mleValue) {
|
|
408
|
+
const matched = suggestionsRef.current.find((s) => s.value === mleValue);
|
|
409
|
+
if (matched) {
|
|
410
|
+
if (allowFreeText) {
|
|
411
|
+
handleAutoCompleteSelectRef.current?.(matched.value, matched);
|
|
412
|
+
} else {
|
|
413
|
+
handleCatalogSelectChangeRef.current?.(matched.value);
|
|
414
|
+
}
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const spanText = contentEl.querySelector('span')?.textContent?.trim();
|
|
420
|
+
const fullText = contentEl.textContent?.trim();
|
|
421
|
+
const labelText = spanText || fullText;
|
|
422
|
+
if (labelText) {
|
|
423
|
+
const matched = suggestionsRef.current.find((s) => s.displayText === labelText || String(s.label) === labelText);
|
|
424
|
+
if (matched) {
|
|
425
|
+
if (allowFreeText) {
|
|
426
|
+
handleAutoCompleteSelectRef.current?.(matched.value, matched);
|
|
427
|
+
} else {
|
|
428
|
+
handleCatalogSelectChangeRef.current?.(matched.value);
|
|
429
|
+
}
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
358
432
|
}
|
|
359
433
|
}
|
|
360
434
|
return;
|
|
361
435
|
}
|
|
362
436
|
|
|
437
|
+
if (!allowFreeText) return;
|
|
438
|
+
|
|
363
439
|
const text = inputTextRef.current || '';
|
|
364
440
|
// Prázdný input bez předchozí hodnoty → nechat AG Grid zpracovat (cancel)
|
|
365
441
|
if (!text.trim() && pendingValueRef.current === null && !initialText) return;
|
|
@@ -408,30 +484,42 @@ const ModuleLookupEditor = React.forwardRef(({
|
|
|
408
484
|
// ─── Render: catalog-only mód ─────────────────────────────────────────────────
|
|
409
485
|
if (!allowFreeText) {
|
|
410
486
|
const vf = moduleDefinition?.valueField || 'id';
|
|
411
|
-
const selectOptions = allItems.map(item =>
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
487
|
+
const selectOptions = allItems.map(item => {
|
|
488
|
+
const displayText = moduleDefinition?.getDisplayValue?.(item) || item.code || item.name || String(item[vf] || '');
|
|
489
|
+
const originalLabel = moduleDefinition?.renderOption ? moduleDefinition.renderOption(item, displayText) : displayText;
|
|
490
|
+
return {
|
|
491
|
+
value: item[vf],
|
|
492
|
+
label: (
|
|
493
|
+
<div data-mle-value={String(item[vf])} style={{ display: 'contents' }}>
|
|
494
|
+
{originalLabel}
|
|
495
|
+
</div>
|
|
496
|
+
),
|
|
497
|
+
displayText,
|
|
498
|
+
};
|
|
499
|
+
});
|
|
415
500
|
|
|
416
501
|
return (
|
|
417
|
-
<div style={{ width: '100%', minWidth: 250,
|
|
418
|
-
<div style={{ display: 'flex',
|
|
502
|
+
<div style={{ width: '100%', minWidth: 250, height: rowHeight, display: 'flex', alignItems: 'stretch' }} ref={editorWrapperRef}>
|
|
503
|
+
<div style={{ display: 'flex', flex: 1, height: '100%' }}>
|
|
419
504
|
<Select
|
|
420
|
-
style={{ flex: 1 }}
|
|
505
|
+
style={{ flex: 1, height: '100%' }}
|
|
421
506
|
value={catalogCurrentId}
|
|
422
507
|
options={selectOptions}
|
|
508
|
+
optionLabelProp="displayText"
|
|
423
509
|
loading={allItemsLoading}
|
|
424
510
|
showSearch
|
|
425
511
|
filterOption={(input, option) =>
|
|
426
|
-
String(option?.label || '').toLowerCase().includes(input.toLowerCase())
|
|
512
|
+
String(option?.displayText || option?.label || '').toLowerCase().includes(input.toLowerCase())
|
|
427
513
|
}
|
|
428
514
|
onChange={handleCatalogSelectChange}
|
|
515
|
+
onSearch={handleInputChange}
|
|
429
516
|
getPopupContainer={() => document.body}
|
|
430
517
|
dropdownStyle={{ zIndex: 9999 }}
|
|
431
518
|
placeholder={moduleDefinition?.placeholder || 'Vyberte...'}
|
|
519
|
+
defaultOpen={autoOpen}
|
|
432
520
|
autoFocus
|
|
433
521
|
/>
|
|
434
|
-
<Button icon={<SearchOutlined />} onMouseDown={e => e.preventDefault()} onClick={() => setCatalogModalOpen(true)} type="primary" />
|
|
522
|
+
<Button icon={<SearchOutlined />} onMouseDown={e => e.preventDefault()} onClick={() => setCatalogModalOpen(true)} type="primary" style={{ height: '100%', borderRadius: 0 }} />
|
|
435
523
|
</div>
|
|
436
524
|
{catalogModalContent}
|
|
437
525
|
</div>
|
|
@@ -440,8 +528,8 @@ const ModuleLookupEditor = React.forwardRef(({
|
|
|
440
528
|
|
|
441
529
|
// ─── Render: free-text mód ────────────────────────────────────────────────────
|
|
442
530
|
return (
|
|
443
|
-
<div style={{ width: '100%', minWidth: 250,
|
|
444
|
-
<div ref={
|
|
531
|
+
<div style={{ width: '100%', minWidth: 250, height: rowHeight, display: 'flex', alignItems: 'stretch' }}>
|
|
532
|
+
<div ref={editorWrapperRef} style={{ display: 'flex', flex: 1, height: '100%' }}>
|
|
445
533
|
<AutoComplete
|
|
446
534
|
style={{ flex: 1 }}
|
|
447
535
|
value={inputText}
|
|
@@ -469,6 +557,7 @@ const ModuleLookupEditor = React.forwardRef(({
|
|
|
469
557
|
setCatalogModalOpen(true);
|
|
470
558
|
}}
|
|
471
559
|
type="primary"
|
|
560
|
+
style={{ height: '100%', borderRadius: 0 }}
|
|
472
561
|
/>
|
|
473
562
|
</div>
|
|
474
563
|
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
import React, { useState, useRef, useCallback, useEffect } from 'react';
|
|
3
|
+
import { useGridCellEditor } from 'ag-grid-react';
|
|
4
|
+
import DraggableModal from '@bit.rhplus/draggable-modal';
|
|
5
|
+
import { useSharedGridItems } from '@bit.rhplus/shared-grid-form';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Vnitřní komponenta editoru — přístup k Jotai SharedGridForm kontextu.
|
|
9
|
+
*/
|
|
10
|
+
const RouteEditorInner = ({
|
|
11
|
+
instanceId,
|
|
12
|
+
initialData,
|
|
13
|
+
gridComponent: GridComponent,
|
|
14
|
+
onCancel,
|
|
15
|
+
onSave,
|
|
16
|
+
}) => {
|
|
17
|
+
const { effectiveItems, setItems } = useSharedGridItems(instanceId);
|
|
18
|
+
const distanceKmRef = useRef(null);
|
|
19
|
+
|
|
20
|
+
// Inicializace dat při mountu
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (Array.isArray(initialData)) {
|
|
23
|
+
setItems(initialData);
|
|
24
|
+
}
|
|
25
|
+
}, []); // Pouze při mountu
|
|
26
|
+
|
|
27
|
+
const handleRouteCalculated = useCallback((info) => {
|
|
28
|
+
if (info?.distance != null && info.distance > 0) {
|
|
29
|
+
distanceKmRef.current = Math.round(info.distance / 1000);
|
|
30
|
+
}
|
|
31
|
+
}, []);
|
|
32
|
+
|
|
33
|
+
const handleOk = useCallback(() => {
|
|
34
|
+
onSave(effectiveItems, distanceKmRef.current);
|
|
35
|
+
}, [effectiveItems, onSave]);
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<DraggableModal
|
|
39
|
+
title="Editace trasy"
|
|
40
|
+
open={true}
|
|
41
|
+
onOk={handleOk}
|
|
42
|
+
onCancel={onCancel}
|
|
43
|
+
width={1100}
|
|
44
|
+
height="80vh"
|
|
45
|
+
resizable={true}
|
|
46
|
+
getContainer={() => document.body}
|
|
47
|
+
destroyOnClose
|
|
48
|
+
styles={{ body: { height: '100%', overflow: 'hidden', padding: 0 } }}
|
|
49
|
+
centered
|
|
50
|
+
maskClosable={false}
|
|
51
|
+
>
|
|
52
|
+
<div style={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
|
53
|
+
<GridComponent instanceId={instanceId} showAddButton={true} onRouteCalculated={handleRouteCalculated} />
|
|
54
|
+
</div>
|
|
55
|
+
</DraggableModal>
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* RouteCellEditor — AG Grid 35 Cell Editor pro správu tras.
|
|
61
|
+
*
|
|
62
|
+
* Používá useGridCellEditor hook (povinné v AG Grid 31+).
|
|
63
|
+
* Props jsou předány přímo z AG Grid (value, onValueChange, stopEditing + cellEditorParams).
|
|
64
|
+
*/
|
|
65
|
+
/**
|
|
66
|
+
* RouteCellEditor — AG Grid 35 Cell Editor pro správu tras.
|
|
67
|
+
*
|
|
68
|
+
* Používá useGridCellEditor hook (povinné v AG Grid 31+).
|
|
69
|
+
* Props jsou předány přímo z AG Grid (value, onValueChange, stopEditing + cellEditorParams).
|
|
70
|
+
*
|
|
71
|
+
* @param {Function} transformValue - volitelná funkce z cellEditorParams pro transformaci
|
|
72
|
+
* effectiveItems před uložením (např. pro převod temp ID na null a type string na transportTypeId).
|
|
73
|
+
* Pokud není zadána, data se uloží bez transformace.
|
|
74
|
+
*/
|
|
75
|
+
const RouteCellEditor = ({ value, onValueChange, stopEditing, data, gridComponent: GridComponent, transformValue }) => {
|
|
76
|
+
const [visible, setVisible] = useState(true);
|
|
77
|
+
|
|
78
|
+
// Ref pro synchronní přístup z getValue() — AG Grid 35 volá getValue() při stopEditing()
|
|
79
|
+
const valueRef = useRef(value);
|
|
80
|
+
|
|
81
|
+
// AG Grid 35: useGridCellEditor je povinné pro správné getValue() při stopEditing()
|
|
82
|
+
useGridCellEditor({
|
|
83
|
+
getValue: () => valueRef.current,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Stabilní instanceId pro celý editační proces
|
|
87
|
+
const [instanceId] = useState(() => `route-editor-${data?.id || Date.now()}`);
|
|
88
|
+
|
|
89
|
+
const handleSave = useCallback((items, distanceKm) => {
|
|
90
|
+
// Transformace dat před uložením — připraví formát kompatibilní s backendem
|
|
91
|
+
const prepared = transformValue ? transformValue(items) : items;
|
|
92
|
+
const payload = distanceKm != null ? { routes: prepared, destination: distanceKm } : prepared;
|
|
93
|
+
valueRef.current = payload;
|
|
94
|
+
if (onValueChange) onValueChange(payload);
|
|
95
|
+
setVisible(false);
|
|
96
|
+
stopEditing();
|
|
97
|
+
}, [transformValue, onValueChange, stopEditing]);
|
|
98
|
+
|
|
99
|
+
const handleCancel = useCallback(() => {
|
|
100
|
+
setVisible(false);
|
|
101
|
+
setTimeout(() => stopEditing(true), 0); // true = cancel, necommitovat
|
|
102
|
+
}, [stopEditing]);
|
|
103
|
+
|
|
104
|
+
if (!visible) return null;
|
|
105
|
+
|
|
106
|
+
if (!GridComponent) {
|
|
107
|
+
console.error('RouteCellEditor: gridComponent missing in cellEditorParams');
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<RouteEditorInner
|
|
113
|
+
instanceId={instanceId}
|
|
114
|
+
initialData={value}
|
|
115
|
+
gridComponent={GridComponent}
|
|
116
|
+
onSave={handleSave}
|
|
117
|
+
onCancel={handleCancel}
|
|
118
|
+
/>
|
|
119
|
+
);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
RouteCellEditor.displayName = 'RouteCellEditor';
|
|
123
|
+
|
|
124
|
+
export default RouteCellEditor;
|
package/Editors/index.jsx
CHANGED
package/OnCellValueChanged.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
/* eslint-disable no-param-reassign */
|
|
1
|
+
/* eslint-disable */
|
|
3
2
|
export const RhPlusOnCellValueChanged = (event, options) =>
|
|
4
3
|
{
|
|
4
|
+
console.log("onCellValueChanged");
|
|
5
5
|
event.data._rh_plus_ag_grid_new_item = false;
|
|
6
6
|
event.data._rh_plus_ag_grid_row_changed = true;
|
|
7
7
|
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { MapPin, Flag, ChevronRight } from 'lucide-react';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* RouteCellRenderer — zobrazuje trasu se šipkami a ikonami pro nakládku/vykládku.
|
|
7
|
+
* Bucovice => slavkov u Brna => brno => praha
|
|
8
|
+
*/
|
|
9
|
+
const RouteCellRenderer = (params) => {
|
|
10
|
+
const routes = params.value || params.data?.routes || [];
|
|
11
|
+
if (!Array.isArray(routes) || routes.length === 0) {
|
|
12
|
+
return <span style={{ color: '#bfbfbf', fontStyle: 'italic' }}>Nezadána</span>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<div style={{
|
|
18
|
+
display: 'flex',
|
|
19
|
+
alignItems: 'center',
|
|
20
|
+
gap: '4px',
|
|
21
|
+
overflow: 'hidden',
|
|
22
|
+
height: '100%',
|
|
23
|
+
padding: '0 4px'
|
|
24
|
+
}}>
|
|
25
|
+
{routes.map((route, index) => {
|
|
26
|
+
const isLast = index === routes.length - 1;
|
|
27
|
+
const isFirst = index === 0;
|
|
28
|
+
|
|
29
|
+
// Typ: 1 = loading (nakládka), 2 = unloading (vykládka)
|
|
30
|
+
const transportTypeId = route.transportTypeId || (route.type === 'loading' ? 1 : 2);
|
|
31
|
+
|
|
32
|
+
const isLoading = transportTypeId === 1;
|
|
33
|
+
const color = isLoading ? '#1890ff' : '#52c41a';
|
|
34
|
+
const Icon = isLoading ? MapPin : Flag;
|
|
35
|
+
|
|
36
|
+
const name = route.project?.name || route.name || route.address || '???';
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<React.Fragment key={route.id || index}>
|
|
40
|
+
<div style={{
|
|
41
|
+
display: 'flex',
|
|
42
|
+
alignItems: 'center',
|
|
43
|
+
gap: '2px',
|
|
44
|
+
flexShrink: 0,
|
|
45
|
+
maxWidth: '120px'
|
|
46
|
+
}}>
|
|
47
|
+
<Icon
|
|
48
|
+
size={14}
|
|
49
|
+
color={color}
|
|
50
|
+
fill={isFirst || isLast ? `${color}33` : 'none'}
|
|
51
|
+
style={{ flexShrink: 0 }}
|
|
52
|
+
/>
|
|
53
|
+
<span style={{
|
|
54
|
+
fontSize: '12px',
|
|
55
|
+
fontWeight: isFirst || isLast ? '500' : '400',
|
|
56
|
+
overflow: 'hidden',
|
|
57
|
+
textOverflow: 'ellipsis',
|
|
58
|
+
whiteSpace: 'nowrap',
|
|
59
|
+
color: '#262626'
|
|
60
|
+
}}>
|
|
61
|
+
{name}
|
|
62
|
+
</span>
|
|
63
|
+
</div>
|
|
64
|
+
{!isLast && (
|
|
65
|
+
<ChevronRight size={12} color="#bfbfbf" style={{ flexShrink: 0 }} />
|
|
66
|
+
)}
|
|
67
|
+
</React.Fragment>
|
|
68
|
+
);
|
|
69
|
+
})}
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
RouteCellRenderer.displayName = 'RouteCellRenderer';
|
|
75
|
+
|
|
76
|
+
export default RouteCellRenderer;
|
|
@@ -2,16 +2,37 @@
|
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import * as LucideIcons from 'lucide-react';
|
|
4
4
|
|
|
5
|
-
export default (
|
|
5
|
+
export default (params) => {
|
|
6
|
+
const { value, data, displayField = 'name', imageField = null, icon, iconColor } = params;
|
|
6
7
|
if (value == null) return null;
|
|
8
|
+
|
|
7
9
|
const displayText = typeof value === 'object'
|
|
8
10
|
? (value[displayField] ?? value.name ?? '')
|
|
9
11
|
: String(value);
|
|
10
|
-
|
|
12
|
+
|
|
11
13
|
const imageUrl = imageField && typeof value === 'object' ? value[imageField] : null;
|
|
12
14
|
|
|
13
|
-
//
|
|
14
|
-
const
|
|
15
|
+
// Lucide ikona z hodnoty (hodnota má pole icon s názvem Lucide komponenty)
|
|
16
|
+
const lucideIconName = value?.icon;
|
|
17
|
+
const LucideIcon = !imageUrl && !icon && lucideIconName && LucideIcons[lucideIconName]
|
|
18
|
+
? LucideIcons[lucideIconName]
|
|
19
|
+
: null;
|
|
20
|
+
|
|
21
|
+
// Custom ikona předaná přes cellRendererParams.icon — může být element, funkce nebo komponenta
|
|
22
|
+
let iconElement = null;
|
|
23
|
+
if (!imageUrl && icon) {
|
|
24
|
+
const resolvedColor = typeof iconColor === 'function' ? iconColor({ data, value }) : iconColor;
|
|
25
|
+
const colorStyle = resolvedColor ? { color: resolvedColor } : {};
|
|
26
|
+
|
|
27
|
+
if (React.isValidElement(icon)) {
|
|
28
|
+
iconElement = React.cloneElement(icon, { style: { ...colorStyle, ...icon.props.style } });
|
|
29
|
+
} else if (typeof icon === 'function') {
|
|
30
|
+
const rendered = icon({ data, value });
|
|
31
|
+
iconElement = resolvedColor && React.isValidElement(rendered)
|
|
32
|
+
? React.cloneElement(rendered, { style: { ...colorStyle, ...rendered.props?.style } })
|
|
33
|
+
: rendered;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
15
36
|
|
|
16
37
|
return (
|
|
17
38
|
<div style={{
|
|
@@ -29,14 +50,19 @@ export default ({value, displayField = 'name', imageField = null}) => {
|
|
|
29
50
|
onError={(e) => { e.currentTarget.style.display = 'none'; }}
|
|
30
51
|
/>
|
|
31
52
|
)}
|
|
32
|
-
{
|
|
53
|
+
{iconElement && (
|
|
54
|
+
<span style={{ flexShrink: 0, display: 'flex', alignItems: 'center' }}>
|
|
55
|
+
{iconElement}
|
|
56
|
+
</span>
|
|
57
|
+
)}
|
|
58
|
+
{!iconElement && LucideIcon && (
|
|
33
59
|
<span style={{ flexShrink: 0, display: 'flex', alignItems: 'center' }}>
|
|
34
|
-
<
|
|
60
|
+
<LucideIcon size={16} />
|
|
35
61
|
</span>
|
|
36
62
|
)}
|
|
37
63
|
<span style={{ overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
|
38
64
|
{displayText}
|
|
39
65
|
</span>
|
|
40
66
|
</div>
|
|
41
|
-
)
|
|
67
|
+
);
|
|
42
68
|
}
|
package/Renderers/index.jsx
CHANGED
|
@@ -2,3 +2,4 @@ export {default as SelectCellRenderer} from './SelectCellRenderer';
|
|
|
2
2
|
export {default as ModuleLookupRenderer} from './ModuleLookupRenderer';
|
|
3
3
|
export {default as AddressLookupRenderer} from './AddressLookupRenderer';
|
|
4
4
|
export {default as NumberedListRenderer} from './NumberedListRenderer';
|
|
5
|
+
export {default as RouteCellRenderer} from './RouteCellRenderer';
|
package/dist/Aggregations.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* eslint-disable */
|
|
2
2
|
// ✅ OPTIMALIZACE: Enumerable odstraněn - nahrazen native JS metodami
|
|
3
3
|
import moment from 'moment';
|
|
4
|
-
import { currencySymbols } from '@bit.rhplus/ui.grid
|
|
4
|
+
import { currencySymbols } from '@bit.rhplus/ui.grid';
|
|
5
5
|
const getColumnOrderDirection = (range) => (range.columns[0].colId === range.startColumn.colId) ? 1 : -1;
|
|
6
6
|
const getColumnOrderIndex = (range, index) => {
|
|
7
7
|
if (getColumnOrderDirection(range) === 1) {
|