@topconsultnpm/sdkui-react 6.21.0-dev3.9 → 6.21.0-dev4.10

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.
Files changed (60) hide show
  1. package/lib/components/NewComponents/ContextMenu/TMContextMenu.js +28 -2
  2. package/lib/components/NewComponents/FloatingMenuBar/styles.d.ts +6 -6
  3. package/lib/components/base/TMAreaManager.js +11 -0
  4. package/lib/components/base/TMDataGrid.js +12 -2
  5. package/lib/components/base/TMDataGridExportForm.js +19 -8
  6. package/lib/components/base/TMModal.js +2 -2
  7. package/lib/components/base/TMPopUp.d.ts +1 -0
  8. package/lib/components/base/TMPopUp.js +59 -2
  9. package/lib/components/base/TMSpinner.d.ts +4 -2
  10. package/lib/components/base/TMSpinner.js +33 -6
  11. package/lib/components/choosers/TMDistinctValues.js +1 -1
  12. package/lib/components/choosers/TMGroupChooser.js +1 -1
  13. package/lib/components/editors/TMEditorStyled.d.ts +4 -4
  14. package/lib/components/editors/TMFormulaEditor.d.ts +1 -0
  15. package/lib/components/editors/TMFormulaEditor.js +98 -49
  16. package/lib/components/editors/TMMetadataValues.js +23 -6
  17. package/lib/components/editors/TMTextBox.d.ts +1 -0
  18. package/lib/components/editors/TMTextBox.js +2 -1
  19. package/lib/components/features/documents/TMDcmtForm.d.ts +2 -0
  20. package/lib/components/features/documents/TMDcmtForm.js +2 -1
  21. package/lib/components/features/documents/TMMasterDetailDcmts.d.ts +2 -0
  22. package/lib/components/features/documents/TMMasterDetailDcmts.js +7 -4
  23. package/lib/components/features/documents/TMMergeToPdfForm.d.ts +2 -2
  24. package/lib/components/features/documents/TMMergeToPdfForm.js +91 -48
  25. package/lib/components/features/documents/TMRelationViewer.js +58 -5
  26. package/lib/components/features/documents/mergePdfUtils.d.ts +52 -0
  27. package/lib/components/features/documents/mergePdfUtils.js +268 -0
  28. package/lib/components/features/search/TMMetadataOutputForm.d.ts +17 -0
  29. package/lib/components/features/search/TMMetadataOutputForm.js +225 -0
  30. package/lib/components/features/search/TMMetadataSorterForm.d.ts +17 -0
  31. package/lib/components/features/search/TMMetadataSorterForm.js +243 -0
  32. package/lib/components/features/search/TMSearch.d.ts +2 -0
  33. package/lib/components/features/search/TMSearch.js +2 -2
  34. package/lib/components/features/search/TMSearchQueryPanel.js +249 -58
  35. package/lib/components/features/search/TMSearchResult.d.ts +3 -0
  36. package/lib/components/features/search/TMSearchResult.js +77 -22
  37. package/lib/components/features/search/metadataFormHelper.d.ts +16 -0
  38. package/lib/components/features/search/metadataFormHelper.js +77 -0
  39. package/lib/components/forms/Login/TMLoginForm.js +15 -3
  40. package/lib/components/wizard/TMWizard.d.ts +1 -0
  41. package/lib/components/wizard/TMWizard.js +5 -3
  42. package/lib/helper/Enum_Localizator.js +2 -0
  43. package/lib/helper/SDKUI_Localizator.d.ts +14 -0
  44. package/lib/helper/SDKUI_Localizator.js +152 -12
  45. package/lib/helper/certificateImportHelper.d.ts +43 -0
  46. package/lib/helper/certificateImportHelper.js +403 -0
  47. package/lib/helper/checkinCheckoutManager.js +10 -2
  48. package/lib/helper/helpers.d.ts +2 -1
  49. package/lib/helper/helpers.js +1 -0
  50. package/lib/helper/index.d.ts +1 -0
  51. package/lib/helper/index.js +1 -0
  52. package/lib/hooks/useDataUserIdItem.js +1 -1
  53. package/lib/hooks/useDcmtOperations.d.ts +2 -1
  54. package/lib/hooks/useDcmtOperations.js +177 -4
  55. package/lib/hooks/useDocumentOperations.d.ts +2 -0
  56. package/lib/hooks/useDocumentOperations.js +47 -6
  57. package/lib/services/platform_services.d.ts +4 -4
  58. package/lib/ts/types.d.ts +3 -1
  59. package/lib/ts/types.js +2 -0
  60. package/package.json +13 -7
@@ -0,0 +1,268 @@
1
+ import { DcmtTypeListCacheService, LayoutModes, MetadataDataTypes, SDK_Globals } from '@topconsultnpm/sdk-ts';
2
+ import { Globalization, isConvertibleToPdfExt, searchResultToMetadataValues } from '../../../helper';
3
+ import { isPdfExt, MIN_PDF_FOR_MERGE } from './copyAndMergeDcmtsShared';
4
+ /** Documento di soli metadati: FILEEXT null/undefined oppure FILECOUNT 0 (verrà convertito frontend) */
5
+ export const isMetadataOnlyDcmt = (fileExt, fileCount) => {
6
+ if (!fileExt)
7
+ return true;
8
+ return Number(fileCount) === 0;
9
+ };
10
+ /**
11
+ * Calcola tutte le statistiche di selezione necessarie per il merge PDF.
12
+ * Restituisce info su file convertibili, non-PDF e conteggio file unibili.
13
+ *
14
+ * @param selectedDcmtInfos - Lista dei DcmtInfo selezionati (usata se showTMRelationViewer è false)
15
+ * @param selectedItemsFull - Lista completa con FILEEXT e FILECOUNT per check documenti di soli metadati
16
+ * @param selectedItemsRelationViewer - Lista degli IRelatedDcmt selezionati nel RelationViewer
17
+ * @param showTMRelationViewer - Se true, usa selectedItemsRelationViewer, altrimenti selectedDcmtInfos
18
+ */
19
+ export const getMergePdfSelectionStats = (selectedDcmtInfos, selectedItemsFull, selectedItemsRelationViewer, showTMRelationViewer) => {
20
+ // Mappa per lookup veloce di FILECOUNT da selectedItemsFull (usato solo per isMetadataOnly)
21
+ const fileCountMap = new Map(selectedItemsFull.map(d => [`${d.TID}_${d.DID}`, d.FILECOUNT]));
22
+ // Pre-calcolo delle informazioni sui documenti selezionati
23
+ const docs = showTMRelationViewer
24
+ ? selectedItemsRelationViewer
25
+ .filter(i => i.isDcmt)
26
+ .map(i => {
27
+ const ext = i.fileExt ?? undefined;
28
+ return {
29
+ key: `${i.tid}_${i.did}`,
30
+ ext,
31
+ isPdf: isPdfExt(ext),
32
+ isConvertible: isConvertibleToPdfExt(ext),
33
+ isMetadataOnly: isMetadataOnlyDcmt(ext, null)
34
+ };
35
+ })
36
+ : selectedDcmtInfos.map(d => {
37
+ const ext = d.FILEEXT ?? undefined;
38
+ const key = `${d.TID}_${d.DID}`;
39
+ const fileCount = fileCountMap.get(key);
40
+ return {
41
+ key,
42
+ ext,
43
+ isPdf: isPdfExt(ext),
44
+ isConvertible: isConvertibleToPdfExt(ext),
45
+ isMetadataOnly: isMetadataOnlyDcmt(ext, fileCount)
46
+ };
47
+ });
48
+ // Elementi convertibili: file con estensione convertibile (esclusi documenti di soli metadati)
49
+ const convertibleSelectedItems = docs
50
+ .filter(i => i.isConvertible && !i.isMetadataOnly)
51
+ .map(i => ({ key: i.key, ext: i.ext }));
52
+ // Elementi NON PDF e NON convertibili (esclusi documenti di soli metadati)
53
+ const nonPdfSelectedItems = docs
54
+ .filter(i => !i.isPdf && !i.isConvertible && !i.isMetadataOnly)
55
+ .map(i => ({ key: i.key, ext: i.ext }));
56
+ // Documenti di soli metadati (senza file allegato)
57
+ const metadataOnlySelectedItems = docs
58
+ .filter(i => i.isMetadataOnly)
59
+ .map(i => ({ key: i.key, ext: i.ext }));
60
+ // Conteggio file unibili: PDF nativi + convertibili + documenti di soli metadati
61
+ const mergeableSelectedCount = docs
62
+ .filter(i => i.isPdf || i.isConvertible || i.isMetadataOnly).length;
63
+ return {
64
+ convertibleSelectedItems,
65
+ hasConvertibleSelected: convertibleSelectedItems.length > 0,
66
+ nonPdfSelectedItems,
67
+ hasNonPdfSelected: nonPdfSelectedItems.length > 0,
68
+ metadataOnlySelectedItems,
69
+ hasMetadataOnlySelected: metadataOnlySelectedItems.length > 0,
70
+ mergeableSelectedCount,
71
+ hasEnoughPdfForMerge: mergeableSelectedCount >= MIN_PDF_FOR_MERGE,
72
+ };
73
+ };
74
+ // LAZY LOADING pdf-lib
75
+ let pdfLibModule = null;
76
+ const loadPdfLib = async () => {
77
+ if (!pdfLibModule) {
78
+ pdfLibModule = await import('pdf-lib');
79
+ }
80
+ return pdfLibModule;
81
+ };
82
+ // HELPER FUNCTIONS
83
+ /** Ottiene label localizzata del metadato (customName > nameLoc > name) */
84
+ const getMetadataLabel = (md, customName) => {
85
+ if (customName)
86
+ return customName;
87
+ if (!md)
88
+ return '';
89
+ return md.nameLoc || md.name || '';
90
+ };
91
+ /** Formatta valore metadato per PDF (null/undefined -> stringa vuota, datetime -> dd/MM/yyyy HH:mm) */
92
+ const formatMetadataValueForPdf = (value, md) => {
93
+ if (value === undefined || value === null)
94
+ return '';
95
+ // Se il metadato è di tipo DateTime, formatta in modo leggibile usando Globalization
96
+ if (md?.dataType === MetadataDataTypes.DateTime) {
97
+ return Globalization.getDateTimeDisplayValueCompact(value, undefined, md.format?.format);
98
+ }
99
+ return value.toString().trim();
100
+ };
101
+ // ============================================================================
102
+ // CREAZIONE PDF DA METADATI DOCUMENTO
103
+ // ============================================================================
104
+ /** Crea PDF con metadati documento (header + lista label=valore). Supporta wrapping e paginazione.
105
+ * @param dcmtInfo - Informazioni del documento
106
+ * @param showSystemMetadata - Se true, include i metadati di sistema (mid < 100). Default: false
107
+ */
108
+ export const createMetadataPdfFromDocument = async (dcmtInfo, showSystemMetadata = false) => {
109
+ // Configurazione ottimale per PDF A4
110
+ const pageWidth = 595; // A4 width in points
111
+ const pageHeight = 842; // A4 height in points
112
+ const margin = 50; // Margine confortevole
113
+ const titleFontSize = 14; // Titolo leggibile ma non troppo grande
114
+ const contentFontSize = 10; // Font contenuto ottimale per leggibilità
115
+ try {
116
+ // Recupera i metadati del documento
117
+ const metadata = await SDK_Globals.tmSession?.NewSearchEngine().GetMetadataAsync(dcmtInfo.TID, dcmtInfo.DID, true);
118
+ if (!metadata) {
119
+ console.warn('createMetadataPdfFromDocument: impossibile recuperare i metadati');
120
+ return null;
121
+ }
122
+ const dtdResult = metadata.dtdResult;
123
+ const rows = dtdResult?.rows?.[0] ?? [];
124
+ const mids = metadata.selectMIDs;
125
+ // Recupera il tipo documento con metadati
126
+ const dtdWithMetadata = await DcmtTypeListCacheService.GetWithNotGrantedAsync(dcmtInfo.TID, dcmtInfo.DID, metadata);
127
+ if (!dtdWithMetadata) {
128
+ console.warn('createMetadataPdfFromDocument: impossibile recuperare il tipo documento');
129
+ return null;
130
+ }
131
+ const mdList = dtdWithMetadata.metadata ?? [];
132
+ // Converte i metadati in formato leggibile
133
+ const metadataList = searchResultToMetadataValues(dcmtInfo.TID, dtdResult, rows, mids, mdList, LayoutModes.Update);
134
+ // Ottiene il nome del tipo documento (localizzato se abilitato)
135
+ const dcmtTypeName = dtdWithMetadata.nameLoc || dtdWithMetadata.name || 'Documento';
136
+ // Prepara la lista di metadati chiave-valore
137
+ const metadataKeyValues = metadataList
138
+ .filter(mvd => mvd.md && mvd.mid && mvd.mid > 0) // Filtra solo metadati validi
139
+ .filter(mvd => showSystemMetadata || (mvd.mid && mvd.mid >= 100)) // Filtra metadati di sistema se showSystemMetadata è false
140
+ .map(mvd => ({
141
+ label: getMetadataLabel(mvd.md, mvd.customName),
142
+ value: formatMetadataValueForPdf(mvd.value, mvd.md)
143
+ }))
144
+ .filter(kv => kv.label.length > 0); // Rimuovi metadati senza label
145
+ // Carica pdf-lib on-demand
146
+ const { PDFDocument, StandardFonts, rgb } = await loadPdfLib();
147
+ // Crea il documento PDF
148
+ const pdfDoc = await PDFDocument.create();
149
+ const helveticaFont = await pdfDoc.embedFont(StandardFonts.Helvetica);
150
+ const helveticaBoldFont = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
151
+ // Calcola dimensioni
152
+ const contentWidth = pageWidth - (margin * 2);
153
+ const lineHeight = contentFontSize + 5;
154
+ const titleLineHeight = titleFontSize + 6;
155
+ // Funzione per aggiungere una nuova pagina
156
+ const addPage = () => {
157
+ const page = pdfDoc.addPage([pageWidth, pageHeight]);
158
+ return { page, yPosition: pageHeight - margin };
159
+ };
160
+ // Funzione per calcolare la larghezza del testo
161
+ const getTextWidth = (text, font, size) => {
162
+ try {
163
+ return font.widthOfTextAtSize(text, size);
164
+ }
165
+ catch {
166
+ // Fallback per caratteri non supportati
167
+ return text.length * size * 0.5;
168
+ }
169
+ };
170
+ // Funzione per spezzare il testo in righe che entrano nella larghezza disponibile
171
+ const wrapText = (text, font, fontSize, maxWidth) => {
172
+ const lines = [];
173
+ const words = text.split(' ');
174
+ let currentLine = '';
175
+ for (const word of words) {
176
+ const testLine = currentLine ? `${currentLine} ${word}` : word;
177
+ const testWidth = getTextWidth(testLine, font, fontSize);
178
+ if (testWidth > maxWidth && currentLine) {
179
+ lines.push(currentLine);
180
+ currentLine = word;
181
+ }
182
+ else {
183
+ currentLine = testLine;
184
+ }
185
+ }
186
+ if (currentLine) {
187
+ lines.push(currentLine);
188
+ }
189
+ // Se una singola parola è troppo lunga, spezzala in più righe (carattere per carattere)
190
+ const result = [];
191
+ for (const line of lines) {
192
+ if (getTextWidth(line, font, fontSize) <= maxWidth) {
193
+ result.push(line);
194
+ }
195
+ else {
196
+ // Spezza la parola/linea troppo lunga carattere per carattere
197
+ let chunk = '';
198
+ for (const char of line) {
199
+ const testChunk = chunk + char;
200
+ if (getTextWidth(testChunk, font, fontSize) > maxWidth && chunk) {
201
+ result.push(chunk);
202
+ chunk = char;
203
+ }
204
+ else {
205
+ chunk = testChunk;
206
+ }
207
+ }
208
+ if (chunk) {
209
+ result.push(chunk);
210
+ }
211
+ }
212
+ }
213
+ return result;
214
+ };
215
+ // Sanitizza testo per Helvetica (solo ASCII)
216
+ const sanitize = (text) => text.replace(/[^\x20-\x7E]/g, '_');
217
+ let { page, yPosition } = addPage();
218
+ // Header (titolo centrato)
219
+ const titleText = sanitize(`${dcmtTypeName}`);
220
+ const titleWidth = getTextWidth(titleText, helveticaBoldFont, titleFontSize);
221
+ const titleX = Math.max(margin, margin + (contentWidth - titleWidth) / 2);
222
+ page.drawText(titleText, { x: titleX, y: yPosition, size: titleFontSize, font: helveticaBoldFont, color: rgb(0, 0, 0) });
223
+ yPosition -= titleLineHeight + 8;
224
+ // Metadati (label in grassetto, valore normale)
225
+ for (const kv of metadataKeyValues) {
226
+ const labelText = sanitize(kv.label) + ': ';
227
+ const labelWidth = getTextWidth(labelText, helveticaBoldFont, contentFontSize);
228
+ const valueMaxWidth = contentWidth - labelWidth;
229
+ const continuationIndent = labelWidth; // Indentazione per righe successive
230
+ // Wrappa il valore
231
+ const valueText = sanitize(kv.value);
232
+ const valueLines = wrapText(valueText, helveticaFont, contentFontSize, valueMaxWidth);
233
+ // Prima riga: label (bold) + primo pezzo di valore
234
+ if (yPosition < margin + lineHeight) {
235
+ ({ page, yPosition } = addPage());
236
+ }
237
+ page.drawText(labelText, { x: margin, y: yPosition, size: contentFontSize, font: helveticaBoldFont, color: rgb(0, 0, 0) });
238
+ if (valueLines.length > 0) {
239
+ page.drawText(valueLines[0], { x: margin + labelWidth, y: yPosition, size: contentFontSize, font: helveticaFont, color: rgb(0, 0, 0) });
240
+ }
241
+ yPosition -= lineHeight;
242
+ // Righe successive del valore (indentate)
243
+ for (let i = 1; i < valueLines.length; i++) {
244
+ if (yPosition < margin + lineHeight) {
245
+ ({ page, yPosition } = addPage());
246
+ }
247
+ page.drawText(valueLines[i], { x: margin + continuationIndent, y: yPosition, size: contentFontSize, font: helveticaFont, color: rgb(0, 0, 0) });
248
+ yPosition -= lineHeight;
249
+ }
250
+ }
251
+ // Salva il PDF
252
+ const pdfBytes = await pdfDoc.save();
253
+ // Converte in Blob e File
254
+ const pdfBuffer = pdfBytes.buffer.slice(pdfBytes.byteOffset, pdfBytes.byteOffset + pdfBytes.byteLength);
255
+ const blob = new Blob([pdfBuffer], { type: 'application/pdf' });
256
+ const finalFileName = `${dcmtTypeName}_${dcmtInfo.DID}.pdf`;
257
+ const file = new File([blob], finalFileName, { type: 'application/pdf' });
258
+ return {
259
+ file,
260
+ blob,
261
+ bytes: pdfBytes
262
+ };
263
+ }
264
+ catch (error) {
265
+ console.error('createMetadataPdfFromDocument: errore durante la creazione del PDF', error);
266
+ return null;
267
+ }
268
+ };
@@ -0,0 +1,17 @@
1
+ import { MetadataDescriptor, QueryDescriptor, SelectItem } from "@topconsultnpm/sdk-ts";
2
+ interface TMMetadataOutputFormProps {
3
+ /** QueryDescriptor da cui estrarre i metadati disponibili */
4
+ qd?: QueryDescriptor;
5
+ /** Lista di SelectItem attualmente selezionati */
6
+ selectedSelectItems?: SelectItem[];
7
+ /** Consente di visualizzare i metadati di sistema (es. MID < 150) */
8
+ allowSysMetadata?: boolean;
9
+ /** Funzione predicato per filtrare i metadati (es. solo quelli con permesso canView) */
10
+ filterMetadata?: (value: MetadataDescriptor, index: number, array: MetadataDescriptor[]) => unknown;
11
+ /** Callback chiamata alla chiusura del form */
12
+ onClose: () => void;
13
+ /** Callback chiamata quando l'utente conferma la selezione dei SelectItem */
14
+ onChoose?: (selectItems: SelectItem[] | undefined) => void;
15
+ }
16
+ declare const TMMetadataOutputForm: (props: TMMetadataOutputFormProps) => import("react/jsx-runtime").JSX.Element;
17
+ export default TMMetadataOutputForm;
@@ -0,0 +1,225 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
+ import { SDK_Localizator, SelectItem, SelectItemVisibilities } from "@topconsultnpm/sdk-ts";
4
+ import { IconAddCircleOutline, IconApply, IconClear, IconDelete, IconDraggabledots, IconUndo, SDKUI_Localizator } from "../../../helper";
5
+ import TMToppyMessage from "../../../helper/TMToppyMessage";
6
+ import { ButtonNames, TMMessageBoxManager } from "../../base/TMPopUp";
7
+ import { TMColors } from "../../../utils/theme";
8
+ import { StyledToolbarForm } from "../../base/Styled";
9
+ import TMButton from "../../base/TMButton";
10
+ import TMModal from "../../base/TMModal";
11
+ import { TMMetadataChooserForm } from "../../choosers/TMMetadataChooser";
12
+ import { TMMetadataIcon } from "../../viewers/TMMidViewer";
13
+ import { loadMetadataFromQd, removeDuplicatesByTidMid, areArraysEqual } from "./metadataFormHelper";
14
+ const TMMetadataOutputForm = (props) => {
15
+ const { qd, selectedSelectItems, allowSysMetadata = true, filterMetadata, onClose, onChoose } = props;
16
+ // =============================================================================
17
+ // STATE
18
+ // =============================================================================
19
+ // Stato iniziale dei SelectItem (per confronto isModified)
20
+ const initialSelectItemsRef = useRef((selectedSelectItems ?? []).map(item => {
21
+ const newItem = new SelectItem();
22
+ newItem.tid = item.tid;
23
+ newItem.mid = item.mid;
24
+ newItem.visibility = item.visibility ?? SelectItemVisibilities.Visible;
25
+ return newItem;
26
+ }));
27
+ // Lista dei SelectItem correnti (inizializzata da selectedSelectItems)
28
+ const [selectItems, setSelectItems] = useState(() => {
29
+ // Clona i SelectItem iniziali per evitare mutazioni
30
+ return (selectedSelectItems ?? []).map(item => {
31
+ const newItem = new SelectItem();
32
+ newItem.tid = item.tid;
33
+ newItem.mid = item.mid;
34
+ newItem.visibility = item.visibility ?? SelectItemVisibilities.Visible;
35
+ return newItem;
36
+ });
37
+ });
38
+ // Calcola se ci sono modifiche rispetto allo stato iniziale
39
+ const isModified = useMemo(() => {
40
+ return !areArraysEqual(selectItems, initialSelectItemsRef.current);
41
+ }, [selectItems]);
42
+ // Lista "piatta" di tutti i metadati estratti dai DTD, con chiave univoca
43
+ const [metadataList, setMetadataList] = useState([]);
44
+ // Flag di caricamento
45
+ const [isLoading, setIsLoading] = useState(true);
46
+ // Flag per mostrare il TMMetadataChooserForm
47
+ const [showMetadataChooser, setShowMetadataChooser] = useState(false);
48
+ // Item in fase di drag
49
+ const [draggingItem, setDraggingItem] = useState(undefined);
50
+ // =============================================================================
51
+ // MEMOIZZAZIONI per evitare re-render del TMMetadataChooserForm
52
+ // =============================================================================
53
+ // Lista di TID_MID già selezionati (per escluderli dal chooser)
54
+ const selectedIDs = useMemo(() => selectItems.map(si => ({ tid: si.tid, mid: si.mid })), [selectItems]);
55
+ // Callback per chiudere il chooser
56
+ const handleCloseChooser = useCallback(() => {
57
+ setShowMetadataChooser(false);
58
+ }, []);
59
+ // =============================================================================
60
+ // EFFECT: Carica i metadati al mount
61
+ // =============================================================================
62
+ useEffect(() => {
63
+ const load = async () => {
64
+ setIsLoading(true);
65
+ try {
66
+ const { metadata } = await loadMetadataFromQd(qd, filterMetadata);
67
+ setMetadataList(metadata);
68
+ }
69
+ catch (error) {
70
+ console.error("Errore nel caricamento dei metadati:", error);
71
+ setMetadataList([]);
72
+ }
73
+ finally {
74
+ setIsLoading(false);
75
+ }
76
+ };
77
+ load();
78
+ }, [qd, filterMetadata]);
79
+ // =============================================================================
80
+ // HELPER: Trova il MetadataDescriptor per un SelectItem
81
+ // =============================================================================
82
+ const getMetadataForSelectItem = useCallback((item) => {
83
+ return metadataList.find(md => md.customData1 === item.tid && md.id === item.mid);
84
+ }, [metadataList]);
85
+ // =============================================================================
86
+ // HANDLER: Conferma la scelta dal chooser (sincronizza la lista completa)
87
+ // =============================================================================
88
+ const handleChooseFromChooser = useCallback((tid_mids) => {
89
+ // Se la lista è vuota o undefined, rimuovi tutti
90
+ if (!tid_mids || tid_mids.length === 0) {
91
+ setSelectItems([]);
92
+ setShowMetadataChooser(false);
93
+ return;
94
+ }
95
+ setSelectItems(prev => {
96
+ // Filtra quelli esistenti che sono ancora selezionati (mantenendo ordine)
97
+ const kept = prev.filter(si => tid_mids.some(tm => tm.tid === si.tid && tm.mid === si.mid));
98
+ // Trova i nuovi da aggiungere
99
+ const newItems = [];
100
+ for (const tm of tid_mids) {
101
+ const exists = prev.some(si => si.tid === tm.tid && si.mid === tm.mid);
102
+ if (!exists) {
103
+ const newItem = new SelectItem();
104
+ newItem.tid = tm.tid;
105
+ newItem.mid = tm.mid;
106
+ newItem.visibility = SelectItemVisibilities.Visible;
107
+ newItems.push(newItem);
108
+ }
109
+ }
110
+ return removeDuplicatesByTidMid([...kept, ...newItems]);
111
+ });
112
+ setShowMetadataChooser(false);
113
+ }, []);
114
+ // =============================================================================
115
+ // HANDLER: Rimuove un metadato dall'output
116
+ // =============================================================================
117
+ const handleRemoveSelectItem = useCallback((index) => {
118
+ setSelectItems(prev => prev.filter((_, i) => i !== index));
119
+ }, []);
120
+ // =============================================================================
121
+ // DRAG & DROP HANDLERS
122
+ // =============================================================================
123
+ const handleDragStart = useCallback((e, item) => {
124
+ // Previeni il drag se il target è l'icona elimina
125
+ const target = e.target;
126
+ if (target.closest('[data-no-drag="true"]')) {
127
+ e.preventDefault();
128
+ return;
129
+ }
130
+ setDraggingItem(item);
131
+ e.dataTransfer.setData('text/plain', '');
132
+ e.dataTransfer.effectAllowed = 'move';
133
+ }, []);
134
+ const handleDragEnd = useCallback(() => {
135
+ setDraggingItem(undefined);
136
+ }, []);
137
+ const handleDragOver = useCallback((e) => {
138
+ e.preventDefault();
139
+ e.dataTransfer.dropEffect = 'move';
140
+ }, []);
141
+ const handleDrop = useCallback((e, targetItem) => {
142
+ e.preventDefault();
143
+ if (!draggingItem)
144
+ return;
145
+ const currentIndex = selectItems.indexOf(draggingItem);
146
+ const targetIndex = selectItems.indexOf(targetItem);
147
+ if (currentIndex === -1 || targetIndex === -1 || currentIndex === targetIndex) {
148
+ setDraggingItem(undefined);
149
+ return;
150
+ }
151
+ const listCopy = [...selectItems];
152
+ listCopy.splice(currentIndex, 1);
153
+ listCopy.splice(targetIndex, 0, draggingItem);
154
+ setSelectItems(listCopy);
155
+ setDraggingItem(undefined);
156
+ }, [draggingItem, selectItems]);
157
+ // =============================================================================
158
+ // HANDLER: Rollback allo stato iniziale
159
+ // =============================================================================
160
+ const handleRollback = useCallback(() => {
161
+ setSelectItems(initialSelectItemsRef.current.map(item => {
162
+ const newItem = new SelectItem();
163
+ newItem.tid = item.tid;
164
+ newItem.mid = item.mid;
165
+ newItem.visibility = item.visibility;
166
+ return newItem;
167
+ }));
168
+ }, []);
169
+ // =============================================================================
170
+ // HANDLER: Chiusura con conferma se modificato
171
+ // =============================================================================
172
+ const confirmCloseContainerId = "TMMetadataOutputFormConfirmClose";
173
+ const handleClose = useCallback(() => {
174
+ if (!isModified) {
175
+ onClose();
176
+ return;
177
+ }
178
+ TMMessageBoxManager.show({
179
+ parentId: confirmCloseContainerId,
180
+ message: SDKUI_Localizator.ConfirmOnCancel,
181
+ buttons: [ButtonNames.YES, ButtonNames.NO],
182
+ onButtonClick: (buttonClicked) => {
183
+ if (buttonClicked === ButtonNames.YES) {
184
+ onClose();
185
+ }
186
+ }
187
+ });
188
+ }, [isModified, onClose]);
189
+ // =============================================================================
190
+ // HANDLER: Conferma la selezione
191
+ // =============================================================================
192
+ const handleConfirm = useCallback(() => {
193
+ onChoose?.(selectItems.length > 0 ? selectItems : undefined);
194
+ onClose();
195
+ }, [selectItems, onChoose, onClose]);
196
+ // =============================================================================
197
+ // RENDER
198
+ // =============================================================================
199
+ return (_jsxs(_Fragment, { children: [_jsxs(TMModal, { title: `${SDKUI_Localizator.Configure} - ${SDK_Localizator.QuerySelect}${selectItems.length > 0 ? ` (${selectItems.length})` : ''}`, width: "600px", height: "500px", onClose: handleClose, hidePopup: false, askClosingConfirm: isModified, children: [_jsx("div", { id: confirmCloseContainerId }), _jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%', gap: '10px' }, children: [_jsxs(StyledToolbarForm, { children: [_jsx(TMButton, { caption: SDKUI_Localizator.Confirm, icon: _jsx(IconApply, {}), btnStyle: "toolbar", color: "success", disabled: !isModified, onClick: handleConfirm }), _jsx(TMButton, { caption: SDKUI_Localizator.Undo, icon: _jsx(IconUndo, {}), btnStyle: "toolbar", color: "tertiary", disabled: !isModified, onClick: handleRollback }), _jsx(TMButton, { caption: SDKUI_Localizator.Add, icon: _jsx(IconAddCircleOutline, {}), btnStyle: "toolbar", onClick: () => setShowMetadataChooser(true) }), _jsx(TMButton, { caption: SDKUI_Localizator.Clear, icon: _jsx(IconClear, {}), btnStyle: "toolbar", disabled: selectItems.length === 0, onClick: () => setSelectItems([]) })] }), _jsxs("div", { style: {
200
+ flex: 1,
201
+ overflow: 'auto',
202
+ border: `1px solid ${TMColors.border_normal}`,
203
+ borderRadius: '4px',
204
+ padding: '5px'
205
+ }, children: [isLoading && _jsxs("p", { style: { textAlign: 'center', padding: '20px' }, children: [SDKUI_Localizator.Loading, "..."] }), !isLoading && selectItems.length === 0 && (_jsx(TMToppyMessage, { message: SDKUI_Localizator.NoOutputMetadata, titleTooltip: SDKUI_Localizator.NoOutputMetadata })), !isLoading && selectItems.length > 0 && (_jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: '2px' }, children: selectItems.map((item, index) => {
206
+ const md = getMetadataForSelectItem(item);
207
+ const isDragging = draggingItem === item;
208
+ return (_jsxs("div", { draggable: true, onDragStart: (e) => handleDragStart(e, item), onDragEnd: handleDragEnd, onDragOver: handleDragOver, onDrop: (e) => handleDrop(e, item), style: {
209
+ display: 'flex',
210
+ alignItems: 'center',
211
+ gap: '6px',
212
+ padding: '4px 8px',
213
+ backgroundColor: isDragging ? TMColors.primary_container : TMColors.default_background,
214
+ borderRadius: '3px',
215
+ border: `1px solid ${isDragging ? TMColors.primaryColor : TMColors.border_normal}`,
216
+ minHeight: '32px',
217
+ cursor: 'grab',
218
+ transition: 'all 0.2s ease',
219
+ transform: isDragging ? 'scale(1.02)' : 'scale(1)',
220
+ boxShadow: isDragging ? '0 4px 12px rgba(0,0,0,0.15)' : 'none',
221
+ opacity: isDragging ? 0.9 : 1,
222
+ }, children: [_jsx("div", { style: { display: 'flex', cursor: 'grab' }, children: _jsx(IconDraggabledots, { fontSize: 15, color: TMColors.button_icon }) }), _jsx(TMMetadataIcon, { tid: item.tid ?? 0, md: md }), _jsx("span", { style: { flex: 1, fontSize: '13px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: md?.name ?? `MID: ${item.mid}` }), _jsx("div", { "data-no-drag": "true", onMouseDown: (e) => e.stopPropagation(), children: _jsx(TMButton, { caption: SDKUI_Localizator.Remove, icon: _jsx(IconDelete, { color: TMColors.error }), btnStyle: "icon", onClick: () => handleRemoveSelectItem(index) }) })] }, `${item.tid}_${item.mid}_${index}`));
223
+ }) }))] })] })] }), showMetadataChooser && (_jsx(TMMetadataChooserForm, { allowMultipleSelection: true, height: "600px", width: "700px", allowSysMetadata: allowSysMetadata, filterMetadata: filterMetadata, qd: qd, selectedIDs: selectedIDs, onClose: handleCloseChooser, onChoose: handleChooseFromChooser }))] }));
224
+ };
225
+ export default TMMetadataOutputForm;
@@ -0,0 +1,17 @@
1
+ import { MetadataDescriptor, OrderByItem, QueryDescriptor } from "@topconsultnpm/sdk-ts";
2
+ interface TMMetadataSorterFormProps {
3
+ /** QueryDescriptor da cui estrarre i metadati disponibili */
4
+ qd?: QueryDescriptor;
5
+ /** Lista di OrderByItem attualmente selezionati */
6
+ selectedOrderByItems?: OrderByItem[];
7
+ /** Consente di visualizzare i metadati di sistema (es. MID < 150) */
8
+ allowSysMetadata?: boolean;
9
+ /** Funzione predicato per filtrare i metadati (es. solo quelli con permesso canView) */
10
+ filterMetadata?: (value: MetadataDescriptor, index: number, array: MetadataDescriptor[]) => unknown;
11
+ /** Callback chiamata alla chiusura del form */
12
+ onClose: () => void;
13
+ /** Callback chiamata quando l'utente conferma la selezione degli OrderByItem */
14
+ onChoose?: (orderByItems: OrderByItem[] | undefined) => void;
15
+ }
16
+ declare const TMMetadataSorterForm: (props: TMMetadataSorterFormProps) => import("react/jsx-runtime").JSX.Element;
17
+ export default TMMetadataSorterForm;