Compare commits

...
This repository has been archived on 2023-08-16. You can view files and clone it, but cannot push or open issues or pull requests.

5 commits

21 changed files with 4705 additions and 206 deletions

2161
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -29,14 +29,19 @@
"@angular/router": "5.2.0",
"@angular/upgrade": "5.2.0",
"@types/moment": "2.13.0",
"angular-scrollable-table": "^1.1.2",
"chart.js": "2.7.1",
"core-js": "2.5.1",
"js-marker-clusterer": "1.0.0",
"moment": "^2.19.2",
"ng2-charts": "1.6.0",
"ng2-expanding-table": "^1.5.2",
"ng2-validation-manager": "0.5.3",
"ngx-bootstrap": "2.0.0-beta.8",
"ngx-order-pipe": "^1.1.2",
"ngx-pagination": "3.0.3",
"ngx-pipes": "^2.1.0",
"node": "^9.4.0",
"rxjs": "5.5.6",
"ts-helpers": "1.1.2",
"webpack": "3.8.1",
@ -44,7 +49,8 @@
"zone.js": "0.8.18"
},
"devDependencies": {
"@angular/cli": "1.6.4",
"@angular-devkit/schematics": "0.0.48",
"@angular/cli": "^1.6.6",
"@angular/compiler-cli": "5.2.0",
"@types/jasmine": "2.8.2",
"@types/jasminewd2": "2.0.3",
@ -61,6 +67,6 @@
"protractor": "5.2.0",
"ts-node": "3.3.0",
"tslint": "5.8.0",
"typescript": "2.6.x"
"typescript": "^2.5.3"
}
}

View file

@ -0,0 +1,4 @@
export interface ChartData {
data: Array<number>;
label: string;
}

View file

@ -25,6 +25,9 @@ import { CustGraphsService } from './providers/cust-graphs.service';
import { OrgSnippetsService } from './providers/org-snippets.service';
import { CustSnippetsService } from './providers/cust-snippets.service';
import { CustPiesService } from './providers/cust-pies.service';
import { MedalsService } from './providers/medals.service';
import { HeroPointsSnippetsService } from './providers/hero-points-snippets.service';
import { HeroPointsGraphService } from './providers/hero-points-graph.service';
// Layouts
import { FullLayoutComponent } from './layouts/full-layout.component';
@ -38,11 +41,15 @@ import { P500Component } from './pages/500.component';
import { AuthModule } from './auth/auth.module';
import { DashboardModule } from './dashboard/dashboard.module';
// Pipes
import { NgPipesModule } from 'ngx-pipes';
@NgModule({
imports: [
BrowserModule,
HttpClientModule,
NgxPaginationModule,
NgPipesModule,
BsDropdownModule.forRoot(),
TabsModule.forRoot(),
AuthModule,
@ -70,12 +77,18 @@ import { DashboardModule } from './dashboard/dashboard.module';
OrgSnippetsService,
CustGraphsService,
CustSnippetsService,
HeroPointsSnippetsService,
MedalsService,
CustPiesService,
HeroPointsGraphService,
{
provide: LocationStrategy,
useClass: HashLocationStrategy
}
],
exports: [
NgPipesModule,
],
bootstrap: [ AppComponent ]
})
export class AppModule { }

View file

@ -3,10 +3,10 @@
<div class="row">
<div *ngFor="let widget of widgetList" class="col-sm-6 col-lg-3">
<widget-graph *ngIf="widget.type == 'graph'"
[graphName]="widget.name"
[graphTitle]="widget.title"
[graphIcon]="widget.icon"
[dataType]="widget.dataType">
[graphName]="widget.name"
[graphTitle]="widget.title"
[graphIcon]="widget.icon"
[dataType]="widget.dataType">
</widget-graph>
</div><!--/.col-->
</div><!--/.row-->

View file

@ -21,10 +21,13 @@ import { PayrollLogComponent } from './payroll-log.component';
import { LeaderboardComponent } from './leaderboard.component';
import { MapComponent } from './map.component';
import { TrailMapComponent } from './trail-map.component';
import { HeroPointsComponent } from './hero-points.component';
import { GraphWidget } from '../widgets/graph-widget.component';
import { OrgBarSnippetComponent } from '../snippets/org-snippet-bar.component';
import { CustBarSnippetComponent } from '../snippets/cust-snippet-bar.component';
import { HeroPointsSnippetBarComponent } from '../snippets/hero-points-snippet-bar.component';
import { HeroPointsGraphWidget } from '../widgets/hero-points-graph-widget.component';
import { GraphPanel } from '../panels/graph-panel.component';
import { PiePanel } from '../panels/pie-panel.component';
@ -38,6 +41,9 @@ import { LeaderboardResultComponent } from '../shared/leaderboard-result.compone
// API key env variable import
import { environment } from '../../environments/environment';
// Pipes
import { NgPipesModule } from 'ngx-pipes';
@NgModule({
imports: [
// Angular imports
@ -53,6 +59,7 @@ import { environment } from '../../environments/environment';
NgxPaginationModule,
DashboardRoutingModule,
ModalModule.forRoot(),
NgPipesModule,
],
declarations: [
DashboardComponent,
@ -74,8 +81,11 @@ import { environment } from '../../environments/environment';
GraphWidget,
OrgBarSnippetComponent,
CustBarSnippetComponent,
HeroPointsSnippetBarComponent,
HeroPointsGraphWidget,
GraphPanel,
PiePanel,
HeroPointsComponent,
],
providers: [
CurrencyPipe,

View file

@ -17,6 +17,7 @@ import { PayrollLogComponent } from './payroll-log.component';
import { LeaderboardComponent } from './leaderboard.component';
import { MapComponent } from './map.component';
import { TrailMapComponent } from './trail-map.component';
import { HeroPointsComponent } from './hero-points.component';
// Using child path to allow for FullLayout theming
const routes: Routes = [
@ -84,6 +85,11 @@ const routes: Routes = [
path: 'feedback',
component: FeedbackComponent,
data: { title: 'Give Feedback' },
},
{
path: 'hero-points',
component: HeroPointsComponent,
data: { title: 'Hero Points' }
}
],
}

View file

@ -0,0 +1,254 @@
<div class="animated fadeIn">
<snippet-bar-hero-points></snippet-bar-hero-points><!-- Snippet for hero points (four circles) -->
<div class="row"> <!-- second row -->
<div class="col-md-6 col-xl-3"> <!-- Hero points gain charts -->
<div *ngFor="let widget of widgetList"> <!-- Points gain line graph -->
<hero-points-widget-graph *ngIf="widget.type == 'graph'"
[graphName]="widget.name"
[graphTitle]="widget.title"
[graphIcon]="widget.icon"
[dataType]="widget.dataType">
</hero-points-widget-graph>
</div> <!-- let widget of widgetList -->
<div class="card"> <!-- Points gain by week charts -->
<div class="card-block">
<div class="row">
<div class="col-12">
<h5 class="card-title float-left mb-0">Points Gain by Week</h5>
</div> <!--/.col-->
</div> <!--/.row-->
<div class="chart-wrapper">
<ul class="horizontal-bars type-2">
<li> <!-- This Week -->
<span class="title">This Week</span>
<span class="value">{{ heroPointsStats.this_week }}<span class="text-muted small"> ({{ heroPointsStats.this_week / heroPointsStats.max | percent:'1.0-0' }})</span></span>
<div class="bars">
<div class="progress" style="height: 6px;">
<div class="progress-bar bg-success" role="progressbar"
[style.width]="heroPointsStats.this_week / heroPointsStats.max | percent:'1.0-0'" aria-valuemin="0" aria-valuemax="100"></div>
</div> <!-- progress -->
</div> <!-- bars -->
</li>
<li> <!-- Last Week -->
<span class="title">Last Week</span>
<span class="value">{{ heroPointsStats.last_week }}<span class="text-muted small"> ({{ heroPointsStats.last_week / heroPointsStats.max | percent:'1.0-0' }})</span></span>
<div class="bars">
<div class="progress" style="height: 6px;">
<div class="progress-bar bg-success" role="progressbar"
[style.width]="heroPointsStats.last_week / heroPointsStats.max | percent:'1.0-0'" aria-valuemin="0" aria-valuemax="100"></div>
</div> <!-- progress -->
</div> <!-- bars -->
</li>
<li> <!-- Week Maximum -->
<span class="title">Week Maximum</span>
<span class="value">{{ heroPointsStats.max }}<span class="text-muted small"> ({{ heroPointsStats.max / heroPointsStats.max | percent:'1.0-0' }})</span></span>
<div class="bars">
<div class="progress" style="height: 6px;">
<div class="progress-bar bg-success" role="progressbar"
[style.width]="heroPointsStats.max / heroPointsStats.max | percent:'1.0-0'" aria-valuemin="0" aria-valuemax="100"></div>
</div> <!-- progress -->
</div> <!-- bars -->
</li>
<li> <!-- Weekly Average -->
<span class="title">Weekly Average</span>
<span class="value">{{ heroPointsStats.avg }}<span class="text-muted small"> ({{ heroPointsStats.avg / heroPointsStats.max | percent:'1.0-0' }})</span></span>
<div class="bars">
<div class="progress" style="height: 6px;">
<div class="progress-bar bg-success" role="progressbar"
[style.width]="heroPointsStats.avg / heroPointsStats.max | percent:'1.0-0'" aria-valuemin="0" aria-valuemax="100"></div>
</div> <!-- progess -->
</div> <!-- bars -->
</li>
</ul> <!-- horizontal-bars -->
</div> <!-- chart-wrapper -->
</div> <!-- card-block -->
</div> <!-- card -->
</div> <!-- col -->
<div class="col-md-6 col-xl-3"> <!-- Leaderboard -->
<div *ngIf="!noUserList">
<table class="table table-striped table-hover"> <!-- Leaderboard Table -->
<thead>
<tr>
<th>Rank</th>
<th>Name</th>
<th>Hero Points</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let user of userList | orderBy: '-heroPoints' | paginate: paginateLeadConfig; let i = index" [ngStyle]="{'background-color': user.userId === 1 ? '#F39C12': 'auto', 'color': user.userId === 1 ? '#ffffff': 'auto'}">
<td>{{ i + 1 + (paginateLeadConfig.itemsPerPage * paginateLeadConfig.currentPage) - paginateLeadConfig.itemsPerPage }}</td> <!-- uses page number and number of pages to calculate the rankings -->
<td>{{ user.displayName }}</td> <!-- Display Name -->
<td>{{ user.heroPoints }}</td> <!-- Hero Points -->
</tr>
</tbody>
</table>
<pagination-template #p="paginationApi" [id]="paginateLeadConfig.id" (pageChange)="paginateLeadConfig.currentPage = $event"> <!-- Leaderboard pagination -->
<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> <!-- if !noUserList -->
<div *ngIf="noUserList" class="card-block">
No Leaderboard available.
</div> <!-- if noUserList -->
</div>
<div class="col-md-6 col-xl-6"> <!-- Medal Table -->
<div *ngIf="!noGlobalMedalList && !noOrganisationMedalList">
<table class="table header-fixed">
<thead>
<tr>
<th class="col-3">Medal</th>
<th class="col-9">Description</th>
</tr>
</thead>
<tbody>
<ng-container *ngFor="let medals of globalMedalList"> <!-- ngFor each group of medals (global) -->
<ng-container *ngFor="let group of medals | values; let groupIndex = index">
<ng-container *ngIf="(group[0] | values).length - 1 > 1"> <!-- if the group is longer than 1 -->
<!-- *ngIf 'global' medal and is/isn't expanded (headers)-->
<tr *ngIf="group.expanded" (click)="group.expanded = false" [ngStyle]="{'background-color':(group[0] | values | filterBy: ['awarded']: 'true' | pluck: 'awarded').length === (group[0] | values | filterBy: ['awarded']).length ? '#5FBC47' : '#20A8D8', 'color': '#ffffff' }">
<td class="col-3">{{(medals | keys)[groupIndex]}}</td>
<td class="col-6">Click to collapse</td>
<td class="col-1">-</td>
<td class="col-2" [ngStyle]="{'text-align': 'right'}">{{(group[0] | values | filterBy: ['awarded']: 'true' | pluck: 'awarded').length}}/{{(group[0] | values | filterBy: ['awarded']).length}}</td>
</tr>
<tr *ngIf="!group.expanded" (click)="group.expanded = true" [ngStyle]="{'background-color':(group[0] | values | filterBy: ['awarded']: 'true' | pluck: 'awarded').length === (group[0] | values | filterBy: ['awarded']).length ? '#5FBC47' : '#20A8D8', 'color': '#ffffff' }">
<td class="col-3">{{(medals | keys)[groupIndex]}}</td>
<td class="col-6">Click to expand</td>
<td class="col-1">+</td>
<td class="col-2" [ngStyle]="{'text-align': 'right'}">{{(group[0] | values | filterBy: ['awarded']: 'true' | pluck: 'awarded').length}}/{{(group[0] | values | filterBy: ['awarded']).length}}</td>
</tr>
<!-- display actual medals -->
<ng-container *ngFor="let threshold of group[0] | values | orderBy: '+threshold'; let thresholdIndex = index"> <!-- list each medal in each group, ordered by threshold, ascending order -->
<ng-container *ngIf="threshold | isObject">
<ng-container *ngIf="threshold.awarded">
<tr *ngIf="group.expanded" [ngStyle]="{'background-color': '#5FBC47', 'color': '#ffffff' }">
<ng-container *ngIf="">
</ng-container>
<td class="col-3">{{(medals | keys)[groupIndex]}} {{thresholdIndex + 1}}</td>
<td class="col-7">DESCRIPTION</td>
<ng-container *ngIf="thresholdIndex + 1 != (group[0] | values | orderBy: '+threshold').length - 1">
<td class="col-2" [ngStyle]="{'text-align': 'right'}">{{threshold.threshold}}/{{threshold.threshold}}</td> <!-- progress -->
</ng-container>
<ng-container *ngIf="thresholdIndex + 1 == (group[0] | values | orderBy: '+threshold').length - 1">
<td class="col-2" [ngStyle]="{'text-align': 'right'}">{{group[0].total}}/{{threshold.threshold}}</td> <!-- progress -->
</ng-container>
</tr>
</ng-container>
<ng-container *ngIf="!threshold.awarded">
<tr *ngIf="group.expanded" [ngStyle]="{'background-color': '#F25F5F', 'color': '#ffffff' }">
<td class="col-3">{{(medals | keys)[groupIndex]}} {{thresholdIndex + 1}}</td>
<td class="col-7">DESCRIPTION</td>
<td class="col-2" [ngStyle]="{'text-align': 'right'}">{{group[0].total}}/{{threshold.threshold}}</td> <!-- progress -->
</tr>
</ng-container>
</ng-container>
</ng-container>
</ng-container>
<ng-container *ngIf="(group[0] | values).length - 1 == 1"> <!-- if there is only one medal in the group, just display medal, no header -->
<ng-container *ngFor="let threshold of group[0] | values | orderBy: '+threshold'; let thresholdIndex = index">
<ng-container *ngIf="threshold | isObject">
<tr [ngStyle]="{'background-color':threshold.awarded === true ? '#5FBC47' : '#F25F5F', 'color': '#ffffff' }">
<td class="col-3">{{(medals | keys)[groupIndex]}}</td>
<td class="col-7">DESCRIPTION</td>
<td class="col-2" [ngStyle]="{'text-align': 'right'}">{{group[0].total}}/{{threshold.threshold}}</td> <!-- progress -->
</tr>
</ng-container>
</ng-container>
</ng-container>
</ng-container>
</ng-container> <!-- ngFor each group of medals (global) -->
<ng-container *ngFor="let medals of organisationMedalList">
<ng-container *ngFor="let orgs of medals | values">
<ng-container *ngFor="let org of orgs; let orgIndex = index">
<tr *ngIf="org.expanded" (click)="org.expanded = false" [ngStyle]="{'background-color': '#F39C12', 'color': '#ffffff' }">
<td class="col-3">{{org.name}}</td>
<td class="col-6">Click to collapse</td>
<td class="col-1">-</td>
<td class="col-2" [ngStyle]="{'text-align': 'right'}">{{org.count}}/{{org.threshold}}</td>
</tr>
<tr *ngIf="!org.expanded" (click)="org.expanded = true" [ngStyle]="{'background-color': '#F39C12', 'color': '#ffffff' }">
<td class="col-3">{{org.name}}</td>
<td class="col-6">Click to expand</td>
<td class="col-1">+</td>
<td class="col-2" [ngStyle]="{'text-align': 'right'}">{{org.count}}/{{org.threshold}}</td>
</tr>
<ng-container *ngFor="let group of org | values; let groupIndex = index">
<ng-container *ngIf="group | isObject">
<ng-container *ngIf="(group[0] | keys).length - 1 > 1">
<tr *ngIf="group.expanded && org.expanded" (click)="group.expanded = false" [ngStyle]="{'background-color': '#20A8D8', 'color': '#ffffff' }">
<td class="col-3">{{(org | keys)[groupIndex]}}</td>
<td class="col-6">Click to collapse</td>
<td class="col-1">-</td>
<td class="col-2" [ngStyle]="{'text-align': 'right'}">{{(group[0] | values | filterBy: ['awarded']: 'true' | pluck: 'awarded').length}}/{{(group[0] | values | filterBy: ['awarded']).length}}</td>
</tr>
<tr *ngIf="!group.expanded && org.expanded" (click)="group.expanded = true" [ngStyle]="{'background-color': '#20A8D8', 'color': '#ffffff' }">
<td class="col-3">{{(org | keys)[groupIndex]}}</td>
<td class="col-6">Click to expand</td>
<td class="col-1">+</td>
<td class="col-2" [ngStyle]="{'text-align': 'right'}">{{(group[0] | values | filterBy: ['awarded']: 'true' | pluck: 'awarded').length}}/{{(group[0] | values | filterBy: ['awarded']).length}}</td>
</tr>
<ng-container *ngFor="let threshold of group[0] | values | orderBy: '+threshold'; let thresholdIndex = index">
<ng-container *ngIf="threshold | isObject">
<ng-container *ngIf="threshold.awarded">
<tr *ngIf="group.expanded && org.expanded" [ngStyle]="{'background-color': '#5FBC47', 'color': '#ffffff' }">
<td class="col-3">{{(org | keys)[groupIndex]}} {{thresholdIndex + 1}}</td>
<td class="col-7">DESCRIPTION</td>
<ng-container *ngIf="thresholdIndex + 1 != (group[0] | values | orderBy: '+threshold').length - 1">
<td class="col-2" [ngStyle]="{'text-align': 'right'}">{{threshold.threshold}}/{{threshold.threshold}}</td>
</ng-container>
<ng-container *ngIf="thresholdIndex + 1 == (group[0] | values | orderBy: '+threshold').length - 1">
<td class="col-2" [ngStyle]="{'text-align': 'right'}">{{group[0].total}}/{{threshold.threshold}}</td>
</ng-container>
</tr>
</ng-container>
<ng-container *ngIf="!threshold.awarded">
<tr *ngIf="group.expanded && org.expanded" [ngStyle]="{'background-color': '#F25F5F', 'color': '#ffffff' }">
<td class="col-3">{{(org | keys)[groupIndex]}} {{thresholdIndex + 1}}</td>
<td class="col-7">DESCRIPTION</td>
<td class="col-2" [ngStyle]="{'text-align': 'right'}">{{group[0].total}}/{{threshold.threshold}}</td>
</tr>
</ng-container>
</ng-container>
</ng-container>
</ng-container>
<ng-container *ngIf="(group[0] | keys).length - 1 == 1">
<ng-container *ngFor="let threshold of group[0] | values | orderBy: '+threshold'; let thresholdIndex = index">
<ng-container *ngIf="threshold | isObject">
<ng-container *ngIf="org.expanded">
<tr [ngStyle]="{'background-color':threshold.awarded === true ? '#5FBC47' : '#F25F5F', 'color': '#ffffff' }">
<td class="col-3">{{(org | keys)[groupIndex]}}</td>
<td class="col-7">DESCRIPTION</td>
<td class="col-2" [ngStyle]="{'text-align': 'right'}">{{group[0].total}}/{{threshold.threshold}}</td>
</tr>
</ng-container>
</ng-container>
</ng-container>
</ng-container>
</ng-container>
</ng-container>
</ng-container>
</ng-container>
</ng-container>
</tbody>
</table>
</div> <!-- if !noMedalList -->
<div *ngIf="noGlobalMedalList && noOrganisationMedalList" class="card-block">
No Leaderboard available.
</div> <!-- if noMedalList -->
</div><!-- Row -->
</div>
</div>

File diff suppressed because it is too large Load diff

View file

@ -97,6 +97,14 @@
</div>
</a>
</li>
<li *ngIf="accountType == 'customer'" class="nav-item">
<a class="nav-link" routerLinkActive="active" [routerLink]="['/hero-points']">
<div class="row no-gutters align-items-center">
<div class="col-2"><i class="icon-trophy"></i></div>
<div class="col-10">Hero Points</div>
</div>
</a>
</li>
</ul>
</nav>
</div>

View file

@ -9,6 +9,7 @@ export class CustGraphsService {
public getGraph(name: string, data: any = {}) {
data.graph = name;
//console.log(this.api.post(this.custGraphUrl, data));
return this.api.post(this.custGraphUrl, data);
}
}

View file

@ -0,0 +1,15 @@
import { Injectable } from '@angular/core';
import { ApiService } from './api-service';
import { Observable } from 'rxjs/Rx';
@Injectable()
export class HeroPointsGraphService {
private heroPointsGraphUrl = '/v1/user/points';
constructor(private api: ApiService) { }
// This endpoint should mimic basicStats
public getHeroPointsGraph(): Observable<any> {
return this.api.post(this.heroPointsGraphUrl);
}
}

View file

@ -0,0 +1,8 @@
import { Injectable } from '@angular/core';
@Injectable()
export class HeroPointsLeaderboardService {
constructor() { }
}

View file

@ -0,0 +1,15 @@
import { Injectable } from '@angular/core';
import { ApiService } from './api-service';
import { Observable } from 'rxjs/Rx';
@Injectable()
export class HeroPointsSnippetsService {
private heroPointsSnippetsUrl = '/v1/user/points';
constructor(private api: ApiService) { }
// This endpoint should mimic basicStats
public getHeroPointsSnippets(): Observable<any> {
return this.api.post(this.heroPointsSnippetsUrl);
}
}

View file

@ -0,0 +1,16 @@
import { Injectable } from '@angular/core';
import { ApiService } from './api-service';
import { Observable } from 'rxjs/Rx';
@Injectable()
export class MedalsService {
private medalsUrl = '/v1/user/medals';
constructor(private api: ApiService) { }
public getMedals(): Observable<any> {
return this.api.post(this.medalsUrl);
//return Observable.of()
}
}

View file

@ -5,6 +5,7 @@ import { CustSnippetsService } from '../providers/cust-snippets.service';
selector: 'snippet-bar-cust',
templateUrl: 'cust-snippet-bar.component.html',
})
export class CustBarSnippetComponent implements OnInit {
public userSum = 0;

View file

@ -0,0 +1,50 @@
<div class="row">
<div class="col-md-6 col-xl-3">
<div class="card text-center" style="background-color: transparent; border: none">
<div class="card-block">
<ul style="list-style: none; padding-left: 0;">
<li class="hidden-sm-down">
<div><h5 class="text-orange">My Hero Points</h5></div>
<div class="number-circle-hero mx-auto"><strong>{{ heroPointsSnippets.points_total | number:'1.0-0' }}</strong></div>
</li>
</ul>
</div>
</div>
</div>
<div class="col-md-6 col-xl-3">
<div class="card text-center" style="background-color: transparent; border: none">
<div class="card-block">
<ul style="list-style: none; padding-left: 0;">
<li class="hidden-sm-down">
<div><h5 class="text-dark-green">Last Transaction Points</h5></div>
<div class="number-circle mx-auto"><strong>{{ heroPointsSnippets.point_last | number:'1.0-0' }}</strong></div>
</li>
</ul>
</div>
</div>
</div>
<div class="col-md-6 col-xl-3">
<div class="card text-center" style="background-color: transparent; border: none">
<div class="card-block">
<ul style="list-style: none; padding-left: 0;">
<li class="hidden-sm-down">
<div><h5 class="text-dark-green">Total Transactions</h5></div>
<div class="number-circle mx-auto"><strong>{{ heroPointsSnippets.trans_count | number:'1.0-0' }}</strong></div>
</li>
</ul>
</div>
</div>
</div>
<div class="col-md-6 col-xl-3">
<div class="card text-center" style="background-color: transparent; border: none">
<div class="card-block">
<ul style="list-style: none; padding-left: 0;">
<li class="hidden-sm-down">
<div><h5 class="text-dark-green">Average Multiplier</h5></div>
<div class="number-circle mx-auto"><strong>{{ heroPointsSnippets.avg_multi | number:'1.0-0' }}</strong></div>
</li>
</ul>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,34 @@
import { Component, OnInit } from '@angular/core';
import { HeroPointsSnippetsService } from '../providers/hero-points-snippets.service';
@Component({
selector: 'snippet-bar-hero-points',
templateUrl: './hero-points-snippet-bar.component.html',
})
export class HeroPointsSnippetBarComponent implements OnInit {
// Hero Points snippets
public heroPointsSnippets = {
avg_multi: 0,
point_last: 0,
points_total: 0,
trans_count: 0,
};
constructor(
private heroPointsSnippetsService: HeroPointsSnippetsService,
) { }
public ngOnInit(): void {
this.heroPointsSnippetsService.getHeroPointsSnippets()
.subscribe(
result => {
this.heroPointsSnippets.avg_multi = result.snippets.avg_multi;
this.heroPointsSnippets.point_last = result.snippets.point_last;
this.heroPointsSnippets.points_total = result.snippets.points_total;
this.heroPointsSnippets.trans_count = result.snippets.trans_count;
}
)
}
}

View file

@ -0,0 +1,21 @@
<div class="card card-inverse card-primary">
<div class="card-block pb-0">
<button type="button" class="btn btn-transparent p-0 float-right">
<i [ngClass]="graphIcon"></i>
</button>
<h4 *ngIf="dataType == availableDataTypes.number" class="mb-0">{{ graphSum }}</h4>
<p>{{ graphTitle }}</p>
</div>
<div class="chart-wrapper px-3" style="height:70px;">
<canvas baseChart
class="chart"
[datasets]="lineChartData"
[labels]="lineChartLabels"
[options]="lineChartOptions"
[colors]="lineChartColours"
[legend]="lineChartLegend"
[chartType]="lineChartType"
(chartHover)="chartHovered($event)"
(chartClick)="chartClicked($event)"></canvas>
</div>
</div>

View file

@ -0,0 +1,154 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { OrgGraphsService } from '../providers/org-graphs.service';
import { DataType } from '../shared/data-types.enum';
interface ChartData {
data: Array<number>;
label: string;
}
@Component({
selector: 'hero-points-widget-graph',
templateUrl: 'hero-points-graph-widget.component.html',
})
export class HeroPointsGraphWidget implements OnInit {
@Input() public graphName: string;
@Input() public graphTitle = 'Graph';
@Input() public graphIcon = 'icon-graph';
@Input() public dataType: DataType = DataType.number;
@Output() public graphHover = new EventEmitter();
@Output() public graphClick = new EventEmitter();
public graphSum: Number = 0;
public availableDataTypes = DataType;
public lineChartData: Array<ChartData> = [
{
data: [],
label: 'Series A'
}
];
public lineChartLabels: Array<string>;
public lineChartOptions: any = {
maintainAspectRatio: false,
scales: {
xAxes: [{
type: 'time',
time: {
unit: 'day',
displayFormats: {
day: 'MMM D',
},
tooltipFormat: 'MMM D',
},
gridLines: {
color: 'transparent',
zeroLineColor: 'transparent'
},
ticks: {
fontSize: 2,
source: 'data',
fontColor: 'transparent',
}
}],
yAxes: [{
display: false,
ticks: {
display: false,
}
}],
},
elements: {
line: {
borderWidth: 1
},
point: {
radius: 4,
hitRadius: 10,
hoverRadius: 4,
},
},
legend: {
display: false
},
tooltips: {
callbacks: {
label: (tooltip, data) => {
return this.tooltipLabelCallback(tooltip, data);
},
},
},
};
public lineChartColours: Array<any> = [
{
backgroundColor: '#20a8d8',
borderColor: 'rgba(255,255,255,.55)'
}
];
public lineChartLegend = false;
public lineChartType = 'line';
constructor(
private graphService: OrgGraphsService,
) { }
ngOnInit(): void {
if ( this.graphName == null ) {
throw new Error('Attribute \'graphName\' is required on component \'widget-graph\'');
}
if ( this.dataType === undefined ) {
// Need to do this as it may be passed in a loop with an undefined value
this.dataType = DataType.number;
}
if ( !( this.dataType in DataType ) ) {
console.warn('Unknown DataType for graph \'' + this.graphName + '\' - defaulting to number');
}
this.graphService.getGraph(this.graphName)
.subscribe( result => this.setData(result.graph) );
}
private setData(data: any) {
this.setChartData(data.data);
this.setChartLabels(data.labels);
this.setChartBounds(data.bounds);
}
private setChartBounds(data) {
this.lineChartOptions.scales.xAxes[0].time.max = data.max;
this.lineChartOptions.scales.xAxes[0].time.min = data.min;
}
private setChartData(data: Array<number>) {
this.lineChartData[0].data = data;
this.graphSum = data.reduce((a, b) => a + b, 0);
// Set point size based on data
if ( data.length < 15 ) {
this.lineChartOptions.elements.point.radius = 4;
this.lineChartOptions.elements.line.borderWidth = 1;
} else {
this.lineChartOptions.elements.point.radius = 2;
this.lineChartOptions.elements.line.borderWidth = 2;
}
}
private setChartLabels(data: Array<string>) {
this.lineChartLabels = data;
}
// events
public chartClicked(e: any): void {
console.log(e);
}
public chartHovered(e: any): void {
console.log(e);
}
private tooltipLabelCallback(tooltipItem: any, data: any) {
const value = tooltipItem.yLabel;
return value || '0';
}
}

View file

@ -6,8 +6,14 @@
.table-striped tbody tr:nth-of-type(odd) {
background-color: #d2eef7;
}
.table-striped tbody tr:nth-of-type(even) {
background-color: #ffffff;
}
.table-hover tbody tr:hover td {
background-color: $table-bg-hover;
color: #000000;
}
.table thead tr {
@ -15,6 +21,63 @@
color: #e8ebed;
}
.header-fixed {
width: 100%;
table-layout:fixed;
display: table;
}
.header-fixed > thead,
.header-fixed > tbody,
.header-fixed > thead > tr,
.header-fixed > tbody > tr,
.header-fixed > thead > tr > th,
.header-fixed > tbody > tr > td {
display: block;
}
.header-fixed > tbody > tr:after,
.header-fixed > thead > tr:after {
content: ' ';
display: block;
visibility: hidden;
clear: both;
}
.header-fixed {
tbody {
overflow-y: auto;
height: 370px;
}
}
tr.collapse.in {
display:table-row;
}
.header-fixed > tbody > tr > td,
.header-fixed > thead > tr > th,
{
//width: 33.33%;
float: left;
overflow-wrap: break-word;
.col-1 {width: 8.33%;}
.col-2 {width: 16.66%;}
.col-3 {width: 25%;}
.col-4 {width: 33.33%;}
.col-5 {width: 41.66%;}
.col-6 {width: 50%;}
.col-7 {width: 58.33%;}
.col-8 {width: 66.66%;}
.col-9 {width: 75%;}
.col-10 {width: 83.33%;}
.col-11 {width: 91.66%;}
.col-12 {width: 100%;}
}
// Map styling
agm-map {
height: 75vh;
@ -43,6 +106,23 @@ agm-map {
color: #10602c;
}
.number-circle-hero {
height: 10rem;
border-radius:50%;
text-align:center;
width: 10rem;
padding: 5rem 5%;
line-height: 0;
position: relative;
background: #F39C12;
color: white;
font-size: 0.875rem;
}
.text-orange {
color: #D35400;
}
// white title font variant on type-2 as defined in _widgets.css
.horizontal-bars {
padding: 0;