diff --git a/web/angular/package-lock.json b/web/angular/package-lock.json
index 54ee2cf..db8e5e8 100644
--- a/web/angular/package-lock.json
+++ b/web/angular/package-lock.json
@@ -838,6 +838,12 @@
"semver-intersect": "1.4.0"
}
},
+ "@types/chart.js": {
+ "version": "2.7.42",
+ "resolved": "https://registry.npmjs.org/@types/chart.js/-/chart.js-2.7.42.tgz",
+ "integrity": "sha512-+0v+PV2J9wsV7u36Vqt5Oke7ugtiZqNeYJgNk5mgNMkEkqtjxo2Q9N5Q9znHlgmXeOTfQL6e1sfcAbTnPHAzlA==",
+ "dev": true
+ },
"@types/jasmine": {
"version": "2.8.15",
"resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-2.8.15.tgz",
diff --git a/web/angular/package.json b/web/angular/package.json
index 2bbe0d5..7f7b988 100644
--- a/web/angular/package.json
+++ b/web/angular/package.json
@@ -36,6 +36,7 @@
"@angular/cli": "~7.2.2",
"@angular/compiler-cli": "~7.2.0",
"@angular/language-service": "~7.2.0",
+ "@types/chart.js": "^2.7.42",
"@types/jasmine": "~2.8.8",
"@types/jasminewd2": "~2.0.3",
"@types/node": "~8.9.4",
diff --git a/web/angular/src/app/project-dashboard/project-dashboard.component.css b/web/angular/src/app/project-dashboard/project-dashboard.component.css
index 7589a2b..d355e0b 100644
--- a/web/angular/src/app/project-dashboard/project-dashboard.component.css
+++ b/web/angular/src/app/project-dashboard/project-dashboard.component.css
@@ -1,44 +1,14 @@
-#status {
- height: 360px;
- position: relative;
- width: 360px;
- margin: 1em;
+#timeline-wrapper {
+ width: 100%;
}
-#assignees {
- height: 360px;
- position: relative;
- width: 360px;
- margin: 1em;
+#status-pie-wrapper {
+ height: 50%;
+ width: 400px;
}
-.pie-label {
- left: 130px;
- padding: 10px;
- position: absolute;
- text-align: center;
- top: 150px;
- width: 80px;
- z-index: 10;
- font-family: Roboto, "Helvetica Neue", sans-serif;
- font-weight: bold;
+#assignees-pie-wrapper {
+ margin-top: 1em;
+ height: 50%;
+ width: 400px;
}
-
-.tooltip {
- background: #eee;
- box-shadow: 0 0 5px #999999;
- color: #333;
- display: none;
- font-size: 12px;
- left: 130px;
- padding: 10px;
- position: absolute;
- text-align: center;
- top: 95px;
- width: 80px;
- z-index: 10;
-}
-
-/*---------*/
-
-
diff --git a/web/angular/src/app/project-dashboard/project-dashboard.component.html b/web/angular/src/app/project-dashboard/project-dashboard.component.html
index 59e2084..f26f4ae 100644
--- a/web/angular/src/app/project-dashboard/project-dashboard.component.html
+++ b/web/angular/src/app/project-dashboard/project-dashboard.component.html
@@ -1,5 +1,5 @@
-
+
{{"dashboard.title" | translate}} "{{project.name}}"
@@ -11,10 +11,18 @@
{{project.motd}}
@@ -26,7 +34,7 @@
-
diff --git a/web/angular/src/app/project-dashboard/project-dashboard.component.ts b/web/angular/src/app/project-dashboard/project-dashboard.component.ts
index 57269aa..9042eab 100644
--- a/web/angular/src/app/project-dashboard/project-dashboard.component.ts
+++ b/web/angular/src/app/project-dashboard/project-dashboard.component.ts
@@ -3,6 +3,8 @@ import {ApiService} from "../api.service";
import {Project} from "../models/project";
import {ActivatedRoute} from "@angular/router";
+import {Chart, ChartData, Point} from "chart.js";
+
@Component({
selector: 'app-project-dashboard',
@@ -13,7 +15,22 @@ export class ProjectDashboardComponent implements OnInit {
private projectId;
project: Project;
+ private timeline: Chart;
+ private statusPie: Chart;
+ private assigneesPir: Chart;
+ private colors = {
+ new: "#76FF03",
+ failed: "#FF3D00",
+ closed: "#E0E0E0",
+ awaiting: "#FFB74D"
+ };
+
+ tmpLabels = [];
+ tmpNew = [];
+ tmpFailed = [];
+ tmpClosed = [];
+ tmpAwaiting = [];
constructor(private apiService: ApiService, private route: ActivatedRoute) {
}
@@ -23,9 +40,208 @@ export class ProjectDashboardComponent implements OnInit {
this.route.params.subscribe(params => {
this.projectId = params["id"];
this.getProject();
+ });
+
+
+ let n = 40;
+ for (let i = 0; i < n; i++) {
+ this.tmpLabels.push((1549501926 + 600 * i) * 1000);
+ this.tmpNew.push(Math.ceil(Math.random() * 30))
+ this.tmpClosed.push(Math.ceil(Math.random() * 100))
+ this.tmpFailed.push(Math.ceil(Math.random() * 13))
+ this.tmpAwaiting.push(Math.ceil(Math.random() * 40))
+ }
+
+ this.setupTimeline();
+ this.setupStatusPie();
+ this.setupAssigneesPie();
+ }
+
+ private setupTimeline() {
+ let elem = document.getElementById("timeline") as any;
+ let ctx = elem.getContext("2d");
+
+ this.timeline = new Chart(ctx, {
+ type: "bar",
+ data: {
+ labels: this.tmpLabels,
+ datasets: [
+ {
+ label: "New",
+ type: "line",
+ fill: false,
+ borderColor: this.colors.new,
+ backgroundColor: this.colors.new,
+ data: this.tmpNew,
+ pointRadius: 0,
+ lineTension: 0.2,
+ },
+ {
+ label: "Failed",
+ type: "line",
+ fill: false,
+ borderColor: this.colors.failed,
+ backgroundColor: this.colors.failed,
+ data: this.tmpFailed,
+ pointRadius: 0,
+ lineTension: 0.2,
+ },
+ {
+ label: "Closed",
+ type: "line",
+ fill: false,
+ borderColor: this.colors.closed,
+ backgroundColor: this.colors.closed,
+ pointRadius: 0,
+ data: this.tmpClosed,
+ lineTension: 0.2,
+ },
+ {
+ label: "Awaiting verification",
+ type: "line",
+ fill: false,
+ borderColor: this.colors.awaiting,
+ backgroundColor: this.colors.awaiting,
+ data: this.tmpAwaiting,
+ pointRadius: 0,
+ lineTension: 0.2,
+ },
+ ],
+ },
+ options: {
+ title: {
+ display: true,
+ text: "Task status timeline",
+ position: "bottom"
+ },
+ legend: {
+ position: 'left',
+ },
+ scales: {
+ xAxes: [{
+ type: "time",
+ distribution: "series",
+ ticks: {
+ source: "auto"
+ },
+ time: {
+ unit: "minute",
+ unitStepSize: 10,
+ }
+ }]
+ },
+ tooltips: {
+ enabled: true,
+ intersect: false,
+ mode: "index",
+ position: "nearest",
+ },
+ }
})
}
+ private setupStatusPie() {
+
+ let elem = document.getElementById("status-pie") as any;
+ let ctx = elem.getContext("2d");
+
+ this.statusPie = new Chart(ctx, {
+ type: "doughnut",
+ data: {
+ labels: [
+ "New",
+ "Failed",
+ "Closed",
+ "Awaiting verification",
+ ],
+ datasets: [
+ {
+ label: "Task status",
+ data: [
+ 10,
+ 24,
+ 301,
+ 90,
+ ],
+ backgroundColor: [
+ this.colors.new,
+ this.colors.failed,
+ this.colors.closed,
+ this.colors.awaiting
+ ],
+ }
+ ],
+ },
+ options: {
+ responsive: true,
+ legend: {
+ position: 'left',
+ },
+ title: {
+ display: true,
+ text: "Current task status",
+ position: "bottom"
+
+ },
+ animation: {
+ animateScale: true,
+ animateRotate: true
+ }
+ }
+ });
+ }
+
+ private setupAssigneesPie() {
+
+ let elem = document.getElementById("assignees-pie") as any;
+ let ctx = elem.getContext("2d");
+
+ this.statusPie = new Chart(ctx, {
+ type: "doughnut",
+ data: {
+ labels: [
+ "marc",
+ "simon",
+ "bernie",
+ "natasha",
+ ],
+ datasets: [
+ {
+ label: "Task status",
+ data: [
+ 10,
+ 24,
+ 1,
+ 23,
+ ],
+ backgroundColor: [
+ this.colors.new,
+ this.colors.failed,
+ this.colors.closed,
+ this.colors.awaiting
+ ],
+ }
+ ],
+ },
+ options: {
+ responsive: true,
+ legend: {
+ position: 'left',
+ },
+ title: {
+ display: true,
+ text: "Task assignment",
+ position: "bottom"
+
+ },
+ animation: {
+ animateScale: true,
+ animateRotate: true
+ }
+ }
+ });
+ }
+
private getProject() {
this.apiService.getProject(this.projectId).subscribe(data => {
this.project = {