Conformance: Have activity Log Results chart, Effect done.

This commit is contained in:
chiayin
2023-07-20 15:45:22 +08:00
parent 97ac9535f9
commit dc4ede1d62
7 changed files with 496 additions and 182 deletions

View File

@@ -1,39 +1,45 @@
<template> <template>
<section class="p-4 mr-0.5 space-y-2 h-full w-[calc(100vw_-_316px)] overflow-y-auto scrollbar float-right"> <section class="p-4 mr-0.5 space-y-2 h-full w-[calc(100vw_-_316px)] overflow-y-auto scrollbar float-right">
<!-- cover plate -->
<div v-show="isCoverPlate" class="w-[calc(100vw_-_300px)] h-screen-main fixed bottom-0 right-0 bg-gradient-to-tr from-neutral-500/50 to-neutral-900/50 z-[1]">
</div>
<!-- title --> <!-- title -->
<p class="h2 text-base">Conformance Checking Results<span class="material-symbols-outlined text-base align-middle ml-2" v-tooltip.bottom="tooltip.results" type="text">info</span></p> <p class="h2 text-base">Conformance Checking Results<span class="material-symbols-outlined text-base align-middle ml-2" v-tooltip.bottom="tooltip.results" type="text">info</span></p>
<!-- total group --> <!-- total group -->
<ul class=" text-neutral-10 text-sm flex gap-2 py-2"> <ul class=" text-neutral-10 text-sm flex gap-2 py-2">
<li class=" bg-cfm-primary rounded-full px-4 py-1 space-x-2"> <li class=" bg-cfm-primary rounded-full px-4 py-1 space-x-2">
<span class="material-symbols-outlined text-base align-middle mr-2">check_circle</span>Conforming<span>3,890</span> <span class="material-symbols-outlined text-base align-middle mr-2">check_circle</span>Conforming<span>{{ data.counts.conforming }}</span>
</li> </li>
<li class=" bg-cfm-secondary rounded-full px-4 py-1 space-x-2"> <li class=" bg-cfm-secondary rounded-full px-4 py-1 space-x-2">
<span class="material-symbols-outlined text-base align-middle mr-2">cancel</span>Not Conforming<span>27,230</span> <span class="material-symbols-outlined text-base align-middle mr-2">cancel</span>Not Conforming<span>{{ data.counts.not_conforming }}</span>
</li> </li>
<li class=" bg-neutral-700 rounded-full px-4 py-1 space-x-2"> <li class=" bg-neutral-700 rounded-full px-4 py-1 space-x-2" v-show="data.counts.not_applicable != 0">
<iconNA class="inline-block mr-1"></iconNA>Not Applicable<span>7,780</span> <iconNA class="inline-block mr-1"></iconNA>Not Applicable<span>{{ data.counts.not_applicable }}</span>
</li> </li>
</ul> </ul>
<!-- chart --> <!-- chart -->
<div class="flex gap-4 w-full"> <div class="flex gap-4">
<div class="border rounded border-neutral-300 p-2 bg-neutral-10 basis-1/3"> <div class="border rounded border-neutral-300 p-2 bg-neutral-10 w-1/2">
<p class="h2 pl-2 flex justify-between items-center"> <p class="h2 pl-2 flex justify-between items-center">
<span>Conformance Rate<span class="material-symbols-outlined text-sm align-middle ml-2" v-tooltip.bottom="tooltip.rate">info</span></span> <span>Conformance Rate<span class="material-symbols-outlined text-sm align-middle ml-2" v-tooltip.bottom="tooltip.rate">info</span></span>
<span class="text-2xl">10%</span> <span class="text-2xl">{{ data.charts.rate.rate }}%</span>
</p> </p>
<Chart type="line" :data="rateChartData" :options="rateChartOptions" class=""/>
</div> </div>
<div class="border rounded border-neutral-300 p-2 bg-neutral-10 basis-1/3"> <div class="border rounded border-neutral-300 p-2 bg-neutral-10 w-1/2">
<p class="h2 pl-2 flex justify-between items-center"> <p class="h2 pl-2 flex justify-between items-center">
<span>Cases<span class="material-symbols-outlined text-sm align-middle ml-2">info</span></span> <span>Cases<span class="material-symbols-outlined text-sm align-middle ml-2">info</span></span>
<span class="text-2xl"><span class="text-cfm-primary">3.89K</span>/38.9K</span> <span class="text-2xl"><span class="text-cfm-primary">{{ data.charts.cases.conforming }}</span>&nbsp/&nbsp{{ data.charts.cases.total }}</span>
</p> </p>
<Chart type="bar" :data="casesChartData" :options="casesChartOptions" class=""/>
</div> </div>
<div class="border rounded border-neutral-300 p-2 bg-neutral-10 basis-1/3"> <!-- Fitness 暫時不做 basis-1/3 basis-1/2 -->
<!-- <div class="border rounded border-neutral-300 p-2 bg-neutral-10 basis-1/3">
<p class="h2 pl-2 flex justify-between items-center"> <p class="h2 pl-2 flex justify-between items-center">
<span>Fitness<span class="material-symbols-outlined text-sm align-middle ml-2">info</span></span> <span>Fitness<span class="material-symbols-outlined text-sm align-middle ml-2">info</span></span>
<span class="text-2xl">13.5</span> <span class="text-2xl">{{ data.charts.fitness }}</span>
</p> </p>
</div> </div> -->
</div> </div>
<!-- effect --> <!-- effect -->
<section> <section>
@@ -41,34 +47,56 @@
<div class="flex gap-4 w-full"> <div class="flex gap-4 w-full">
<div class="border rounded border-neutral-300 p-2 bg-neutral-10 basis-1/2"> <div class="border rounded border-neutral-300 p-2 bg-neutral-10 basis-1/2">
<p class="h2 pl-2 mb-2">Throughput Time</p> <p class="h2 pl-2 mb-2">Throughput Time</p>
<p class="pl-2 space-x-2"> <div v-if="data.effect.time !== null">
<span class="text-cfm-primary text-2xl font-medium">48</span> <p class="pl-2 space-x-2" v-if="data.effect.time.not_conforming === null">
<span>All cases are conforming to set rules. Average throughput time is</span>
<span class="text-cfm-primary text-2xl font-medium">{{ data.effect.time.conforming }}</span>
<span>days.</span>
</p>
<p class="pl-2 space-x-2" v-else-if="data.effect.time.conforming === null">
<span>None of the cases is conforming to set rules. Average throughput time is</span>
<span class="text-cfm-secondary text-2xl font-medium">{{ data.effect.time.not_conforming }}</span>
<span>days.</span>
</p>
<p class="pl-2 space-x-2 max-w-full" v-else>
<span class="text-cfm-primary text-2xl font-medium">{{ data.effect.time.conforming }}</span>
<span>vs</span> <span>vs</span>
<span class="text-cfm-secondary text-2xl font-medium">56</span> <span class="text-cfm-secondary text-2xl font-medium">{{ data.effect.time.not_conforming }}</span>
<span>days,</span> <span>days,</span>
<span class="text-2xl font-medium">-8</span> <span class="text-2xl font-medium">{{ data.effect.time.difference }}</span>
<span>days of difference.</span> <span>days of difference.</span>
</p> </p>
</div> </div>
</div>
<div class="border rounded border-neutral-300 p-2 bg-neutral-10 basis-1/2"> <div class="border rounded border-neutral-300 p-2 bg-neutral-10 basis-1/2">
<p class="h2 pl-2 mb-2">Activities per Case</p> <p class="h2 pl-2 mb-2">Activities per Case</p>
<p class="pl-2 space-x-2"> <div v-if="data.effect.tasks !== null">
<span class="text-cfm-primary text-2xl font-medium">7.0</span> <p class="pl-2 space-x-2" v-if="data.effect.tasks.not_conforming === null">
<span>All cases are conforming to set rules. Average activities in per cases is</span>
<span class="text-cfm-primary text-2xl font-medium">{{ data.effect.tasks.conforming }}</span> .
</p>
<p class="pl-2 space-x-2" v-else-if="data.effect.tasks.conforming === null">
<span>None of the cases is conforming to set rules. Average activities in per cases is</span>
<span class="text-cfm-secondary text-2xl font-medium">{{ data.effect.tasks.not_conforming }}</span> .
</p>
<p class="pl-2 space-x-2 max-w-full" v-else>
<span class="text-cfm-primary text-2xl font-medium">{{ data.effect.tasks.conforming }}</span>
<span>vs</span> <span>vs</span>
<span class="text-cfm-secondary text-2xl font-medium">3.9</span> <span class="text-cfm-secondary text-2xl font-medium">{{ data.effect.tasks.not_conforming }}</span>
<span>activities,</span> <span>activities,</span>
<span class="text-2xl font-medium">3.1</span> <span class="text-2xl font-medium">{{ data.effect.tasks.difference }}</span>
<span>activities of difference.</span> <span>activities of difference.</span>
</p> </p>
</div> </div>
</div> </div>
</div>
</section> </section>
<!-- Issues group --> <!-- Issues group -->
<section> <section>
<p class="h2 text-base">Non-conformance Issues</p> <p class="h2 text-base">Non-conformance Issues</p>
<div class="flex gap-4 w-full"> <div class="flex gap-4 w-full" v-if="data.issues !== null">
<!-- Issues chart --> <!-- Issues chart -->
<div class="border rounded border-neutral-300 p-2 bg-neutral-10 w-full"> <div class="border rounded border-neutral-300 p-2 bg-neutral-10 w-full" v-if="null">
<p class="h2 pl-2 flex justify-between items-center"> <p class="h2 pl-2 flex justify-between items-center">
<span>Time Trend<span class="material-symbols-outlined text-sm align-middle ml-2">info</span></span> <span>Time Trend<span class="material-symbols-outlined text-sm align-middle ml-2">info</span></span>
<span class="text-2xl"><span class="text-cfm-secondary">3.89K</span>/38.9K</span> <span class="text-2xl"><span class="text-cfm-secondary">3.89K</span>/38.9K</span>
@@ -79,106 +107,7 @@
<p class="h2 pl-2 mb-2">Issue List</p> <p class="h2 pl-2 mb-2">Issue List</p>
<table class="text-sm min-w-full table-fixed"> <table class="text-sm min-w-full table-fixed">
<tbody> <tbody>
<tr v-for="(trace, key) in traceList" :key="key"> <tr v-for="(trace, key) in data.issues" :key="key">
<td class="p-2 pl-6 truncate max-w-0 w-1/3">
<span class="material-symbols-outlined disc text-sm align-middle mr-1">fiber_manual_record</span>{{ trace.issue }}
</td>
<td class="p-2 min-w-[96px] w-2/5">
<div class="h-4 w-full bg-neutral-300 rounded-sm overflow-hidden">
<div class="h-full bg-primary" :style="trace.value"></div>
</div>
</td>
<td class="p-2 text-right truncate">{{ trace.count }}</td>
<td class="p-2 text-center">{{ trace.ratio }}%</td>
<td class="p-2 text-center">
<div class="btn btn-sm btn-c-primary cursor-pointer" @click="issusModal = true">More</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</section>
<!-- title -->
<p class="h2 text-base">Conformance Checking Results<span class="material-symbols-outlined text-base align-middle ml-2" v-tooltip.bottom="tooltip.results" type="text">info</span></p>
<!-- total group -->
<ul class=" text-neutral-10 text-sm flex gap-2 py-2">
<li class=" bg-cfm-primary rounded-full px-4 py-1 space-x-2">
<span class="material-symbols-outlined text-base align-middle mr-2">check_circle</span>Conformance<span>3,890</span>
</li>
<li class=" bg-cfm-secondary rounded-full px-4 py-1 space-x-2">
<span class="material-symbols-outlined text-base align-middle mr-2">cancel</span>Non-Conformance<span>27,230</span>
</li>
<li class=" bg-neutral-700 rounded-full px-4 py-1 space-x-2">
<iconNA class="inline-block mr-1"></iconNA>Not Applicable<span>7,780</span>
</li>
</ul>
<!-- chart -->
<div class="flex gap-4 w-full">
<div class="border rounded border-neutral-300 p-2 bg-neutral-10 basis-1/3">
<p class="h2 pl-2 flex justify-between items-center">
<span>Conformance Rate<span class="material-symbols-outlined text-sm align-middle ml-2" v-tooltip.bottom="tooltip.rate">info</span></span>
<span class="text-2xl">10%</span>
</p>
</div>
<div class="border rounded border-neutral-300 p-2 bg-neutral-10 basis-1/3">
<p class="h2 pl-2 flex justify-between items-center">
<span>Cases<span class="material-symbols-outlined text-sm align-middle ml-2">info</span></span>
<span class="text-2xl"><span class="text-cfm-primary">3.89K</span>/38.9K</span>
</p>
</div>
<div class="border rounded border-neutral-300 p-2 bg-neutral-10 basis-1/3">
<p class="h2 pl-2 flex justify-between items-center">
<span>Fitness<span class="material-symbols-outlined text-sm align-middle ml-2">info</span></span>
<span class="text-2xl">13.5</span>
</p>
</div>
</div>
<!-- effect -->
<section>
<p class="h2 text-base">Effect</p>
<div class="flex gap-4 w-full">
<div class="border rounded border-neutral-300 p-2 bg-neutral-10 basis-1/2">
<p class="h2 pl-2 mb-2">Throughput Time</p>
<p class="pl-2 space-x-2">
<span class="text-cfm-primary text-2xl font-medium">48</span>
<span>vs</span>
<span class="text-cfm-secondary text-2xl font-medium">56</span>
<span>days,</span>
<span class="text-2xl font-medium">-8</span>
<span>days of difference.</span>
</p>
</div>
<div class="border rounded border-neutral-300 p-2 bg-neutral-10 basis-1/2">
<p class="h2 pl-2 mb-2">Activities per Case</p>
<p class="pl-2 space-x-2">
<span class="text-cfm-primary text-2xl font-medium">7.0</span>
<span>vs</span>
<span class="text-cfm-secondary text-2xl font-medium">3.9</span>
<span>activities,</span>
<span class="text-2xl font-medium">3.1</span>
<span>activities of difference.</span>
</p>
</div>
</div>
</section>
<!-- Issues group -->
<section>
<p class="h2 text-base">Non-conformance Issues</p>
<div class="flex gap-4 w-full">
<!-- Issues chart -->
<div class="border rounded border-neutral-300 p-2 bg-neutral-10 w-full">
<p class="h2 pl-2 flex justify-between items-center">
<span>Time Trend<span class="material-symbols-outlined text-sm align-middle ml-2">info</span></span>
<span class="text-2xl"><span class="text-cfm-secondary">3.89K</span>/38.9K</span>
</p>
</div>
<!-- Issues list -->
<div class="border rounded border-neutral-300 p-2 bg-neutral-10 w-full">
<p class="h2 pl-2 mb-2">Issue List</p>
<table class="text-sm min-w-full table-fixed">
<tbody>
<tr v-for="(trace, key) in traceList" :key="key">
<td class="p-2 pl-6 truncate max-w-0 w-1/3"> <td class="p-2 pl-6 truncate max-w-0 w-1/3">
<span class="material-symbols-outlined disc text-sm align-middle mr-1">fiber_manual_record</span>{{ trace.issue }} <span class="material-symbols-outlined disc text-sm align-middle mr-1">fiber_manual_record</span>{{ trace.issue }}
</td> </td>
@@ -204,34 +133,52 @@
<script> <script>
import iconNA from '@/components/icons/IconNA.vue'; import iconNA from '@/components/icons/IconNA.vue';
import MoreModal from './MoreModal.vue'; import MoreModal from './MoreModal.vue';
import { storeToRefs } from 'pinia';
import ConformanceStore from '@/stores/conformance.js';
import getNumberLabel from '@/module/numberLabel.js';
import { setLineChartData, setBarChartData } from '@/module/setChartData.js';
import abbreviateNumber from '@/module/abbreviateNumber.js';
import getMoment from 'moment';
export default { export default {
setup() {
const conformanceStore = ConformanceStore();
const { conformanceTempReportData } = storeToRefs(conformanceStore);
return { conformanceTempReportData, conformanceStore }
},
data() { data() {
return { return {
data: {
counts: {
conforming: '--',
not_conforming: '--',
not_applicable: 0,
},
charts: {
rate: {
rate: '--',
chart: {},
},
cases: {
conforming: '--',
total: '--',
chart: {},
},
fitness: '--',
},
effect: {
time: null,
tasks: null,
},
issues: null
},
isCoverPlate: false,
issusModal: false, issusModal: false,
traceList: [ rateChartData: null,
{ rateChartOptions: null,
id: 1, casesChartData: null,
issue: 'Skip', casesChartOptions: null,
value: this.progressWidth(80),
count: '1,234,567,890',
ratio: 80,
},
{
id: 2,
issue: 'Order Switch',
value: this.progressWidth(30),
count: '567,890',
ratio: 30,
},
{
id: 3,
issue: 'Activity不包含到院、出院、出院、出院',
value: this.progressWidth(70),
count: '4,567,890',
ratio: 70,
}
],
tooltip: { tooltip: {
results: { results: {
value: 'This page will perform a conformance check based on the filtering results of the map.', value: 'This page will perform a conformance check based on the filtering results of the map.',
@@ -246,6 +193,14 @@ export default {
iconNA, iconNA,
MoreModal, MoreModal,
}, },
watch: {
conformanceTempReportData: {
handler: function(newValue) {
// this.data = newValue;
this.data = this.setConformanceTempReportData(newValue);
},
}
},
methods: { methods: {
/** /**
* set progress bar width * set progress bar width
@@ -255,7 +210,235 @@ export default {
progressWidth(value){ progressWidth(value){
return `width:${value}%;` return `width:${value}%;`
}, },
/**
* Number to percentage
* @param {number} val
* @returns {string} 轉換完成的百分比字串
*/
getPercentLabel(val){
return (val * 100 === 100) ? (val * 100) : (val * 100).toFixed(1);
},
/**
* Convert seconds to days
* @param {number} sec
* @returns {number} day
*/
convertSecToDay(sec) {
return (sec / 86400)
},
/**
* set conformance report data
* @param {object} data new watch's value
*/
setConformanceTempReportData(data){
let sum = Object.values(data.counts).reduce((acc, cur) => acc + cur, 0);
let rate = ((data.counts.conforming / sum) * 100).toFixed(1);
let isNullTime = value => value === null ? null : getNumberLabel((value / 86400).toFixed(1));
let isNullCase = value => value === null ? null : getNumberLabel(value.toFixed(1));
let result = {
counts: {
conforming: getNumberLabel(data.counts.conforming),
not_conforming: getNumberLabel(data.counts.not_conforming),
not_applicable: getNumberLabel(data.counts.not_applicable),
},
charts: {
rate: {
rate: rate,
data: setLineChartData(data.charts.rate.data, data.charts.rate.x_axis.max, data.charts.rate.x_axis.min),
},
cases: {
conforming: getNumberLabel(data.counts.conforming),
total: getNumberLabel(sum),
data: {
conforming: setBarChartData(data.charts.cases.data.filter(item => item.label === 'conforming').map(item => item.data)[0]),
not_conforming: setBarChartData(data.charts.cases.data.filter(item => item.label === 'not-conforming').map(item => item.data)[0]),
} }
},
fitness: getNumberLabel(data.charts.fitness),
},
effect: {
time: {
conforming: isNullTime(data.effect.time.conforming),
not_conforming: isNullTime(data.effect.time.not_conforming),
difference: isNullTime(data.effect.time.conforming - data.effect.time.not_conforming),
},
tasks: {
conforming: isNullCase(data.effect.tasks.conforming),
not_conforming: isNullCase(data.effect.tasks.not_conforming),
difference: isNullCase(data.effect.tasks.conforming - data.effect.tasks.not_conforming),
},
},
issues: data.issues.map(item => {
return {
no: item.no,
issue: item.description,
value: `width:${this.getPercentLabel(item.count / sum)}%;`,
count: item.count,
ratio: this.getPercentLabel(item.count / sum),
}
})
};
this.setRateChartData(result.charts.rate.data); // 建立圖表 Rate Chart.js
this.setCasesChartData(result.charts.cases.data.conforming, result.charts.cases.data.not_conforming, data.charts.cases.x_axis.max, data.charts.cases.x_axis.min); // 建立圖表 Cases Chart.js
return result;
},
setRateChartData(data){
this.rateChartData = {
labels:[],
datasets: [
{
label: 'Rate',
data: data,
fill: false,
pointRadius: 0, // 隱藏點
pointHoverRadius: 0, // 隱藏點的 hover
tension: 0.4,
borderColor: '#0099FF',
x: 'x',
y: 'y',
}
]
};
this.rateChartOptions = {
responsive: true,
maintainAspectRatio: false,
aspectRatio: 0.6,
plugins: {
legend: false, // 圖例
tooltip: {
enabled: false // 隱藏 工具提示框
}
},
scales: {
x: {
type: 'time',
// time: {
// max: '2022-01-12T02:29:42',
// min: '2022-01-03T00:56:25',
// displayFormats: {
// day: 'yyyy/M/d'
// }
// },
ticks: {
maxRotation: 0, // 不旋轉 lable 0~50
color: '#334155',
source: 'data',
align: 'inner', // label 在軸線的位置
callback: function(value, index, values) {
return (index === 0 || index === values.length - 1) ? getMoment(value).format('YYYY/M/D') : null;
},
},
grid: {
display: false, // 隱藏 x 軸網格
},
border: {
color: '#334155',
},
},
y: {
beginAtZero: true, // scale 包含 0
suggestedMin: 0,
suggestedMax: 1,
ticks:{ // 設定間隔數值
includeBounds: true,
color: '#334155',
align: 'inner',
callback: function(value, index, values) {
if (value === 0) return `${value * 100}%`;
else if (value === 1) return `${value * 100}%`;
},
},
grid: {
display: false, // 隱藏 y 軸網格
},
border: {
color: '#334155',
},
},
},
};
},
setCasesChartData(conformingData, notConformingData, xMax, xMin){
this.casesChartData = {
datasets: [
{
type: 'bar',
label: 'Conforming',
data: conformingData,
backgroundColor: '#0099FF',
},
{
type: 'bar',
label: 'Not Conforming',
data: notConformingData,
backgroundColor: '#FFAA44',
},
]
};
this.casesChartOptions = {
responsive: true,
maintainAspectRatio: false,
aspectRatio: 0.8,
plugins: {
tooltips: {
mode: 'index',
intersect: false
},
legend: false, // 圖例
},
scales: {
x: {
stacked: true,
ticks: {
autoSkip: false,
maxRotation: 0, // 不旋轉 lable 0~50
color: '#334155',
align: 'center', // label 在軸線的位置
callback: function(value, index, values) {
if(index === 0) return getMoment(xMin).format('yyyy/M/D');
else if(index === values.length - 1) return getMoment(xMax).format('yyyy/M/D');
else return null;
},
},
grid: {
display: false, // 隱藏 x 軸網格
},
border: {
color: '#334155',
},
},
y: {
stacked: true,
beginAtZero: true, // scale 包含 0
ticks:{
color: '#334155',
align: 'inner',
callback: function(value, index, values) {
if (index === 0) return abbreviateNumber(value);
else if (index === values.length - 1) return abbreviateNumber(value);
},
},
grid: {
// display: false, // 隱藏 y 軸網格
color: function(context) {
return context.tick.value === 0 ? '#334155' : null;
},
drawTicks: false,
},
border: {
color: '#334155',
},
},
},
};
},
},
created() {
this.$emitter.on('coverPlate', boolean => {
this.isCoverPlate = boolean;
});
},
} }
</script> </script>
<style scoped> <style scoped>

View File

@@ -1,10 +1,9 @@
<template> <template>
<section class="h-full shadow-[1px_0px_4px_rgba(0,0,0,0.25)] bg-neutral-100 absolute inset-y-0 left-0" :class="isShowBar?'w-full':'w-[300px]'"> <section class="h-full shadow-[1px_0px_4px_rgba(0,0,0,0.25)] bg-neutral-100 absolute inset-y-0 left-0 z-10" :class="isShowBar?'w-full':'w-[300px]'">
<!-- header --> <!-- header -->
<div class="bg-neutral-200 px-4"> <div class="bg-neutral-200 px-4">
<p class="h2">Rule Settings</p> <p class="h2">Rule Settings</p>
</div> </div>
<!-- contanier --> <!-- contanier -->
<div class="p-4 h-[calc(100%_-_40px)] flex"> <div class="p-4 h-[calc(100%_-_40px)] flex">
<div class="h-full flex flex-col justify-between w-[268px]"> <div class="h-full flex flex-col justify-between w-[268px]">
@@ -23,7 +22,6 @@
<ConformanceTimeRange v-if="selectedRuleType === 'Activity duration' || selectedRuleType === 'Processing time' || selectedRuleType === 'Waiting time' || selectedRuleType === 'Cycle time'"></ConformanceTimeRange> <ConformanceTimeRange v-if="selectedRuleType === 'Activity duration' || selectedRuleType === 'Processing time' || selectedRuleType === 'Waiting time' || selectedRuleType === 'Cycle time'"></ConformanceTimeRange>
</div> </div>
<!-- button --> <!-- button -->
{{ selectConformanceTask }}
<div class="space-x-4 p-4 flex justify-center items-content border-t border-neutral-300"> <div class="space-x-4 p-4 flex justify-center items-content border-t border-neutral-300">
<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>
<button type="button" class="btn btn-sm btn-neutral" @click="submit">Apply</button> <button type="button" class="btn btn-sm btn-neutral" @click="submit">Apply</button>
@@ -37,6 +35,7 @@
<script> <script>
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import LoadingStore from '@/stores/loading.js';
import ConformanceStore from '@/stores/conformance.js'; import ConformanceStore from '@/stores/conformance.js';
import ConformanceRadioGroup from './ConformanceSidebar/ConformanceRadioGroup.vue'; import ConformanceRadioGroup from './ConformanceSidebar/ConformanceRadioGroup.vue';
import ConformanceShowBar from './ConformanceSidebar/ConformanceShowBar.vue'; import ConformanceShowBar from './ConformanceSidebar/ConformanceShowBar.vue';
@@ -45,10 +44,12 @@ import ConformanceSelectResult from './ConformanceSidebar/ConformanceSelectResul
export default { export default {
setup() { setup() {
const loadingStore = LoadingStore();
const conformanceStore = ConformanceStore(); const conformanceStore = ConformanceStore();
const { isLoading } = storeToRefs(loadingStore);
const { selectedRuleType, selectedActivitySequence, selectedMode, selectedProcessScope, selectedActSeqMore, selectedActSeqFromTo } = storeToRefs(conformanceStore); const { selectedRuleType, selectedActivitySequence, selectedMode, selectedProcessScope, selectedActSeqMore, selectedActSeqFromTo } = storeToRefs(conformanceStore);
return { selectedRuleType, selectedActivitySequence, selectedMode, selectedProcessScope, selectedActSeqMore, selectedActSeqFromTo } return { isLoading, selectedRuleType, selectedActivitySequence, selectedMode, selectedProcessScope, selectedActSeqMore, selectedActSeqFromTo, conformanceStore }
}, },
data() { data() {
return { return {
@@ -65,21 +66,47 @@ export default {
}, },
methods: { methods: {
/** /**
* @param {boolean} massage true | false 清空選 * @param {boolean} massage true | false 清空選單選項, 選擇的結果
*/ */
reset(massage) { reset() {
// Results page Cover Plate
this.$emitter.emit('coverPlate', true);
// Have activity // Have activity
this.selectConformanceTask = null; this.selectConformanceTask = null;
this.$emitter.emit('reset', null); this.$emitter.emit('reset', null);
// 成功訊息
massage ? this.$toast.success('Reset Success.') : null; // reset 成功訊息
this.$toast.success('Reset Success.');
},
/**
* Apply button 發送選項,取得 Checker Id.
*/
async submit() {
let data;
if(this.selectedRuleType === 'Have activity') {
if(!this.selectConformanceTask?.length) return this.$toast.error('Not selected.');
else data = {
type: 'contains-tasks',
tasks: this.selectConformanceTask,
}
}
this.isLoading = true;
await this.conformanceStore.addLogConformanceTempCheckerId(data);
await this.conformanceStore.getLogConformanceTempReport();
this.isShowBar = false;
this.isLoading = false;
// Results page Cover Plate
this.$emitter.emit('coverPlate', false);
this.$toast.success('Apply Success. View the Conformance Checking Results.');
}, },
}, },
created() { created() {
this.isShowBar = true; // this.isShowBar = true;
this.$emitter.on('actListData', (data) => { this.$emitter.on('actListData', (data) => {
this.selectConformanceTask = data; this.selectConformanceTask = data;
}) });
}, },
} }
</script> </script>

View File

@@ -1,5 +1,5 @@
<template> <template>
<section class="w-full top-0 absolute shadow-[0px_6px_6px_inset_rgba(0,0,0,0.1)]"> <section class="w-full top-0 absolute shadow-[0px_6px_6px_inset_rgba(0,0,0,0.1)] z-20">
<!-- status content --> <!-- status content -->
<ul class="bg-neutral-100 flex justify-start shadow-[0px_1px_4px_rgba(0,0,0,0.2)] gap-3 p-3 text-sm overflow-x-auto scrollbar duration-700" v-show="isPanel" v-if="statData"> <ul class="bg-neutral-100 flex justify-start shadow-[0px_1px_4px_rgba(0,0,0,0.2)] gap-3 p-3 text-sm overflow-x-auto scrollbar duration-700" v-show="isPanel" v-if="statData">
<li class="bg-neutral-10 rounded p-3 w-full"> <li class="bg-neutral-10 rounded p-3 w-full">
@@ -137,7 +137,7 @@ export default {
ratio: this.getPercentLabel(this.stats.tasks.ratio) ratio: this.getPercentLabel(this.stats.tasks.ratio)
}, },
started_at: getMoment(this.stats.started_at).format('YYYY-MM-DD HH:mm'), started_at: getMoment(this.stats.started_at).format('YYYY-MM-DD HH:mm'),
completed_at: getMoment(this.stats.started_at).format('YYYY-MM-DD HH:mm'), completed_at: getMoment(this.stats.completed_at).format('YYYY-MM-DD HH:mm'),
case_duration: { case_duration: {
min: getTimeLabel(this.stats.case_duration.min), min: getTimeLabel(this.stats.case_duration.min),
max: getTimeLabel(this.stats.case_duration.max), max: getTimeLabel(this.stats.case_duration.max),
@@ -151,7 +151,7 @@ export default {
if(this.$route.params.type === 'log') this.logId = this.$route.params.fileId; if(this.$route.params.type === 'log') this.logId = this.$route.params.fileId;
await this.allMapDataStore.getAllMapData(); await this.allMapDataStore.getAllMapData();
await this.getStatData(); await this.getStatData();
this.isPanel = true; // this.isPanel = true;
} }
} }
</script> </script>

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="w-full h-full fixed inset-0 m-auto flex justify-center items-center bg-gradient-to-tr from-neutral-500/50 to-neutral-900/50 z-50"> <div class="w-full h-full fixed inset-0 m-auto flex justify-center items-center bg-gradient-to-tr from-neutral-500/50 to-neutral-900/50 z-[9999]">
<span class="loader block"></span> <span class="loader block"></span>
</div> </div>
</template> </template>

View File

@@ -0,0 +1,14 @@
/**
* 將數字轉換成簡寫的形式1k、1m、1b等
* @param {number} number
* @returns {string}
*/
export default function abbreviateNumber(number) {
const SI_SYMBOLS = ["", "k", "m", "b", "t"];
const tier = Math.log10(Math.abs(number)) / 3 | 0;
const suffix = SI_SYMBOLS[tier];
const scale = Math.pow(10, tier * 3);
const scaledNumber = number / scale;
return Math.round(scaledNumber) + suffix;
}

View File

@@ -0,0 +1,67 @@
import getMoment from 'moment';
/**
* 將後端的 chart data 加入最大、最小值,折線圖
* @param {array} baseData 後端給的 10 個時間點
* @param {number} xMax 2022-05-23T18:00:00
* @param {number} xMin 2022-05-23T08:00:00
* @returns {array} 可直接套入 chart.js 的 data
*/
export function setLineChartData(baseData, xMax, xMin) {
let data = baseData.map(i=>({x:i.x,y:i.y}))
// y 軸斜率計算請參考 ./public/timeFrameSlope 的圖
// x 值為 0 ~ 11,
// 將三的座標(ax, ay), (bx, by), (cx, cy)命名為 (a, b), (c, d), (e, f)
// 最小值: (f - b)(c - a) = (e - a)(d - b),求 b = (ed - ad - fa - fc) / (e - c - a)
// 最大值: (f - b)(e - c) = (f - d)(e - a),求 f = (be - bc -de + da) / (a - c)
// y 軸最小值
let a = 0;
let b;
let c = 1;
let d = baseData[0].y;
let e = 2;
let f = baseData[1].y;
b = (e*d - a*d - f*a - f*c) / (e - c - a)
b = b > 1 ? 1 : b < 0 ? 0 : b;
// y 軸最大值
let ma = 9;
let mb = baseData[8].y;
let mc = 10;
let md = baseData[9].y;
let me = 11;
let mf;
mf = (mb*me - mb*mc -md*me + md*ma) / (ma - mc);
mf = mf > 1 ? 1 : mf < 0 ? 0 : mf;
// 添加最小值
data.unshift({
x: xMin,
y: b,
})
// 添加最大值
data.push({
x: xMax,
y: mf,
})
return data;
};
/**
* 將後端的 chart data 加入最大、最小值,長條圖
* @param {array} baseData 後端給的 10 個時間點
* @param {number} xMax 2022-05-23T18:00:00
* @param {number} xMin 2022-05-23T08:00:00
* @returns {array} 可直接套入 chart.js 的 data
*/
export function setBarChartData(baseData) {
let data = baseData.map(i =>{
return {
x: getMoment(i.x).format('YYYY/M/D hh:mm:ss'),
y: i.y
}
})
// console.log(data);
return data
};

View File

@@ -6,15 +6,17 @@ import 'vue-toast-notification/dist/theme-sugar.css';
const loading = loadingStore(pinia); const loading = loadingStore(pinia);
const $toast = useToast(); const $toast = useToast();
// Delay loading and toast 待模組化 // Delay loading and toast 待模組化
let delay = (s = 0) => new Promise((resolve, reject) => setTimeout(resolve, s)); let delay = (s = 0) => new Promise((resolve, reject) => setTimeout(resolve, s));
export default defineStore('conformanceStore', { export default defineStore('conformanceStore', {
state: () => ({ state: () => ({
conformanceLogId: null, conformanceLogId: null,
conformanceLogTempCheckerId: null, conformanceTempCheckerId: null,
conformanceFilterId: null, conformanceFilterId: null,
allConformanceTask: [], allConformanceTask: [],
allConformanceTempReportData: null,
selectedRuleType: 'Have activity', // radio selectedRuleType: 'Have activity', // radio
selectedActivitySequence: 'Start & End', // radio selectedActivitySequence: 'Start & End', // radio
selectedMode: 'Directly follows', // radio selectedMode: 'Directly follows', // radio
@@ -24,7 +26,10 @@ export default defineStore('conformanceStore', {
}), }),
getters: { getters: {
conformanceTask: state => { conformanceTask: state => {
return state.allConformanceTask; return state.allConformanceTask.map(i => i.label);
},
conformanceTempReportData: state => {
return state.allConformanceTempReportData;
} }
}, },
actions: { actions: {
@@ -50,16 +55,14 @@ export default defineStore('conformanceStore', {
/** /**
* Creates a new temporary checker for a log. * Creates a new temporary checker for a log.
*/ */
async addLogConformanceTempCheckerId() { async addLogConformanceTempCheckerId(data) {
let logId = this.conformanceLogId; let logId = this.conformanceLogId;
const api = `/api/temp-log-checkers?log_id=${logId}`; const api = `/api/temp-log-checkers?log_id=${logId}`;
try { try {
const response = await this.$axios.post(api); const response = await this.$axios.post(api, data);
console.log(response); this.conformanceTempCheckerId = response.data.id;
this.conformanceLogTempCheckerId = response.data.id;
} catch(error) { } catch(error) {
console.log(error);
await delay(); await delay();
loading.isLoading = true; loading.isLoading = true;
await delay(1000); await delay(1000);
@@ -67,6 +70,26 @@ export default defineStore('conformanceStore', {
await delay(500); await delay(500);
$toast.default('Failed to add the Temporary Checker for a log.',{position: 'bottom'}); $toast.default('Failed to add the Temporary Checker for a log.',{position: 'bottom'});
} }
},
/**
* Get the Temporary Log Conformance Report
*/
async getLogConformanceTempReport() {
let checkerId = this.conformanceTempCheckerId;
const api = `/api/temp-log-checkers/${checkerId}/report`;
try {
const response = await this.$axios.get(api);
this.allConformanceTempReportData = response.data;
} catch(error) {
await delay();
loading.isLoading = true;
await delay(1000);
loading.isLoading = false;
await delay(500);
$toast.default('Failed to Get the Temporary Log Conformance Report.',{position: 'bottom'});
} }
}, },
},
}) })