Merged PR 207: Fix Canvas and picklists

Fix Canvas and picklists
This commit is contained in:
2025-11-06 17:39:00 +00:00
3 changed files with 304 additions and 44 deletions

View File

@@ -8,20 +8,22 @@
<MultiSelect
v-if="dataSource === 'videoGroups'"
v-model="selectedValue"
:options="options"
:options="safeOptions"
optionLabel="name"
:filter="true"
:placeholder="placeholder || 'Select VideoGroups'"
:disabled="disabled"
:loading="loading"
class="w-full md:w-80"
:virtualScrollerOptions="{ itemSize: 50 }"
class="w-full"
panelClass="video-groups-panel"
@change="onSelectionChange"
>
<template #option="slotProps">
<div class="flex align-items-center">
<i class="pi pi-video mr-2"></i>
<div>
<div>{{ slotProps.option.name }}</div>
<div class="flex align-items-center p-3 hover:bg-gray-50">
<i class="pi pi-video mr-3 text-blue-600"></i>
<div class="flex-1">
<div class="font-medium text-sm">{{ slotProps.option.name }}</div>
<small class="text-muted" v-if="slotProps.option.description">
{{ slotProps.option.description }}
</small>
@@ -34,20 +36,22 @@
<MultiSelect
v-else-if="dataSource === 'ksDocuments'"
v-model="selectedValue"
:options="options"
:options="safeOptions"
optionLabel="fileName"
:filter="true"
:placeholder="placeholder || 'Select Documents'"
:disabled="disabled"
:loading="loading"
class="w-full md:w-80"
:virtualScrollerOptions="{ itemSize: 50 }"
class="w-full"
panelClass="ks-documents-panel"
@change="onSelectionChange"
>
<template #option="slotProps">
<div class="flex align-items-center">
<i :class="getFileIcon(slotProps.option)" class="mr-2"></i>
<div>
<div>{{ slotProps.option.fileName }}</div>
<div class="flex align-items-center p-3 hover:bg-gray-50">
<i :class="getFileIcon(slotProps.option)" class="mr-3"></i>
<div class="flex-1">
<div class="font-medium text-sm">{{ slotProps.option.fileName }}</div>
<small class="text-muted" v-if="slotProps.option.description">
{{ slotProps.option.description }}
</small>
@@ -60,8 +64,8 @@
<Dropdown
v-else-if="multiple === false"
v-model="selectedValue"
:options="options"
:options="safeOptions"
optionLabel="name"
:filter="true"
:placeholder="placeholder || 'Select an option'"
:disabled="disabled"
@@ -72,15 +76,17 @@
<!-- MultiSelect generico per altri tipi -->
<MultiSelect
v-else
v-else-if="safeOptions && safeOptions.length > 0"
v-model="selectedValue"
:options="options"
:options="safeOptions"
optionLabel="name"
:filter="true"
:placeholder="placeholder || 'Select options'"
:disabled="disabled"
:loading="loading"
class="w-full md:w-80"
:virtualScrollerOptions="{ itemSize: 50 }"
class="w-full"
panelClass="generic-multiselect-panel"
@change="onSelectionChange"
/>
</div>
@@ -175,6 +181,11 @@ const selectedValue = computed({
}
});
// Computed property per garantire che le options siano sempre un array valido
const safeOptions = computed(() => {
return Array.isArray(props.options) ? props.options : [];
});
const getFileIcon = (document) => {
if (!document) return 'pi pi-file';
@@ -226,3 +237,52 @@ const onSelectionChange = (event) => {
color: #6c757d;
}
</style>
<style>
/* Personalizzazione delle panel dei MultiSelect per migliorare la visualizzazione */
.ks-documents-panel .p-multiselect-panel,
.video-groups-panel .p-multiselect-panel,
.generic-multiselect-panel .p-multiselect-panel {
min-width: 350px;
max-width: 500px;
}
.ks-documents-panel .p-multiselect-items,
.video-groups-panel .p-multiselect-items,
.generic-multiselect-panel .p-multiselect-items {
max-height: 300px;
}
.ks-documents-panel .p-multiselect-item,
.video-groups-panel .p-multiselect-item,
.generic-multiselect-panel .p-multiselect-item {
padding: 0.75rem;
border-bottom: 1px solid #e9ecef;
min-height: 50px;
display: flex;
align-items: center;
}
.ks-documents-panel .p-multiselect-item:last-child,
.video-groups-panel .p-multiselect-item:last-child,
.generic-multiselect-panel .p-multiselect-item:last-child {
border-bottom: none;
}
.ks-documents-panel .p-multiselect-item:hover,
.video-groups-panel .p-multiselect-item:hover,
.generic-multiselect-panel .p-multiselect-item:hover {
background-color: #f8f9fa;
}
/* Stili per le icone dei file */
.ks-documents-panel .pi {
font-size: 1.2rem;
min-width: 1.5rem;
}
.video-groups-panel .pi-video {
font-size: 1.2rem;
min-width: 1.5rem;
}
</style>

View File

@@ -3,8 +3,7 @@
<ProgressSpinner style="width: 50px; height: 50px; margin-top: 50px" strokeWidth="3" fill="transparent" />
</div>
<div v-else>
<div class="flex items-center justify-between p-2">
</div>
<div class="flex items-center justify-between p-2"></div>
</div>
<div v-if="loading_data" class="flex justify-center">
@@ -31,8 +30,8 @@
<tr v-for="(input, index) in filteredInputs" :key="index">
<th v-if="index === 'MultiFileUpload'" class="border border-gray-300 px-4 py-2">Files Uploaded</th>
<th v-else-if="index === 'SingleFileUpload'" class="border border-gray-300 px-4 py-2 bg-gray-500 text-white">Parameter</th>
<th v-else-if="index === 'input_multiselect_name'">
{{ scenario.inputs && Array.isArray(scenario.inputs) ? scenario.inputs.find((i) => i.name === 'input_multiselect')?.label : null }}
<th v-else-if="index.includes('input_multiselect') && index.endsWith('_name')">
{{ scenario.inputs && Array.isArray(scenario.inputs) ? scenario.inputs.find((i) => i.name === index.replace('_name', ''))?.label : null }}
</th>
<th v-else class="border border-gray-300 px-4 py-2">
{{ index.replace(/_/g, ' ').replace(/\b\w/g, (char) => char.toUpperCase()) }}
@@ -227,8 +226,14 @@ const retrieveScenarioExec = (id) => {
});
};
const filteredInputs = computed(() => {
const { input_multiselect_id, ...rest } = inputs.value;
return rest;
const filtered = {};
for (const [key, value] of Object.entries(inputs.value)) {
// Escludi tutti i campi che contengono "input_multiselect" e finiscono con "_id"
if (!(key.includes('input_multiselect') && key.endsWith('_id'))) {
filtered[key] = value;
}
}
return filtered;
});
const extractFiles = async (base64String, type, zip) => {
@@ -319,7 +324,6 @@ const getFileNamesInput = (zipData) => {
return files;
};
const downloadFile = async (filePath) => {
try {
let relativePath = filePath;

View File

@@ -29,7 +29,6 @@
:maxFileSize="20971520"
v-model:files="uploadedFiles"
@before-send="onBeforeSend"
>
<template #content="{ files, uploadedFiles, removeUploadedFileCallback, removeFileCallback }">
<div class="pt-4">
@@ -90,7 +89,6 @@
:maxFileSize="20971520"
v-model:files="uploadedFiles"
@before-send="onBeforeSend"
>
<template #content="{ files, uploadedFiles, removeUploadedFileCallback, removeFileCallback }">
<div class="pt-4">
@@ -131,6 +129,20 @@
</FileUpload>
</div>
</div>
<div v-else-if="input.type === 'multiselect'" class="mt-4">
<DynamicPicker
v-model="formData[input.name]"
:input-name="input.name"
:label="input.label"
:data-source="input.dataSource || 'videoGroups'"
:options="getOptionsForInput(input)"
:disabled="loadingStore.exectuion_loading"
:loading="loadingOptionsFor[input.dataSource || 'videoGroups'] || false"
:show-status="input.dataSource === 'ksDocuments'"
no-margin
@change="onDynamicPickerChange(input.name, $event)"
/>
</div>
<div v-else>
<label :for="input.name"
><b>{{ input.label }}</b></label
@@ -189,7 +201,7 @@
<button class="p-button p-button-primary" @click="addToCanvas">Add to Canvas</button>
</div>
<div>
<MdPreview class="editor" v-model="scenario_output" language="en-US" />
<MdPreview class="editor" v-model="scenario_output" language="en-US" />
</div>
</div>
</Panel>
@@ -204,8 +216,12 @@
</template>
<script setup>
import DynamicPicker from '@/components/DynamicPicker.vue';
import { KSDocumentService } from '@/service/KSDocumentService';
import { ScenarioService } from '@/service/ScenarioService';
import { KsVideoGroupStore } from '@/stores/KsVideoGroupStore';
import { LoadingStore } from '@/stores/LoadingStore';
import { UserPrefStore } from '@/stores/UserPrefStore';
import { useAuth } from '@websanova/vue-auth/src/v3.js';
import axios from 'axios';
import JsonEditorVue from 'json-editor-vue';
@@ -216,17 +232,20 @@ import 'md-editor-v3/lib/style.css';
import moment from 'moment';
import { usePrimeVue } from 'primevue/config';
import InputText from 'primevue/inputtext';
import MultiSelect from 'primevue/multiselect';
import Select from 'primevue/select';
import Textarea from 'primevue/textarea';
import { useConfirm } from 'primevue/useconfirm';
import { useToast } from 'primevue/usetoast';
import { computed, defineEmits, onMounted, ref } from 'vue';
import { computed, defineEmits, onMounted, onUnmounted, reactive, ref, watch } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { JellyfishLoader } from 'vue3-spinner';
const props = defineProps(['scenario'])
const emit = defineEmits(['add'])
const props = defineProps(['scenario']);
const emit = defineEmits(['add']);
const loadingStore = LoadingStore();
const ksVideoGroupStore = KsVideoGroupStore();
const userPrefStore = UserPrefStore();
const toast = useToast();
const zip = ref(null);
const router = useRouter();
@@ -246,6 +265,10 @@ const debug_modal = ref(false);
let pollingInterval = null;
const folderName = ref('');
const fileNamesOutput = ref([]);
// Aggiunte per gestire le opzioni dei multiselect
const videoGroups = ref([]);
const ksDocuments = ref([]);
const loadingOptionsFor = reactive({});
// URL di upload
const uploadUrlBase = import.meta.env.VITE_BACKEND_URL;
const uploadUrl = ref('');
@@ -258,6 +281,9 @@ const acceptedFormats = ref('.docx');
// :url="`http://localhost:8081/uploadListFiles/${folderName}`"
const auth = useAuth();
// Stato per tracciare se il componente è montato
const isMounted = ref(false);
// Stato per l'ID univoco della cartella
const uniqueFolderId = ref(generateUniqueId());
const confirm = useConfirm();
@@ -285,7 +311,7 @@ function stopTimer() {
const onBeforeSend = (event) => {
const { xhr } = event; // Estraggo l'oggetto XMLHttpRequest
console.log('xhr', xhr);
var token = auth.token()
var token = auth.token();
xhr.setRequestHeader('Authorization', 'Bearer ' + token); // Imposta il tipo di contenuto
};
@@ -306,6 +332,7 @@ const isInputFilled = computed(() => {
onMounted(() => {
console.log('MdScenarioExecutionDialog montato!');
isMounted.value = true;
const timestamp = Date.now(); //Ottiene il timestamp corrente
const randomNumber = Math.floor(Math.random() * 1000);
folderName.value = `${timestamp}_${randomNumber}`;
@@ -313,8 +340,21 @@ onMounted(() => {
uploadUrlPR.value = uploadUrl.value + '/PR';
uploadUrlOther.value = uploadUrl.value + '/OTHER';
console.log('Upload URL:', uploadUrl);
// Carica le opzioni per i multiselect
loadOptionsForScenario();
});
// Cleanup quando il componente viene smontato
onUnmounted(() => {
isMounted.value = false;
if (pollingInterval) {
clearInterval(pollingInterval);
}
if (timerInterval.value) {
clearInterval(timerInterval.value);
}
});
const getInputComponent = (type) => {
switch (type) {
@@ -324,11 +364,133 @@ const getInputComponent = (type) => {
return Textarea;
case 'select':
return Select;
case 'multiselect':
return MultiSelect;
default:
return InputText;
}
};
// Funzione per ottenere le opzioni per gli input multiselect
const getOptionsForInput = (input) => {
// Basato sul dataSource, restituisce le opzioni appropriate
let options = [];
switch (input.dataSource) {
case 'videoGroups':
options = videoGroups.value || [];
break;
case 'ksDocuments':
options = ksDocuments.value || [];
break;
default:
options = [];
}
// Assicuriamoci che sia sempre un array
return Array.isArray(options) ? options : [];
};
const onDynamicPickerChange = (inputName, value) => {
console.log(`Dynamic picker changed for ${inputName}:`, value);
formData.value[inputName] = value;
};
// Funzione per caricare i video groups
const loadVideoGroups = async () => {
try {
if (!isMounted.value) return;
if (!userPrefStore.selectedProject?.id) {
console.log('No selected project, skipping video groups load');
videoGroups.value = [];
return;
}
await ksVideoGroupStore.fetchKsVideoGroup(userPrefStore.selectedProject.id);
if (!isMounted.value) return; // Check again after async call
videoGroups.value = [...(ksVideoGroupStore.ksVideoGroup || [])];
console.log('Video groups loaded:', videoGroups.value);
} catch (error) {
console.error('Error loading video groups:', error);
if (isMounted.value) {
videoGroups.value = [];
}
}
};
// Funzione per caricare i documenti KS
const loadKsDocuments = async () => {
try {
if (!isMounted.value) return;
console.log('Starting KS documents load...');
const docsResponse = await KSDocumentService.getKSDocuments();
console.log('KS documents response:', docsResponse);
if (!isMounted.value) return; // Check again after async call
ksDocuments.value = docsResponse.data || [];
console.log('KS documents loaded:', ksDocuments.value.length, 'items');
// Debug: verifica struttura dei documenti
if (ksDocuments.value.length > 0) {
console.log('First KS document structure:', ksDocuments.value[0]);
}
} catch (error) {
console.error('Error loading KS documents:', error);
if (isMounted.value) {
ksDocuments.value = [];
}
}
};
// Carica le opzioni necessarie basate sui dataSource presenti negli inputs dello scenario
const loadOptionsForScenario = async () => {
if (!props.scenario.inputs) return;
console.log('Loading options for scenario inputs...');
// Trova tutti i dataSource unici negli input multiselect
const dataSources = new Set();
props.scenario.inputs.forEach((input) => {
if (input.type === 'multiselect' && input.dataSource) {
dataSources.add(input.dataSource);
}
});
// Crea le funzioni di caricamento per ogni dataSource
const loadingPromises = Array.from(dataSources).map(async (dataSource) => {
try {
// Imposta lo stato di loading per questo dataSource
loadingOptionsFor[dataSource] = true;
console.log(`Loading options for dataSource: ${dataSource}`);
switch (dataSource) {
case 'videoGroups':
await loadVideoGroups();
break;
case 'ksDocuments':
await loadKsDocuments();
break;
default:
console.warn(`Unknown dataSource: ${dataSource}`);
}
} catch (error) {
console.error(`Error loading options for ${dataSource}:`, error);
} finally {
// Reset lo stato di loading per questo dataSource
loadingOptionsFor[dataSource] = false;
}
});
// Aspetta che tutti i caricamenti siano completati
await Promise.all(loadingPromises);
};
const execScenario = () => {
if (numberPrFiles.value !== 1 && reqMultiFile.value) {
toast.add({
@@ -344,9 +506,30 @@ const execScenario = () => {
loadingStore.exectuion_loading = true;
// Crea una copia dei dati del form
const processedData = { ...formData.value };
// Elabora tutti i multiselect dinamici
if (props.scenario.inputs) {
props.scenario.inputs.forEach((input) => {
if (input.type === 'multiselect' && processedData[input.name]) {
// Trasforma array di oggetti in array di IDs/nomi
if (Array.isArray(processedData[input.name])) {
if (input.dataSource === 'videoGroups') {
processedData[input.name + '_names'] = processedData[input.name].map((item) => item.name || item);
processedData[input.name + '_ids'] = processedData[input.name].map((item) => item.id || item);
} else if (input.dataSource === 'ksDocuments') {
processedData[input.name + '_names'] = processedData[input.name].map((item) => item.fileName || item);
processedData[input.name + '_ids'] = processedData[input.name].map((item) => item.id || item);
}
}
}
});
}
const data = {
scenario_id: props.scenario.id,
inputs: { ...formData.value }
inputs: processedData
};
axios
@@ -711,11 +894,24 @@ const formatSize = (bytes) => {
};
const addToCanvas = () => {
console.log("Added");
emit('add',scenario_output.value)
}
</script>
console.log('Added');
emit('add', scenario_output.value);
};
// Ricarica le opzioni quando lo scenario cambia
watch(
() => props.scenario,
async (newScenario) => {
if (!isMounted.value) return;
if (newScenario && newScenario.inputs) {
formData.value = { ...newScenario.inputs.reduce((acc, input) => ({ ...acc, [input.name]: '' }), {}) };
await loadOptionsForScenario();
}
},
{ immediate: true, deep: true }
);
</script>
<style>
.editor ol {