Discover: sidebarFilter layout done
This commit is contained in:
347
src/components/Discover/sidebarFilter.vue
Normal file
347
src/components/Discover/sidebarFilter.vue
Normal file
@@ -0,0 +1,347 @@
|
||||
<template>
|
||||
<Sidebar :visible="sidebarFilter" :closeIcon="'pi pi-chevron-left'" :modal="false" position="left" :dismissable="true" class="!w-11/12 !bg-neutral-100">
|
||||
<template #header>
|
||||
<ul class="flex space-x-4">
|
||||
<li class="h1 border-r-2 border-neutral-300 pr-4 cursor-pointer hover:text-neutral-900 hover:duration-700" @click="switchTab('filter')" :class="tab === 'filter'? 'text-neutral-900': 'text-neutral-500'">Filter</li>
|
||||
<li class="h1 border-r-2 border-neutral-300 pr-4 cursor-pointer hover:text-neutral-900 hover:duration-700" @click="switchTab('funnel')" :class="tab === 'funnel'? 'text-neutral-900': 'text-neutral-500'">Funnel</li>
|
||||
</ul>
|
||||
</template>
|
||||
<!-- header: filter -->
|
||||
<div v-if="tab === 'filter'" class="pt-4 bg-neutral-100 flex w-full h-full">
|
||||
<!-- title: filter silect -->
|
||||
<div class="space-y-2 mr-4 w-56">
|
||||
<div>
|
||||
<p class="h2">Filter Type</p>
|
||||
<div v-for="(item, index) in selectFilter['Filter Type']" :key="index" class="flex align-items-center">
|
||||
<RadioButton v-model="selectValue[0]" :inputId="item + index" name="Filter Type" :value="item" />
|
||||
<label :for="item + index" class="ml-2">{{ item }}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="selectValue[0] === 'Sequence'">
|
||||
<p class="h2">Activity Sequence</p>
|
||||
<div v-for="(item, index) in selectFilter['Activity Sequence']" :key="index" class="flex align-items-center">
|
||||
<RadioButton v-model="selectValue[1]" :inputId="item + index" name="Activity Sequence" :value="item" />
|
||||
<label :for="item + index" class="ml-2">{{ item }}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="selectValue[1] === 'Start activity & end activity'">
|
||||
<p class="h2">Start & End</p>
|
||||
<div v-for="(item, index) in selectFilter['Start & End']" :key="index" class="flex align-items-center">
|
||||
<RadioButton v-model="selectValue[2]" :inputId="item + index" name="Start & End" :value="item" />
|
||||
<label :for="item + index" class="ml-2">{{ item }}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="selectValue[0] === 'Sequence' && selectValue[1] === 'Sequence'">
|
||||
<p class="h2">Mode</p>
|
||||
<div v-for="(item, index) in selectFilter['Mode']" :key="index" class="flex align-items-center">
|
||||
<RadioButton v-model="selectValue[3]" :inputId="item + index" name="Mode" :value="item" />
|
||||
<label :for="item + index" class="ml-2">{{ item }}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="selectValue[0] === 'Attributes'">
|
||||
<p class="h2">Mode</p>
|
||||
<div v-for="(item, index) in selectFilter['ModeAtt']" :key="index" class="flex align-items-center">
|
||||
<RadioButton v-model="selectValue[4]" :inputId="item + index" name="ModeAtt" :value="item" />
|
||||
<label :for="item + index" class="ml-2">{{ item }}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p class="h2">Refine</p>
|
||||
<div v-for="(item, index) in selectFilter['Refine']" :key="index" class="flex align-items-center">
|
||||
<RadioButton v-model="selectValue[5]" :inputId="item + index" name="Refinee" :value="item" />
|
||||
<label :for="item + index" class="ml-2">{{ item }}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="selectValue[0] === 'Timeframes'">
|
||||
<p class="h2">Containment</p>
|
||||
<div v-for="(item, index) in selectFilter['Containment']" :key="index" class="flex align-items-center">
|
||||
<RadioButton v-model="selectValue[6]" :inputId="item + index" name="Containment" :value="item" />
|
||||
<label :for="item + index" class="ml-2">{{ item }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- title: Activity Select -->
|
||||
<div class="space-y-2 w-[calc(100%_-_240px)] h-[calc(100%_-_106px)]">
|
||||
<p class="h2 ml-1">Activity Select</p>
|
||||
|
||||
<!-- 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>
|
||||
<!-- Filter Start Data -->
|
||||
<ActOcc v-if="selectValue[0] === 'Sequence' && selectValue[1] === 'Start activity & end activity' && selectValue[2] === 'Start'" :tableTitle="'Start activity'" :tableData="filterStartData" :tableSelect="selectFilterStart" :progressWidth ="progressWidth" @on-row-select="onRowStart"></ActOcc>
|
||||
<!-- Filter End Data -->
|
||||
<ActOcc v-if="selectValue[0] === 'Sequence' && selectValue[1] === 'Start activity & end activity' && selectValue[2] === 'End'" :tableTitle="'End activity'" :tableData="filterEndData" :tableSelect="selectFilterEnd" :progressWidth ="progressWidth" @on-row-select="onRowEnd"></ActOcc>
|
||||
<!-- Filter Start And End Data -->
|
||||
<div v-if="selectValue[0] === 'Sequence' && selectValue[1] === 'Start activity & end activity' && selectValue[2] === 'Start & End'" class="flex justify-between items-center w-full h-full space-x-4 ">
|
||||
<ActOcc :tableTitle="'Start activity'" :tableData="filterStartToEndData" :tableSelect="selectFilterStartToEnd" :progressWidth ="progressWidth" class="w-1/2" @on-row-select="startRow"></ActOcc>
|
||||
<ActOcc :tableTitle="'End activity'" :tableData="filterEndToStartData" :tableSelect="selectFilterEndToStart" :progressWidth ="progressWidth" class="w-1/2" @on-row-select="endRow"></ActOcc>
|
||||
</div>
|
||||
<!-- Filter Sequence -->
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<!-- Button -->
|
||||
<div class="float-right space-x-4 px-4 py-2">
|
||||
<button class="btn btn-sm btn-neutral" @click="reset">Reset</button>
|
||||
<button class="btn btn-sm btn-neutral">Apply</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- header: funnel -->
|
||||
<div v-if="tab === 'funnel'"></div>
|
||||
|
||||
</Sidebar>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ActOccCase from '@/components/Discover/table/actOccCase.vue';
|
||||
import ActOcc from '@/components/Discover/table/actOcc.vue';
|
||||
import ActAndSeq from '@/components/Discover/table/actAndSeq.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
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,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectFilter: {
|
||||
'Filter Type': ['Sequence', 'Attributes', 'Trace', 'Timeframes'],
|
||||
'Activity Sequence':['Have activity(s)', 'Start activity & end activity', 'Sequence'],
|
||||
'Start & End': ['Start', 'End', 'Start & End'],
|
||||
'Mode': ['Directly follows', 'Eventually follows'],
|
||||
'ModeAtt': ['Case', 'Activity'],
|
||||
'Refine': ['Include', 'Exclude'],
|
||||
'Containment': ['Contained in', 'Started in', 'End in', 'Activity in', 'Trim'],
|
||||
},
|
||||
tab: 'filter',
|
||||
selectValue: ['Sequence', 'Have activity(s)', 'Start', 'Directly follows', 'Case', 'Include', 'Contained in'],
|
||||
selectFilterTask: null,
|
||||
selectFilterStart: null,
|
||||
selectFilterEnd: null,
|
||||
selectFilterStartToEnd: null,
|
||||
selectFilterEndToStart: null,
|
||||
listSeq: [],
|
||||
//若第一次選擇 start, 則 end 連動改變,若第一次選擇 end, 則 start 連動改變
|
||||
isStartSelected: null,
|
||||
isEndSelected: null,
|
||||
rowData: [],
|
||||
}
|
||||
},
|
||||
components: {
|
||||
ActOccCase,
|
||||
ActOcc,
|
||||
ActAndSeq,
|
||||
},
|
||||
computed: {
|
||||
// All Task
|
||||
filterTaskData: function() {
|
||||
let list = [];
|
||||
this.filterTasks.forEach((task, index) => {
|
||||
let data = {
|
||||
label: task.label,
|
||||
occ_value: Number(task.occurrence_ratio * 100),
|
||||
occurrences: Number(task.occurrences).toLocaleString('en-US'),
|
||||
occurrence_ratio: this.getPercentLabel(task.occurrence_ratio),
|
||||
case_value: Number(task.case_ratio * 100),
|
||||
cases: task.cases.toLocaleString('en-US'),
|
||||
case_ratio: this.getPercentLabel(task.case_ratio),
|
||||
};
|
||||
list.push(data);
|
||||
});
|
||||
list.sort((x, y) => y.occurrences - x.occurrences);
|
||||
return list;
|
||||
},
|
||||
// Start and End Task
|
||||
filterStartData: function() {
|
||||
return this.setActData(this.filterStartToEnd);
|
||||
},
|
||||
filterEndData: function() {
|
||||
return this.setActData(this.filterEndToStart);
|
||||
},
|
||||
filterStartToEndData: function() {
|
||||
return this.isEndSelected ? this.setStartAndEndData(this.filterEndToStart, this.rowData, 'sources') : this.setActData(this.filterStartToEnd);
|
||||
},
|
||||
filterEndToStartData: function() {
|
||||
return this.isStartSelected ? this.setStartAndEndData(this.filterStartToEnd, this.rowData, 'sinks') : this.setActData(this.filterEndToStart);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* @param {string} switch Summary or Insight
|
||||
*/
|
||||
switchTab(tab) {
|
||||
this.tab = tab;
|
||||
},
|
||||
/**
|
||||
* Number to percentage
|
||||
* @param {number} val
|
||||
* @returns {string} 轉換完成的百分比字串
|
||||
*/
|
||||
getPercentLabel(val){
|
||||
return (val * 100 === 100) ? `${val * 100}%` : `${(val * 100).toFixed(1)}%`;
|
||||
},
|
||||
/**
|
||||
* set progress bar width
|
||||
* @param {number} value
|
||||
* @returns {string} 樣式的寬度設定
|
||||
*/
|
||||
progressWidth(value){
|
||||
return `width:${value}%;`
|
||||
},
|
||||
// 調整 filterStartData / filterEndData / filterStartToEndData / filterEndToStartData 的內容
|
||||
/**
|
||||
* @param {array} array filterStartToEnd / filterEndToStart
|
||||
*/
|
||||
setActData(array) {
|
||||
let list = [];
|
||||
array.forEach((task, index) => {
|
||||
let data = {
|
||||
label: task.label,
|
||||
occ_value: Number(task.occurrence_ratio * 100),
|
||||
occurrences: Number(task.occurrences).toLocaleString('en-US'),
|
||||
occurrence_ratio: this.getPercentLabel(task.occurrence_ratio),
|
||||
};
|
||||
list.push(data);
|
||||
});
|
||||
return list;
|
||||
},
|
||||
/**
|
||||
* @param {array} select select Have activity(s) rows
|
||||
*/
|
||||
onRowAct(select){
|
||||
this.selectFilterTask = select;
|
||||
},
|
||||
/**
|
||||
* @param {object} e select Start rows
|
||||
*/
|
||||
onRowStart(e){
|
||||
this.selectFilterStart = e.data;
|
||||
},
|
||||
/**
|
||||
* @param {object} e select End rows
|
||||
*/
|
||||
onRowEnd(e){
|
||||
this.selectFilterEnd = e.data;
|
||||
},
|
||||
/**
|
||||
* @param {array} e Update List Seq
|
||||
*/
|
||||
onUpdateListSeq(listSeq) {
|
||||
console.log(this.listSeq);
|
||||
this.listSeq = listSeq;
|
||||
},
|
||||
// 在 Start & End 若第一次選擇 start, 則 end 連動改變,若第一次選擇 end, 則 start 連動改變
|
||||
/**
|
||||
* @param {object} e object contains selected row's data
|
||||
*/
|
||||
startRow(e){
|
||||
this.selectFilterStartToEnd = e.data;
|
||||
if(this.isStartSelected === null || this.isStartSelected === true){
|
||||
this.isStartSelected = true;
|
||||
this.isEndSelected = false;
|
||||
this.rowData = e.data;
|
||||
}
|
||||
},
|
||||
endRow(e) {
|
||||
this.selectFilterEndToStart = e.data;
|
||||
if(this.isEndSelected === null || this.isEndSelected === true){
|
||||
this.isEndSelected = true;
|
||||
this.isStartSelected = false;
|
||||
this.rowData = e.data;
|
||||
}
|
||||
},
|
||||
// 重新設定連動的 filterStartToEndData / filterEndToStartData 內容
|
||||
/**
|
||||
* @param {array} eventData Start or End List
|
||||
* @param {object} rowData 所選擇的 row's data
|
||||
* @param {string} event sinks / sources
|
||||
*/
|
||||
setStartAndEndData(eventData, rowData, event){
|
||||
const filterData = event === 'sinks' ? this.filterEndToStart : this.filterStartToEnd;
|
||||
const relatedItems = eventData
|
||||
.find(task => task.label === rowData.label)
|
||||
?.[event]?.filter(item => filterData.some(ele => ele.label === item))
|
||||
?.map(item => filterData.find(ele => ele.label === item));
|
||||
|
||||
if (!relatedItems) return [];
|
||||
return relatedItems.map(item => ({
|
||||
label: item.label,
|
||||
occ_value: Number(item.occurrence_ratio * 100),
|
||||
occurrences: Number(item.occurrences).toLocaleString('en-US'),
|
||||
occurrence_ratio: this.getPercentLabel(item.occurrence_ratio),
|
||||
}));
|
||||
// 以下是優化前的程式碼:
|
||||
// let list = [];
|
||||
// eventData.forEach((task) => {
|
||||
// if(task.label === rowData.label) {
|
||||
// task[event].forEach(item => {
|
||||
// const filterData = event === 'sinks' ? this.filterEndToStart : this.filterStartToEnd;
|
||||
// const element = filterData.find(ele => ele.label === item);
|
||||
// if (element !== undefined) {
|
||||
// const data = {
|
||||
// label: element.label,
|
||||
// occ_value: Number(element.occurrence_ratio * 100),
|
||||
// occurrences: Number(element.occurrences).toLocaleString('en-US'),
|
||||
// occurrence_ratio: this.getPercentLabel(element.occurrence_ratio),
|
||||
// };
|
||||
// list.push(data);
|
||||
// };
|
||||
// })
|
||||
// };
|
||||
// });
|
||||
// return list;
|
||||
},
|
||||
/**
|
||||
* 清空選項
|
||||
*/
|
||||
reset() {
|
||||
this.selectFilterTask = null;
|
||||
this.selectFilterStart = null;
|
||||
this.selectFilterEnd = null;
|
||||
this.selectFilterStartToEnd = null;
|
||||
this.selectFilterEndToStart = null;
|
||||
this.listSeq = [];
|
||||
this.isStartSelected = null;
|
||||
this.isEndSelected = null;
|
||||
// this.rowData = [];
|
||||
console.log('reset');
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style scoped>
|
||||
#searchFiles::-webkit-search-cancel-button{
|
||||
appearance: none;
|
||||
}
|
||||
</style>
|
||||
@@ -19,7 +19,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(trace, key) in traceList" :key="key" class=" cursor-pointer hover:text-primary" @dblclick="switchCaseData(trace.id)">
|
||||
<tr v-for="(trace, key) in traceList" :key="key" class=" cursor-pointer hover:text-primary" @click="switchCaseData(trace.id)">
|
||||
<td class="text-xs p-2">#{{ trace.id }}</td>
|
||||
<td class="text-xs p-2 w-24">
|
||||
<div class="h-4 w-full bg-neutral-300 rounded-sm overflow-hidden">
|
||||
@@ -37,7 +37,7 @@
|
||||
<section class="pl-4 h-full w-[calc(100%_-_320px)]">
|
||||
<p class="h2 mb-2">Trace #{{ showTraceId }}</p>
|
||||
<div class="h-52 w-full px-2 mb-2 border border-neutral-300 rounded">
|
||||
<div class="h-full w-full scrollbar overflow-x-auto">
|
||||
<div class="h-full w-full">
|
||||
<div id="cyTrace" ref="cyTrace" class="h-full min-w-full relative"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
86
src/components/Discover/table/actAndSeq.vue
Normal file
86
src/components/Discover/table/actAndSeq.vue
Normal file
@@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<!-- Activity List -->
|
||||
<div class="bg-neutral-10 border border-neutral-300 rounded-xl px-4 w-full h-full">
|
||||
<div class="flex justify-between items-center my-2 flex-wrap">
|
||||
<p class="h2">Activity List ({{ data.length }})</p>
|
||||
</div>
|
||||
<!-- Table -->
|
||||
<div class="overflow-y-auto overflow-x-auto scrollbar -mx-2 h-[calc(100%_-_64px)]">
|
||||
<table class="border-separate border-spacing-x-2 table-auto min-w-full" :class="data.length === 0? 'h-full': null">
|
||||
<thead class="sticky top-0 left-0 z-10 bg-neutral-10">
|
||||
<tr>
|
||||
<th class="text-start text-base font-semibold leading-10 px-2 border-b border-neutral-500">Activity</th>
|
||||
<th class="text-base font-semibold leading-10 px-2 border-b border-neutral-500 text-start" colspan="3">Occurrences</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<Draggable :list="data" group="people" itemKey="name" tag="tbody" animation="300" @end="onEnd">
|
||||
<template #item="{ element, index }">
|
||||
<tr>
|
||||
<td class="px-4 py-2">{{ element.label }}</td>
|
||||
<td class="px-4 py-2 w-24">
|
||||
<div class="h-4 min-w-[96px] bg-neutral-300 rounded-sm overflow-hidden">
|
||||
<div class="h-full bg-primary" :style="progressWidth(element.occ_value)"></div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-4 py-2 text-right">{{ element.occurrences }}</td>
|
||||
<td class="px-4 py-2 text-right">{{ element.occurrence_ratio }}</td>
|
||||
</tr>
|
||||
</template>
|
||||
</Draggable>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Sequence -->
|
||||
<div class="bg-neutral-10 border border-neutral-300 rounded-xl px-4 pb-4 w-full h-full relative">
|
||||
<p class="h2 border-b border-500 my-2">Sequence ({{ listSeq.length }})</p>
|
||||
<!-- No Data -->
|
||||
<div v-if="listSequence.length === 0" class="p-4 w-[calc(100%_-_32px)] h-5/6 flex justify-center items-center absolute">
|
||||
<p class="text-neutral-500">Please drag and drop activity(s) here and sort.</p>
|
||||
</div>
|
||||
<!-- Have Data -->
|
||||
<div class="py-4 m-auto w-full h-[calc(100%_-_56px)]">
|
||||
<div class="w-full h-full overflow-y-auto overflow-x-auto scrollbar px-4 text-center">
|
||||
<draggable class="h-full" :list="listSequence" group="people" itemKey="name" animation="300" @end="onEnd">
|
||||
<template #item="{ element, index }">
|
||||
<div>
|
||||
<div class="w-full p-2 border border-primary rounded text-primary">
|
||||
<span>{{ element.label }}</span>
|
||||
</div>
|
||||
<span v-show="index !== listSeq.length - 1" class="pi pi-chevron-down !text-lg inline-block py-2 "></span>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
filterTaskData: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
progressWidth: {
|
||||
type: Function,
|
||||
required: false,
|
||||
},
|
||||
listSeq: {
|
||||
type: Array,
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
listSequence: this.listSeq,
|
||||
data: this.filterTaskData,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onEnd() {
|
||||
this.$emit('update:listSeq', this.listSequence);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
71
src/components/Discover/table/actOcc.vue
Normal file
71
src/components/Discover/table/actOcc.vue
Normal file
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<div class="bg-neutral-10 border border-neutral-300 rounded-xl px-4 w-full h-full">
|
||||
<div class="flex justify-between items-center my-2 flex-wrap">
|
||||
<p class="h2">{{ tableTitle }} ({{ tableData.length }})</p>
|
||||
<!-- Search -->
|
||||
<!-- <Search></Search> -->
|
||||
</div>
|
||||
<!-- Table -->
|
||||
<div class="overflow-y-auto overflow-x-auto scrollbar -mx-2 h-[calc(100%_-_64px)]">
|
||||
<DataTable v-model:selection="select" :value="tableData" dataKey="label" tableClass="w-full !border-separate !border-spacing-x-2 !table-auto" @row-select="onRowSelect">
|
||||
<ColumnGroup type="header">
|
||||
<Row>
|
||||
<Column selectionMode="single" headerClass="w-8 !p-2 !bg-neutral-10 !border-neutral-500 sticky top-0 left-0 z-10 bg-neutral-10"></Column>
|
||||
<Column field="label" header="Activity" headerClass="!bg-neutral-10 !border-neutral-500 !py-2 sticky top-0 left-0 z-10 bg-neutral-10" sortable />
|
||||
<Column field="occ_value" header="Occurrences" headerClass="!bg-neutral-10 !border-neutral-500 !py-2 sticky top-0 left-0 z-10 bg-neutral-10" sortable :colspan="3" />
|
||||
</Row>
|
||||
</ColumnGroup>
|
||||
<Column selectionMode="single" bodyClass="!p-2 !border-0"></Column>
|
||||
<Column field="label" header="Activity" bodyClass="break-words !py-2 !border-0"></Column>
|
||||
<Column header="進度條" bodyClass="!py-2 !border-0 min-w-[96px]">
|
||||
<template #body="slotProps">
|
||||
<div class="h-4 w-full bg-neutral-300 rounded-sm overflow-hidden">
|
||||
<div class="h-full bg-primary" :style="progressWidth(slotProps.data.occ_value)"></div>
|
||||
</div>
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="occurrences" header="Occurrences" bodyClass="!text-right !py-2 !border-0"></Column>
|
||||
<Column field="occurrence_ratio" header="Occurrence Ratio" bodyClass="!text-right !py-2 !border-0"></Column>
|
||||
</DataTable>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Search from '@/components/Search.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
tableTitle: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
tableData: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
tableSelect: {
|
||||
type: [Object, Array],
|
||||
default: null
|
||||
},
|
||||
progressWidth: {
|
||||
type: Function,
|
||||
required: false,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
select: this.tableSelect,
|
||||
metaKey: true
|
||||
}
|
||||
},
|
||||
components: {
|
||||
Search,
|
||||
},
|
||||
methods: {
|
||||
onRowSelect(e) {
|
||||
this.$emit('on-row-select', e)
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
92
src/components/Discover/table/actOccCase.vue
Normal file
92
src/components/Discover/table/actOccCase.vue
Normal file
@@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<div class="bg-neutral-10 border border-neutral-300 rounded-xl px-4 h-full">
|
||||
<div class="flex justify-between items-center my-2">
|
||||
<p class="h2">{{ tableTitle }} ({{ data.length }})</p>
|
||||
<!-- Search -->
|
||||
<!-- <Search></Search> -->
|
||||
</div>
|
||||
<!-- Table -->
|
||||
<div class="overflow-y-auto overflow-x-auto scrollbar -mx-2 h-[calc(100%_-_64px)]">
|
||||
<DataTable v-model:selection="select" :value="data" tableClass="w-full !border-separate !border-spacing-x-2 !table-auto" @row-select="onRowSelect" @row-unselect="onRowUnselect" @row-select-all="onRowSelectAll" @row-unselect-all="onRowUnelectAll">
|
||||
<ColumnGroup type="header">
|
||||
<Row>
|
||||
<Column selectionMode="multiple" headerClass="w-8 !p-2 !bg-neutral-10 !border-neutral-500 sticky top-0 left-0 z-10 bg-neutral-10"></Column>
|
||||
<Column field="label" header="Activity" headerClass="!bg-neutral-10 !border-neutral-500 !py-2 sticky top-0 left-0 z-10 bg-neutral-10" sortable />
|
||||
<Column field="occ_value" header="Occurrences" headerClass="!bg-neutral-10 !border-neutral-500 !py-2 sticky top-0 left-0 z-10 bg-neutral-10" sortable :colspan="3" />
|
||||
<Column field="case_value" headerClass="!bg-neutral-10 !border-neutral-500 !py-2 sticky top-0 left-0 z-10 bg-neutral-10" header="Cases with Activity" sortable :colspan="3" />
|
||||
</Row>
|
||||
</ColumnGroup>
|
||||
<Column selectionMode="multiple" bodyClass="!p-2 !border-0"></Column>
|
||||
<Column field="label" header="Activity" bodyClass="break-words !py-2 !border-0"></Column>
|
||||
<Column header="進度條" bodyClass="!py-2 !border-0 min-w-[96px]">
|
||||
<template #body="slotProps">
|
||||
<div class="h-4 w-full bg-neutral-300 rounded-sm overflow-hidden">
|
||||
<div class="h-full bg-primary" :style="progressWidth(slotProps.data.occ_value)"></div>
|
||||
</div>
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="occurrences" header="Occurrences" bodyClass="!text-right !py-2 !border-0"></Column>
|
||||
<Column field="occurrence_ratio" header="O2" bodyClass="!text-right !py-2 !border-0"></Column>
|
||||
<Column header="進度條" bodyClass="!py-2 !border-0 min-w-[96px]">
|
||||
<template #body="slotProps">
|
||||
<div class="h-4 w-full bg-neutral-300 rounded-sm overflow-hidden">
|
||||
<div class="h-full bg-primary" :style="progressWidth(slotProps.data.case_value)"></div>
|
||||
</div>
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="cases" header="Cases with Activity" bodyClass="!text-right !py-2 !border-0"></Column>
|
||||
<Column field="case_ratio" header="C2" bodyClass="!text-right !py-2 !border-0"></Column>
|
||||
</DataTable>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Search from '@/components/Search.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
tableTitle: {
|
||||
type: String,
|
||||
require: true,
|
||||
},
|
||||
tableData: {
|
||||
type: Array,
|
||||
require: true,
|
||||
},
|
||||
tableSelect: {
|
||||
type: Array,
|
||||
require: true,
|
||||
},
|
||||
progressWidth: {
|
||||
type: Function,
|
||||
require: false,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
select: this.tableSelect,
|
||||
data: this.tableData
|
||||
}
|
||||
},
|
||||
components: {
|
||||
Search,
|
||||
},
|
||||
methods: {
|
||||
onRowSelect() {
|
||||
this.$emit('on-row-select', this.select);
|
||||
},
|
||||
onRowUnselect() {
|
||||
this.$emit('on-row-select', this.select);
|
||||
},
|
||||
onRowSelectAll(e) {
|
||||
this.select = e.data;
|
||||
this.$emit('on-row-select', this.select);
|
||||
},
|
||||
onRowUnelectAll(e) {
|
||||
this.select = null;
|
||||
this.$emit('on-row-select', this.select)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -22,7 +22,7 @@
|
||||
<span class="absolute top-2 bottom-1.5 right-0.5 flex justify-center items-center gap-2">
|
||||
<IconSetting class="w-6 h-6 cursor-pointer"></IconSetting>
|
||||
<span class="w-px h-6 block after:border after:border-neutral-300 after:content-['']"></span>
|
||||
<button class="pr-2 py-1 rounded-r-full">
|
||||
<button class="pr-2">
|
||||
<IconSearch class="w-6 h-6"></IconSearch>
|
||||
</button>
|
||||
</span>
|
||||
@@ -44,7 +44,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import filesStore from '@/stores/files.js';
|
||||
import filesStore from '@/stores/files.js';
|
||||
import IconSearch from '@/components/icons/IconSearch.vue';
|
||||
import IconSetting from '@/components/icons/IconSetting.vue';
|
||||
|
||||
|
||||
26
src/components/Search.vue
Normal file
26
src/components/Search.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<form role="search">
|
||||
<label for="searchFiles" class="mr-4 relative">
|
||||
<input type="search" id="searchFiles" placeholder="Search Activity" class="px-5 py-2 w-52 rounded-full text-sm align-middle duration-300 border bg-neutral-100 border-neutral-300 hover:border-neutral-500 focus:outline-none focus:ring focus:border-neutral-500">
|
||||
<span class="absolute top-2 bottom-0.5 right-0.5 flex justify-center items-center gap-2">
|
||||
<IconSetting class="w-6 h-6 cursor-pointer"></IconSetting>
|
||||
<span class="w-px h-6 block after:border after:border-neutral-300 after:content-['']"></span>
|
||||
<button class="pr-2">
|
||||
<IconSearch class="w-6 h-6"></IconSearch>
|
||||
</button>
|
||||
</span>
|
||||
</label>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import IconSearch from '@/components/icons/IconSearch.vue';
|
||||
import IconSetting from '@/components/icons/IconSetting.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
IconSearch,
|
||||
IconSetting
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user