Apply repository-wide ESLint auto-fix formatting pass
Co-Authored-By: Codex <codex@openai.com>
This commit is contained in:
@@ -1,65 +1,112 @@
|
||||
<template>
|
||||
<Dialog :visible="listModal" @update:visible="emit('closeModal', $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">
|
||||
<caption class="hidden">Trace List</caption>
|
||||
<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 text-right">{{ trace.ratio }}%</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<Dialog
|
||||
:visible="listModal"
|
||||
@update:visible="emit('closeModal', $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>
|
||||
</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-36 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>
|
||||
</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">
|
||||
<caption class="hidden">
|
||||
Trace List
|
||||
</caption>
|
||||
<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 text-right">{{ trace.ratio }}%</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overflow-y-auto overflow-x-auto scrollbar h-[calc(100%_-_200px)] infiniteTable" @scroll="handleScroll">
|
||||
<DataTable :value="caseData" showGridlines tableClass="text-sm" breakpoint="0">
|
||||
</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-36 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%_-_200px)] infiniteTable"
|
||||
@scroll="handleScroll"
|
||||
>
|
||||
<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">
|
||||
<template #body="{ data }">
|
||||
<div :class="data[col.field]?.length > 18 ? 'whitespace-normal' : 'whitespace-nowrap'">
|
||||
<div
|
||||
:class="
|
||||
data[col.field]?.length > 18
|
||||
? 'whitespace-normal'
|
||||
: 'whitespace-nowrap'
|
||||
"
|
||||
>
|
||||
{{ data[col.field] }}
|
||||
</div>
|
||||
</template>
|
||||
</Column>
|
||||
</div>
|
||||
</DataTable>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</Dialog>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup>
|
||||
// The Lucia project.
|
||||
@@ -74,30 +121,39 @@
|
||||
* results with expandable activity sequences.
|
||||
*/
|
||||
|
||||
import { ref, computed, watch, nextTick, useTemplateRef } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useConformanceStore } from '@/stores/conformance';
|
||||
import cytoscapeMapTrace from '@/module/cytoscapeMapTrace.js';
|
||||
import { ref, computed, watch, nextTick, useTemplateRef } from "vue";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { useConformanceStore } from "@/stores/conformance";
|
||||
import cytoscapeMapTrace from "@/module/cytoscapeMapTrace.js";
|
||||
|
||||
const props = defineProps(['listModal', 'listNo', 'traceId', 'firstCases', 'listTraces', 'taskSeq', 'cases', 'category']);
|
||||
const emit = defineEmits(['closeModal']);
|
||||
const props = defineProps([
|
||||
"listModal",
|
||||
"listNo",
|
||||
"traceId",
|
||||
"firstCases",
|
||||
"listTraces",
|
||||
"taskSeq",
|
||||
"cases",
|
||||
"category",
|
||||
]);
|
||||
const emit = defineEmits(["closeModal"]);
|
||||
|
||||
const conformanceStore = useConformanceStore();
|
||||
const { infinite404 } = storeToRefs(conformanceStore);
|
||||
|
||||
// template ref
|
||||
const cfmTrace = useTemplateRef('cfmTrace');
|
||||
const cfmTrace = useTemplateRef("cfmTrace");
|
||||
|
||||
// data
|
||||
const contentClass = ref('!bg-neutral-100 border-t border-neutral-300 h-full');
|
||||
const contentClass = ref("!bg-neutral-100 border-t border-neutral-300 h-full");
|
||||
const showTraceId = ref(null);
|
||||
const infiniteData = ref(null);
|
||||
const maxItems = ref(false);
|
||||
const infiniteFinish = ref(true); // Whether infinite scroll loading is complete
|
||||
const startNum = ref(0);
|
||||
const processMap = ref({
|
||||
nodes:[],
|
||||
edges:[],
|
||||
nodes: [],
|
||||
edges: [],
|
||||
});
|
||||
|
||||
// computed
|
||||
@@ -106,23 +162,27 @@ const traceTotal = computed(() => {
|
||||
});
|
||||
|
||||
const traceList = computed(() => {
|
||||
const sum = props.listTraces.map(trace => trace.count).reduce((acc, cur) => acc + cur, 0);
|
||||
const sum = props.listTraces
|
||||
.map((trace) => trace.count)
|
||||
.reduce((acc, cur) => acc + cur, 0);
|
||||
|
||||
return props.listTraces.map(trace => {
|
||||
return {
|
||||
id: trace.id,
|
||||
value: Number((getPercentLabel(trace.count / sum))),
|
||||
count: trace.count.toLocaleString('en-US'),
|
||||
count_base: trace.count,
|
||||
ratio: getPercentLabel(trace.count / sum),
|
||||
};
|
||||
}).sort((x, y) => x.id - y.id);
|
||||
return props.listTraces
|
||||
.map((trace) => {
|
||||
return {
|
||||
id: trace.id,
|
||||
value: Number(getPercentLabel(trace.count / sum)),
|
||||
count: trace.count.toLocaleString("en-US"),
|
||||
count_base: trace.count,
|
||||
ratio: getPercentLabel(trace.count / sum),
|
||||
};
|
||||
})
|
||||
.sort((x, y) => x.id - y.id);
|
||||
});
|
||||
|
||||
const caseData = computed(() => {
|
||||
if(infiniteData.value !== null){
|
||||
if (infiniteData.value !== null) {
|
||||
const data = JSON.parse(JSON.stringify(infiniteData.value)); // Deep copy the original cases data
|
||||
data.forEach(item => {
|
||||
data.forEach((item) => {
|
||||
item.facets.forEach((facet, index) => {
|
||||
item[`fac_${index}`] = facet.value; // Create a new key-value pair
|
||||
});
|
||||
@@ -132,47 +192,74 @@ const caseData = computed(() => {
|
||||
item[`att_${index}`] = attribute.value; // Create a new key-value pair
|
||||
});
|
||||
delete item.attributes; // Remove the original attributes property
|
||||
})
|
||||
});
|
||||
return data;
|
||||
}
|
||||
});
|
||||
|
||||
const columnData = computed(() => {
|
||||
const data = JSON.parse(JSON.stringify(props.cases)); // Deep copy the original cases data
|
||||
const facetName = facName => facName.trim().replace(/^(.)(.*)$/, (match, firstChar, restOfString) => firstChar.toUpperCase() + restOfString.toLowerCase());
|
||||
const facetName = (facName) =>
|
||||
facName
|
||||
.trim()
|
||||
.replace(
|
||||
/^(.)(.*)$/,
|
||||
(match, firstChar, restOfString) =>
|
||||
firstChar.toUpperCase() + restOfString.toLowerCase(),
|
||||
);
|
||||
|
||||
const result = [
|
||||
{ field: 'id', header: 'Case Id' },
|
||||
{ field: 'started_at', header: 'Start time' },
|
||||
{ field: 'completed_at', header: 'End time' },
|
||||
...data[0].facets.map((fac, index) => ({ field: `fac_${index}`, header: facetName(fac.name) })),
|
||||
...data[0].attributes.map((att, index) => ({ field: `att_${index}`, header: att.key })),
|
||||
{ field: "id", header: "Case Id" },
|
||||
{ field: "started_at", header: "Start time" },
|
||||
{ field: "completed_at", header: "End time" },
|
||||
...data[0].facets.map((fac, index) => ({
|
||||
field: `fac_${index}`,
|
||||
header: facetName(fac.name),
|
||||
})),
|
||||
...data[0].attributes.map((att, index) => ({
|
||||
field: `att_${index}`,
|
||||
header: att.key,
|
||||
})),
|
||||
];
|
||||
return result
|
||||
return result;
|
||||
});
|
||||
|
||||
// watch
|
||||
watch(() => props.listModal, (newValue) => { // Draw the chart when the modal is opened for the first time
|
||||
if(newValue) createCy();
|
||||
});
|
||||
watch(
|
||||
() => props.listModal,
|
||||
(newValue) => {
|
||||
// Draw the chart when the modal is opened for the first time
|
||||
if (newValue) createCy();
|
||||
},
|
||||
);
|
||||
|
||||
watch(() => props.taskSeq, (newValue) => {
|
||||
if (newValue !== null) createCy();
|
||||
});
|
||||
watch(
|
||||
() => props.taskSeq,
|
||||
(newValue) => {
|
||||
if (newValue !== null) createCy();
|
||||
},
|
||||
);
|
||||
|
||||
watch(() => props.traceId, (newValue) => {
|
||||
// Update showTraceId when the traceId prop changes
|
||||
showTraceId.value = newValue;
|
||||
});
|
||||
watch(
|
||||
() => props.traceId,
|
||||
(newValue) => {
|
||||
// Update showTraceId when the traceId prop changes
|
||||
showTraceId.value = newValue;
|
||||
},
|
||||
);
|
||||
|
||||
watch(showTraceId, (newValue, oldValue) => {
|
||||
const isScrollTop = document.querySelector('.infiniteTable');
|
||||
if(isScrollTop && typeof isScrollTop.scrollTop !== 'undefined') if(newValue !== oldValue) isScrollTop.scrollTop = 0;
|
||||
const isScrollTop = document.querySelector(".infiniteTable");
|
||||
if (isScrollTop && typeof isScrollTop.scrollTop !== "undefined")
|
||||
if (newValue !== oldValue) isScrollTop.scrollTop = 0;
|
||||
});
|
||||
|
||||
watch(() => props.firstCases, (newValue) => {
|
||||
infiniteData.value = newValue;
|
||||
});
|
||||
watch(
|
||||
() => props.firstCases,
|
||||
(newValue) => {
|
||||
infiniteData.value = newValue;
|
||||
},
|
||||
);
|
||||
|
||||
watch(infinite404, (newValue) => {
|
||||
if (newValue === 404) maxItems.value = true;
|
||||
@@ -184,8 +271,8 @@ watch(infinite404, (newValue) => {
|
||||
* @param {number} val - The raw ratio value.
|
||||
* @returns {string} The formatted percentage string.
|
||||
*/
|
||||
function getPercentLabel(val){
|
||||
if((val * 100).toFixed(1) >= 100) return 100;
|
||||
function getPercentLabel(val) {
|
||||
if ((val * 100).toFixed(1) >= 100) return 100;
|
||||
else return parseFloat((val * 100).toFixed(1));
|
||||
}
|
||||
/**
|
||||
@@ -193,78 +280,93 @@ function getPercentLabel(val){
|
||||
* @param {number} value - The percentage value.
|
||||
* @returns {string} The CSS width style string.
|
||||
*/
|
||||
function progressWidth(value){
|
||||
return `width:${value}%;`
|
||||
function progressWidth(value) {
|
||||
return `width:${value}%;`;
|
||||
}
|
||||
/**
|
||||
* switch case data
|
||||
* @param {number} id case id
|
||||
*/
|
||||
async function switchCaseData(id) {
|
||||
if(id == showTraceId.value) return;
|
||||
if (id == showTraceId.value) return;
|
||||
infinite404.value = null;
|
||||
maxItems.value = false;
|
||||
startNum.value = 0;
|
||||
|
||||
let result;
|
||||
if(props.category === 'issue') result = await conformanceStore.getConformanceTraceDetail(props.listNo, id, 0);
|
||||
else if(props.category === 'loop') result = await conformanceStore.getConformanceLoopsTraceDetail(props.listNo, id, 0);
|
||||
if (props.category === "issue")
|
||||
result = await conformanceStore.getConformanceTraceDetail(
|
||||
props.listNo,
|
||||
id,
|
||||
0,
|
||||
);
|
||||
else if (props.category === "loop")
|
||||
result = await conformanceStore.getConformanceLoopsTraceDetail(
|
||||
props.listNo,
|
||||
id,
|
||||
0,
|
||||
);
|
||||
infiniteData.value = await result;
|
||||
showTraceId.value = id; // Set after getDetail so the case table finishes loading before switching showTraceId
|
||||
}
|
||||
/**
|
||||
* Assembles the trace element nodes data for Cytoscape rendering.
|
||||
*/
|
||||
function setNodesData(){
|
||||
function setNodesData() {
|
||||
// Clear nodes to prevent accumulation on each render
|
||||
processMap.value.nodes = [];
|
||||
// Populate nodes with data returned from the API call
|
||||
if(props.taskSeq !== null) {
|
||||
if (props.taskSeq !== null) {
|
||||
props.taskSeq.forEach((node, index) => {
|
||||
processMap.value.nodes.push({
|
||||
data: {
|
||||
id: index,
|
||||
label: node,
|
||||
backgroundColor: '#CCE5FF',
|
||||
bordercolor: '#003366',
|
||||
shape: 'round-rectangle',
|
||||
backgroundColor: "#CCE5FF",
|
||||
bordercolor: "#003366",
|
||||
shape: "round-rectangle",
|
||||
height: 80,
|
||||
width: 100
|
||||
}
|
||||
width: 100,
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Assembles the trace edge line data for Cytoscape rendering.
|
||||
*/
|
||||
function setEdgesData(){
|
||||
function setEdgesData() {
|
||||
processMap.value.edges = [];
|
||||
if(props.taskSeq !== null) {
|
||||
if (props.taskSeq !== null) {
|
||||
props.taskSeq.forEach((edge, index) => {
|
||||
processMap.value.edges.push({
|
||||
data: {
|
||||
source: `${index}`,
|
||||
target: `${index + 1}`,
|
||||
lineWidth: 1,
|
||||
style: 'solid'
|
||||
}
|
||||
style: "solid",
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
// The number of edges is one less than the number of nodes
|
||||
processMap.value.edges.pop();
|
||||
}
|
||||
/**
|
||||
* create trace cytoscape's map
|
||||
*/
|
||||
function createCy(){
|
||||
function createCy() {
|
||||
nextTick(() => {
|
||||
const graphId = cfmTrace.value;
|
||||
|
||||
setNodesData();
|
||||
setEdgesData();
|
||||
if(graphId !== null) cytoscapeMapTrace(processMap.value.nodes, processMap.value.edges, graphId);
|
||||
if (graphId !== null)
|
||||
cytoscapeMapTrace(
|
||||
processMap.value.nodes,
|
||||
processMap.value.edges,
|
||||
graphId,
|
||||
);
|
||||
});
|
||||
}
|
||||
/**
|
||||
@@ -273,12 +375,16 @@ function createCy(){
|
||||
async function fetchData() {
|
||||
try {
|
||||
infiniteFinish.value = false;
|
||||
startNum.value += 20
|
||||
const result = await conformanceStore.getConformanceTraceDetail(props.listNo, showTraceId.value, startNum.value);
|
||||
startNum.value += 20;
|
||||
const result = await conformanceStore.getConformanceTraceDetail(
|
||||
props.listNo,
|
||||
showTraceId.value,
|
||||
startNum.value,
|
||||
);
|
||||
infiniteData.value = [...infiniteData.value, ...result];
|
||||
infiniteFinish.value = true;
|
||||
} catch(error) {
|
||||
console.error('Failed to load data:', error);
|
||||
} catch (error) {
|
||||
console.error("Failed to load data:", error);
|
||||
}
|
||||
}
|
||||
/**
|
||||
@@ -286,10 +392,16 @@ async function fetchData() {
|
||||
* @param {Event} event - The scroll event.
|
||||
*/
|
||||
function handleScroll(event) {
|
||||
if(maxItems.value || infiniteData.value.length < 20 || infiniteFinish.value === false) return;
|
||||
if (
|
||||
maxItems.value ||
|
||||
infiniteData.value.length < 20 ||
|
||||
infiniteFinish.value === false
|
||||
)
|
||||
return;
|
||||
|
||||
const container = event.target;
|
||||
const overScrollHeight = container.scrollTop + container.clientHeight + 20 >= container.scrollHeight;
|
||||
const overScrollHeight =
|
||||
container.scrollTop + container.clientHeight + 20 >= container.scrollHeight;
|
||||
|
||||
if (overScrollHeight) fetchData();
|
||||
}
|
||||
@@ -298,22 +410,22 @@ function handleScroll(event) {
|
||||
@reference "../../../assets/tailwind.css";
|
||||
/* Progress bar color */
|
||||
:deep(.p-progressbar .p-progressbar-value) {
|
||||
@apply bg-primary
|
||||
@apply bg-primary;
|
||||
}
|
||||
/* Table set */
|
||||
:deep(.p-datatable-thead) {
|
||||
@apply sticky top-0 left-0 z-10 bg-neutral-10
|
||||
@apply sticky top-0 left-0 z-10 bg-neutral-10;
|
||||
}
|
||||
:deep(.p-datatable .p-datatable-thead > tr > th) {
|
||||
@apply !border-y-0 border-neutral-500 bg-neutral-100 after:absolute after:left-0 after:w-full after:h-full after:block after:top-0 after:border-b after:border-t after:border-neutral-500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
:deep(.p-datatable .p-datatable-tbody > tr > td) {
|
||||
@apply border-neutral-500 !border-t-0 text-center
|
||||
@apply border-neutral-500 !border-t-0 text-center;
|
||||
}
|
||||
/* Center header title */
|
||||
:deep(.p-column-header-content) {
|
||||
@apply justify-center
|
||||
@apply justify-center;
|
||||
}
|
||||
:deep(.p-datatable.p-datatable-gridlines .p-datatable-tbody > tr > td) {
|
||||
min-width: 72px;
|
||||
|
||||
Reference in New Issue
Block a user