(dashboards/finance) Added finance dashboard

(dashboards/crypto) Added crypto dashboard
This commit is contained in:
sercan 2021-07-23 22:23:42 +03:00
parent 569809aabb
commit 1581ea74cc
19 changed files with 3784 additions and 0 deletions

View File

@ -79,6 +79,8 @@ export const appRoutes: Route[] = [
{path: 'dashboards', children: [
{path: 'project', loadChildren: () => import('app/modules/admin/dashboards/project/project.module').then(m => m.ProjectModule)},
{path: 'analytics', loadChildren: () => import('app/modules/admin/dashboards/analytics/analytics.module').then(m => m.AnalyticsModule)},
{path: 'finance', loadChildren: () => import('app/modules/admin/dashboards/finance/finance.module').then(m => m.FinanceModule)},
{path: 'crypto', loadChildren: () => import('app/modules/admin/dashboards/crypto/crypto.module').then(m => m.CryptoModule)},
]},
// Apps

View File

@ -22,6 +22,20 @@ export const defaultNavigation: FuseNavigationItem[] = [
type : 'basic',
icon : 'heroicons_outline:chart-pie',
link : '/dashboards/analytics'
},
{
id : 'dashboards.finance',
title: 'Finance',
type : 'basic',
icon : 'heroicons_outline:cash',
link : '/dashboards/finance'
},
{
id : 'dashboards.crypto',
title: 'Crypto',
type : 'basic',
icon : 'heroicons_outline:currency-dollar',
link : '/dashboards/crypto'
}
]
},

View File

@ -0,0 +1,38 @@
import { Injectable } from '@angular/core';
import { cloneDeep } from 'lodash-es';
import { FuseMockApiService } from '@fuse/lib/mock-api';
import { crypto as cryptoData } from 'app/mock-api/dashboards/crypto/data';
@Injectable({
providedIn: 'root'
})
export class CryptoMockApi
{
private _crypto: any = cryptoData;
/**
* Constructor
*/
constructor(private _fuseMockApiService: FuseMockApiService)
{
// Register Mock API handlers
this.registerHandlers();
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Register Mock API handlers
*/
registerHandlers(): void
{
// -----------------------------------------------------------------------------------------------------
// @ Crypto - GET
// -----------------------------------------------------------------------------------------------------
this._fuseMockApiService
.onGet('api/dashboards/crypto')
.reply(() => [200, cloneDeep(this._crypto)]);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,38 @@
import { Injectable } from '@angular/core';
import { cloneDeep } from 'lodash-es';
import { FuseMockApiService } from '@fuse/lib/mock-api';
import { finance as financeData } from 'app/mock-api/dashboards/finance/data';
@Injectable({
providedIn: 'root'
})
export class FinanceMockApi
{
private _finance: any = financeData;
/**
* Constructor
*/
constructor(private _fuseMockApiService: FuseMockApiService)
{
// Register Mock API handlers
this.registerHandlers();
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Register Mock API handlers
*/
registerHandlers(): void
{
// -----------------------------------------------------------------------------------------------------
// @ Sales - GET
// -----------------------------------------------------------------------------------------------------
this._fuseMockApiService
.onGet('api/dashboards/finance')
.reply(() => [200, cloneDeep(this._finance)]);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -5,8 +5,10 @@ import { AuthMockApi } from 'app/mock-api/common/auth/api';
import { CalendarMockApi } from 'app/mock-api/apps/calendar/api';
import { ChatMockApi } from 'app/mock-api/apps/chat/api';
import { ContactsMockApi } from 'app/mock-api/apps/contacts/api';
import { CryptoMockApi } from 'app/mock-api/dashboards/crypto/api';
import { ECommerceInventoryMockApi } from 'app/mock-api/apps/ecommerce/inventory/api';
import { FileManagerMockApi } from 'app/mock-api/apps/file-manager/api';
import { FinanceMockApi } from 'app/mock-api/dashboards/finance/api';
import { HelpCenterMockApi } from 'app/mock-api/apps/help-center/api';
import { IconsMockApi } from 'app/mock-api/ui/icons/api';
import { MailboxMockApi } from 'app/mock-api/apps/mailbox/api';
@ -29,8 +31,10 @@ export const mockApiServices = [
CalendarMockApi,
ChatMockApi,
ContactsMockApi,
CryptoMockApi,
ECommerceInventoryMockApi,
FileManagerMockApi,
FinanceMockApi,
HelpCenterMockApi,
IconsMockApi,
MailboxMockApi,

View File

@ -0,0 +1,277 @@
<div class="absolute inset-0 flex flex-col min-w-0 overflow-hidden">
<mat-drawer-container class="flex-auto h-full">
<!-- Drawer -->
<mat-drawer
class="w-80"
[autoFocus]="false"
[mode]="drawerMode"
[opened]="drawerOpened"
#matDrawer>
<div class="flex flex-col flex-auto h-full dark:bg-default">
<!-- Watchlist -->
<div class="flex flex-col flex-0">
<div
class="flex flex-0 items-center p-5 border-b"
*ngFor="let item of data.watchlist">
<div class="flex flex-col flex-auto pr-6">
<div class="flex items-baseline">
<div class="mr-1 font-medium text-md text-secondary">{{item.title}}</div>
<div class="font-medium text-sm text-hint uppercase tracking-wider">({{item.iso}})</div>
</div>
<div class="flex items-end mt-2">
<div class="min-w-20 font-mono text-2xl tracking-tighter leading-none">
{{item.amount | currency:'USD':'symbol':'1.2-4'}}
</div>
<mat-icon
class="text-green-500 icon-size-3.5 mx-0.5 mb-px"
[ngClass]="{'text-green-500': item.trend.dir === 'up', 'text-red-500': item.trend.dir === 'down'}"
[svgIcon]="item.trend.dir === 'up' ? 'heroicons_solid:arrow-narrow-up' : 'heroicons_solid:arrow-narrow-down'"></mat-icon>
<div
class="font-mono font-medium text-sm leading-none mb-px"
[ngClass]="{'text-green-500': item.trend.dir === 'up', 'text-red-500': item.trend.dir === 'down'}">
{{item.trend.amount}}%
</div>
</div>
</div>
<apx-chart
class="flex flex-auto items-center h-10 overflow-hidden"
[chart]="watchlistChartOptions.chart"
[colors]="item.trend.dir === 'up' ? ['#48BB78']: ['#F56565']"
[series]="item.series"
[stroke]="watchlistChartOptions.stroke"
[tooltip]="watchlistChartOptions.tooltip"
[xaxis]="watchlistChartOptions.xaxis"></apx-chart>
</div>
</div>
<!-- Buy / Sell -->
<div class="flex flex-col flex-auto flex-shrink-0 pt-6 bg-gray-50 dark:bg-transparent">
<!-- Action -->
<div class="flex flex-col px-6 pb-2">
<mat-form-field>
<mat-label>Action</mat-label>
<span
class="flex items-center justify-center"
matPrefix>
<ng-container *ngIf="buySellSelect.value === 'buy'">
<mat-icon
class="icon-size-5"
[svgIcon]="'heroicons_solid:download'"></mat-icon>
</ng-container>
<ng-container *ngIf="buySellSelect.value === 'sell'">
<mat-icon
class="icon-size-5"
[svgIcon]="'heroicons_solid:upload'"></mat-icon>
</ng-container>
</span>
<mat-select
[value]="'buy'"
#buySellSelect="matSelect">
<mat-option [value]="'buy'">Buy</mat-option>
<mat-option [value]="'sell'">Sell</mat-option>
</mat-select>
</mat-form-field>
</div>
<!-- Wallet -->
<div class="flex flex-col px-6 pb-2">
<mat-form-field class="w-full">
<mat-label>Wallet</mat-label>
<mat-select
[value]="'btc'"
#walletSelector="matSelect">
<mat-select-trigger>
<span class="flex items-center">
<span>{{walletSelector.triggerValue}}</span>
<span class="mx-1 text-hint">-</span>
<span class="flex items-center font-mono">
<span>{{data.wallets[walletSelector.value]}}</span>
<span class="ml-1">{{walletSelector.value | uppercase}}</span>
</span>
</span>
</mat-select-trigger>
<mat-option [value]="'btc'">Bitcoin</mat-option>
<mat-option [value]="'eth'">Ethereum</mat-option>
<mat-option [value]="'bch'">Bitcoin Cash</mat-option>
<mat-option [value]="'xrp'">XRP</mat-option>
</mat-select>
<mat-hint class="flex items-center">
<span class="mr-1">USD:</span>
<span class="font-mono font-medium text-normal">
{{data.wallets[walletSelector.value] * data.prices[walletSelector.value] | currency:'USD'}}
</span>
</mat-hint>
</mat-form-field>
</div>
<!-- Buy form -->
<form
class="flex flex-col px-6"
*ngIf="buySellSelect.value === 'buy'">
<mat-form-field class="w-full">
<mat-label>Amount</mat-label>
<input
matInput
autocomplete="off"
#buyAmount>
<mat-select
[value]="'coin'"
matSuffix
#buyType="matSelect">
<mat-option [value]="'coin'">{{walletSelector.value | uppercase}}</mat-option>
<mat-option [value]="'usd'">USD</mat-option>
</mat-select>
<span
matPrefix
*ngIf="buyType.value === 'usd'">
$
</span>
<mat-hint class="flex items-center">
<ng-container *ngIf="buyType.value === 'coin'">
<span class="mr-1">It will cost:</span>
<span class="font-mono font-medium text-normal">
{{buyAmount.value * data.prices[walletSelector.value] | currency:'USD':'symbol':'1.2-4'}}
</span>
</ng-container>
<ng-container *ngIf="buyType.value === 'usd'">
<span class="mr-1">You will receive:</span>
<span class="font-mono font-medium text-normal">
{{buyAmount.value / data.prices[walletSelector.value] | number:'1.2-6'}} {{walletSelector.value | uppercase}}
</span>
</ng-container>
</mat-hint>
</mat-form-field>
<button
class="mt-4 mb-8"
mat-flat-button
[color]="'primary'">
BUY
</button>
</form>
<!-- Sell form -->
<form
class="flex flex-col px-6"
*ngIf="buySellSelect.value === 'sell'">
<mat-form-field class="w-full">
<mat-label>Amount</mat-label>
<input
matInput
autocomplete="off"
#sellAmount>
<mat-select
[value]="'coin'"
matSuffix
#sellType="matSelect">
<mat-option [value]="'coin'">{{walletSelector.value | uppercase}}</mat-option>
<mat-option [value]="'usd'">USD</mat-option>
</mat-select>
<span
matPrefix
*ngIf="sellType.value === 'usd'">
$
</span>
<mat-hint class="flex items-center">
<ng-container *ngIf="sellType.value === 'coin'">
<span class="mr-1">You will receive:</span>
<span class="font-mono font-medium text-normal">
{{sellAmount.value * data.prices[walletSelector.value] | currency:'USD':'symbol':'1.2-4'}}
</span>
</ng-container>
<ng-container *ngIf="sellType.value === 'usd'">
<span class="mr-1">You will sell:</span>
<span class="font-mono font-medium text-normal">
{{sellAmount.value / data.prices[walletSelector.value] | number:'1.2-6'}} {{walletSelector.value | uppercase}}
</span>
</ng-container>
</mat-hint>
</mat-form-field>
<button
class="mt-4 mb-8"
mat-flat-button
[color]="'primary'">
SELL
</button>
</form>
</div>
</div>
</mat-drawer>
<!-- Content -->
<mat-drawer-content class="flex flex-col">
<!-- BTC Price -->
<div class="flex flex-col flex-auto min-h-full bg-card dark:bg-default">
<div class="flex flex-wrap items-center pl-4 pr-6 py-3 md:pl-6 border-b">
<button
class="mr-6 lg:hidden"
mat-icon-button
(click)="matDrawer.toggle()">
<mat-icon [svgIcon]="'heroicons_outline:menu'"></mat-icon>
</button>
<div class="flex flex-col flex-auto my-3 mr-6">
<div class="flex items-center">
<div class="font-medium text-2xl text-secondary mr-2">Bitcoin</div>
<div class="font-medium text-lg text-hint tracking-wider">(BTC)</div>
</div>
<div class="flex items-end mt-1">
<div class="mr-2 font-mono text-3xl leading-none tracking-tight">{{data.btc.amount | currency:'USD':'symbol':'1.2-2'}}</div>
<mat-icon
class="text-green-500 icon-size-5 mr-0.5 mb-px"
[ngClass]="{'text-green-500': data.btc.trend.dir === 'up', 'text-red-500': data.btc.trend.dir === 'down'}"
[svgIcon]="data.btc.trend.dir === 'up' ? 'heroicons_solid:arrow-narrow-up' : 'heroicons_solid:arrow-narrow-down'"></mat-icon>
<div
class="font-mono font-medium text-lg leading-none mb-px"
[ngClass]="{'text-green-500': data.btc.trend.dir === 'up', 'text-red-500': data.btc.trend.dir === 'down'}">
{{data.btc.trend.amount}}%
</div>
</div>
</div>
<div class="hidden sm:flex items-center my-3">
<div class="p-4 leading-none rounded-l-xl border border-r-0">
<div class="text-sm font-medium text-secondary">Market Cap</div>
<div class="mt-2 font-mono text-xl">{{(data.btc.marketCap / 1000000000) | number: '1.0-2' | currency}}B</div>
</div>
<div class="p-4 leading-none border border-r-0">
<div class="text-sm font-medium text-secondary">Volume</div>
<div class="mt-2 font-mono text-xl">{{(data.btc.volume / 1000000000) | number: '1.0-2' | currency}}B</div>
</div>
<div class="p-4 leading-none border border-r-0">
<div class="text-sm font-medium text-secondary">Supply</div>
<div class="mt-2 font-mono text-xl">{{(data.btc.supply / 1000000) | number: '1.0-2'}}M</div>
</div>
<div class="p-4 leading-none rounded-r-xl border">
<div class="text-sm font-medium text-secondary">All Time High</div>
<div class="mt-2 font-mono text-xl">{{data.btc.allTimeHigh | currency:'USD'}}</div>
</div>
</div>
</div>
<div class="relative flex flex-auto bg-gray-50 dark:bg-transparent">
<apx-chart
class="relative w-full h-160 md:absolute md:inset-0 md:h-auto overflow-hidden"
[chart]="btcOptions.chart"
[colors]="btcOptions.colors"
[dataLabels]="btcOptions.dataLabels"
[grid]="btcOptions.grid"
[legend]="btcOptions.legend"
[series]="btcOptions.series"
[stroke]="btcOptions.stroke"
[tooltip]="btcOptions.tooltip"
[xaxis]="btcOptions.xaxis"
[yaxis]="btcOptions.yaxis"
#btcChartComponent></apx-chart>
</div>
</div>
</mat-drawer-content>
</mat-drawer-container>
</div>

View File

@ -0,0 +1,238 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import * as moment from 'moment';
import { ApexOptions, ChartComponent } from 'ng-apexcharts';
import { FuseMediaWatcherService } from '@fuse/services/media-watcher';
import { CryptoService } from 'app/modules/admin/dashboards/crypto/crypto.service';
@Component({
selector : 'crypto',
templateUrl : './crypto.component.html',
encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CryptoComponent implements OnInit, OnDestroy
{
@ViewChild('btcChartComponent') btcChartComponent: ChartComponent;
appConfig: any;
btcOptions: ApexOptions = {};
data: any;
drawerMode: 'over' | 'side' = 'side';
drawerOpened: boolean = true;
watchlistChartOptions: ApexOptions = {};
private _unsubscribeAll: Subject<any> = new Subject<any>();
/**
* Constructor
*/
constructor(
private _cryptoService: CryptoService,
private _changeDetectorRef: ChangeDetectorRef,
private _fuseMediaWatcherService: FuseMediaWatcherService
)
{
}
// -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks
// -----------------------------------------------------------------------------------------------------
/**
* On init
*/
ngOnInit(): void
{
// Subscribe to media changes
this._fuseMediaWatcherService.onMediaChange$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(({matchingAliases}) => {
// Set the drawerMode and drawerOpened if 'lg' breakpoint is active
if ( matchingAliases.includes('lg') )
{
this.drawerMode = 'side';
this.drawerOpened = true;
}
else
{
this.drawerMode = 'over';
this.drawerOpened = false;
}
// Mark for check
this._changeDetectorRef.markForCheck();
});
// Get the data
this._cryptoService.data$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((data) => {
// Store the data
this.data = data;
// Prepare the chart data
this._prepareChartData();
});
}
/**
* On destroy
*/
ngOnDestroy(): void
{
// Unsubscribe from all subscriptions
this._unsubscribeAll.next();
this._unsubscribeAll.complete();
}
// -----------------------------------------------------------------------------------------------------
// @ Private methods
// -----------------------------------------------------------------------------------------------------
/**
* Prepare the chart data from the data
*
* @private
*/
private _prepareChartData(): void
{
// BTC
this.btcOptions = {
chart : {
animations: {
enabled: false
},
fontFamily: 'inherit',
foreColor : 'inherit',
width : '100%',
height : '100%',
type : 'line',
toolbar : {
show: false
},
zoom : {
enabled: false
}
},
colors : ['#5A67D8'],
dataLabels: {
enabled: false
},
grid : {
borderColor : 'var(--fuse-border)',
position : 'back',
show : true,
strokeDashArray: 6,
xaxis : {
lines: {
show: true
}
},
yaxis : {
lines: {
show: true
}
}
},
legend : {
show: false
},
series : this.data.btc.price.series,
stroke : {
width: 2,
curve: 'straight'
},
tooltip : {
shared: true,
theme : 'dark',
y : {
formatter: (value: number): string => '$' + value.toFixed(2)
}
},
xaxis : {
type : 'numeric',
crosshairs: {
show : true,
position: 'back',
fill : {
type : 'color',
color: 'var(--fuse-border)'
},
width : 3,
stroke : {
dashArray: 0,
width : 0
},
opacity : 0.9
},
tickAmount: 8,
axisTicks : {
show : true,
color: 'var(--fuse-border)'
},
axisBorder: {
show: false
},
tooltip : {
enabled: false
},
labels : {
show : true,
trim : false,
rotate : 0,
minHeight : 40,
hideOverlappingLabels: true,
formatter : (value): string => moment().subtract(Math.abs(parseInt(value, 10)), 'minutes').format('HH:mm'),
style : {
colors: 'currentColor'
}
}
},
yaxis : {
axisTicks : {
show : true,
color: 'var(--fuse-border)'
},
axisBorder : {
show: false
},
forceNiceScale: true,
labels : {
minWidth : 40,
formatter: (value: number): string => '$' + value.toFixed(0),
style : {
colors: 'currentColor'
}
}
}
};
// Watchlist options
this.watchlistChartOptions = {
chart : {
animations: {
enabled: false
},
width : '100%',
height : '100%',
type : 'line',
sparkline : {
enabled: true
}
},
colors : ['#A0AEC0'],
stroke : {
width: 2,
curve: 'smooth'
},
tooltip: {
enabled: false
},
xaxis : {
type: 'category'
}
};
}
}

View File

@ -0,0 +1,42 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { MatButtonModule } from '@angular/material/button';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatMenuModule } from '@angular/material/menu';
import { MatSelectModule } from '@angular/material/select';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatSortModule } from '@angular/material/sort';
import { MatTableModule } from '@angular/material/table';
import { MatTabsModule } from '@angular/material/tabs';
import { NgApexchartsModule } from 'ng-apexcharts';
import { SharedModule } from 'app/shared/shared.module';
import { CryptoComponent } from 'app/modules/admin/dashboards/crypto/crypto.component';
import { cryptoRoutes } from 'app/modules/admin/dashboards/crypto/crypto.routing';
@NgModule({
declarations: [
CryptoComponent
],
imports : [
RouterModule.forChild(cryptoRoutes),
MatButtonModule,
MatButtonToggleModule,
MatFormFieldModule,
MatIconModule,
MatInputModule,
MatMenuModule,
MatSelectModule,
MatSidenavModule,
MatSortModule,
MatTableModule,
MatTabsModule,
NgApexchartsModule,
SharedModule
]
})
export class CryptoModule
{
}

View File

@ -0,0 +1,32 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { CryptoService } from 'app/modules/admin/dashboards/crypto/crypto.service';
@Injectable({
providedIn: 'root'
})
export class CryptoResolver implements Resolve<any>
{
/**
* Constructor
*/
constructor(private _cryptoService: CryptoService)
{
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Resolver
*
* @param route
* @param state
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any>
{
return this._cryptoService.getData();
}
}

View File

@ -0,0 +1,13 @@
import { Route } from '@angular/router';
import { CryptoComponent } from 'app/modules/admin/dashboards/crypto/crypto.component';
import { CryptoResolver } from 'app/modules/admin/dashboards/crypto/crypto.resolvers';
export const cryptoRoutes: Route[] = [
{
path : '',
component: CryptoComponent,
resolve : {
data: CryptoResolver
}
}
];

View File

@ -0,0 +1,47 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class CryptoService
{
private _data: BehaviorSubject<any> = new BehaviorSubject(null);
/**
* Constructor
*/
constructor(private _httpClient: HttpClient)
{
}
// -----------------------------------------------------------------------------------------------------
// @ Accessors
// -----------------------------------------------------------------------------------------------------
/**
* Getter for data
*/
get data$(): Observable<any>
{
return this._data.asObservable();
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Get data
*/
getData(): Observable<any>
{
return this._httpClient.get('api/dashboards/crypto').pipe(
tap((response: any) => {
this._data.next(response);
})
);
}
}

View File

@ -0,0 +1,526 @@
<div class="flex flex-col flex-auto w-full">
<div class="flex flex-wrap w-full max-w-screen-xl mx-auto p-6 md:p-8">
<!-- Title and action buttons -->
<div class="flex items-center justify-between w-full">
<div>
<h2 class="text-3xl font-semibold tracking-tight leading-8">Finance dashboard</h2>
<div class="font-medium tracking-tight text-secondary">Keep track of your financial status</div>
</div>
<div class="flex items-center ml-6">
<button
class="hidden sm:inline-flex"
mat-stroked-button>
<mat-icon
class="icon-size-5"
[svgIcon]="'heroicons_solid:document-report'"></mat-icon>
<span class="ml-2">Reports</span>
</button>
<button
class="hidden sm:inline-flex ml-3"
mat-stroked-button>
<mat-icon
class="icon-size-5"
[svgIcon]="'heroicons_solid:cog'"></mat-icon>
<span class="ml-2">Settings</span>
</button>
<button
class="hidden sm:inline-flex ml-3"
mat-flat-button
[color]="'primary'">
<mat-icon
class="icon-size-5"
[svgIcon]="'heroicons_solid:save'"></mat-icon>
<span class="ml-2">Export</span>
</button>
<!-- Actions menu (visible on xs) -->
<div class="sm:hidden">
<button
[matMenuTriggerFor]="actionsMenu"
mat-icon-button>
<mat-icon [svgIcon]="'heroicons_outline:dots-vertical'"></mat-icon>
</button>
<mat-menu #actionsMenu="matMenu">
<button mat-menu-item>Export</button>
<button mat-menu-item>Reports</button>
<button mat-menu-item>Settings</button>
</mat-menu>
</div>
</div>
</div>
<div class="grid grid-cols-1 xl:grid-cols-2 gap-8 w-full mt-8">
<div class="grid gap-8 sm:grid-flow-col xl:grid-flow-row">
<!-- Previous statement -->
<div class="relative flex flex-col flex-auto p-6 pr-3 pb-3 bg-card rounded-2xl shadow overflow-hidden">
<div class="absolute bottom-0 right-0 w-24 h-24 -m-6">
<mat-icon
class="icon-size-24 opacity-25 text-green-500 dark:text-green-400"
[svgIcon]="'heroicons_outline:check-circle'"></mat-icon>
</div>
<div class="flex items-center">
<div class="flex flex-col">
<div class="text-lg font-medium tracking-tight leading-6 truncate">Previous Statement</div>
<div class="text-green-600 font-medium text-sm">
Paid on {{data.previousStatement.date}}
</div>
</div>
<div class="ml-auto -mt-2">
<button
mat-icon-button
[matMenuTriggerFor]="previousStatementMenu">
<mat-icon
class="icon-size-5"
[svgIcon]="'heroicons_solid:dots-vertical'"></mat-icon>
</button>
<mat-menu #previousStatementMenu="matMenu">
<button mat-menu-item>
<span class="flex items-center">
<mat-icon
class="icon-size-5 mr-3"
[svgIcon]="'heroicons_solid:credit-card'"></mat-icon>
<span>View statement</span>
</span>
</button>
<button mat-menu-item>
<span class="flex items-center">
<mat-icon
class="icon-size-5 mr-3"
[svgIcon]="'heroicons_solid:cash'"></mat-icon>
<span>Spending breakdown</span>
</span>
</button>
<button mat-menu-item>
<span class="flex items-center">
<mat-icon
class="icon-size-5 mr-3"
[svgIcon]="'heroicons_solid:receipt-tax'"></mat-icon>
<span>Tax breakdown</span>
</span>
</button>
<mat-divider class="my-2"></mat-divider>
<button mat-menu-item>
<span class="flex items-center">
<mat-icon
class="icon-size-5 mr-3"
[svgIcon]="'heroicons_solid:printer'"></mat-icon>
<span>Print statement</span>
</span>
</button>
<button mat-menu-item>
<span class="flex items-center">
<mat-icon
class="icon-size-5 mr-3"
[svgIcon]="'heroicons_solid:mail'"></mat-icon>
<span>Email statement</span>
</span>
</button>
</mat-menu>
</div>
</div>
<div class="flex flex-row flex-wrap mt-4 -mx-6">
<div class="flex flex-col mx-6 my-3">
<div class="text-sm font-medium leading-none text-secondary">Card Limit</div>
<div class="mt-2 font-medium text-3xl leading-none">{{data.previousStatement.limit | currency:'USD'}}</div>
</div>
<div class="flex flex-col mx-6 my-3">
<div class="text-sm font-medium leading-none text-secondary">Spent</div>
<div class="mt-2 font-medium text-3xl leading-none">{{data.previousStatement.spent | currency:'USD'}}</div>
</div>
<div class="flex flex-col mx-6 my-3">
<div class="text-sm font-medium leading-none text-secondary">Minimum</div>
<div class="mt-2 font-medium text-3xl leading-none">{{data.previousStatement.minimum | currency:'USD'}}</div>
</div>
</div>
</div>
<!-- Current statement -->
<div class="relative flex flex-col flex-auto p-6 pr-3 pb-3 bg-card rounded-2xl shadow overflow-hidden">
<div class="absolute bottom-0 right-0 w-24 h-24 -m-6">
<mat-icon
class="icon-size-24 opacity-25 text-red-500 dark:text-red-400"
[svgIcon]="'heroicons_outline:exclamation-circle'"></mat-icon>
</div>
<div class="flex items-center">
<div class="flex flex-col">
<div class="text-lg font-medium tracking-tight leading-6 truncate">Current Statement</div>
<div class="text-red-600 font-medium text-sm">
Must be paid before {{data.currentStatement.date}}
</div>
</div>
<div class="ml-auto -mt-2">
<button
mat-icon-button
[matMenuTriggerFor]="currentStatementMenu">
<mat-icon
class="icon-size-5"
[svgIcon]="'heroicons_solid:dots-vertical'"></mat-icon>
</button>
<mat-menu #currentStatementMenu="matMenu">
<button mat-menu-item>
<span class="flex items-center">
<mat-icon
class="icon-size-5 mr-3"
[svgIcon]="'heroicons_solid:credit-card'"></mat-icon>
<span>View statement</span>
</span>
</button>
<button mat-menu-item>
<span class="flex items-center">
<mat-icon
class="icon-size-5 mr-3"
[svgIcon]="'heroicons_solid:cash'"></mat-icon>
<span>Spending breakdown</span>
</span>
</button>
<button mat-menu-item>
<span class="flex items-center">
<mat-icon
class="icon-size-5 mr-3"
[svgIcon]="'heroicons_solid:receipt-tax'"></mat-icon>
<span>Tax breakdown</span>
</span>
</button>
<mat-divider class="my-2"></mat-divider>
<button mat-menu-item>
<span class="flex items-center">
<mat-icon
class="icon-size-5 mr-3"
[svgIcon]="'heroicons_solid:printer'"></mat-icon>
<span>Print statement</span>
</span>
</button>
<button mat-menu-item>
<span class="flex items-center">
<mat-icon
class="icon-size-5 mr-3"
[svgIcon]="'heroicons_solid:mail'"></mat-icon>
<span>Email statement</span>
</span>
</button>
</mat-menu>
</div>
</div>
<div class="flex flex-row flex-wrap mt-4 -mx-6">
<div class="flex flex-col mx-6 my-3">
<div class="text-sm font-medium leading-none text-secondary">Card Limit</div>
<div class="mt-2 font-medium text-3xl leading-none">{{data.currentStatement.limit | currency:'USD'}}</div>
</div>
<div class="flex flex-col mx-6 my-3">
<div class="text-sm font-medium leading-none text-secondary">Spent</div>
<div class="mt-2 font-medium text-3xl leading-none">{{data.currentStatement.spent | currency:'USD'}}</div>
</div>
<div class="flex flex-col mx-6 my-3">
<div class="text-sm font-medium leading-none text-secondary">Minimum</div>
<div class="mt-2 font-medium text-3xl leading-none">{{data.currentStatement.minimum | currency:'USD'}}</div>
</div>
</div>
</div>
</div>
<!-- Account balance -->
<div class="flex flex-col flex-auto bg-card shadow rounded-2xl overflow-hidden">
<div class="flex flex-col p-6 pb-4">
<div class="flex items-center justify-between">
<div class="flex flex-col">
<div class="mr-4 text-lg font-medium tracking-tight leading-6 truncate">Account Balance</div>
<div class="text-secondary font-medium">Monthly balance growth and avg. monthly income</div>
</div>
<div class="ml-2">
<button
class="h-6 min-h-6 px-2 rounded-full bg-hover"
mat-button
[matMenuTriggerFor]="accountBalanceMenu">
<span class="font-medium text-sm text-secondary">12 months</span>
</button>
<mat-menu #accountBalanceMenu="matMenu">
<button mat-menu-item>3 months</button>
<button mat-menu-item>6 months</button>
<button mat-menu-item>9 months</button>
<button mat-menu-item>12 months</button>
</mat-menu>
</div>
</div>
<div class="flex items-start mt-6 mr-2">
<div class="flex flex-col">
<div class="font-semibold text-3xl md:text-5xl tracking-tighter">{{data.accountBalance.growRate}}%</div>
<div class="font-medium text-sm text-secondary leading-none">Average Monthly Growth</div>
</div>
<div class="flex flex-col ml-8 md:ml-16">
<div class="font-semibold text-3xl md:text-5xl tracking-tighter">{{data.accountBalance.ami | currency:'USD'}}</div>
<div class="font-medium text-sm text-secondary leading-none">Average Monthly Income</div>
</div>
</div>
</div>
<div class="flex flex-col flex-auto">
<apx-chart
class="flex-auto w-full h-full"
[chart]="accountBalanceOptions.chart"
[colors]="accountBalanceOptions.colors"
[fill]="accountBalanceOptions.fill"
[series]="accountBalanceOptions.series"
[stroke]="accountBalanceOptions.stroke"
[tooltip]="accountBalanceOptions.tooltip"
[xaxis]="accountBalanceOptions.xaxis"></apx-chart>
</div>
</div>
</div>
<div class="grid grid-cols-1 xl:grid-cols-3 gap-8 w-full mt-8">
<!-- Recent transactions table -->
<div class="xl:col-span-2 flex flex-col flex-auto bg-card shadow rounded-2xl overflow-hidden">
<div class="p-6">
<div class="mr-4 text-lg font-medium tracking-tight leading-6 truncate">Recent transactions</div>
<div class="text-secondary font-medium">1 pending, 4 completed</div>
</div>
<div class="overflow-x-auto mx-6">
<table
class="w-full bg-transparent"
mat-table
matSort
[dataSource]="recentTransactionsDataSource"
[trackBy]="trackByFn"
#recentTransactionsTable>
<!-- Transaction ID -->
<ng-container matColumnDef="transactionId">
<th
mat-header-cell
mat-sort-header
*matHeaderCellDef>
Transaction ID
</th>
<td
mat-cell
*matCellDef="let transaction">
<span class="pr-6 font-medium text-sm text-secondary whitespace-nowrap">
{{transaction.transactionId}}
</span>
</td>
</ng-container>
<!-- Date -->
<ng-container matColumnDef="date">
<th
mat-header-cell
mat-sort-header
*matHeaderCellDef>
Date
</th>
<td
mat-cell
*matCellDef="let transaction">
<span class="pr-6 whitespace-nowrap">
{{transaction.date | date:'MMM dd, y'}}
</span>
</td>
</ng-container>
<!-- Name -->
<ng-container matColumnDef="name">
<th
mat-header-cell
mat-sort-header
*matHeaderCellDef>
Name
</th>
<td
mat-cell
*matCellDef="let transaction">
<span class="pr-6 whitespace-nowrap">
{{transaction.name}}
</span>
</td>
</ng-container>
<!-- Amount -->
<ng-container matColumnDef="amount">
<th
mat-header-cell
mat-sort-header
*matHeaderCellDef>
Amount
</th>
<td
mat-cell
*matCellDef="let transaction">
<span class="pr-6 font-medium whitespace-nowrap">
{{transaction.amount | currency:'USD'}}
</span>
</td>
</ng-container>
<!-- Status -->
<ng-container matColumnDef="status">
<th
mat-header-cell
mat-sort-header
*matHeaderCellDef>
Status
</th>
<td
mat-cell
*matCellDef="let transaction">
<span
class="inline-flex items-center font-bold text-xs px-2.5 py-0.5 rounded-full tracking-wide uppercase"
[ngClass]="{'bg-red-200 text-red-800 dark:bg-red-600 dark:text-red-50': transaction.status === 'pending',
'bg-green-200 text-green-800 dark:bg-green-600 dark:text-green-50': transaction.status === 'completed'}">
<span class="leading-relaxed whitespace-nowrap">{{transaction.status}}</span>
</span>
</td>
</ng-container>
<!-- Footer -->
<ng-container matColumnDef="recentOrdersTableFooter">
<td
class="py-6 px-0 border-0"
mat-footer-cell
*matFooterCellDef
colspan="6">
<button mat-stroked-button>See all transactions</button>
</td>
</ng-container>
<tr
mat-header-row
*matHeaderRowDef="recentTransactionsTableColumns"></tr>
<tr
class="order-row h-16"
mat-row
*matRowDef="let row; columns: recentTransactionsTableColumns;"></tr>
<tr
class="h-16 border-0"
mat-footer-row
*matFooterRowDef="['recentOrdersTableFooter']"></tr>
</table>
</div>
</div>
<!-- Budget -->
<div class="flex flex-col flex-auto p-6 bg-card rounded-2xl shadow">
<div class="flex items-center">
<div class="flex flex-col">
<div class="mr-4 text-lg font-medium tracking-tight leading-6 truncate">Budget</div>
<div class="text-secondary font-medium">Monthly budget summary</div>
</div>
<div class="ml-auto -mt-2 -mr-2">
<button
mat-icon-button
[matMenuTriggerFor]="budgetMenu">
<mat-icon
class="icon-size-5"
[svgIcon]="'heroicons_solid:dots-vertical'"></mat-icon>
</button>
<mat-menu #budgetMenu="matMenu">
<button mat-menu-item>Expenses breakdown</button>
<button mat-menu-item>Savings breakdown</button>
<button mat-menu-item>Bills breakdown</button>
<mat-divider class="my-2"></mat-divider>
<button mat-menu-item>
<span class="flex items-center">
<mat-icon
class="icon-size-5 mr-3"
[svgIcon]="'heroicons_solid:printer'"></mat-icon>
<span>Print budget summary</span>
</span>
</button>
<button mat-menu-item>
<span class="flex items-center">
<mat-icon
class="icon-size-5 mr-3"
[svgIcon]="'heroicons_solid:mail'"></mat-icon>
<span>Email budget summary</span>
</span>
</button>
</mat-menu>
</div>
</div>
<div class="mt-6">
Last month; you had <strong>223</strong> expense transactions, <strong>12</strong> savings entries and <strong>4</strong> bills.
</div>
<div class="my-8 space-y-8">
<div class="flex flex-col">
<div class="flex items-center">
<div class="flex items-center justify-center w-14 h-14 rounded bg-red-100 text-red-800 dark:bg-red-600 dark:text-red-50">
<mat-icon
class="text-current"
[svgIcon]="'heroicons_outline:credit-card'"></mat-icon>
</div>
<div class="flex-auto ml-4 leading-none">
<div class="text-sm font-medium text-secondary">Expenses</div>
<div class="mt-2 font-medium text-2xl">{{data.budget.expenses | currency:'USD'}}</div>
<mat-progress-bar
class="mt-3 rounded-full"
[color]="'warn'"
[mode]="'determinate'"
[value]="(data.budget.expenses * 100) / data.budget.expensesLimit"></mat-progress-bar>
</div>
<div class="flex items-end justify-end min-w-18 mt-auto ml-6">
<div class="text-lg leading-none">2.6%</div>
<mat-icon
class="text-green-600 icon-size-4 ml-1"
[svgIcon]="'heroicons_solid:arrow-narrow-down'"></mat-icon>
</div>
</div>
</div>
<div class="flex flex-col">
<div class="flex items-center">
<div class="flex items-center justify-center w-14 h-14 rounded bg-indigo-100 text-indigo-800 dark:bg-indigo-600 dark:text-indigo-50">
<mat-icon
class="text-current"
[svgIcon]="'heroicons_outline:cash'"></mat-icon>
</div>
<div class="flex-auto ml-4 leading-none">
<div class="text-sm font-medium text-secondary">Savings</div>
<div class="mt-2 font-medium text-2xl">{{data.budget.savings | currency:'USD'}}</div>
<mat-progress-bar
class="mt-3 rounded-full"
[mode]="'determinate'"
[value]="(data.budget.savings * 100) / data.budget.savingsGoal"></mat-progress-bar>
</div>
<div class="flex items-end justify-end min-w-18 mt-auto ml-6">
<div class="text-lg leading-none">12.7%</div>
<mat-icon
class="text-red-600 icon-size-4 ml-1"
[svgIcon]="'heroicons_solid:arrow-narrow-up'"></mat-icon>
</div>
</div>
</div>
<div class="flex flex-col">
<div class="flex items-center">
<div class="flex items-center justify-center w-14 h-14 rounded bg-teal-100 text-teal-800 dark:bg-teal-600 dark:text-teal-50">
<mat-icon
class="text-current"
[svgIcon]="'heroicons_outline:light-bulb'"></mat-icon>
</div>
<div class="flex-auto ml-4 leading-none">
<div class="text-sm font-medium text-secondary">Bills</div>
<div class="mt-2 font-medium text-2xl">{{data.budget.bills | currency:'USD'}}</div>
<mat-progress-bar
class="mt-3 rounded-full"
[mode]="'determinate'"
[value]="(data.budget.bills * 100) / data.budget.billsLimit"></mat-progress-bar>
</div>
<div class="flex items-end justify-end min-w-18 mt-auto ml-6">
<div class="text-lg leading-none">105.7%</div>
<mat-icon
class="text-red-600 icon-size-4 ml-1"
[svgIcon]="'heroicons_solid:arrow-narrow-up'"></mat-icon>
</div>
</div>
<div class="mt-3 text-md text-secondary">Exceeded your personal limit! Be careful next month.</div>
</div>
</div>
<div class="flex items-center mt-auto">
<button
class="mt-2"
mat-stroked-button>
Download Summary
</button>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,146 @@
import { AfterViewInit, ChangeDetectionStrategy, Component, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { ApexOptions } from 'ng-apexcharts';
import { FinanceService } from 'app/modules/admin/dashboards/finance/finance.service';
@Component({
selector : 'finance',
templateUrl : './finance.component.html',
encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class FinanceComponent implements OnInit, AfterViewInit, OnDestroy
{
@ViewChild('recentTransactionsTable', {read: MatSort}) recentTransactionsTableMatSort: MatSort;
data: any;
accountBalanceOptions: ApexOptions;
recentTransactionsDataSource: MatTableDataSource<any> = new MatTableDataSource();
recentTransactionsTableColumns: string[] = ['transactionId', 'date', 'name', 'amount', 'status'];
private _unsubscribeAll: Subject<any> = new Subject<any>();
/**
* Constructor
*/
constructor(private _financeService: FinanceService)
{
}
// -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks
// -----------------------------------------------------------------------------------------------------
/**
* On init
*/
ngOnInit(): void
{
// Get the data
this._financeService.data$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((data) => {
// Store the data
this.data = data;
// Store the table data
this.recentTransactionsDataSource.data = data.recentTransactions;
// Prepare the chart data
this._prepareChartData();
});
}
/**
* After view init
*/
ngAfterViewInit(): void
{
// Make the data source sortable
this.recentTransactionsDataSource.sort = this.recentTransactionsTableMatSort;
}
/**
* On destroy
*/
ngOnDestroy(): void
{
// Unsubscribe from all subscriptions
this._unsubscribeAll.next();
this._unsubscribeAll.complete();
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Track by function for ngFor loops
*
* @param index
* @param item
*/
trackByFn(index: number, item: any): any
{
return item.id || index;
}
// -----------------------------------------------------------------------------------------------------
// @ Private methods
// -----------------------------------------------------------------------------------------------------
/**
* Prepare the chart data from the data
*
* @private
*/
private _prepareChartData(): void
{
// Account balance
this.accountBalanceOptions = {
chart : {
animations: {
speed : 400,
animateGradually: {
enabled: false
}
},
fontFamily: 'inherit',
foreColor : 'inherit',
width : '100%',
height : '100%',
type : 'area',
sparkline : {
enabled: true
}
},
colors : ['#A3BFFA', '#667EEA'],
fill : {
colors : ['#CED9FB', '#AECDFD'],
opacity: 0.5,
type : 'solid'
},
series : this.data.accountBalance.series,
stroke : {
curve: 'straight',
width: 2
},
tooltip: {
followCursor: true,
theme : 'dark',
x : {
format: 'MMM dd, yyyy'
},
y : {
formatter: (value): string => value + '%'
}
},
xaxis : {
type: 'datetime'
}
};
}
}

View File

@ -0,0 +1,34 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { MatButtonModule } from '@angular/material/button';
import { MatDividerModule } from '@angular/material/divider';
import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatSortModule } from '@angular/material/sort';
import { MatTableModule } from '@angular/material/table';
import { NgApexchartsModule } from 'ng-apexcharts';
import { SharedModule } from 'app/shared/shared.module';
import { FinanceComponent } from 'app/modules/admin/dashboards/finance/finance.component';
import { financeRoutes } from 'app/modules/admin/dashboards/finance/finance.routing';
@NgModule({
declarations: [
FinanceComponent
],
imports : [
RouterModule.forChild(financeRoutes),
MatButtonModule,
MatDividerModule,
MatIconModule,
MatMenuModule,
MatProgressBarModule,
MatSortModule,
MatTableModule,
NgApexchartsModule,
SharedModule
]
})
export class FinanceModule
{
}

View File

@ -0,0 +1,32 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { FinanceService } from 'app/modules/admin/dashboards/finance/finance.service';
@Injectable({
providedIn: 'root'
})
export class FinanceResolver implements Resolve<any>
{
/**
* Constructor
*/
constructor(private _financeService: FinanceService)
{
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Resolver
*
* @param route
* @param state
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any>
{
return this._financeService.getData();
}
}

View File

@ -0,0 +1,13 @@
import { Route } from '@angular/router';
import { FinanceComponent } from 'app/modules/admin/dashboards/finance/finance.component';
import { FinanceResolver } from 'app/modules/admin/dashboards/finance/finance.resolvers';
export const financeRoutes: Route[] = [
{
path : '',
component: FinanceComponent,
resolve : {
data: FinanceResolver
}
}
];

View File

@ -0,0 +1,47 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class FinanceService
{
private _data: BehaviorSubject<any> = new BehaviorSubject(null);
/**
* Constructor
*/
constructor(private _httpClient: HttpClient)
{
}
// -----------------------------------------------------------------------------------------------------
// @ Accessors
// -----------------------------------------------------------------------------------------------------
/**
* Getter for data
*/
get data$(): Observable<any>
{
return this._data.asObservable();
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Get data
*/
getData(): Observable<any>
{
return this._httpClient.get('api/dashboards/finance').pipe(
tap((response: any) => {
this._data.next(response);
})
);
}
}