Files
hermione-fe/src/views/pages/DashExecution.vue

971 lines
36 KiB
Vue

<template>
<div class="dash-container">
<div class="dash-header">
<h1 class="dash-title">Dashboard</h1>
</div>
<div v-if="loading" class="flex justify-center">
<ProgressSpinner style="width: 50px; height: 50px; margin-top: 50px" strokeWidth="3" fill="transparent" />
</div>
<div v-else class="dash-execution">
<div class="filter-section flex justify-start gap-4 items-end">
<div class="filter-item">
<label class="filter-label">Date Range:</label>
<Calendar v-model="dateRange" selectionMode="range" placeholder="Date Range" />
</div>
<!-- Dropdown account -->
<div class="filter-item">
<label class="filter-label">Account:</label>
<Dropdown v-model="selectedAccount" :options="uniqueAccounts" optionLabel="name" optionValue="id" placeholder="Select an Account" class="w-full md:w-20rem" />
</div>
<!-- MultiSelect progetti -->
<div class="filter-item">
<label class="filter-label">Project:</label>
<MultiSelect v-model="selectedProjects" :options="filteredProjects" optionLabel="fe_name" optionValue="id" display="chip" placeholder="Select Projects" :maxSelectedLabels="3" class="w-full" style="min-width: 100px" />
</div>
<!-- MultiSelect applicazione -->
<!-- <div class="filter-item">
<label class="filter-label">Application:</label>
<MultiSelect
v-model="selectedApplications"
:options="appOptions"
optionLabel="fe_name"
optionValue="id"
display="chip"
placeholder="All Applications"
:maxSelectedLabels="3"
class="w-full"
style="min-width: 100px;"
/>
</div>
<div class="filter-item" style="position: relative; display: inline-block; width: 100%; min-width: 100px;">
<label class="filter-label">User:</label>
<MultiSelect
v-model="selectedUsers"
:options="userOptions"
optionLabel="username"
filter
optionValue="id"
display="chip"
placeholder="All Users"
:maxSelectedLabels="3"
class="w-full"
style="min-width: 100px;"
:disabled="loadingUsers"
/>
<div v-if="loadingUsers" class="user-loading-overlay">
<i class="pi pi-spin pi-spinner" style="font-size: 1.2em;"></i> Loading users...
</div>
</div> -->
<!-- MultiSelect scenarios -->
<div class="filter-item">
<label class="filter-label">Scenario:</label>
<MultiSelect v-model="selectedScenarios" :options="scenarioOptions" optionLabel="name" optionValue="id" display="chip" placeholder="Select Scenarios" :maxSelectedLabels="3" class="w-full" style="min-width: 100px" />
</div>
<!-- <div class=" apply-button-container">
<label class="filter-label">&nbsp;</label>
<Button label="Apply" @click="onApplyFilters" class="p-button-purple apply-button" />
</div>
</div> -->
<div class="apply-button-container">
<label class="filter-label">&nbsp;</label>
<div class="button-group">
<Button label="Clear" @click="onClearFilters" class="p-button-outlined p-button-secondary clear-button" icon="pi pi-times" />
<Button label="Apply" @click="onApplyFilters" class="p-button-purple apply-button" icon="pi pi-search" />
</div>
</div>
</div>
<div v-if="showTables">
<!-- Tabella Aggregations -->
<div class="p-my-3 mb-8">
<DataTable
:value="dashboardResponse"
class="p-datatable-sm"
v-model:expandedRows="expandedRows"
@rowExpand="onRowExpand"
@rowCollapse="onRowCollapse"
:paginator="true"
:rows="10"
paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink CurrentPageReport RowsPerPageDropdown"
currentPageReportTemplate="Showing {first} to {last} of {totalRecords} records"
:rowsPerPageOptions="[10, 15, 20, 50, 100]"
dataKey="id"
:rowHover="true"
rowGroupMode="subheader"
:sortOrder="1"
filterDisplay="menu"
responsiveLayout="scroll"
>
<template #header>
<div class="execution-section flex justify-between items-center">
<h3 class="text-xl font-bold mt-6">Executions Stats</h3>
<Button label="Export" icon="pi pi-download" class="p-button-purple" @click="exportAggregations" />
</div>
</template>
<Column field="projectName" header="Project" sortable>
<template #body="slotProps">
<span>{{ slotProps.data.projectName }}</span>
</template>
</Column>
<Column field="scenarioInternalName" header="Scenario" sortable>
<template #body="slotProps">
<span>{{ slotProps.data.scenarioInternalName }}</span>
</template>
</Column>
<Column field="totalExecutions" header="Executions" sortable>
<template #body="slotProps">
<span>{{ slotProps.data.totalExecutions }}</span>
</template>
</Column>
<Column field="totalTokens" header="Tokens" sortable>
<template #body="slotProps">
<span>{{ slotProps.data.totalTokens }}</span>
</template>
</Column>
<!-- <Column field="totalChatInteractions" header="Chat interactions" sortable>
<template #body="slotProps">
<span>{{ slotProps.data.totalChatInteractions }}</span>
</template>
</Column> -->
</DataTable>
</div>
<!-- Tabella Execution List -->
<div class="mb-8">
<DataTable
:value="executions"
ref="executionTable"
:totalRecords="executions.length"
v-model:expandedRows="expandedRows"
@rowExpand="onRowExpand"
@rowCollapse="onRowCollapse"
:paginator="true"
:rows="10"
paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink CurrentPageReport RowsPerPageDropdown"
currentPageReportTemplate="Showing {first} to {last} of {totalRecords} records"
:rowsPerPageOptions="[10, 15, 20, 50, 100]"
dataKey="id"
:rowHover="true"
rowGroupMode="subheader"
:sortOrder="1"
filterDisplay="menu"
responsiveLayout="scroll"
>
<template #header>
<div class="execution-section flex justify-between items-center">
<h3 class="text-xl font-bold mt-6">Execution List</h3>
<Button label="Export" icon="pi pi-download" class="p-button-purple" @click="exportExecutions" />
</div>
</template>
<Column field="startDate" header="StartDate" sortable :exportFunction="(row) => moment(row.startDate || row.data?.startDate || row.data).format('DD-MM-YYYY HH:mm:ss')">
<template #body="slotProps">
{{ moment(slotProps.data.startDate).format('DD-MM-YYYY HH:mm:ss') }}
</template>
</Column>
<Column field="executedByUsername" header="User" sortable :exportFunction="(row) => row.executedByUsername || ''">
<template #body="slotProps">
{{ slotProps.data.executedByUsername }}
</template>
</Column>
<Column
field="execSharedMap.user_input.selected_project"
header="Project"
sortable
:exportFunction="
(row) => {
try {
return row.execSharedMap?.user_input?.selected_project || '';
} catch (e) {
return '';
}
}
"
>
<template #body="slotProps">
{{ slotProps.data.execSharedMap?.user_input?.selected_project || '' }}
</template>
</Column>
<Column
field="execSharedMap.user_input.selected_application"
header="App"
sortable
:exportFunction="
(row) => {
try {
return row.execSharedMap?.user_input?.selected_application || '';
} catch (e) {
return '';
}
}
"
>
<template #body="slotProps">
{{ slotProps.data.execSharedMap?.user_input?.selected_application || '' }}
</template>
</Column>
<Column
field="scenario.name"
header="Scenario Name"
sortable
:exportFunction="
(row) => {
try {
return row.scenario?.name || '';
} catch (e) {
return '';
}
}
"
>
<template #body="slotProps">
{{ slotProps.data.scenario?.name || '' }}
</template>
</Column>
<Column
field="scenario.category"
header="Scenario Type"
sortable
:exportFunction="
(row) => {
try {
return row.scenario?.category || '';
} catch (e) {
return '';
}
}
"
>
<template #body="slotProps">
<div class="flex items-center gap-2">
{{ slotProps.data.scenario?.category || '' }}
<i class="pi pi-info-circle text-violet-600 cursor-pointer" v-tooltip="'Tipo di scenario: ' + (slotProps.data.scenario?.category || '')"></i>
</div>
</template>
</Column>
<Column
field="scenario.aiModel.model"
header="Model"
sortable
:exportFunction="
(row) => {
try {
return row.scenario?.aiModel?.model || '';
} catch (e) {
return '';
}
}
"
>
<template #body="slotProps">
{{ slotProps.data.scenario?.aiModel?.model || '' }}
</template>
</Column>
<Column field="usedTokens" header="Token" sortable :exportFunction="(row) => row.usedTokens || ''">
<template #body="slotProps">
{{ slotProps.data.usedTokens || '' }}
</template>
</Column>
<Column field="rating" header="Rating" sortable :exportFunction="(row) => row.rating || ''">
<template #body="slotProps">
<Rating :modelValue="slotProps.data.rating" :stars="5" :readonly="true" />
</template>
</Column>
<Column
field="elapsed"
header="Elapsed"
sortable
:exportFunction="
(row) => {
if (!row.endDate || !row.startDate) return '';
const elapsed = (new Date(row.endDate) - new Date(row.startDate)) / 1000;
return formatElapsed(elapsed);
}
"
>
<template #body="slotProps">
{{ formatElapsed((new Date(slotProps.data.endDate) - new Date(slotProps.data.startDate)) / 1000) }}
</template>
</Column>
<Column
field="response"
header="Response"
sortable
:exportFunction="
(row) => {
if (row.latestStepStatus === 'ERROR') return 'N';
if (row.status === 'ERROR') return 'N';
return 'Y';
}
"
>
<template #body="slotProps">
{{ slotProps.data.latestStepStatus === 'ERROR' ? 'N' : 'Y' }}
</template>
</Column>
<template #empty>
<tr>
<td :colspan="12" class="text-center">No execution found</td>
</tr>
</template>
</DataTable>
</div>
<!-- Tabella Chat List -->
<!-- <div class="mb-8">
<DataTable
:value="paginatedChats"
ref="chatTable"
:paginator="true"
:rows="rowsPerPage"
:totalRecords="chats.length"
:first="(currentPageExecutions - 1) * rowsPerPage"
paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink CurrentPageReport RowsPerPageDropdown"
currentPageReportTemplate="Showing {first} to {last} of {totalRecords} records"
:sortOrder="1" filterDisplay="menu"
tableStyle="min-width: 70rem"
@page="onPage"
@sort="onSort"
removableSort
responsiveLayout="scroll"
>
<template #header>
<div class="chat-section flex justify-between items-center mt-4">
<h3 class="text-xl font-bold mt-6">Chat List</h3>
<Button label="Export" icon="pi pi-download" class="p-button-purple" @click="exportChats" />
</div>
</template>
<Column field="utente" header="User" sortable></Column>
<Column field="chat" header="Chat" sortable></Column>
<Column field="currentMessage" header="Current Message" sortable></Column>
<Column field="scenarioChat" header="Scenario Chat" sortable></Column>
<Column field="scenario" header="Scenario Name" sortable></Column>
<template #empty>
<tr>
<td :colspan="5" class="text-center">No chat found</td>
</tr>
</template>
</DataTable>
</div> -->
</div>
<div v-if="loading_data" class="flex justify-center">
<ProgressSpinner style="width: 30px; height: 30px; margin: 30px" strokeWidth="6" fill="transparent" />
</div>
</div>
</div>
</template>
<script setup>
import { DashboardScenarioStore } from '@/stores/dashboard/DashboardScenarioStore';
import { UserPrefStore } from '@/stores/UserPrefStore';
import moment from 'moment';
import { Rating } from 'primevue';
import ProgressSpinner from 'primevue/progressspinner';
import { useToast } from 'primevue/usetoast';
import { computed, onMounted, ref, watch } from 'vue';
import { useRouter } from 'vue-router';
const loading = ref(false);
const loading_data = ref(false);
const showTables = ref(false);
const dateRange = ref(null);
const selectedAccount = ref(null);
const selectedProjects = ref([]);
const executions = ref([]);
const chats = ref([]);
const currentPageExecutions = ref(1);
const currentPageChats = ref(1);
const rowsPerPage = ref(100);
const executionTable = ref(null);
const chatTable = ref(null);
const router = useRouter();
const toast = useToast();
const dashboardScenarioStore = DashboardScenarioStore();
console.log(dashboardScenarioStore);
const userPrefStore = UserPrefStore();
const filteredProjectOption = ref([]);
const projectOptions = ref([]);
const filteredProjectMulti = ref([]);
const selectedApplications = ref([]);
const selectedUsers = ref([]);
const appOptions = ref([]);
const userOptions = ref([]);
const selectedScenarios = ref([]);
const scenarioOptions = ref([]);
const filteredProjects = ref([]);
const dashboardResponse = ref(null);
const totalExecutions = ref(null);
const totalTokens = ref(null);
const loadingUsers = ref(false);
function formatElapsed(seconds) {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return mins > 0 ? `${mins}m ${secs}s` : `${secs}s`;
}
// Computed accounts unici da projectOptions
const uniqueAccounts = computed(() => {
const lstProjects = userPrefStore.user?.lstProjects || [];
const accounts = [...new Set(lstProjects.map((p) => p.account).filter(Boolean))];
console.log('Unique accounts:', accounts);
return accounts.map((acc) => ({ name: acc, id: acc })); // usa anche `id` se serve per la picklist
});
// Filter progetti per account selezionato
// const filteredProjects = computed(() => {
// if (!projectOptions.value) return []
// let filteredList = projectOptions.value
// if (selectedAccount.value) {
// filteredList = filteredList.filter(p => p.account === selectedAccount.value.name)
// }
// return filteredList;
// })
const onPage = (event) => {
currentPageExecutions.value = event.page + 1;
// currentPageChats.value = event.page + 1
console.log('Page changed:', event);
};
const exportExecutions = () => {
if (!executions.value.length) {
toast.add({
severity: 'warn',
summary: 'Warning',
detail: 'No data to export'
});
return;
}
// Converte i dati Proxy in oggetti normali per l'export
const dataForExport = executions.value.map((row) => ({
StartDate: moment(row.startDate).format('DD-MM-YYYY HH:mm:ss'),
User: row.executedByUsername || '',
Project: row.execSharedMap?.user_input?.selected_project || '',
App: row.execSharedMap?.user_input?.selected_application || '',
'Scenario Name': row.scenario?.name || '',
'Scenario Type': row.scenario?.category || '',
Model: row.scenario?.aiModel?.model || '',
Token: row.usedTokens || '',
Rating: row.rating || '',
Elapsed: row.endDate && row.startDate ? formatElapsed((new Date(row.endDate) - new Date(row.startDate)) / 1000) : '',
Response: row.latestStepStatus === 'ERROR' ? 'N' : 'Y'
}));
// Crea il CSV manualmente
const headers = Object.keys(dataForExport[0]);
const csvContent = [headers.join(';'), ...dataForExport.map((row) => headers.map((header) => `"${row[header]}"`).join(';'))].join('\n');
// Download del file
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', `Executions_${moment().format('YYYY-MM-DD_mm-ss')}.csv`);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};
const exportAggregations = () => {
if (!dashboardResponse.value || dashboardResponse.value.length === 0) {
toast.add({
severity: 'warn',
summary: 'Warning',
detail: 'No aggregation data to export'
});
return;
}
const dataForExport = dashboardResponse.value.map((item) => ({
'Project Name': item.projectName,
'Scenario Name': item.scenarioInternalName,
'Total Executions': item.totalExecutions,
'Total Tokens': item.totalTokens
}));
const headers = Object.keys(dataForExport[0]);
const csvContent = [headers.join(';'), ...dataForExport.map((row) => headers.map((header) => `"${row[header]}"`).join(';'))].join('\n');
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', `Aggregations_${moment().format('DD-MM-YYYY_HH-mm-ss')}.csv`);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};
// const exportChats = () => {
// if (chatTable.value) chatTable.value.exportCSV()
// }
// Caricamento dati con filtri
const loadData = async () => {
loading_data.value = true;
try {
const filterParams = {
// Date
dateFrom: dateRange.value?.[0] ? dateRange.value[0].toISOString().split('T')[0] : null,
dateTo: dateRange.value?.[1] ? dateRange.value[1].toISOString().split('T')[0] : null,
account: selectedAccount.value || null,
// Progetti
projectIds: selectedProjects.value.length ? selectedProjects.value.map((id) => String(id)) : [],
projectNames: selectedProjects.value.length
? selectedProjects.value
.map((id) => {
const project = projectOptions.value.find((p) => p.id === id);
return project ? project.fe_name : null;
})
.filter(Boolean)
: [],
// // Applicazioni
// applicationIds: selectedApplications.value.length
// ? selectedApplications.value.map(id => String(id))
// : [],
// applicationNames: selectedApplications.value.length
// ? selectedApplications.value
// .map(id => {
// const app = appOptions.value.find(a => a.id === id)
// return app ? app.fe_name : null
// })
// .filter(Boolean)
// : [],
// // Utenti
// userIds: selectedUsers.value.length
// ? selectedUsers.value.map(id => String(id))
// : [],
// userNames: selectedUsers.value.length
// ? selectedUsers.value
// .map(id => {
// const user = userOptions.value.find(u => u.id === id)
// return user ? user.username : null
// })
// .filter(Boolean)
// : [],
// Scenari
scenarioIds: selectedScenarios.value.length ? selectedScenarios.value.map((id) => String(id)) : [],
scenarioNames: selectedScenarios.value.length
? selectedScenarios.value
.map((id) => {
const scenario = scenarioOptions.value.find((s) => s.id === id);
return scenario ? scenario.name : null;
})
.filter(Boolean)
: []
};
console.log('Filter parameters:', filterParams);
const chatParams = {
dateFrom: dateRange.value?.[0] ? dateRange.value[0].toISOString().split('T')[0] : null,
dateTo: dateRange.value?.[1] ? dateRange.value[1].toISOString().split('T')[0] : null,
projectIds: selectedProjects.value.length ? selectedProjects.value.map((id) => String(id)) : [],
projectNames: selectedProjects.value.length
? selectedProjects.value
.map((id) => {
const project = projectOptions.value.find((p) => p.id === id);
return project ? project.fe_name : null;
})
.filter(Boolean)
: [],
scenarioIds: selectedScenarios.value.length ? selectedScenarios.value.map((id) => String(id)) : []
};
console.log('Chat parameters:', chatParams);
await dashboardScenarioStore.loadExecutions(filterParams);
await dashboardScenarioStore.loadExecutionsStats(filterParams);
executions.value = dashboardScenarioStore.executions;
console.log('Executions:', executions);
dashboardResponse.value = dashboardScenarioStore.dashboardResponse;
console.log('Execution Stats:', dashboardResponse);
} catch (error) {
toast.add({
severity: 'error',
summary: 'Error',
detail: 'Error while loading data'
});
console.error(error);
} finally {
loading_data.value = false;
}
};
const tableData = computed(() => {
const data = dashboardResponse?.value;
if (!data || (!data.totalExecutions && !data.totalTokens)) {
return [];
}
return [
{
project: data.project,
scenario: data.scenario,
totalExecutions: data.totalExecutions,
totalTokens: data.totalTokens,
totalChatInteractions: data.totalChatInteractions
}
];
});
// Validazione range data max 30 giorni
const validateDateRange = () => {
if (dateRange.value && dateRange.value[0] && dateRange.value[1]) {
const start = new Date(dateRange.value[0]);
const end = new Date(dateRange.value[1]);
const diffInDays = (end - start) / (1000 * 60 * 60 * 24);
if (diffInDays > 30) {
toast.add({
severity: 'warn',
summary: 'Range Too Wide!',
detail: 'You can select a maximum of 30 days',
life: 5000
});
return false;
}
}
return true;
};
// Applica filtro e carica dati
const onApplyFilters = async () => {
if (!validateDateRange()) return;
currentPageExecutions.value = 1;
showTables.value = false;
await loadData();
showTables.value = true;
};
// Funzione per cancellare filtri
const onClearFilters = () => {
dateRange.value = null;
selectedAccount.value = null;
selectedProjects.value = [];
selectedApplications.value = [];
selectedUsers.value = [];
selectedScenarios.value = [];
showTables.value = false;
};
// Al montaggio carica progetti
onMounted(async () => {
loading.value = true;
try {
const today = new Date();
const sevenDaysAgo = new Date();
sevenDaysAgo.setDate(today.getDate() - 7);
dateRange.value = [sevenDaysAgo, today];
await userPrefStore.fetchUserData();
const lstProjects = userPrefStore.user?.lstProjects || [];
console.log('Projects loaded:', lstProjects);
projectOptions.value = lstProjects.filter((p) => p.fe_name).map((p) => ({ ...p }));
selectedProjects.value = [];
// Popola appOptions in base alle applicazioni nei progetti
const applicationMap = new Map();
lstProjects.forEach((project) => {
if (Array.isArray(project.lstApplications)) {
project.lstApplications.forEach((app) => {
// Evita duplicati usando la mappa con ID come chiave
if (!applicationMap.has(app.id)) {
applicationMap.set(app.id, app);
}
});
}
});
appOptions.value = Array.from(applicationMap.values());
// await dashboardScenarioStore.loadChats() //per caricare le chat
} catch (e) {
toast.add({
severity: 'error',
summary: 'Error',
detail: 'Error loading data',
life: 3000
});
} finally {
loading.value = false;
}
});
// watch(selectedProjects, (newSelectedProjects) => {
// if (newSelectedProjects && newSelectedProjects.length > 0) {
// filterAppsByProjects(newSelectedProjects)
// filterUsersByProjects(newSelectedProjects)
// } else {
// // Nessun progetto selezionato, svuota le app e gli utenti
// appOptions.value = []
// userOptions.value = []
// }
// })
watch(selectedAccount, (newAccount) => {
if (newAccount) {
filteredProjects.value = userPrefStore.user?.lstProjects.filter((p) => p.account === newAccount);
const appMap = new Map();
console.log('Filtered projects:', filteredProjects.value);
filteredProjects.value.forEach((project, index) => {
if (Array.isArray(project.lstApplications)) {
project.lstApplications.forEach((app) => {
if (!appMap.has(app.id)) {
appMap.set(app.id, app);
}
});
}
});
appOptions.value = Array.from(appMap.values());
loadScenariosByAccount(newAccount);
} else {
appOptions.value = [];
}
});
//watch per app e utenti selezionati
// watch(selectedApplications, (newApps) => {
// console.log('Applicazioni selezionate:', newApps)
// })
// watch(selectedUsers, (newUsers) => {
// if (newUsers && newUsers.length > 0) {
// console.log('Utenti selezionati:', newUsers)
// } else {
// console.log('Nessun utente selezionato')
// }
// })
watch(selectedScenarios, (newScenarios) => {
if (newScenarios && newScenarios.length > 0) {
console.log('Selected scenarios:', newScenarios);
} else {
console.log('No scenario selected');
}
});
//Nuova funzione
function filterAppsByProjects(projectIds) {
const lstProjects = userPrefStore.user?.lstProjects || [];
appOptions.value = lstProjects.filter((project) => projectIds.includes(project.id)).flatMap((project) => project.lstApplications || []);
console.log('App options:', appOptions.value);
}
//funzione User
async function filterUsersByProjects(projectIds) {
try {
loadingUsers.value = true;
if (!projectIds || projectIds.length === 0) {
userOptions.value = [];
loadingUsers.value = false;
return;
}
const projectIdsArr = projectIds.join(',');
const response = await dashboardScenarioStore.loadUsers(projectIdsArr);
console.log('Users:', response.data);
userOptions.value = response.data.map((user) => ({ ...user }));
} catch (error) {
console.error('Errore nel caricamento utenti:', error);
userOptions.value = [];
} finally {
loadingUsers.value = false;
}
}
console.log('Selected scenarios:', selectedScenarios);
//function per Scenarios
async function loadScenariosByAccount(selectedAccount) {
try {
await dashboardScenarioStore.loadScenarios(selectedAccount);
scenarioOptions.value = dashboardScenarioStore.allScenarios.map((scenario) => ({ ...scenario }));
console.log('Scenarios loaded:', scenarioOptions.value);
} catch (error) {
console.error('Errore nel caricamento scenari:', error);
scenarioOptions.value = [];
}
}
function filterProjectsByAccount(account) {
const lstProjects = userPrefStore.user?.lstProjects || [];
// Filtra i progetti associati all'account selezionato
const filteredProjectMulti = lstProjects.filter((p) => p.account === account.name || p.account === account);
console.log('Filtered projects:', filteredProjectMulti);
projectOptions.value = filteredProjectMulti.map((p) => ({ ...p }));
selectedProjects.value = projectOptions.value.map((p) => p.id);
// Reset delle applicazioni e utenti quando cambia l'account
appOptions.value = [];
userOptions.value = [];
selectedApplications.value = [];
selectedUsers.value = [];
}
</script>
<style scoped>
.dash-container {
max-width: 1400px;
margin: 0 auto;
padding: 0 20px;
}
.dash-header {
margin-bottom: 2rem;
padding-top: 1rem;
}
.dash-title {
font-size: 2rem;
font-weight: 600;
margin: 0;
}
.dash-execution {
padding: 0;
}
.filter-section {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: auto auto auto;
gap: 1rem;
margin-bottom: 1.5rem;
border-bottom: 1px solid #e5e7eb;
padding-bottom: 1rem;
}
.filter-label {
font-weight: bold;
margin-bottom: 0.25rem;
}
.uniform-input {
width: 100% !important;
height: 42px !important;
}
.execution-section,
.chat-section {
margin-top: 1rem;
margin-bottom: 30px;
}
.mt-4 {
margin-top: 20px;
}
.p-paginator-rpp-options {
display: none !important;
}
.filter-item {
display: flex;
flex-direction: column;
position: relative;
}
/* Posizionamento specifico per ogni elemento */
.filter-item:nth-child(1) {
grid-column: 1;
grid-row: 1;
}
.filter-item:nth-child(2) {
grid-column: 2;
grid-row: 1;
}
.filter-item:nth-child(3) {
grid-column: 1;
grid-row: 2;
}
.filter-item:nth-child(4) {
grid-column: 2;
grid-row: 2;
}
.apply-button-container {
grid-column: 1 / -1;
grid-row: 3;
justify-self: end;
align-self: end;
margin-top: 0.5rem;
}
.filter-label {
font-weight: bold;
}
.user-loading-overlay {
position: absolute;
bottom: -22px;
left: 0;
font-size: 0.85em;
color: gray;
display: flex;
align-items: center;
pointer-events: none;
}
.button-group {
display: flex;
gap: 0.5rem;
align-items: center;
}
.clear-button {
background-color: transparent !important;
border: 1px solid #6b7280 !important;
color: #6b7280 !important;
}
.clear-button:hover {
background-color: #f3f4f6 !important;
border-color: #4b5563 !important;
color: #4b5563 !important;
}
</style>