233 lines
7.6 KiB
Vue
233 lines
7.6 KiB
Vue
<template>
|
|
<Dialog :visible="issusModal" @update:visible="$emit('update:isVisible', $event)" modal :style="{ width: '90vw', height: '90vh' }" :contentClass="contentClass">
|
|
<template #header>
|
|
<div class=" py-5">
|
|
<p class="text-base font-bold">Non-conformance Issue</p>
|
|
</div>
|
|
</template>
|
|
<div class="h-full flex items-start justify-start p-4">
|
|
<!-- Trace List -->
|
|
<section class="w-80 h-full pr-4">
|
|
<p class="h2 px-2 mb-2">Trace List ({{ traceTotal }})</p>
|
|
<p class="text-primary h2 px-2 mb-2">
|
|
<span class="material-symbols-outlined text-sm align-[-10%] mr-2">info</span>Click trace number to see more.
|
|
</p>
|
|
<div class="overflow-y-scroll overflow-x-hidden scrollbar mx-[-8px] max-h-[calc(100%_-_96px)]" >
|
|
<table class="border-separate border-spacing-x-2 text-sm">
|
|
<thead class="sticky top-0 z-10 bg-neutral-100">
|
|
<tr>
|
|
<th class="h2 px-2 border-b border-neutral-500">Trace</th>
|
|
<th class="h2 px-2 border-b border-neutral-500 text-start" colspan="3">Occurrences</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="(trace, key) in traceList" :key="key" class=" cursor-pointer hover:text-primary" @click="switchCaseData(trace.id)">
|
|
<td class="p-2">#{{ trace.id }}</td>
|
|
<td class="p-2 w-24">
|
|
<div class="h-4 w-full bg-neutral-300 rounded-sm overflow-hidden">
|
|
<div class="h-full bg-primary" :style="progressWidth(trace.value)"></div>
|
|
</div>
|
|
</td>
|
|
<td class="py-2 text-right">{{ trace.count }}</td>
|
|
<td class="p-2">{{ trace.ratio }}%</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
<!-- Trace item Table -->
|
|
<section class="px-4 py-2 h-full w-[calc(100%_-_320px)] bg-neutral-10 rounded-xl">
|
|
<p class="h2 mb-2 px-4">Trace #{{ showTraceId }}</p>
|
|
<div class="h-52 w-full px-2 mb-2 border border-neutral-300 rounded">
|
|
<div class="h-full w-full">
|
|
<!-- <div id="cfmTrace" ref="cfmTrace" class="h-full min-w-full relative"></div> -->
|
|
</div>
|
|
</div>
|
|
<div class="overflow-y-auto overflow-x-auto scrollbar h-[calc(100%_-_264px)]">
|
|
<!-- <DataTable :value="caseData" showGridlines tableClass="text-sm" breakpoint="0">
|
|
<div v-for="(col, index) in columnData" :key="index">
|
|
<Column :field="col.field" :header="col.header" sortable></Column>
|
|
</div>
|
|
</DataTable> -->
|
|
<!-- <infinite-loading @infinite="infiniteHandler"></infinite-loading> -->
|
|
<!-- <div v-if="!maxItems">Loading more...</div>
|
|
<div v-else>No more data available.</div> -->
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</Dialog>
|
|
</template>
|
|
<script>
|
|
import { storeToRefs } from 'pinia';
|
|
import ConformanceStore from '@/stores/conformance.js';
|
|
import cytoscapeMapTrace from '@/module/cytoscapeMapTrace.js';
|
|
|
|
export default {
|
|
props: ['issusModal', 'issusNo'],
|
|
setup() {
|
|
const conformanceStore = ConformanceStore();
|
|
const { issueTraces, taskSeq, cases } = storeToRefs(conformanceStore);
|
|
|
|
return { issueTraces, taskSeq, cases, conformanceStore }
|
|
},
|
|
data() {
|
|
return {
|
|
contentClass: '!bg-neutral-100 border-t border-neutral-300 h-full',
|
|
showTraceId: 1,
|
|
infiniteData: [],
|
|
busy: false,
|
|
maxItems: false,
|
|
start: 0,
|
|
pageSize: 20,
|
|
processMap:{
|
|
nodes:[],
|
|
edges:[],
|
|
},
|
|
}
|
|
},
|
|
// directives: {
|
|
// infiniteScroll, // 注册 vue-infinite-scroll 指令
|
|
// },
|
|
computed: {
|
|
traceTotal: function() {
|
|
return this.traceList.length;
|
|
},
|
|
traceList: function() {
|
|
let sum = this.issueTraces.map(trace => trace.count).reduce((acc, cur) => acc + cur, 0);
|
|
|
|
return this.issueTraces.map(trace => {
|
|
return {
|
|
id: trace.id,
|
|
value: Number((this.getPercentLabel(trace.count / sum))),
|
|
count: trace.count,
|
|
ratio: this.getPercentLabel(trace.count / sum),
|
|
};
|
|
}).sort((x, y) => x.id - y.id);
|
|
},
|
|
caseData: function() {
|
|
const data = JSON.parse(JSON.stringify(this.cases)); // 深拷貝原始 cases 的內容
|
|
data.forEach(item => {
|
|
item.attributes.forEach((attribute, index) => {
|
|
item[`att_${index}`] = attribute.value; // 建立新的 key-value pair
|
|
});
|
|
delete item.attributes; // 刪除原本的 attributes 屬性
|
|
})
|
|
return data;
|
|
},
|
|
columnData: function() {
|
|
const data = JSON.parse(JSON.stringify(this.cases)); // 深拷貝原始 cases 的內容
|
|
const result = [
|
|
{ field: 'id', header: 'Case Id' },
|
|
{ field: 'started_at', header: 'Start Date' },
|
|
{ field: 'completed_at', header: 'End Date' },
|
|
...data[0].attributes.map((att, index) => ({ field: `att_${index}`, header: att.key })),
|
|
];
|
|
return result
|
|
},
|
|
},
|
|
methods: {
|
|
/**
|
|
* 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}%;`
|
|
},
|
|
/**
|
|
* switch case data
|
|
* @param {number} id
|
|
*/
|
|
switchCaseData(id) {
|
|
this.showTraceId = id;
|
|
this.conformanceStore.getLogConformanceTraceDetail(this.issusNo, id, 0, 20)
|
|
},
|
|
/**
|
|
* 將 trace element nodes 資料彙整
|
|
*/
|
|
setNodesData(){
|
|
// 避免每次渲染都重複累加
|
|
this.processMap.nodes = [];
|
|
// 將 api call 回來的資料帶進 node
|
|
this.taskSeq.forEach((node, index) => {
|
|
this.processMap.nodes.push({
|
|
data: {
|
|
id: index,
|
|
label: node,
|
|
backgroundColor: '#CCE5FF',
|
|
bordercolor: '#003366',
|
|
shape: 'round-rectangle',
|
|
height: 80,
|
|
width: 100
|
|
}
|
|
});
|
|
})
|
|
},
|
|
/**
|
|
* 將 trace edge line 資料彙整
|
|
*/
|
|
setEdgesData(){
|
|
this.processMap.edges = [];
|
|
this.taskSeq.forEach((edge, index) => {
|
|
this.processMap.edges.push({
|
|
data: {
|
|
source: `${index}`,
|
|
target: `${index + 1}`,
|
|
lineWidth: 1,
|
|
style: 'solid'
|
|
}
|
|
});
|
|
});
|
|
// 關係線數量筆節點少一個
|
|
this.processMap.edges.pop();
|
|
},
|
|
/**
|
|
* create trace cytoscape's map
|
|
*/
|
|
createCy(){
|
|
this.$nextTick(() => {
|
|
let graphId = this.$refs.cfmTrace;
|
|
|
|
this.setNodesData();
|
|
this.setEdgesData();
|
|
cytoscapeMapTrace(this.processMap.nodes, this.processMap.edges, graphId);
|
|
});
|
|
},
|
|
async infiniteHandler() {
|
|
console.log('TT');
|
|
|
|
try {
|
|
await this.conformanceStore.getLogConformanceTraceDetail(this.issusNo, this.issueTraces[0], this.start, this.pageSize)
|
|
this.cases = [...this.infiniteData, ...this.cases];
|
|
console.log(this.cases);
|
|
(this.cases.length < this.pageSize) ? this.maxItems = true : this.pageSize += 20;
|
|
$state.loaded();
|
|
} catch(error) {
|
|
if (error.response && error.response.status === 404) {
|
|
$state.complete();
|
|
} else {
|
|
console.error('Failed to load data:', error);
|
|
}
|
|
} finally {
|
|
this.busy = false;
|
|
}
|
|
},
|
|
},
|
|
watch: {
|
|
taskSeq: function(newValue){
|
|
if(newValue !== null) this.createCy();
|
|
}
|
|
},
|
|
mounted(){
|
|
},
|
|
}
|
|
</script>
|