Skip to content
Snippets Groups Projects
Commit 14ea1373 authored by Zitnik, Anze's avatar Zitnik, Anze
Browse files

Support for multiple trees (ToEs) and history

Merge branch 'feature/multiple-trees' into 'develop'
See merge request medina/cce-frontend!5
parents cf7dd2f6 2e79e6d8
No related branches found
No related tags found
No related merge requests found
{
"name": "cce-frontend",
"version": "0.1.5",
"version": "0.1.6",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "cce-frontend",
"version": "0.1.5",
"version": "0.1.6",
"dependencies": {
"@fortawesome/fontawesome-free": "^6.1.1",
"@fortawesome/fontawesome-svg-core": "^6.1.1",
......@@ -20,7 +20,7 @@
"core-js": "^3.23.1",
"eslint-plugin-vue": "^9.1.1",
"jquery": "^3.6.0",
"moment": "^2.29.3",
"luxon": "^3.0.3",
"vue": "^3.2.37",
"vue-axios": "^3.4.1",
"vue-router": "^4.0.16",
......@@ -2945,9 +2945,9 @@
"dev": true
},
"node_modules/@vue/devtools-api": {
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.1.4.tgz",
"integrity": "sha512-IiA0SvDrJEgXvVxjNkHPFfDx6SXw0b/TUkqMcDZWNg9fnCAHbTpoo59YfJ9QLFkwa3raau5vSlRVzMSLDnfdtQ=="
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.2.1.tgz",
"integrity": "sha512-OEgAMeQXvCoJ+1x8WyQuVZzFo0wcyCmUR3baRVLmKBo1LmYZWMlRiXlux5jd0fqVJu6PfDbOrZItVqUEzLobeQ=="
},
"node_modules/@vue/eslint-config-prettier": {
"version": "7.0.0",
......@@ -7707,6 +7707,14 @@
"node": ">=10"
}
},
"node_modules/luxon": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.0.3.tgz",
"integrity": "sha512-+EfHWnF+UT7GgTnq5zXg3ldnTKL2zdv7QJgsU5bjjpbH17E3qi/puMhQyJVYuCq+FRkogvB5WB6iVvUr+E4a7w==",
"engines": {
"node": ">=12"
}
},
"node_modules/magic-string": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
......@@ -7974,14 +7982,6 @@
"integrity": "sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==",
"dev": true
},
"node_modules/moment": {
"version": "2.29.3",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.3.tgz",
"integrity": "sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw==",
"engines": {
"node": "*"
}
},
"node_modules/mrmime": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz",
......@@ -10350,9 +10350,9 @@
}
},
"node_modules/terser": {
"version": "5.14.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.14.1.tgz",
"integrity": "sha512-+ahUAE+iheqBTDxXhTisdA8hgvbEG1hHOQ9xmNjeUJSoi6DU/gMrKNcfZjHkyY6Alnuyc+ikYJaxxfHkT3+WuQ==",
"version": "5.15.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.15.0.tgz",
"integrity": "sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA==",
"dev": true,
"dependencies": {
"@jridgewell/source-map": "^0.3.2",
......@@ -13871,9 +13871,9 @@
}
},
"@vue/devtools-api": {
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.1.4.tgz",
"integrity": "sha512-IiA0SvDrJEgXvVxjNkHPFfDx6SXw0b/TUkqMcDZWNg9fnCAHbTpoo59YfJ9QLFkwa3raau5vSlRVzMSLDnfdtQ=="
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.2.1.tgz",
"integrity": "sha512-OEgAMeQXvCoJ+1x8WyQuVZzFo0wcyCmUR3baRVLmKBo1LmYZWMlRiXlux5jd0fqVJu6PfDbOrZItVqUEzLobeQ=="
},
"@vue/eslint-config-prettier": {
"version": "7.0.0",
......@@ -17412,6 +17412,11 @@
"yallist": "^4.0.0"
}
},
"luxon": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.0.3.tgz",
"integrity": "sha512-+EfHWnF+UT7GgTnq5zXg3ldnTKL2zdv7QJgsU5bjjpbH17E3qi/puMhQyJVYuCq+FRkogvB5WB6iVvUr+E4a7w=="
},
"magic-string": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
......@@ -17612,11 +17617,6 @@
"integrity": "sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==",
"dev": true
},
"moment": {
"version": "2.29.3",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.3.tgz",
"integrity": "sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw=="
},
"mrmime": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz",
......@@ -19327,9 +19327,9 @@
"dev": true
},
"terser": {
"version": "5.14.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.14.1.tgz",
"integrity": "sha512-+ahUAE+iheqBTDxXhTisdA8hgvbEG1hHOQ9xmNjeUJSoi6DU/gMrKNcfZjHkyY6Alnuyc+ikYJaxxfHkT3+WuQ==",
"version": "5.15.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.15.0.tgz",
"integrity": "sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA==",
"dev": true,
"requires": {
"@jridgewell/source-map": "^0.3.2",
......
......@@ -14,10 +14,8 @@ export default {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
background: #f3f8fb;
padding: 60px;
height: 100vh;
}
</style>
<template>
<div class="dropdown">
<button
class="btn btn-primary dropdown-toggle"
type="button"
data-bs-toggle="dropdown"
aria-expanded="false"
>
{{ currentTreeShown }}
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenuButton1">
<li
v-for="tree in toeHistory"
:key="tree.treeStateId"
:class="disabled(tree)"
@click="getHistoryTree(tree)"
class="dropdown-item"
>
<a>
{{ formatDatetime(tree.timeUpdated) }}
</a>
</li>
</ul>
</div>
</template>
<script>
import { DateTime } from "luxon";
import { getLastTree } from "@/helpers/helpers";
export default {
name: "ToeHistory",
props: ["toeHistory", "currentTreeState"],
data: function () {
return {
formatDatetime(datetime) {
return DateTime.fromISO(datetime).toLocaleString(
DateTime.DATETIME_FULL
);
},
disabled(tree) {
if (tree.treeStateId === this.currentTreeState.treeStateId) {
return "disabled";
}
return "";
},
};
},
computed: {
currentTreeShown() {
if (
this.currentTreeState.treeStateId ===
getLastTree(this.toeHistory).treeStateId
) {
return "Current tree state";
} else {
return this.formatDatetime(this.currentTreeState.timeUpdated);
}
},
},
methods: {
getHistoryTree(tree) {
this.$store.dispatch("getTreeDataByStateId", tree.treeStateId);
},
},
};
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
.disabled {
pointer-events: none;
opacity: 0.6;
}
.dropdown-item {
cursor: pointer;
}
</style>
<template>
<div class="dropdown">
<button
class="btn btn-primary dropdown-toggle"
type="button"
data-bs-toggle="dropdown"
aria-expanded="false"
>
{{ initSelectedToe.name }}
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenuButton1">
<li
v-for="toe in toeList"
:key="toe.toeId"
:class="disabled(toe)"
@click="setToeTree(toe)"
class="dropdown-item"
>
<a>{{ toe.name }}</a>
</li>
</ul>
</div>
</template>
<script>
export default {
name: "ToeList",
props: ["toeList", "initSelectedToe"],
data: function () {
return {
disabled(toe) {
if (toe.toeId === this.initSelectedToe.targetOfEvaluationId) {
return "disabled";
}
return "";
},
};
},
methods: {
setToeTree(toe) {
this.$store.dispatch("selectToe", toe.toeId);
},
},
};
</script>
<style scoped lang="scss">
@import "@/styles/_variables.scss";
.toe-list {
.dropdown-toggle {
max-width: 100%;
padding: 6px 12px;
overflow: hidden;
text-overflow: ellipsis;
}
.dropdown-item {
cursor: pointer;
}
.disabled {
pointer-events: none;
opacity: 0.6;
}
}
</style>
<template>
<div class="container">
<div id="toe-container" class="container">
<div class="box info-box" v-show="hoveredNode">
<div class="close-button">
<i @click="infoBoxClose" title="Close" class="fa fa-solid fa-xmark"></i>
......@@ -151,7 +151,7 @@
<script>
import VueTree from "@ssthouse/vue3-tree-chart";
import "@ssthouse/vue3-tree-chart/dist/vue3-tree-chart.css";
import { formatDateHelper } from "@/helpers/dataFormat";
import { formatDateHelper } from "@/helpers/helpers";
import $ from "jquery";
import { collapseExpandTree } from "@/mixins/collapseExpandTree";
......@@ -233,6 +233,11 @@ export default {
return formatDateHelper(date);
},
},
watch: {
treeData() {
this.infoBoxClose();
},
},
};
</script>
......@@ -246,6 +251,11 @@ export default {
max-width: 5000px;
}
#toe-container {
position: relative;
padding-bottom: 20px;
}
.tree-container {
background: white;
margin-top: 20px;
......@@ -289,9 +299,11 @@ export default {
}
&.info-box {
position: absolute;
border-color: $color-blue;
z-index: 10;
left: 72px;
left: 12px;
top: 74px;
}
&#instructions {
......
import moment from "moment";
export function formatDateHelper(value) {
if (value) {
return moment(String(value)).format("DD MMM YYYY HH:mm:ss");
}
}
import { DateTime } from "luxon";
export function formatDateHelper(value) {
if (value) {
return DateTime.fromISO(value).toFormat("DD MMM YYYY HH:mm:ss");
}
}
export function getLastTree(historyData) {
if (!historyData.length) return;
let lastTreeIdx = 0;
let closestTime = DateTime.fromISO(historyData[0].timeUpdated);
for (let i = 0; i < historyData.length; i++) {
let diff = DateTime.fromISO(historyData[i].timeUpdated).diff(
DateTime.fromISO(closestTime)
);
if (diff.milliseconds > 0) {
closestTime = historyData[i].timeUpdated;
lastTreeIdx = i;
}
}
return historyData[lastTreeIdx];
}
import api from "@/client/client";
export const ApiService = {
getEvaluationTreeData() {
return api.get("/tree");
getToeList() {
return api.get("/toeList");
},
getToeHistory(toeId) {
return api.get("/toes/" + toeId + "/listHistory");
},
getTreeByStateId(stateId) {
return api.get("/history/" + stateId);
},
};
......@@ -5,33 +5,88 @@ import { collapseExpandTree } from "@/mixins/collapseExpandTree";
const store = createStore({
state: {
treeData: {},
toeList: {},
toeHistory: {},
treeDataListByStateId: [],
},
mutations: {
SAVE_TREE_DATA(state, treeData) {
state.treeData = treeData;
},
SAVE_TOE_LIST(state, toeList) {
state.toeList = toeList;
},
SAVE_TOE_HISTORY(state, toeHistory) {
state.toeHistory = toeHistory;
},
SAVE_TREE_DATA_LIST(state, treeData) {
state.treeDataListByStateId.push(treeData);
},
},
getters: {
treeData: (state) => state.treeData,
toeList: (state) => state.toeList,
toeHistory: (state) => state.toeHistory,
treeByStateId: (state) => (stateId) => {
return state.treeDataListByStateId.find(
(tree) => tree.treeStateId === stateId
);
},
},
actions: {
async getTreeData({ commit }) {
await ApiService.getEvaluationTreeData()
async getTreeDataByStateId({ commit }, stateId) {
if (!stateId) return;
if (stateId && this.getters.treeByStateId(stateId)) {
return commit("SAVE_TREE_DATA", this.getters.treeByStateId(stateId));
}
ApiService.getTreeByStateId(stateId)
.then((response) => {
// collapse all except root
collapseExpandTree.methods.collapseAllFromLevel(
response.data.root,
1
);
commit("SAVE_TREE_DATA", response.data);
commit("SAVE_TREE_DATA_LIST", response.data);
})
.catch((err) => {
console.error("Error getting tree by tree state id: " + err);
});
},
async getToeList({ commit }) {
await ApiService.getToeList()
.then((response) => {
commit("SAVE_TOE_LIST", response.data);
})
.catch((err) => {
console.log("ERROR at action getTreeData: " + err);
console.error("Error getting ToE list: " + err);
});
},
async updateTreeData({ commit }, data) {
commit("SAVE_TREE_DATA", data);
async getToeHistory({ commit }, id) {
await ApiService.getToeHistory(id)
.then((response) => {
commit("SAVE_TOE_HISTORY", response.data);
})
.catch((err) => {
console.error("Error getting ToE history: " + err);
});
},
async initToeListAndTree({ dispatch }) {
// TODO: add logic for when there are no trees
await dispatch("getToeList").then(() => {
dispatch("getToeHistory", this.state.toeList[0].toeId).then(() => {
dispatch(
"getTreeDataByStateId",
this.state.toeHistory[0].treeStateId
);
});
});
},
async selectToe({ dispatch }, toeId) {
await dispatch("getToeHistory", toeId).then(() => {
dispatch("getTreeDataByStateId", this.state.toeHistory[0].treeStateId);
});
},
},
});
......
<template>
<div id="home">
<div id="home" class="container" v-if="treeData.root">
<div class="row toe-dropdowns">
<div class="col-sm-6 toe-list">
<ToeList :toeList="toeList" :initSelectedToe="treeData"></ToeList>
</div>
<div class="col-sm-6 toe-history">
<ToeHistory
:toeHistory="toeHistory"
:currentTreeState="treeData"
></ToeHistory>
</div>
</div>
<Tree :treeData="treeData.root"></Tree>
</div>
</template>
......@@ -7,6 +18,8 @@
<script>
import { mapGetters } from "vuex";
import Tree from "@/components/Tree";
import ToeList from "@/components/ToeList";
import ToeHistory from "@/components/ToeHistory";
export default {
name: "Home",
......@@ -15,14 +28,27 @@ export default {
},
computed: {
...mapGetters(["treeData"]),
...mapGetters(["toeList"]),
...mapGetters(["toeHistory"]),
},
components: {
Tree,
ToeList,
ToeHistory,
},
created() {
this.$store.dispatch("getTreeData");
this.$store.dispatch("initToeListAndTree");
},
};
</script>
<style scoped lang="scss"></style>
<style scoped lang="scss">
.toe-dropdowns {
padding: 40px 12px 10px;
}
.toe-history {
.dropdown {
float: right;
}
}
</style>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment