987 lines
30 KiB
Vue
987 lines
30 KiB
Vue
<template>
|
|
<section
|
|
class="p-4 mr-0.5 space-y-2 h-full w-[calc(100vw_-_316px)] overflow-y-auto scrollbar float-right"
|
|
>
|
|
<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 -->
|
|
<p class="text-base leading-10 font-bold">
|
|
Conformance Checking Results ({{ data.total }})
|
|
</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
|
|
>Conforming<span>{{ data.counts.conforming }}</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
|
|
>Not Conforming<span>{{ data.counts.not_conforming }}</span>
|
|
</li>
|
|
<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>{{
|
|
data.counts.not_applicable
|
|
}}</span>
|
|
</li>
|
|
</ul>
|
|
<!-- chart -->
|
|
<div class="flex gap-4">
|
|
<div class="border rounded border-neutral-300 p-2 bg-neutral-10 w-1/2">
|
|
<div class="p-2 flex justify-between items-center">
|
|
<div>
|
|
<span class="block text-sm font-bold mb-2"
|
|
>Conformance Rate<span
|
|
class="material-symbols-outlined !text-sm align-middle ml-2 cursor-pointer"
|
|
v-tooltip.bottom="tooltip.rate"
|
|
>info</span
|
|
></span
|
|
>
|
|
<small class="text-neutral-700 font-normal block"
|
|
>{{ data.charts.rate.xMin }} ~ {{ data.charts.rate.xMax }}</small
|
|
>
|
|
</div>
|
|
<span class="text-2xl font-bold">{{ data.charts.rate.rate }}%</span>
|
|
</div>
|
|
<Chart
|
|
type="line"
|
|
:data="rateChartData"
|
|
:options="rateChartOptions"
|
|
class="w-[99%]"
|
|
/>
|
|
</div>
|
|
<div class="border rounded border-neutral-300 p-2 bg-neutral-10 w-1/2">
|
|
<div class="p-2 flex justify-between items-center">
|
|
<div>
|
|
<span class="block text-sm font-bold mb-2"
|
|
>Cases<span
|
|
class="material-symbols-outlined !text-sm align-middle ml-2 cursor-pointer"
|
|
v-tooltip.bottom="tooltip.case"
|
|
>info</span
|
|
></span
|
|
>
|
|
<small class="text-neutral-700 font-normal block"
|
|
>{{ data.charts.cases.xMin }} ~
|
|
{{ data.charts.cases.xMax }}</small
|
|
>
|
|
</div>
|
|
<span class="text-2xl font-bold"
|
|
><span class="text-cfm-primary">{{
|
|
data.charts.cases.conforming
|
|
}}</span
|
|
> / {{ data.charts.cases.total }}</span
|
|
>
|
|
</div>
|
|
<Chart
|
|
type="bar"
|
|
:data="casesChartData"
|
|
:options="casesChartOptions"
|
|
class="w-[99%]"
|
|
/>
|
|
</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 w-1/2">
|
|
<p class="h2 pl-2 mb-2">Throughput Time</p>
|
|
<div v-if="data.effect.time !== null">
|
|
<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 inline-block"
|
|
>{{ data.effect.time.conforming }}</span
|
|
>
|
|
<span>vs</span>
|
|
<span
|
|
class="text-cfm-secondary text-2xl font-medium inline-block"
|
|
>{{ data.effect.time.not_conforming }}</span
|
|
>
|
|
<span>days,</span>
|
|
<span class="text-2xl font-medium inline-block">{{
|
|
data.effect.time.difference
|
|
}}</span>
|
|
<span>days of difference.</span>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div class="border rounded border-neutral-300 p-2 bg-neutral-10 w-1/2">
|
|
<p class="h2 pl-2 mb-2">Activities per Case</p>
|
|
<div v-if="data.effect.tasks !== null">
|
|
<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 inline-block"
|
|
>{{ data.effect.tasks.conforming }}</span
|
|
>
|
|
<span>vs</span>
|
|
<span
|
|
class="text-cfm-secondary text-2xl font-medium inline-block"
|
|
>{{ data.effect.tasks.not_conforming }}</span
|
|
>
|
|
<span>activities,</span>
|
|
<span class="text-2xl font-medium inline-block">{{
|
|
data.effect.tasks.difference
|
|
}}</span>
|
|
<span>activities of difference.</span>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
<!-- Loop group -->
|
|
<section>
|
|
<div v-if="data.loops === null"></div>
|
|
<div v-else>
|
|
<p class="h2 text-base">Loop List</p>
|
|
<div class="border rounded border-neutral-300 p-2 bg-neutral-10 w-full">
|
|
<p class="h2 pl-2 mb-2">Short Loop(s)</p>
|
|
<table class="text-sm min-w-full table-fixed">
|
|
<caption class="hidden">
|
|
Loop List
|
|
</caption>
|
|
<thead class="hidden">
|
|
<tr>
|
|
<th class="w-1/5 px-4 py-2 hidden"></th>
|
|
<th class="w-1/5 px-4 py-2 hidden"></th>
|
|
<th class="w-1/5 px-4 py-2 hidden"></th>
|
|
<th class="w-1/5 px-4 py-2 hidden"></th>
|
|
<th class="w-1/5 px-4 py-2 hidden"></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="(trace, key) in data.loops" :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.label }}
|
|
</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-right">{{ trace.ratio }}%</td>
|
|
<td class="p-2 text-center">
|
|
<div
|
|
class="btn btn-sm btn-c-primary cursor-pointer"
|
|
@click="openLoopMore(trace.no)"
|
|
>
|
|
More
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
<!-- Issues group -->
|
|
<section>
|
|
<div v-if="data.issues === 'reset'">
|
|
<p class="h2 text-base">Non-conformance Issues</p>
|
|
<div class="border rounded border-neutral-300 p-2 bg-neutral-10 w-full">
|
|
<p class="h2 pl-2 mb-2">Issue List</p>
|
|
</div>
|
|
</div>
|
|
<div v-else>
|
|
<div>
|
|
<div
|
|
v-if="
|
|
(data.issues?.length === 0 || data.issues === null) &&
|
|
data.timeTrend.chart === null
|
|
"
|
|
></div>
|
|
<p v-else class="h2 text-base">Non-conformance Issues</p>
|
|
</div>
|
|
<div
|
|
class="flex w-full"
|
|
:class="
|
|
data.issues === null || data.issues?.length === 0 ? '' : 'gap-4'
|
|
"
|
|
>
|
|
<!-- Issues chart -->
|
|
<div
|
|
v-if="data.timeTrend.chart !== null"
|
|
class="border rounded border-neutral-300 p-2 bg-neutral-10"
|
|
:class="
|
|
data.issues === null || data.issues?.length === 0
|
|
? 'w-full'
|
|
: 'w-1/2'
|
|
"
|
|
>
|
|
<p class="h2 p-2 flex justify-between items-center">
|
|
<span
|
|
>Time Trend<span
|
|
class="material-symbols-outlined !text-sm align-middle ml-2"
|
|
v-tooltip.bottom="tooltip.timeTrend"
|
|
>info</span
|
|
></span
|
|
>
|
|
<span class="text-2xl"
|
|
><span class="text-cfm-secondary">{{
|
|
data.timeTrend.not_conforming
|
|
}}</span
|
|
> / {{ data.timeTrend.total }}</span
|
|
>
|
|
</p>
|
|
<Chart
|
|
type="line"
|
|
:data="timeChartData"
|
|
:options="timeChartOptions"
|
|
class="w-[99%]"
|
|
/>
|
|
</div>
|
|
<!-- Issues list -->
|
|
<div v-if="data.issues === null || data.issues?.length === 0"></div>
|
|
<div
|
|
v-else
|
|
class="border rounded border-neutral-300 p-2 bg-neutral-10"
|
|
:class="data.timeTrend.chart !== null ? 'w-1/2' : 'w-full'"
|
|
>
|
|
<p class="h2 pl-2 mb-2">
|
|
Issue List<span
|
|
class="material-symbols-outlined !text-sm align-middle ml-2 cursor-pointer"
|
|
v-tooltip.bottom="tooltip.issueList"
|
|
>info</span
|
|
>
|
|
</p>
|
|
<table class="text-sm min-w-full table-fixed">
|
|
<caption class="hidden">
|
|
Issues List
|
|
</caption>
|
|
<thead class="hidden">
|
|
<tr>
|
|
<th class="w-1/5 px-4 py-2 hidden"></th>
|
|
<th class="w-1/5 px-4 py-2 hidden"></th>
|
|
<th class="w-1/5 px-4 py-2 hidden"></th>
|
|
<th class="w-1/5 px-4 py-2 hidden"></th>
|
|
<th class="w-1/5 px-4 py-2 hidden"></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<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.label }}
|
|
</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-cfm-secondary"
|
|
:style="trace.value"
|
|
></div>
|
|
</div>
|
|
</td>
|
|
<td class="p-2 text-right truncate">{{ trace.count }}</td>
|
|
<td class="p-2 text-right">{{ trace.ratio }}%</td>
|
|
<td class="p-2 text-center">
|
|
<div
|
|
class="btn btn-sm btn-cfm-secondary cursor-pointer"
|
|
@click="openMore(trace.no)"
|
|
>
|
|
More
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
<MoreModal
|
|
:listModal="issuesModal"
|
|
@closeModal="issuesModal = $event"
|
|
:listTraces="issueTraces"
|
|
:taskSeq="taskSeq"
|
|
:cases="cases"
|
|
:listNo="issuesNo"
|
|
:traceId="traceId"
|
|
:firstCases="firstCases"
|
|
:category="'issue'"
|
|
></MoreModal>
|
|
<MoreModal
|
|
:listModal="loopModal"
|
|
@closeModal="loopModal = $event"
|
|
:listTraces="loopTraces"
|
|
:taskSeq="loopTaskSeq"
|
|
:cases="loopCases"
|
|
:listNo="loopNo"
|
|
:traceId="looptraceId"
|
|
:firstCases="loopFirstCases"
|
|
:category="'loop'"
|
|
></MoreModal>
|
|
</section>
|
|
</template>
|
|
<script setup>
|
|
// The Lucia project.
|
|
// Copyright 2023-2026 DSP, inc. All rights reserved.
|
|
// Authors:
|
|
// chiayin.kuo@dsp.im (chiayin), 2023/1/31
|
|
// cindy.chang@dsp.im (Cindy Chang), 2024/5/30
|
|
// imacat.yang@dsp.im (imacat), 2023/9/23
|
|
/**
|
|
* @module components/Discover/Conformance/ConformanceResults
|
|
* Conformance checking results panel displaying rule
|
|
* check outcomes in a data table with status indicators.
|
|
*/
|
|
|
|
import { ref, watch } from "vue";
|
|
import { storeToRefs } from "pinia";
|
|
import { useConformanceStore } from "@/stores/conformance";
|
|
import iconNA from "@/components/icons/IconNA.vue";
|
|
import MoreModal from "./MoreModal.vue";
|
|
import getNumberLabel from "@/module/numberLabel.js";
|
|
import {
|
|
setLineChartData,
|
|
setBarChartData,
|
|
timeRange,
|
|
yTimeRange,
|
|
getXIndex,
|
|
formatTime,
|
|
formatMaxTwo,
|
|
} from "@/module/setChartData.js";
|
|
import shortScaleNumber from "@/module/shortScaleNumber.js";
|
|
import getMoment from "moment";
|
|
import emitter from "@/utils/emitter";
|
|
|
|
const conformanceStore = useConformanceStore();
|
|
const {
|
|
conformanceTempReportData,
|
|
issueTraces,
|
|
taskSeq,
|
|
cases,
|
|
loopTraces,
|
|
loopTaskSeq,
|
|
loopCases,
|
|
} = storeToRefs(conformanceStore);
|
|
|
|
const data = ref({
|
|
total: "--",
|
|
counts: {
|
|
conforming: "--",
|
|
not_conforming: "--",
|
|
not_applicable: 0,
|
|
},
|
|
charts: {
|
|
rate: {
|
|
rate: "--",
|
|
chart: {},
|
|
},
|
|
cases: {
|
|
conforming: "--",
|
|
total: "--",
|
|
chart: {},
|
|
},
|
|
fitness: "--",
|
|
},
|
|
effect: {
|
|
time: null,
|
|
tasks: null,
|
|
},
|
|
loops: null,
|
|
issues: "reset",
|
|
timeTrend: {
|
|
not_conforming: "--",
|
|
total: "--",
|
|
chart: {},
|
|
},
|
|
});
|
|
const isCoverPlate = ref(false);
|
|
const issuesModal = ref(false);
|
|
const loopModal = ref(false);
|
|
const rateChartData = ref(null);
|
|
const rateChartOptions = ref(null);
|
|
const casesChartData = ref(null);
|
|
const casesChartOptions = ref(null);
|
|
const timeChartData = ref(null);
|
|
const timeChartOptions = ref(null);
|
|
const issuesNo = ref(null);
|
|
const traceId = ref(null);
|
|
const firstCases = ref(null);
|
|
const loopNo = ref(null);
|
|
const looptraceId = ref(null);
|
|
const loopFirstCases = ref(null);
|
|
const selectDurationTime = ref(null);
|
|
const tooltip = ref({
|
|
rate: {
|
|
value: "= Conforming / (Conforming + Not Conforming) * 100%",
|
|
class: "!max-w-[36rem] !text-[10px] !opacity-90",
|
|
},
|
|
case: {
|
|
value: "= Conforming / (Conforming + Not Conforming)",
|
|
class: "!max-w-[36rem] !text-[10px] !opacity-90",
|
|
},
|
|
timeTrend: {
|
|
value: "=Not Conforming / (total Conforming+total Not Conforming)",
|
|
class: "!max-w-[36rem] !text-[10px] !opacity-90",
|
|
},
|
|
issueList: {
|
|
value:
|
|
"Percentage of Issue Type (%) = Cases of Issue Type / Total Cases of All Issue Types.",
|
|
class: "!max-w-[36rem] !text-[10px] !opacity-90",
|
|
},
|
|
});
|
|
|
|
/**
|
|
* set progress bar width
|
|
* @param {number} value - The percentage value.
|
|
* @returns {string} The CSS width style string.
|
|
*/
|
|
const progressWidth = (value) => {
|
|
return `width:${value}%;`;
|
|
};
|
|
|
|
/**
|
|
* Number to percentage
|
|
* @param {number} val - The raw ratio value.
|
|
* @returns {string} The formatted percentage string.
|
|
*/
|
|
const getPercentLabel = (val) => {
|
|
if ((val * 100).toFixed(1) >= 100) return 100;
|
|
else return parseFloat((val * 100).toFixed(1));
|
|
};
|
|
|
|
/**
|
|
* Convert seconds to days
|
|
* @param {number} sec - The number of seconds.
|
|
* @returns {number} day
|
|
*/
|
|
const convertSecToDay = (sec) => {
|
|
return sec / 86400;
|
|
};
|
|
|
|
/**
|
|
* Open Issues Modal.
|
|
* @param {number} no - The trace number.
|
|
*/
|
|
const openMore = async (no) => {
|
|
// Use async/await to prevent errors caused by asynchronous data not being available yet
|
|
issuesNo.value = no;
|
|
await conformanceStore.getConformanceIssue(no);
|
|
if (issueTraces.value.length === 0) return;
|
|
traceId.value = issueTraces.value[0].id;
|
|
firstCases.value = await conformanceStore.getConformanceTraceDetail(
|
|
no,
|
|
issueTraces.value[0].id,
|
|
0,
|
|
);
|
|
issuesModal.value = true;
|
|
};
|
|
|
|
/**
|
|
* Open Loop Modal.
|
|
* @param {number} no - The trace number.
|
|
*/
|
|
const openLoopMore = async (no) => {
|
|
// Use async/await to prevent errors caused by asynchronous data not being available yet
|
|
loopNo.value = no;
|
|
await conformanceStore.getConformanceLoop(no);
|
|
if (loopTraces.value.length === 0) return;
|
|
looptraceId.value = loopTraces.value[0].id;
|
|
loopFirstCases.value = await conformanceStore.getConformanceLoopsTraceDetail(
|
|
no,
|
|
loopTraces.value[0].id,
|
|
0,
|
|
);
|
|
loopModal.value = true;
|
|
};
|
|
|
|
/**
|
|
* set conformance report data
|
|
* @param {object} data - The report data received from the backend.
|
|
*/
|
|
const setConformanceTempReportData = (newData) => {
|
|
const total = getNumberLabel(
|
|
Object.values(newData.counts).reduce((acc, val) => acc + val, 0),
|
|
);
|
|
const sum = newData.counts.conforming + newData.counts.not_conforming;
|
|
const rate = ((newData.counts.conforming / sum) * 100).toFixed(1);
|
|
const isNullTime = (value) =>
|
|
value === null ? null : getNumberLabel((value / 86400).toFixed(1));
|
|
const isNullCase = (value) =>
|
|
value === null ? null : getNumberLabel(value.toFixed(1));
|
|
const setLoopData = (value) =>
|
|
value.map((item) => {
|
|
return {
|
|
no: item.no,
|
|
label: item.description,
|
|
value: `width:${getPercentLabel(item.count / newData.counts.conforming)}%;`,
|
|
count: getNumberLabel(item.count),
|
|
ratio: getPercentLabel(item.count / newData.counts.conforming),
|
|
};
|
|
});
|
|
const setIssueData = (value) =>
|
|
value.map((item) => {
|
|
return {
|
|
no: item.no,
|
|
label: item.description,
|
|
value: `width:${getPercentLabel(item.count / newData.counts.not_conforming)}%;`,
|
|
count: getNumberLabel(item.count),
|
|
ratio: getPercentLabel(item.count / newData.counts.not_conforming),
|
|
};
|
|
});
|
|
const isNullLoops = (value) => (value === null ? null : setLoopData(value));
|
|
const isNullIsssue = (value) => (value === null ? null : setIssueData(value));
|
|
|
|
const result = {
|
|
total: `Total ${total}`,
|
|
counts: {
|
|
conforming: getNumberLabel(newData.counts.conforming),
|
|
not_conforming: getNumberLabel(newData.counts.not_conforming),
|
|
not_applicable: getNumberLabel(newData.counts.not_applicable),
|
|
},
|
|
charts: {
|
|
rate: {
|
|
rate: rate,
|
|
data: setLineChartData(
|
|
newData.charts.rate.data,
|
|
newData.charts.rate.x_axis.max,
|
|
newData.charts.rate.x_axis.min,
|
|
true,
|
|
),
|
|
xMax: getMoment(newData.charts.rate.x_axis.max).format("YYYY/M/D"),
|
|
xMin: getMoment(newData.charts.rate.x_axis.min).format("YYYY/M/D"),
|
|
},
|
|
cases: {
|
|
conforming: getNumberLabel(newData.counts.conforming),
|
|
total: getNumberLabel(sum),
|
|
data: {
|
|
conforming: setBarChartData(
|
|
newData.charts.cases.data
|
|
.filter((item) => item.label === "conforming")
|
|
.map((item) => item.data)[0],
|
|
),
|
|
not_conforming: setBarChartData(
|
|
newData.charts.cases.data
|
|
.filter((item) => item.label === "not-conforming")
|
|
.map((item) => item.data)[0],
|
|
),
|
|
},
|
|
xMax: getMoment(newData.charts.cases.x_axis.max).format("YYYY/M/D"),
|
|
xMin: getMoment(newData.charts.cases.x_axis.min).format("YYYY/M/D"),
|
|
},
|
|
fitness: getNumberLabel(newData.charts.fitness),
|
|
},
|
|
effect: {
|
|
time: {
|
|
conforming: isNullTime(newData.effect.time.conforming),
|
|
not_conforming: isNullTime(newData.effect.time.not_conforming),
|
|
difference: (
|
|
isNullTime(newData.effect.time.conforming) -
|
|
isNullTime(newData.effect.time.not_conforming)
|
|
).toFixed(1),
|
|
},
|
|
tasks: {
|
|
conforming: isNullCase(newData.effect.tasks.conforming),
|
|
not_conforming: isNullCase(newData.effect.tasks.not_conforming),
|
|
difference: (
|
|
isNullCase(newData.effect.tasks.conforming) -
|
|
isNullCase(newData.effect.tasks.not_conforming)
|
|
).toFixed(1),
|
|
},
|
|
},
|
|
loops: isNullLoops(newData.loops),
|
|
issues: isNullIsssue(newData.issues),
|
|
timeTrend: {
|
|
not_conforming: getNumberLabel(newData.counts.not_conforming),
|
|
total: getNumberLabel(sum),
|
|
chart: null,
|
|
xMax: null,
|
|
xMin: null,
|
|
yMax: null,
|
|
yMin: null,
|
|
},
|
|
};
|
|
|
|
if (newData.charts.time) {
|
|
result.timeTrend.chart = setLineChartData(
|
|
newData.charts.time.data,
|
|
newData.charts.time.x_axis.max,
|
|
newData.charts.time.x_axis.min,
|
|
false,
|
|
newData.charts.time.y_axis.max,
|
|
newData.charts.time.y_axis.min,
|
|
);
|
|
result.timeTrend.xMax = newData.charts.time.x_axis.max;
|
|
result.timeTrend.xMin = newData.charts.time.x_axis.min;
|
|
result.timeTrend.yMax = newData.charts.time.y_axis.max;
|
|
result.timeTrend.yMin = newData.charts.time.y_axis.min;
|
|
}
|
|
|
|
setRateChartData(result.charts.rate.data); // Build the Rate Chart.js chart
|
|
setCasesChartData(
|
|
result.charts.cases.data.conforming,
|
|
result.charts.cases.data.not_conforming,
|
|
newData.charts.cases.x_axis.max,
|
|
newData.charts.cases.x_axis.min,
|
|
); // Build the Cases Chart.js chart
|
|
if (newData.charts.time)
|
|
setTimeChartData(
|
|
result.timeTrend.chart,
|
|
result.timeTrend.xMax,
|
|
result.timeTrend.xMin,
|
|
result.timeTrend.yMax,
|
|
result.timeTrend.yMin,
|
|
); // Build the Time Chart.js chart
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* set Rate Chart Data
|
|
* @param {object} data new rate chart data
|
|
*/
|
|
const setRateChartData = (chartData) => {
|
|
rateChartData.value = {
|
|
labels: [],
|
|
datasets: [
|
|
{
|
|
label: "Rate",
|
|
data: chartData,
|
|
fill: false,
|
|
pointRadius: 0, // Hide data points
|
|
pointHoverRadius: 0, // Hide data points on hover
|
|
tension: 0.4,
|
|
borderColor: "#0099FF",
|
|
x: "x",
|
|
y: "y",
|
|
},
|
|
],
|
|
};
|
|
rateChartOptions.value = {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
aspectRatio: 0.6,
|
|
layout: {
|
|
padding: {
|
|
top: 16,
|
|
left: 8,
|
|
right: 8,
|
|
},
|
|
},
|
|
plugins: {
|
|
legend: false, // Hide legend
|
|
tooltip: {
|
|
enabled: false, // Hide tooltip
|
|
},
|
|
},
|
|
scales: {
|
|
x: {
|
|
type: "time",
|
|
ticks: {
|
|
display: false,
|
|
},
|
|
grid: {
|
|
display: false, // Hide x-axis grid lines
|
|
},
|
|
border: {
|
|
color: "#334155",
|
|
},
|
|
},
|
|
y: {
|
|
beginAtZero: true, // Scale includes 0
|
|
suggestedMin: 0,
|
|
suggestedMax: 1,
|
|
ticks: {
|
|
// Set tick intervals
|
|
includeBounds: true,
|
|
color: "#334155",
|
|
align: "inner",
|
|
callback: function (value, index, values) {
|
|
if (value === 0 || value === 1) {
|
|
return `${value * 100}%`;
|
|
}
|
|
},
|
|
},
|
|
grid: {
|
|
display: false, // Hide y-axis grid lines
|
|
},
|
|
border: {
|
|
color: "#334155",
|
|
},
|
|
},
|
|
},
|
|
};
|
|
};
|
|
|
|
/**
|
|
* set Cases Chart Data
|
|
* @param {array} conformingData new cases chart conforming data
|
|
* @param {array} notConformingData new cases chart not conforming data
|
|
* @param {number} xMax new cases chart xMax
|
|
* @param {number} xMin new cases chart xMin
|
|
*/
|
|
const setCasesChartData = (conformingData, notConformingData, xMax, xMin) => {
|
|
casesChartData.value = {
|
|
datasets: [
|
|
{
|
|
type: "bar",
|
|
label: "Conforming",
|
|
data: conformingData,
|
|
backgroundColor: "#0099FF",
|
|
},
|
|
{
|
|
type: "bar",
|
|
label: "Not Conforming",
|
|
data: notConformingData,
|
|
backgroundColor: "#FFAA44",
|
|
},
|
|
],
|
|
};
|
|
casesChartOptions.value = {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
aspectRatio: 0.8,
|
|
layout: {
|
|
padding: {
|
|
top: 16,
|
|
left: 8,
|
|
right: 8,
|
|
},
|
|
},
|
|
plugins: {
|
|
tooltips: {
|
|
mode: "index",
|
|
intersect: false,
|
|
},
|
|
legend: false, // Hide legend
|
|
},
|
|
scales: {
|
|
x: {
|
|
stacked: true,
|
|
ticks: {
|
|
display: false,
|
|
},
|
|
grid: {
|
|
display: false, // Hide x-axis grid lines
|
|
},
|
|
border: {
|
|
color: "#334155",
|
|
},
|
|
},
|
|
y: {
|
|
stacked: true,
|
|
beginAtZero: true, // Scale includes 0
|
|
ticks: {
|
|
color: "#334155",
|
|
align: "inner",
|
|
callback: function (value, index, values) {
|
|
if (index === 0 || index === values.length - 1) {
|
|
return shortScaleNumber(value);
|
|
}
|
|
},
|
|
},
|
|
grid: {
|
|
// display: false, // Hide y-axis grid lines
|
|
color: function (context) {
|
|
return context.tick.value === 0 ? "#334155" : null;
|
|
},
|
|
drawTicks: false,
|
|
},
|
|
border: {
|
|
color: "#334155",
|
|
},
|
|
},
|
|
},
|
|
};
|
|
};
|
|
|
|
/**
|
|
* set Time Trend chart data
|
|
* @param {array} chartData Time Trend chart conforming data
|
|
* @param {number} xMax Time Trend xMax
|
|
* @param {number} xMin Time Trend xMin
|
|
* @param {number} yMax Time Trend yMax
|
|
* @param {number} yMin Time Trend yMin
|
|
*/
|
|
const setTimeChartData = (chartData, xMax, xMin, yMax, yMin) => {
|
|
const max = yMax * 1.1;
|
|
const xVal = timeRange(xMin, xMax, 100);
|
|
const yVal = yTimeRange(chartData, 100, yMin, yMax);
|
|
xVal.map((x, index) => ({ x, y: yVal[index] }));
|
|
let formattedXVal = xVal.map((value) => formatTime(value));
|
|
formattedXVal = formatMaxTwo(formattedXVal);
|
|
const selectTimeMinIndex = getXIndex(xVal, selectDurationTime.value.min);
|
|
const selectTimeMaxIndex = getXIndex(xVal, selectDurationTime.value.max);
|
|
const start = selectTimeMinIndex;
|
|
const end = selectTimeMaxIndex;
|
|
const inside = (ctx, value) =>
|
|
ctx.p0DataIndex >= start && ctx.p1DataIndex <= end ? value : undefined;
|
|
const outside = (ctx, value) =>
|
|
ctx.p0DataIndex < start || ctx.p1DataIndex > end ? value : undefined;
|
|
|
|
timeChartData.value = {
|
|
labels: formattedXVal,
|
|
datasets: [
|
|
{
|
|
label: "Conforming",
|
|
data: yVal,
|
|
fill: true,
|
|
showLine: false,
|
|
tension: 0.4,
|
|
backgroundColor: "rgba(0,153,255)",
|
|
pointRadius: 0,
|
|
pointHitRadius: 0,
|
|
spanGaps: true,
|
|
segment: {
|
|
backgroundColor: (ctx) =>
|
|
inside(ctx, "rgb(0,153,255)") || outside(ctx, "rgb(255,170,68)"),
|
|
},
|
|
x: "x",
|
|
y: "y",
|
|
},
|
|
],
|
|
};
|
|
|
|
timeChartOptions.value = {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
layout: {
|
|
padding: {
|
|
top: 16,
|
|
left: 8,
|
|
right: 8,
|
|
},
|
|
},
|
|
plugins: {
|
|
legend: false, // Hide legend
|
|
tooltip: false,
|
|
},
|
|
scales: {
|
|
x: {
|
|
ticks: {
|
|
maxRotation: 0, // Do not rotate labels (0~50)
|
|
color: "#334155",
|
|
display: true,
|
|
},
|
|
grid: {
|
|
display: false, // Hide x-axis grid lines
|
|
},
|
|
title: {
|
|
display: true,
|
|
text: "Time",
|
|
color: "rgba(100,116,139)",
|
|
},
|
|
},
|
|
y: {
|
|
beginAtZero: true, // Scale includes 0
|
|
max: max,
|
|
ticks: {
|
|
// Set tick intervals
|
|
display: false, // Hide values, only show grid lines
|
|
stepSize: max / 4,
|
|
},
|
|
grid: {
|
|
color: "rgba(100,116,139)",
|
|
drawTicks: false, // Hide extra space on the left
|
|
},
|
|
border: {
|
|
display: false, // Hide the extra border line on the left
|
|
},
|
|
title: {
|
|
display: true,
|
|
text: "Occurrences",
|
|
color: "rgba(100,116,139)",
|
|
},
|
|
},
|
|
},
|
|
};
|
|
};
|
|
|
|
// watch
|
|
watch(conformanceTempReportData, (newValue) => {
|
|
if (newValue?.rule && newValue.rule.min !== null) {
|
|
selectDurationTime.value = {
|
|
min: newValue.rule.min,
|
|
max: newValue.rule.max,
|
|
};
|
|
}
|
|
data.value = setConformanceTempReportData(newValue);
|
|
});
|
|
|
|
// created - emitter listeners
|
|
emitter.on("coverPlate", (boolean) => {
|
|
isCoverPlate.value = boolean;
|
|
});
|
|
// Get selectTimeRange for use by Time Trend
|
|
emitter.on(
|
|
"timeRangeMaxMin",
|
|
(newData) => (selectDurationTime.value = newData),
|
|
);
|
|
</script>
|
|
<style scoped>
|
|
:deep(.disc) {
|
|
font-variation-settings:
|
|
"FILL" 1,
|
|
"wght" 100,
|
|
"GRAD" 0,
|
|
"opsz" 20;
|
|
}
|
|
</style>
|