Discover: Fix SidebarFilter Funnel.

This commit is contained in:
chiayin
2023-04-24 17:48:29 +08:00
parent 0cff786e9a
commit 280da0731d
10 changed files with 159 additions and 162 deletions

View File

@@ -0,0 +1,120 @@
<template>
<div class=" w-full h-full">
<div class="h-[calc(100%_-_58px)] border-b border-neutral-400 mb-2 overflow-y-auto overflow-x-auto scrollbar">
<div v-if="this.temporaryData.length === 0" class="h-full flex justify-center items-center">
<span class="text-neutral-500">No Filter.</span>
</div>
<div v-else>
<Timeline :value="ruleData">
<template #content="rule">
<div class="border-b border-neutral-300 flex justify-between items-center space-x-2">
<!-- content -->
<div class="pl-2 mb-2">
<p class="text-sm font-medium leading-5">{{ rule.item.type }}:&nbsp;<span class="text-neutral-500">{{ rule.item.label }}</span></p>
</div>
<!-- button -->
<div class="min-w-fit">
<InputSwitch v-model="rule.item.toggle" @input="isRule($event, rule.index)"/>
<button type="button" class="m-2 focus:ring focus:ring-danger/20 text-neutral-500 hover:text-danger" @click.stop="deleteRule(rule.index)">
<span class="material-symbols-outlined">delete</span>
</button>
</div>
</div>
</template>
</Timeline>
</div>
</div>
<!-- Button -->
<div class="">
<div class="float-right space-x-4 px-4 py-2">
<button type="button" class="btn btn-sm " :class="[ temporaryData.length === 0 ? 'btn-disable' : 'btn-neutral']" :disabled="temporaryData.length === 0" @click="deleteRule('all')">Delete All</button>
<button type="button" class="btn btn-sm" :class="[ temporaryData.length === 0 ? 'btn-disable' : 'btn-neutral']" :disabled="temporaryData.length === 0" @click="submitAll">Apply All</button>
</div>
</div>
</div>
</template>
<script>
import { storeToRefs } from 'pinia';
import LoadingStore from '@/stores/loading.js';
import AllMapDataStore from '@/stores/allMapData.js';
export default {
setup() {
const loadingStore = LoadingStore();
const allMapDataStore = AllMapDataStore();
const { isLoading } = storeToRefs(loadingStore);
const { hasResultRule, temporaryData, postRuleData, ruleData, isRuleData} = storeToRefs(allMapDataStore);
return { isLoading, hasResultRule, temporaryData, postRuleData, ruleData, isRuleData, allMapDataStore }
},
methods: {
/**
* @param {boolean} e ture | false
* @param {numble} index rule's index
*/
isRule(e, index){
let rule = this.isRuleData[index];
// 先取得 rule object
// 為了讓 data 順序不亂掉,將值指向 0submitAll 時再刪掉
if(!e) this.temporaryData[index] = 0;
else this.temporaryData[index] = rule;
},
// header:Funnel 刪除全部的 Funnel
deleteRule(index) {
if(index === 'all') {
this.temporaryData = [];
this.isRuleData = [];
this.$toast.success('All deleted.');
}else{
this.$toast.success(`Delete ${this.ruleData[index].label}.`);
this.temporaryData.splice(index, 1);
this.isRuleData.splice(index, 1);
this.ruleData.splice(index, 1);
}
},
// header:Funnel 發送暫存的選取資料
async submitAll() {
this.postRuleData = this.temporaryData.filter(item => item !== 0); // 取得 submit 的資料,有 toggle button 的話,找出並刪除陣列中為 0 的項目
await this.allMapDataStore.checkHasResult();
if(this.hasResultRule === null) return;
else if(this.hasResultRule) {
this.isLoading = true;
await this.allMapDataStore.addTempFilterId();
await this.allMapDataStore.getAllMapData();
await this.$emit('submit-all');
this.isLoading = false;
this.$toast.success('Filter Success. View the Map.');
}else {
this.isLoading = true;
await new Promise(resolve => setTimeout(resolve, 1000));
this.isLoading = false;
this.$toast.warning('No Data.');
};
},
},
}
</script>
<style scoped>
/* TimeLine */
:deep(.p-timeline) {
@apply leading-none my-4
}
:deep(.p-timeline-event-opposite) {
@apply hidden
}
:deep(.p-timeline-event-separator) {
@apply mx-4
}
:deep(.p-timeline-event-marker) {
@apply !bg-primary !border-primary !w-2 !h-2
}
:deep(.p-timeline-event-connector) {
@apply !bg-primary my-2 !w-[1px]
}
:deep(.p-timeline-event-content) {
@apply !px-0
}
</style>

View File

@@ -60,11 +60,9 @@
</div> </div>
</div> </div>
</div> </div>
<!-- title: Activity Select --> <!-- title: Activity Select -->
<div class="space-y-2 w-[calc(100%_-_240px)] h-[calc(100%_-_106px)]"> <div class="space-y-2 w-[calc(100%_-_240px)] h-[calc(100%_-_106px)]">
<p class="h2 ml-1">Activity Select</p> <p class="h2 ml-1">Activity Select</p>
<!-- Filter task Data--> <!-- Filter task Data-->
<ActOccCase v-if="selectValue[0] === 'Sequence' && selectValue[1] === 'Have activity(s)'" :tableTitle="'Activity List'" :tableData="filterTaskData" :tableSelect="selectFilterTask" :progressWidth ="progressWidth" @on-row-select="onRowAct"></ActOccCase> <ActOccCase v-if="selectValue[0] === 'Sequence' && selectValue[1] === 'Have activity(s)'" :tableTitle="'Activity List'" :tableData="filterTaskData" :tableSelect="selectFilterTask" :progressWidth ="progressWidth" @on-row-select="onRowAct"></ActOccCase>
<!-- Filter Start Data --> <!-- Filter Start Data -->
@@ -80,7 +78,6 @@
<div v-if="selectValue[0] === 'Sequence' && selectValue[1] === 'Sequence'" class="flex justify-between items-center w-full h-full space-x-4"> <div v-if="selectValue[0] === 'Sequence' && selectValue[1] === 'Sequence'" class="flex justify-between items-center w-full h-full space-x-4">
<ActAndSeq :filterTaskData="filterTaskData" :progressWidth ="progressWidth" :listSeq="listSeq" @update:listSeq="onUpdateListSeq"></ActAndSeq> <ActAndSeq :filterTaskData="filterTaskData" :progressWidth ="progressWidth" :listSeq="listSeq" @update:listSeq="onUpdateListSeq"></ActAndSeq>
</div> </div>
<!-- Button --> <!-- Button -->
<div class="float-right space-x-4 px-4 py-2"> <div class="float-right space-x-4 px-4 py-2">
<button type="button" class="btn btn-sm btn-neutral" @click="reset">Clear</button> <button type="button" class="btn btn-sm btn-neutral" @click="reset">Clear</button>
@@ -88,44 +85,8 @@
</div> </div>
</div> </div>
</div> </div>
<!-- header: funnel --> <!-- header: funnel -->
<div v-if="tab === 'funnel'" class=" w-full h-full"> <Funnel v-if="tab === 'funnel'" @submit-all="sumbitAll"></Funnel>
<div class="h-[calc(100%_-_58px)] border-b border-neutral-400 mb-2 overflow-y-auto overflow-x-auto scrollbar">
<div v-if="this.temporaryData.length === 0" class="h-full flex justify-center items-center">
<span class="text-neutral-500">No Filter.</span>
</div>
<div v-else>
<Timeline :value="ruleData">
<template #content="rule">
<div class="border-b border-neutral-300 flex justify-between items-center space-x-2">
<!-- content -->
<div class="pl-2 mb-2">
<p class="text-sm font-medium leading-5">{{ rule.item.type }}:&nbsp;<span class="text-neutral-500">{{ rule.item.label }}</span></p>
<!-- <p class="text-sm font-medium mb-5">{{ rule.item.type }}</p>
<p class="text-sm text-neutral-500 mb-2">{{ rule.item.label }}</p> -->
</div>
<!-- button -->
<div class="min-w-fit">
<InputSwitch v-model="rule.item.toggle" @input="isRule($event, rule.index)"/>
<button type="button" class="m-2 focus:ring focus:ring-danger/20 text-neutral-500 hover:text-danger" @click.stop="deleteRule(rule.index)">
<span class="material-symbols-outlined">delete</span>
</button>
</div>
</div>
</template>
</Timeline>
</div>
</div>
<!-- Button -->
<div class="">
<div class="float-right space-x-4 px-4 py-2">
<button type="button" class="btn btn-sm " :class="[ temporaryData.length === 0 ? 'btn-disable' : 'btn-neutral']" :disabled="temporaryData.length === 0" @click="deleteRule('all')">Delete All</button>
<button type="button" class="btn btn-sm" :class="[ temporaryData.length === 0 ? 'btn-disable' : 'btn-neutral']" :disabled="temporaryData.length === 0" @click="submitAll">Apply All</button>
</div>
</div>
</div>
</Sidebar> </Sidebar>
</template> </template>
@@ -133,44 +94,20 @@
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import LoadingStore from '@/stores/loading.js'; import LoadingStore from '@/stores/loading.js';
import AllMapDataStore from '@/stores/allMapData.js'; import AllMapDataStore from '@/stores/allMapData.js';
import ActOccCase from '@/components/Discover/table/actOccCase.vue'; import ActOccCase from '@/components/Discover/Filter/ActOccCase.vue';
import ActOcc from '@/components/Discover/table/actOcc.vue'; import ActOcc from '@/components/Discover/Filter/ActOcc.vue';
import ActAndSeq from '@/components/Discover/table/actAndSeq.vue'; import ActAndSeq from '@/components/Discover/Filter/ActAndSeq.vue';
import Funnel from '@/components/Discover/Filter/Funnel.vue';
export default { export default {
props: { props: ['sidebarFilter', 'filterTasks', 'filterStartToEnd', 'filterEndToStart', 'filterTimeframe', 'filterTrace'],
sidebarFilter: {
type: Boolean,
require: true,
},
filterTasks: {
type: Array,
require: true,
},
filterStartToEnd: {
type: Array,
require: true,
},
filterEndToStart: {
type: Array,
require: true,
},
filterTimeframe: {
type: Object,
require: true,
},
filterTrace: {
type: Array,
require: true,
},
},
setup() { setup() {
const loadingStore = LoadingStore(); const loadingStore = LoadingStore();
const allMapDataStore = AllMapDataStore(); const allMapDataStore = AllMapDataStore();
const { isLoading } = storeToRefs(loadingStore); const { isLoading } = storeToRefs(loadingStore);
const { hasResultRule, temporaryData, postRuleData} = storeToRefs(allMapDataStore); const { hasResultRule, temporaryData, postRuleData, ruleData, isRuleData} = storeToRefs(allMapDataStore);
return { isLoading, hasResultRule, temporaryData, postRuleData, allMapDataStore } return { isLoading, hasResultRule, temporaryData, postRuleData, ruleData, isRuleData, allMapDataStore }
}, },
data() { data() {
return { return {
@@ -204,14 +141,13 @@ export default {
isEndSelected: null, isEndSelected: null,
isActAllTask: true, isActAllTask: true,
rowData: [], rowData: [],
ruleData: [],
isRuleData: [], // toggle button data
} }
}, },
components: { components: {
ActOccCase, ActOccCase,
ActOcc, ActOcc,
ActAndSeq, ActAndSeq,
Funnel,
}, },
computed: { computed: {
// All Task // All Task
@@ -233,25 +169,12 @@ export default {
} }
}, },
methods: { methods: {
/** /**
* @param {string} switch Summary or Insight * @param {string} switch Summary or Insight
*/ */
switchTab(tab) { switchTab(tab) {
this.tab = tab; this.tab = tab;
}, },
/**
* @param {boolean} e ture | false
* @param {numble} index rule's index
*/
isRule(e, index){
let rule = this.isRuleData[index];
// rule object
// data 0submitAll
if(!e) this.temporaryData[index] = 0;
else this.temporaryData[index] = rule;
},
/** /**
* Number to percentage * Number to percentage
* @param {number} val * @param {number} val
@@ -371,10 +294,9 @@ export default {
/** /**
* @param {object} e task's object * @param {object} e task's object
*/ */
setRule(e) { setRule(e) {
let label = ""; let label, type;
let type = ""; const includeStr = e.is_exclude?" Exclude ":" Include ";
let includeStr = e.is_exclude?" Exclude ":" Include ";
switch(e.type){ switch(e.type){
case "contains-task": case "contains-task":
@@ -412,17 +334,15 @@ export default {
label = `from ${e.lower} to ${e.upper} ${includeStr}` label = `from ${e.lower} to ${e.upper} ${includeStr}`
type = "Trace" type = "Trace"
break break
default: };
break
}
return { return {
type:type, type,
label:label, label,
toggle:true, toggle:true,
} };
}, },
/** /**
* 清空選項 * @param {boolean} massage true | false 清空選項
*/ */
reset(massage) { reset(massage) {
this.selectFilterTask = null; this.selectFilterTask = null;
@@ -446,7 +366,7 @@ export default {
// //
if(sele[0] === 'Sequence'){ if(sele[0] === 'Sequence'){
if(sele[1] === 'Have activity(s)'){ // Activity Sequence Have activity(s) if(sele[1] === 'Have activity(s)'){ // Activity Sequence Have activity(s)
if(this.selectFilterTask === null || this.selectFilterTask.length === 0) return this.$toast.error('Not selected'); if(!this.selectFilterTask?.length) return this.$toast.error('Not selected');
else { else {
// task obj // task obj
data = this.selectFilterTask.map(task => { data = this.selectFilterTask.map(task => {
@@ -457,7 +377,7 @@ export default {
} }
}) })
}; };
}else if(sele[1] === 'Start activity / end activity') { // Activity Sequence Start activity & end activity }else if(sele[1] === 'Start activity / end activity') { // Activity Sequence Start activity / end activity
if(sele[2] === 'Start') { if(sele[2] === 'Start') {
if(this.selectFilterStart === null || this.selectFilterStart.length === 0) return this.$toast.error('Not selected'); if(this.selectFilterStart === null || this.selectFilterStart.length === 0) return this.$toast.error('Not selected');
else { else {
@@ -502,16 +422,20 @@ export default {
const postData = Array.isArray(data) ? data : [data]; const postData = Array.isArray(data) ? data : [data];
// filter // filter
// this.logId = this.$route.params.logId;
this.postRuleData = postData; this.postRuleData = postData;
await this.allMapDataStore.checkHasResult(); await this.allMapDataStore.checkHasResult();
// Data Funnel Data Funnel // Data Funnel Data Funnel
if(this.hasResultRule === null) return; if(this.hasResultRule === null) return;
else if(this.hasResultRule) { else if(this.hasResultRule) {
this.temporaryData.push(...postData); if(!this.temporaryData?.length){
this.isRuleData = Array.from(this.temporaryData); this.temporaryData.push(...postData);
this.ruleData = this.temporaryData.map(e => this.setRule(e)); this.isRuleData = Array.from(this.temporaryData);
this.ruleData = this.isRuleData.map(e => this.setRule(e));
}else {
this.isRuleData.push(...postData);
this.ruleData = this.isRuleData.map(e => this.setRule(e));
}
this.reset(false); this.reset(false);
this.isLoading = true; this.isLoading = true;
await new Promise(resolve => setTimeout(resolve, 1000)); await new Promise(resolve => setTimeout(resolve, 1000));
@@ -525,67 +449,18 @@ export default {
this.$toast.warning('No Data.'); this.$toast.warning('No Data.');
}; };
}, },
// header:Funnel Funnel /**
deleteRule(index) { * create map
if(index === 'all') { */
this.temporaryData = []; sumbitAll() {
this.isRuleData = []; this.$emit('submit-all');
this.$toast.success('All deleted.'); }
}else{
this.$toast.success(`Delete ${this.ruleData[index].label}.`);
this.temporaryData.splice(index, 1);
this.isRuleData.splice(index, 1);
this.ruleData.splice(index, 1);
}
},
// header:Funnel
async submitAll() {
this.postRuleData = this.temporaryData; // submit
this.postRuleData = this.postRuleData.filter(item => item !== 0); // toggle button 0
await this.allMapDataStore.checkHasResult();
if(this.hasResultRule === null) return;
else if(this.hasResultRule) {
this.isLoading = true;
await this.allMapDataStore.addTempFilterId();
await this.allMapDataStore.getAllMapData();
await this.$emit('submit-all');
this.isLoading = false;
this.$toast.success('Filter Success. View the Map.');
}else {
this.isLoading = true;
await new Promise(resolve => setTimeout(resolve, 1000));
this.isLoading = false;
this.$toast.warning('No Data.');
};
},
}, },
} }
</script> </script>
<style scoped> <style scoped>
#searchFiles::-webkit-search-cancel-button{ #searchFiles::-webkit-search-cancel-button{
appearance: none; appearance: none;
} }
/* TimeLine */
:deep(.p-timeline) {
@apply leading-none my-4
}
:deep(.p-timeline-event-opposite) {
@apply hidden
}
:deep(.p-timeline-event-separator) {
@apply mx-4
}
:deep(.p-timeline-event-marker) {
@apply !bg-primary !border-primary !w-2 !h-2
}
:deep(.p-timeline-event-connector) {
@apply !bg-primary my-2 !w-[1px]
}
:deep(.p-timeline-event-content) {
@apply !px-0
}
</style> </style>

View File

@@ -31,6 +31,8 @@ export default defineStore('allMapDataStore', {
hasResultRule: null, // click Apply 後檢查是否有 Data hasResultRule: null, // click Apply 後檢查是否有 Data
temporaryData: [], // 沒被 apply 的 Data temporaryData: [], // 沒被 apply 的 Data
postRuleData: [], // has-result API & temp-filters API 的 Data postRuleData: [], // has-result API & temp-filters API 的 Data
ruleData: [], // Funnle view's data
isRuleData: [], // toggle button data
}), }),
getters: { getters: {
processMap: state => { processMap: state => {

View File

@@ -58,10 +58,10 @@ import { storeToRefs } from 'pinia';
import LoadingStore from '@/stores/loading.js'; import LoadingStore from '@/stores/loading.js';
import AllMapDataStore from '@/stores/allMapData.js'; import AllMapDataStore from '@/stores/allMapData.js';
import cytoscapeMap from '@/module/cytoscapeMap.js'; import cytoscapeMap from '@/module/cytoscapeMap.js';
import SidebarView from '@/components/Discover/sidebarView.vue'; import SidebarView from '@/components/Discover/SidebarView.vue';
import SidebarState from '@/components/Discover/sidebarState.vue'; import SidebarState from '@/components/Discover/SidebarState.vue';
import SidebarTraces from '@/components/Discover/sidebarTraces.vue'; import SidebarTraces from '@/components/Discover/SidebarTraces.vue';
import SidebarFilter from '@/components/Discover/sidebarFilter.vue'; import SidebarFilter from '@/components/Discover/SidebarFilter.vue';
export default { export default {
setup() { setup() {