Pull request #8: Charts
Merge in RED/ui from charts to master * commit 'bea47054b7dd8ed7a4837cbe62da3e3c9d01cd50': Finished charts on project listing Charts
This commit is contained in:
commit
064efe404a
@ -1,4 +1,4 @@
|
||||
<div class="flex-row">
|
||||
<div [className]="color + ' oval ' + size">{{initials}}</div>
|
||||
<div *ngIf="withName" class="name clamp-2">{{username || ('initials-avatar.unassigned.label' | translate)}}</div>
|
||||
<div *ngIf="withName" class="clamp-2">{{username || ('initials-avatar.unassigned.label' | translate)}}</div>
|
||||
</div>
|
||||
|
||||
@ -1,10 +1,5 @@
|
||||
@import "../../../assets/styles/red-variables";
|
||||
|
||||
.name {
|
||||
font-size: 13px;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.flex-row {
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<div class="rectangle-container" [ngClass]="{ small: small }">
|
||||
<div *ngFor="let rect of config" [className]="'section-wrapper flex-' + rect.length">
|
||||
<div [className]="'rectangle ' + rect.color "></div>
|
||||
<div *ngIf="rect.title" class="subtitle">{{ rect.title }}</div>
|
||||
<div *ngIf="rect.label" [ngClass]="labelClass">{{ rect.label }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -38,20 +38,40 @@
|
||||
.rectangle {
|
||||
height: 4px;
|
||||
|
||||
&.grey {
|
||||
background-color: $grey-4;
|
||||
&.unassigned {
|
||||
background-color: $grey-5;
|
||||
}
|
||||
|
||||
&.yellow {
|
||||
&.under-review {
|
||||
background-color: $yellow-1;
|
||||
}
|
||||
|
||||
&.blue {
|
||||
background-color: $blue-1;
|
||||
&.under-approval {
|
||||
background-color: $red-1;
|
||||
}
|
||||
|
||||
&.green {
|
||||
background-color: $green-1;
|
||||
&.approved {
|
||||
background-color: $blue-2;
|
||||
}
|
||||
|
||||
&.submitted {
|
||||
background-color: $blue-3;
|
||||
}
|
||||
|
||||
&.efsa {
|
||||
background-color: $blue-4;
|
||||
}
|
||||
|
||||
&.finished {
|
||||
background-color: $green-2;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: $primary;
|
||||
}
|
||||
|
||||
&.archived {
|
||||
background-color: rgba($red-1, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import { Color } from '../../utils/types';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-status-bar',
|
||||
@ -10,13 +11,16 @@ export class StatusBarComponent implements OnInit {
|
||||
@Input()
|
||||
public config: {
|
||||
length: number,
|
||||
color: 'green' | 'blue' | 'red' | 'grey' | 'yellow',
|
||||
title?: string,
|
||||
color: Color,
|
||||
label?: string,
|
||||
}[] = [];
|
||||
|
||||
@Input()
|
||||
public small = false;
|
||||
|
||||
@Input()
|
||||
public labelClass = '';
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
|
||||
@ -84,7 +84,8 @@
|
||||
|
||||
<div *ngIf="infoTab" class="tab-content info-container">
|
||||
<redaction-status-bar [small]="true"
|
||||
[config]="[{ length: 1, title: 'Unassigned', color: 'grey'}]"></redaction-status-bar>
|
||||
[labelClass]="'subtitle'"
|
||||
[config]="[{ length: 1, label: 'Unassigned', color: 'unassigned'}]"></redaction-status-bar>
|
||||
|
||||
<div class="subtitle stats-subtitle mt-5">
|
||||
<div>645</div>
|
||||
|
||||
@ -65,8 +65,6 @@ redaction-pdf-viewer {
|
||||
}
|
||||
|
||||
.assign-reviewer {
|
||||
font-size: 13px;
|
||||
line-height: 16px;
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
@ -92,8 +90,6 @@ redaction-pdf-viewer {
|
||||
}
|
||||
|
||||
.page-number {
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
<div class="left-container">
|
||||
<div class="table-header">
|
||||
<span class="subheading">
|
||||
{{'projects.table-header.title.label'| translate:{length: appStateService.allProjects?.length || 0} }}
|
||||
{{'projects.table-header.title.label'| translate:{ length: appStateService.allProjects?.length || 0 } }}
|
||||
</span>
|
||||
<div class="actions">
|
||||
<div translate="projects.table-header.bulk-select.label"></div>
|
||||
@ -57,7 +57,7 @@
|
||||
</div>
|
||||
<div class="stats-bar">
|
||||
<redaction-status-bar
|
||||
[config]="[{ color: 'yellow', length: 2}, { length: 1, color: 'green'}]"
|
||||
[config]="[{ color: 'under-review', length: 2}, { length: 1, color: 'finished'}]"
|
||||
></redaction-status-bar>
|
||||
</div>
|
||||
|
||||
@ -79,8 +79,36 @@
|
||||
</div>
|
||||
|
||||
<div class="right-fixed-container">
|
||||
<redaction-simple-doughnut-chart [data]="projectsChartData" [strokeWidth]="15"></redaction-simple-doughnut-chart>
|
||||
<div>
|
||||
<redaction-simple-doughnut-chart [config]="projectsChartData"
|
||||
[strokeWidth]="15"
|
||||
[subtitle]="'Projects'"
|
||||
></redaction-simple-doughnut-chart>
|
||||
|
||||
<div class="project-stats-container">
|
||||
<div class="project-stats-item">
|
||||
<mat-icon svgIcon="red:files"></mat-icon>
|
||||
<div>
|
||||
<div class="heading">1324</div>
|
||||
<div>Analyzed pages</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="project-stats-item">
|
||||
<mat-icon svgIcon="red:user"></mat-icon>
|
||||
<div>
|
||||
<div class="heading">240</div>
|
||||
<div>Total people</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<redaction-simple-doughnut-chart [config]="documentsChartData"
|
||||
[strokeWidth]="15"
|
||||
[subtitle]="'Total Documents'"
|
||||
></redaction-simple-doughnut-chart>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -15,3 +15,42 @@
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.right-fixed-container {
|
||||
display: flex;
|
||||
width: 470px;
|
||||
padding-top: 50px;
|
||||
|
||||
> div {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.project-stats-container {
|
||||
width: fit-content;
|
||||
|
||||
.project-stats-item {
|
||||
display: flex;
|
||||
width: fit-content;
|
||||
gap: 5px;
|
||||
margin-top: 25px;
|
||||
|
||||
&:first-of-type {
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
mat-icon {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
margin-top: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.left-container {
|
||||
width: calc(100vw - #{$right-container-width} - 130px);
|
||||
}
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {Project, ProjectControllerService} from "@redaction/red-ui-http";
|
||||
import {MatDialog} from "@angular/material/dialog";
|
||||
import {AddEditProjectDialogComponent} from "./add-edit-project-dialog/add-edit-project-dialog.component";
|
||||
import {ConfirmationDialogComponent} from "../../common/confirmation-dialog/confirmation-dialog.component";
|
||||
import {TranslateService} from "@ngx-translate/core";
|
||||
import {NotificationService} from "../../notification/notification.service";
|
||||
import {AppStateService, ProjectWrapper} from "../../state/app-state.service";
|
||||
import {UserService} from "../../user/user.service";
|
||||
import {ProjectDetailsDialogComponent} from "../project-overview-screen/project-details-dialog/project-details-dialog.component";
|
||||
import {DataSeries} from "../../simple-doughnut-chart/simple-doughnut-chart.component";
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Project, ProjectControllerService } from '@redaction/red-ui-http';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { AddEditProjectDialogComponent } from './add-edit-project-dialog/add-edit-project-dialog.component';
|
||||
import { ConfirmationDialogComponent } from '../../common/confirmation-dialog/confirmation-dialog.component';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { NotificationService } from '../../notification/notification.service';
|
||||
import { AppStateService, ProjectWrapper } from '../../state/app-state.service';
|
||||
import { UserService } from '../../user/user.service';
|
||||
import { ProjectDetailsDialogComponent } from '../project-overview-screen/project-details-dialog/project-details-dialog.component';
|
||||
import { DoughnutChartConfig } from '../../simple-doughnut-chart/simple-doughnut-chart.component';
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-project-listing-screen',
|
||||
@ -16,7 +16,8 @@ import {DataSeries} from "../../simple-doughnut-chart/simple-doughnut-chart.comp
|
||||
styleUrls: ['./project-listing-screen.component.scss']
|
||||
})
|
||||
export class ProjectListingScreenComponent implements OnInit {
|
||||
projectsChartData: DataSeries [] = [];
|
||||
projectsChartData: DoughnutChartConfig [] = [];
|
||||
documentsChartData: DoughnutChartConfig [] = [];
|
||||
|
||||
constructor(
|
||||
public readonly appStateService: AppStateService,
|
||||
@ -34,8 +35,14 @@ export class ProjectListingScreenComponent implements OnInit {
|
||||
ngOnInit(): void {
|
||||
this.appStateService.reset();
|
||||
this.projectsChartData = [
|
||||
{value: this.activeProjects, color: '#DD4D50', label: 'active-projects'},
|
||||
{value: this.inactiveProjects, color: '#f8eded', label: 'Archived'}];
|
||||
{ value: this.activeProjects, color: 'active', label: 'active-projects' },
|
||||
{ value: this.inactiveProjects, color: 'archived', label: 'Archived' }
|
||||
];
|
||||
this.documentsChartData = [
|
||||
{ value: 20, color: 'unassigned', label: 'unassigned' },
|
||||
{ value: 40, color: 'under-review', label: 'under review' },
|
||||
{ value: 16, color: 'under-approval', label: 'under approval' },
|
||||
]
|
||||
}
|
||||
|
||||
openAddProjectDialog(project?: Project): void {
|
||||
@ -55,7 +62,7 @@ export class ProjectListingScreenComponent implements OnInit {
|
||||
$event.stopPropagation();
|
||||
const dialogRef = this._dialog.open(ConfirmationDialogComponent, {
|
||||
width: '400px',
|
||||
maxWidth: '90vw',
|
||||
maxWidth: '90vw'
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(result => {
|
||||
@ -84,7 +91,7 @@ export class ProjectListingScreenComponent implements OnInit {
|
||||
}
|
||||
|
||||
get activeProjects() {
|
||||
return this.appStateService.allProjects.reduce((i, p) => i+(p.project.status === Project.StatusEnum.ACTIVE ? 1 :0), 0)
|
||||
return this.appStateService.allProjects.reduce((i, p) => i + (p.project.status === Project.StatusEnum.ACTIVE ? 1 : 0), 0);
|
||||
}
|
||||
|
||||
get inactiveProjects() {
|
||||
|
||||
@ -61,7 +61,7 @@
|
||||
<div class=" status-container">
|
||||
<div class="status-bar-wrapper">
|
||||
<redaction-status-bar
|
||||
[config]="[{ color: 'yellow', length: 1 }]"
|
||||
[config]="[{ color: 'under-review', length: 1 }]"
|
||||
></redaction-status-bar>
|
||||
</div>
|
||||
</div>
|
||||
@ -110,7 +110,7 @@
|
||||
></redaction-initials-avatar>
|
||||
</div>
|
||||
|
||||
<div class="description mt-20">
|
||||
<div class="mt-20">
|
||||
{{ appStateService.activeProject.project.description }}
|
||||
</div>
|
||||
|
||||
|
||||
@ -24,11 +24,6 @@
|
||||
}
|
||||
|
||||
.project-details-container {
|
||||
.description {
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.members-container {
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
@ -1,16 +1,30 @@
|
||||
<svg attr.height="{{size}}" attr.width="{{size}}" attr.viewBox="0 0 {{size}} {{size}}" class="donut-chart">
|
||||
<g *ngFor="let value of data; let i = index">
|
||||
<circle attr.cx="{{cx}}"
|
||||
attr.cy="{{cy}}"
|
||||
attr.r="{{radius}}"
|
||||
attr.stroke="{{data[i].color}}"
|
||||
attr.stroke-width="{{strokeWidth}}"
|
||||
attr.stroke-dasharray="{{adjustedCircumference}}"
|
||||
attr.stroke-dashoffset="{{calculateStrokeDashOffset(value.value, circumference)}}"
|
||||
fill="transparent"
|
||||
attr.transform="{{returnCircleTransformValue(i)}}"/>
|
||||
</g>
|
||||
</svg>
|
||||
<div *ngFor="let val of data">
|
||||
{{val.value}} {{val.label}}
|
||||
<div class="container">
|
||||
<svg attr.height="{{size}}" attr.width="{{size}}" attr.viewBox="0 0 {{size}} {{size}}" class="donut-chart">
|
||||
<g *ngFor="let value of parsedConfig; let i = index">
|
||||
<circle attr.cx="{{cx}}"
|
||||
attr.cy="{{cy}}"
|
||||
attr.r="{{radius}}"
|
||||
[class]="value.color"
|
||||
attr.stroke-width="{{strokeWidth}}"
|
||||
attr.stroke-dasharray="{{adjustedCircumference}}"
|
||||
attr.stroke-dashoffset="{{calculateStrokeDashOffset(value.value, circumference)}}"
|
||||
fill="transparent"
|
||||
attr.transform="{{returnCircleTransformValue(i)}}" />
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
<div class="text-container" [style]="'height: ' + size + 'px; width: ' + size + 'px;'">
|
||||
<div class="heading-xl">{{ dataTotal }}</div>
|
||||
<div class="projects-text mt-5">{{ subtitle }}</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-20 breakdown-container">
|
||||
<div>
|
||||
<div *ngFor="let val of parsedConfig">
|
||||
<redaction-status-bar [small]="true"
|
||||
[config]="[{ length: val.value, color: val.color, label: val.value + ' ' + val.label}]">
|
||||
</redaction-status-bar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
Before Width: | Height: | Size: 673 B After Width: | Height: | Size: 1.2 KiB |
@ -0,0 +1,69 @@
|
||||
@import "../../assets/styles/red-variables";
|
||||
|
||||
.container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.text-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.breakdown-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
div {
|
||||
width: fit-content;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
circle {
|
||||
&.unassigned {
|
||||
stroke: $grey-5;
|
||||
}
|
||||
|
||||
&.under-review {
|
||||
stroke: $yellow-1;
|
||||
}
|
||||
|
||||
&.under-approval {
|
||||
stroke: $red-1;
|
||||
}
|
||||
|
||||
&.approved {
|
||||
stroke: $blue-2;
|
||||
}
|
||||
|
||||
&.submitted {
|
||||
stroke: $blue-3;
|
||||
}
|
||||
|
||||
&.efsa {
|
||||
stroke: $blue-4;
|
||||
}
|
||||
|
||||
&.finished {
|
||||
stroke: $green-2;
|
||||
}
|
||||
|
||||
&.active {
|
||||
stroke: $primary;
|
||||
}
|
||||
|
||||
&.archived {
|
||||
stroke: rgba($red-1, 0.1);
|
||||
}
|
||||
}
|
||||
@ -1,13 +1,12 @@
|
||||
import {Component, Input, OnInit} from '@angular/core';
|
||||
import { Color } from '../utils/types';
|
||||
|
||||
|
||||
export class DataSeries {
|
||||
export class DoughnutChartConfig {
|
||||
value: number;
|
||||
color: string;
|
||||
color: Color;
|
||||
label: string;
|
||||
}
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'redaction-simple-doughnut-chart',
|
||||
templateUrl: './simple-doughnut-chart.component.html',
|
||||
@ -15,18 +14,17 @@ export class DataSeries {
|
||||
})
|
||||
export class SimpleDoughnutChartComponent implements OnInit {
|
||||
|
||||
@Input() title: string;
|
||||
@Input() subtitle: number;
|
||||
@Input() data: DataSeries[] = [];
|
||||
@Input() subtitle: string;
|
||||
@Input() config: DoughnutChartConfig[] = [];
|
||||
@Input() angleOffset = -90;
|
||||
@Input() radius = 80;
|
||||
@Input() radius = 85;
|
||||
@Input() strokeWidth = 20;
|
||||
|
||||
chartData: any[] = [];
|
||||
perimeter: number;
|
||||
cx = 0;
|
||||
cy = 0;
|
||||
size = 0;
|
||||
public chartData: any[] = [];
|
||||
public perimeter: number;
|
||||
public cx = 0;
|
||||
public cy = 0;
|
||||
public size = 0;
|
||||
|
||||
constructor() {
|
||||
}
|
||||
@ -47,11 +45,11 @@ export class SimpleDoughnutChartComponent implements OnInit {
|
||||
};
|
||||
|
||||
get dataTotal() {
|
||||
return this.data.map(v => v.value).reduce((acc, val) => acc + val, 0);
|
||||
return this.config.map(v => v.value).reduce((acc, val) => acc + val, 0);
|
||||
};
|
||||
|
||||
calculateChartData() {
|
||||
this.data.forEach((dataVal) => {
|
||||
this.config.forEach((dataVal) => {
|
||||
const data = {
|
||||
degrees: this.angleOffset,
|
||||
}
|
||||
@ -65,10 +63,6 @@ export class SimpleDoughnutChartComponent implements OnInit {
|
||||
return circumference - strokeDiff;
|
||||
}
|
||||
|
||||
degreesToRadians(angle) {
|
||||
return angle * (Math.PI / 180);
|
||||
}
|
||||
|
||||
dataPercentage(dataVal) {
|
||||
return dataVal / this.dataTotal;
|
||||
}
|
||||
@ -77,4 +71,8 @@ export class SimpleDoughnutChartComponent implements OnInit {
|
||||
return `rotate(${this.chartData[index].degrees}, ${this.cx}, ${this.cy})`;
|
||||
}
|
||||
|
||||
// Eliminate items with value = 0
|
||||
public get parsedConfig() {
|
||||
return this.config.filter((el) => el.value);
|
||||
}
|
||||
}
|
||||
|
||||
10
apps/red-ui/src/app/utils/types.d.ts
vendored
Normal file
10
apps/red-ui/src/app/utils/types.d.ts
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
export type Color =
|
||||
'unassigned' |
|
||||
'under-review' |
|
||||
'under-approval' |
|
||||
'approved' |
|
||||
'submitted' |
|
||||
'efsa' |
|
||||
'finished' |
|
||||
'active' |
|
||||
'archived';
|
||||
@ -9,7 +9,7 @@
|
||||
cursor: pointer;
|
||||
color: $accent;
|
||||
background: $white;
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-family: Inter, sans-serif;
|
||||
font-size: 13px;
|
||||
letter-spacing: 0;
|
||||
line-height: 14px;
|
||||
|
||||
@ -5,8 +5,10 @@ html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100vh;
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-family: Inter, sans-serif;
|
||||
color: $accent;
|
||||
font-size: 13px;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
@ -57,9 +59,6 @@ html, body {
|
||||
}
|
||||
|
||||
.filters {
|
||||
font-size: 13px;
|
||||
line-height: 14px;
|
||||
|
||||
> div {
|
||||
padding: 10px 14px;
|
||||
}
|
||||
@ -157,8 +156,6 @@ html, body {
|
||||
.breadcrumb {
|
||||
text-decoration: none;
|
||||
color: $accent;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
font-weight: 600;
|
||||
@include line-clamp(1);
|
||||
max-width: 320px;
|
||||
|
||||
@ -10,7 +10,6 @@
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
font-size: 13px;
|
||||
|
||||
div {
|
||||
padding: 10px 14px;
|
||||
@ -41,9 +40,7 @@
|
||||
}
|
||||
|
||||
.table-item-title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
line-height: 18px;
|
||||
@include line-clamp(1);
|
||||
}
|
||||
|
||||
|
||||
@ -8,13 +8,18 @@ $dark: #000;
|
||||
|
||||
$grey-1: #283241;
|
||||
$grey-2: #ECECEE;
|
||||
$grey-3: #aaacb3;
|
||||
$grey-3: #AAACB3;
|
||||
$grey-4: #E2E4E9;
|
||||
$grey-5: #D3D5DA;
|
||||
|
||||
$blue-1: #4875F7;
|
||||
$blue-2: #48C9F7;
|
||||
$blue-3: #5B97DB;
|
||||
$blue-4: #374C81;
|
||||
$red-1: #F65757;
|
||||
$yellow-1: #FFB83B;
|
||||
$green-1: #46CE7D;
|
||||
$green-2: #5CE594;
|
||||
|
||||
$separator: rgba(226,228,233,0.9);
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user