Merge pull request #72 from Pear-Trading/finn/graphs

Adding new graph views and fixes
This commit is contained in:
Finn 2018-04-13 18:19:12 +01:00 committed by GitHub
commit 7881ae1103
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 293 additions and 102 deletions

View file

@ -2,6 +2,10 @@
# Next Release # Next Release
# v0.1.7
* Fixed category on upload highlighting
# v0.1.6 # v0.1.6
* Changed layout of category choosing on upload * Changed layout of category choosing on upload

2
package-lock.json generated
View file

@ -1,6 +1,6 @@
{ {
"name": "localloop-web", "name": "localloop-web",
"version": "0.1.4", "version": "0.1.7",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View file

@ -1,6 +1,6 @@
{ {
"name": "localloop-web", "name": "localloop-web",
"version": "0.1.4", "version": "0.1.7",
"description": "LocalLoop Web - Web interface for LocalLoop app", "description": "LocalLoop Web - Web interface for LocalLoop app",
"author": "", "author": "",
"url": "http://www.peartrade.org", "url": "http://www.peartrade.org",

View file

@ -72,7 +72,7 @@
<div class="col-md-9"> <div class="col-md-9">
<div class="row"> <div class="row">
<div class="col-md-6 btn-group-vertical"> <div class="col-md-6 btn-group-vertical">
<label class="btn btn-secondary mb-0" [class.active]="categoryId == ''"> <label class="btn btn-secondary mb-0" [class.active]="categoryId == null">
<input value="" type="radio" name="radios" style="display:none;" [(ngModel)]="categoryId">Uncategorised <input value="" type="radio" name="radios" style="display:none;" [(ngModel)]="categoryId">Uncategorised
</label> </label>
<label *ngFor="let category of leftCategoryList" class="btn btn-secondary mb-0" [class.active]="categoryId == category"> <label *ngFor="let category of leftCategoryList" class="btn btn-secondary mb-0" [class.active]="categoryId == category">

View file

@ -22,7 +22,7 @@
</div> </div>
</li> </li>
<li *ngFor="let categoryEntry of weekList1 | slice:0:categoryLimit1; let i=index;"> <li *ngFor="let categoryEntry of weekList1 | slice:0:categoryLimit1; let i=index;">
<span class="title">{{ categoryList[categoryEntry.category] || 'Uncategorised' }}</span> <span class="title">{{ categoryEntry.category || 'Uncategorised' }}</span>
<span class="value">{{ ( categoryEntry.value || 0 ) | currency:'GBP':'symbol':'1.2-2' }} <span class="text-muted small"> <span class="value">{{ ( categoryEntry.value || 0 ) | currency:'GBP':'symbol':'1.2-2' }} <span class="text-muted small">
({{ (categoryEntry.value || 0 ) / weekListValueSum1 | percent:'1.0-0' }})</span></span> ({{ (categoryEntry.value || 0 ) / weekListValueSum1 | percent:'1.0-0' }})</span></span>
<div class="bars"> <div class="bars">
@ -64,7 +64,7 @@
</div> </div>
</li> </li>
<li *ngFor="let categoryEntry of weekList2 | slice:0:categoryLimit2; let i=index;"> <li *ngFor="let categoryEntry of weekList2 | slice:0:categoryLimit2; let i=index;">
<span class="title">{{ categoryList[categoryEntry.category] || 'Uncategorised' }}</span> <span class="title">{{ categoryEntry.category || 'Uncategorised' }}</span>
<span class="value">{{ ( categoryEntry.value || 0 ) | currency:'GBP':'symbol':'1.2-2' }} <span class="text-muted small"> <span class="value">{{ ( categoryEntry.value || 0 ) | currency:'GBP':'symbol':'1.2-2' }} <span class="text-muted small">
({{ (categoryEntry.value || 0 ) / weekListValueSum2 | percent:'1.0-0' }})</span></span> ({{ (categoryEntry.value || 0 ) / weekListValueSum2 | percent:'1.0-0' }})</span></span>
<div class="bars"> <div class="bars">
@ -106,7 +106,7 @@
</div> </div>
</li> </li>
<li *ngFor="let categoryEntry of weekList3 | slice:0:categoryLimit3; let i=index;"> <li *ngFor="let categoryEntry of weekList3 | slice:0:categoryLimit3; let i=index;">
<span class="title">{{ categoryList[categoryEntry.category] || 'Uncategorised' }}</span> <span class="title">{{ categoryEntry.category || 'Uncategorised' }}</span>
<span class="value">{{ ( categoryEntry.value || 0 ) | currency:'GBP':'symbol':'1.2-2' }} <span class="text-muted small"> <span class="value">{{ ( categoryEntry.value || 0 ) | currency:'GBP':'symbol':'1.2-2' }} <span class="text-muted small">
({{ (categoryEntry.value || 0 ) / weekListValueSum3 | percent:'1.0-0' }})</span></span> ({{ (categoryEntry.value || 0 ) / weekListValueSum3 | percent:'1.0-0' }})</span></span>
<div class="bars"> <div class="bars">
@ -148,7 +148,7 @@
</div> </div>
</li> </li>
<li *ngFor="let categoryEntry of weekList4 | slice:0:categoryLimit4; let i=index;"> <li *ngFor="let categoryEntry of weekList4 | slice:0:categoryLimit4; let i=index;">
<span class="title">{{ categoryList[categoryEntry.category] || 'Uncategorised' }}</span> <span class="title">{{ categoryEntry.category || 'Uncategorised' }}</span>
<span class="value">{{ ( categoryEntry.value || 0 ) | currency:'GBP':'symbol':'1.2-2' }} <span class="text-muted small"> <span class="value">{{ ( categoryEntry.value || 0 ) | currency:'GBP':'symbol':'1.2-2' }} <span class="text-muted small">
({{ (categoryEntry.value || 0 ) / weekListValueSum4 | percent:'1.0-0' }})</span></span> ({{ (categoryEntry.value || 0 ) / weekListValueSum4 | percent:'1.0-0' }})</span></span>
<div class="bars"> <div class="bars">

View file

@ -31,7 +31,6 @@ export class CategoryMonthComponent implements OnInit {
weekEssential3: number = 0; weekEssential3: number = 0;
weekEssential4: number = 0; weekEssential4: number = 0;
categoryList: any;
dayList: any[] = []; dayList: any[] = [];
valueList: number[] = []; valueList: number[] = [];
myWeek1: any; myWeek1: any;
@ -47,16 +46,6 @@ export class CategoryMonthComponent implements OnInit {
private api: ApiService, private api: ApiService,
) { ) {
this.setDate(); this.setDate();
this.api.categoryList().subscribe(
result => {
this.categoryList = result.categories;
console.log('Category List received');
},
error => {
console.log('Retrieval Error');
console.log( error._body );
}
);
this.api.categoryTransactionList().subscribe( this.api.categoryTransactionList().subscribe(
result => { result => {
this.setData(result); this.setData(result);

View file

@ -14,12 +14,74 @@
<div class="col-xl-4 col-md-6"> <div class="col-xl-4 col-md-6">
<panel-pie></panel-pie> <panel-pie></panel-pie>
</div><!--/.col--> </div><!--/.col-->
<div class="col-xl-4 col-md-6">
<div *ngIf="showCategoryDoughnutChart" class="card">
<div class="card-block">
<div class="row">
<div class="col-12">
<h4 class="card-title mb-0">Weekly Spending by Category</h4>
</div>
</div>
<div class="chart-wrapper">
<canvas baseChart class="chart"
[datasets]="doughnutChartDataCategory"
[labels]="doughnutChartLabelsCategory"
[options]="doughtnutChartOptionsCategory"
[legend]="chartLegend"
[chartType]="chartType"
(chartHover)="chartHovered($event)"
(chartClick)="chartClicked($event)"></canvas>
</div>
</div>
</div>
</div><!--/.col-->
<div class="col-xl-4 col-md-6">
<div *ngIf="showEssentialBarChart" class="card">
<div class="card-block">
<div class="row">
<div class="col-12">
<h4 class="card-title mb-0">No. of Essential Purchases</h4>
</div>
</div>
<div class="chart-wrapper">
<canvas baseChart class="chart"
[datasets]="barChartDataEssential"
[labels]="barChartLabelsEssential"
[options]="barChartOptionsEssential"
[chartType]="barChartTypeEssential"
(chartHover)="chartHovered($event)"
(chartClick)="chartClicked($event)"></canvas>
</div>
</div>
</div>
</div><!--/.col-->
<div class="col-xl-4 col-md-6">
<div *ngIf="showCategoryBarChart" class="card">
<div class="card-block">
<div class="row">
<div class="col-12">
<h4 class="card-title mb-0">Monthly Spending by Category</h4>
</div>
</div>
<div class="chart-wrapper">
<canvas baseChart
[datasets]="barChartDataCategory"
[labels]="barChartLabelsCategory"
[options]="barChartOptionsCategory"
[legend]="barChartLegendCategory"
[chartType]="barChartTypeCategory"
(chartHover)="chartHovered($event)"
(chartClick)="chartClicked($event)"></canvas>
</div>
</div>
</div>
</div><!--/.col-->
<div class="col-xl-4 col-md-6"> <div class="col-xl-4 col-md-6">
<div class="card"> <div class="card">
<div class="card-block"> <div class="card-block">
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<h4 class="card-title float-left mb-0">Purchases by Week</h4> <h4 class="card-title float-left mb-0">Weekly Purchase No.</h4>
</div><!--/.col--> </div><!--/.col-->
</div><!--/.row--> </div><!--/.row-->
<div class="chart-wrapper"> <div class="chart-wrapper">

View file

@ -1,10 +1,13 @@
import { Directive, Component, OnInit } from '@angular/core'; import { Directive, Component, OnInit } from '@angular/core';
import { CurrencyPipe } from '@angular/common';
import { ApiService } from '../providers/api-service'; import { ApiService } from '../providers/api-service';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { GraphWidget } from '../widgets/graph-widget.component'; import { GraphWidget } from '../widgets/graph-widget.component';
import { CustBarSnippetComponent } from '../snippets/cust-snippet-bar.component'; import { CustBarSnippetComponent } from '../snippets/cust-snippet-bar.component';
import { PiePanel } from '../panels/pie-panel.component'; import { PiePanel } from '../panels/pie-panel.component';
import { DataType } from '../shared/data-types.enum'; import { DataType } from '../shared/data-types.enum';
import * as moment from 'moment';
import 'rxjs/add/operator/map';
@Component({ @Component({
templateUrl: 'dashboard-customer.component.html' templateUrl: 'dashboard-customer.component.html'
@ -22,6 +25,73 @@ export class DashboardCustomerComponent implements OnInit {
disableSectorButton: boolean = false; disableSectorButton: boolean = false;
public chartType = 'doughnut';
public chartLegend = true;
public doughnutChartDataCategory: any[] = [];
public doughnutChartLabelsCategory: string[] = [];
public doughtnutChartOptionsCategory:any = {
tooltips: {
callbacks: {
label: (tooltip, data) => {
return this.tooltipLabelCallback(tooltip, data);
},
},
},
}
myWeek1: any;
weekList1 = [];
public purchaseNotEssential: number;
public purchaseEssential: number;
public showEssentialBarChart = false;
public showCategoryBarChart = false;
public showCategoryDoughnutChart = false;
public barChartDataEssential:any[]=[
{data: 0, label: 'Essential', stack: '1'},
{data: 0, label: 'Non-Essential', stack: '1'},
];
public barChartLabelsEssential:string[] = ['All Purchases'];
public barChartOptionsEssential:any = {
responsive: true,
scales:{
xAxes:[{
stacked:true
}],
yAxes:[{
stacked:true
}]
}
};
public barChartTypeEssential:string = 'horizontalBar';
public barChartOptionsCategory:any = {
scaleShowVerticalLines: false,
responsive: true,
scales: {
yAxes: [{
ticks: {
beginAtZero: true,
callback: label => `£${label}`
}
}]
},
tooltips: {
callbacks: {
label: (tooltip, data) => {
return this.tooltipLabelCallback(tooltip, data);
},
},
},
};
public barChartTypeCategory:string = 'bar';
public barChartLegendCategory:boolean = false;
public barChartDataCategory:any[]=[];
public barChartLabelsCategory:string[] = [];
weekPurchaseList = { weekPurchaseList = {
first: 0, first: 0,
second: 0, second: 0,
@ -140,11 +210,22 @@ export class DashboardCustomerComponent implements OnInit {
constructor( constructor(
private api: ApiService, private api: ApiService,
private currencyPipe: CurrencyPipe,
) { ) {
this.setDate();
this.api.customerStats().subscribe( this.api.customerStats().subscribe(
result => { result => {
this.setWeekPurchaseList(result.weeks); this.setWeekPurchaseList(result.weeks);
this.setSectorList(result.sectors); this.setSectorList(result.sectors);
this.setWeekData(result);
this.setChartData(result.data.cat_total);
this.purchaseEssential = result.data.essentials.purchase_no_essential_total;
this.purchaseNotEssential = result.data.essentials.purchase_no_total - this.purchaseEssential;
this.barChartDataEssential = [
{data: [this.purchaseEssential], label: 'Essential', stack: '1'},
{data: [this.purchaseNotEssential], label: 'Non-Essential', stack: '1'},
];
this.showEssentialBarChart = true;
}, },
error => { error => {
console.log('Retrieval Error'); console.log('Retrieval Error');
@ -153,6 +234,33 @@ export class DashboardCustomerComponent implements OnInit {
); );
} }
private setChartData(dataCat: any) {
this.barChartLabelsCategory = Object.keys(dataCat);
let barChartDataCategoryInitial = Object.keys(dataCat).map(key => dataCat[key]);
this.barChartDataCategory = [
{data: barChartDataCategoryInitial, label: 'Series A'},
];
let doughnutChartDataCategoryInitial = this.weekList1.map(function(a) {return a.value;});
this.doughnutChartDataCategory = [
{data: doughnutChartDataCategoryInitial, label: 'Series A'},
];
// setTimeout is currently a workaround for ng2-charts labels
setTimeout(() => this.doughnutChartLabelsCategory = this.weekList1.map(function(a) {return a.category;}), 0);
this.showCategoryDoughnutChart = true;
this.showCategoryBarChart = true;
}
private setDate () {
this.myWeek1 = moment().startOf('isoWeek').format('YYYY-MM-DD');
}
private setWeekData (data: any) {
function prop<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
this.weekList1 = prop(data.data.categories, this.myWeek1);
}
public setWeekPurchaseList (data: any) { public setWeekPurchaseList (data: any) {
this.weekPurchaseList = { this.weekPurchaseList = {
first: data.first, first: data.first,
@ -173,6 +281,29 @@ export class DashboardCustomerComponent implements OnInit {
this.sectorLimit = 22; this.sectorLimit = 22;
} }
public convertHex(hex: string, opacity: number) {
hex = hex.replace('#', '');
const r = parseInt(hex.substring(0, 2), 16);
const g = parseInt(hex.substring(2, 4), 16);
const b = parseInt(hex.substring(4, 6), 16);
const rgba = 'rgba(' + r + ', ' + g + ', ' + b + ', ' + opacity / 100 + ')';
return rgba;
}
private tooltipLabelCallback(tooltipItem: any, data: any) {
var dataset = data.datasets[tooltipItem.datasetIndex];
var value = dataset.data[tooltipItem.index];
return this.currencyPipe.transform(value, 'GBP', 'symbol', '1.2-2');
}
// events
public chartClicked(e: any): void {
}
public chartHovered(e: any): void {
}
ngOnInit(): void { ngOnInit(): void {
} }
} }

View file

@ -1,49 +1,6 @@
<div class="animated fadeIn"> <div class="animated fadeIn">
<div class="row"> <div class="row">
<div class="col-lg-12"> <div class="col-lg-12">
<div class="card">
<div class="card-header">
<strong>Log of Outgoing Transactions</strong>
<small>This lists all purchases that have been submitted.</small>
</div>
<div *ngIf="!noTransactionList" class="card-block">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Seller</th>
<th>Value</th>
<th>Purchase Time</th>
</tr>
</thead>
<tbody>
<tr transaction-result *ngFor="let transaction of transactionList | paginate: paginateConfig" [transaction]="transaction"></tr>
</tbody>
</table>
<pagination-template #p="paginationApi"
[id]="paginateConfig.id"
(pageChange)="loadTransactions($event)">
<ul class="pagination">
<li class="page-item" [class.disabled]="p.isFirstPage()">
<a class="page-link clickable" *ngIf="!p.isFirstPage()" (click)="p.previous()">Prev</a>
</li>
<li *ngFor="let page of p.pages" class="page-item" [class.active]="p.getCurrent() === page.value">
<a class="page-link clickable" (click)="p.setCurrent(page.value)" *ngIf="p.getCurrent() !== page.value">
<span>{{ page.label }}</span>
</a>
<div class="page-link" *ngIf="p.getCurrent() === page.value">
<span>{{ page.label }}</span>
</div>
</li>
<li class="page-item" [class.disabled]="p.isLastPage()">
<a class="page-link clickable" *ngIf="!p.isLastPage()" (click)="p.next()">Next</a>
</li>
</ul>
</pagination-template>
</div>
<div *ngIf="noTransactionList" class="card-block">
No Transactions available.
</div>
</div>
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<strong>Recurring Transactions</strong> <strong>Recurring Transactions</strong>
@ -140,6 +97,49 @@
No Recurring Transactions. No Recurring Transactions.
</div> </div>
</div> </div>
<div class="card">
<div class="card-header">
<strong>Log of Outgoing Transactions</strong>
<small>This lists all purchases that have been submitted.</small>
</div>
<div *ngIf="!noTransactionList" class="card-block">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Seller</th>
<th>Value</th>
<th>Purchase Time</th>
</tr>
</thead>
<tbody>
<tr transaction-result *ngFor="let transaction of transactionList | paginate: paginateConfig" [transaction]="transaction"></tr>
</tbody>
</table>
<pagination-template #p="paginationApi"
[id]="paginateConfig.id"
(pageChange)="loadTransactions($event)">
<ul class="pagination">
<li class="page-item" [class.disabled]="p.isFirstPage()">
<a class="page-link clickable" *ngIf="!p.isFirstPage()" (click)="p.previous()">Prev</a>
</li>
<li *ngFor="let page of p.pages" class="page-item" [class.active]="p.getCurrent() === page.value">
<a class="page-link clickable" (click)="p.setCurrent(page.value)" *ngIf="p.getCurrent() !== page.value">
<span>{{ page.label }}</span>
</a>
<div class="page-link" *ngIf="p.getCurrent() === page.value">
<span>{{ page.label }}</span>
</div>
</li>
<li class="page-item" [class.disabled]="p.isLastPage()">
<a class="page-link clickable" *ngIf="!p.isLastPage()" (click)="p.next()">Next</a>
</li>
</ul>
</pagination-template>
</div>
<div *ngIf="noTransactionList" class="card-block">
No Transactions available.
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -97,6 +97,14 @@
</div> </div>
</a> </a>
</li> </li>
<li class="nav-item">
<a class="nav-link">
<div class="row no-gutters align-items-center">
<div class="col-2"><i class="icon-logout"></i></div>
<div class="col-10">Click to Close Menu</div>
</div>
</a>
</li>
</ul> </ul>
</nav> </nav>
</div> </div>

View file

@ -39,7 +39,6 @@ export class FullLayoutComponent implements OnInit {
.logout() .logout()
.subscribe( .subscribe(
result => { result => {
console.log('Logged out!');
localStorage.clear(); localStorage.clear();
this.router.navigate(['/login']); this.router.navigate(['/login']);
} }

View file

@ -1,18 +1,18 @@
<div class="card"> <div class="card">
<div class="card-block"> <div class="card-block">
<div class="row"> <div class="row">
<div class="col-sm-8"> <div class="col-12">
<h4 class="card-title mb-0">All Purchases</h4> <h4 class="card-title mb-0">All Purchases</h4>
</div><!--/.col-->
</div><!--/.row-->
<div class="chart-wrapper">
<canvas baseChart class="chart"
[data]="doughnutChartData"
[labels]="doughnutChartLabels"
[legend]="chartLegend"
[chartType]="chartType"
(chartHover)="chartHovered($event)"
(chartClick)="chartClicked($event)"></canvas>
</div> </div>
</div> </div>
<div class="chart-wrapper">
<canvas baseChart class="chart"
[data]="doughnutChartDataLocal"
[labels]="doughnutChartLabelsLocal"
[legend]="chartLegend"
[chartType]="chartType"
(chartHover)="chartHovered($event)"
(chartClick)="chartClicked($event)"></canvas>
</div>
</div> </div>
</div>

View file

@ -1,7 +1,9 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { ApiService } from '../providers/api-service';
import { CustPiesService } from '../providers/cust-pies.service'; import { CustPiesService } from '../providers/cust-pies.service';
import { DataType } from '../shared/data-types.enum'; import { DataType } from '../shared/data-types.enum';
import { ChartData } from '../_interfaces/chart-data'; import { ChartData } from '../_interfaces/chart-data';
import 'rxjs/add/operator/map';
@Component({ @Component({
selector: 'panel-pie', selector: 'panel-pie',
@ -11,27 +13,32 @@ export class PiePanel implements OnInit {
public chartType = 'doughnut'; public chartType = 'doughnut';
public chartLegend = true; public chartLegend = true;
public doughnutChartLabels: string[] = []; public doughnutChartDataLocal: number[] = [];
public doughnutChartData: number[] = []; public doughnutChartLabelsLocal: string[] = [];
//Old
// public mainChartElements = 7;
constructor( constructor(
private api: ApiService,
private pieService: CustPiesService, private pieService: CustPiesService,
) { } ) {
this.pieService.getPie().subscribe(
public ngOnInit(): void { result => {
this.pieService.getPie() this.setChartData(result.local_all);
.subscribe( result => this.setData(result.pie) ); },
error => {
console.log('Retrieval Error');
console.log( error._body );
}
);
} }
private setData(data: any) { public ngOnInit(): void {
this.doughnutChartData = Object.keys(data).map(key => data[key]);
}
private setChartData(dataLocal: any) {
this.doughnutChartDataLocal = Object.keys(dataLocal).map(key => dataLocal[key]);
// setTimeout is currently a workaround for ng2-charts labels // setTimeout is currently a workaround for ng2-charts labels
setTimeout(() => this.doughnutChartLabels = Object.keys(data), 0); setTimeout(() => this.doughnutChartLabelsLocal = Object.keys(dataLocal), 0);
} }
// convert Hex to RGBA // convert Hex to RGBA
@ -45,13 +52,4 @@ export class PiePanel implements OnInit {
return rgba; return rgba;
} }
// events
public chartClicked(e: any): void {
console.log(e);
}
public chartHovered(e: any): void {
console.log(e);
}
} }