mirror of
https://github.com/akveo/ngx-admin.git
synced 2026-02-19 14:38:07 +01:00
feat(dashboard): add new E-commerce dashboard (#1754)
This commit is contained in:
parent
3482404b88
commit
56e4709a55
106 changed files with 6333 additions and 19 deletions
|
|
@ -76,23 +76,17 @@ export class EchartsBarAnimationComponent implements AfterViewInit, OnDestroy {
|
|||
name: 'bar',
|
||||
type: 'bar',
|
||||
data: data1,
|
||||
animationDelay: function(idx) {
|
||||
return idx * 10;
|
||||
},
|
||||
animationDelay: idx => idx * 10,
|
||||
},
|
||||
{
|
||||
name: 'bar2',
|
||||
type: 'bar',
|
||||
data: data2,
|
||||
animationDelay: function(idx) {
|
||||
return idx * 10 + 100;
|
||||
},
|
||||
animationDelay: idx => idx * 10 + 100,
|
||||
},
|
||||
],
|
||||
animationEasing: 'elasticOut',
|
||||
animationDelayUpdate: function(idx) {
|
||||
return idx * 5;
|
||||
},
|
||||
animationDelayUpdate: idx => idx * 5,
|
||||
};
|
||||
|
||||
for (let i = 0; i < 100; i++) {
|
||||
|
|
|
|||
|
|
@ -51,7 +51,6 @@
|
|||
<li class="dropdown-item" *ngFor="let t of types" (click)="type = t">{{ t }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<ngx-electricity-chart></ngx-electricity-chart>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0.675rem 0.5rem 0.5rem 1.25rem;
|
||||
border: none;
|
||||
}
|
||||
|
||||
nb-card-body {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
<div class="chart-header">
|
||||
<ngx-legend-chart [legendItems]="chartLegend"></ngx-legend-chart>
|
||||
|
||||
<div class="dropdown"
|
||||
[ngClass]="{ 'ghost-dropdown': currentTheme === 'corporate' }"
|
||||
ngbDropdown>
|
||||
<button type="button" ngbDropdownToggle class="btn"
|
||||
[ngClass]="{
|
||||
'btn-outline-success': currentTheme === 'default',
|
||||
'btn-primary': currentTheme !== 'default',
|
||||
'btn-sm': breakpoint.width <= breakpoints.is}">
|
||||
{{ type }}
|
||||
</button>
|
||||
<ul class="dropdown-menu" ngbDropdownMenu>
|
||||
<li class="dropdown-item" *ngFor="let period of types" (click)="changePeriod(period)">
|
||||
{{ period }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
@import '../../../../@theme/styles/themes';
|
||||
@import '~bootstrap/scss/mixins/breakpoints';
|
||||
@import '~@nebular/theme/styles/global/bootstrap/breakpoints';
|
||||
|
||||
@include nb-install-component() {
|
||||
.chart-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 2.125rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
min-width: 8.125rem;
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(is) {
|
||||
.chart-header {
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
ngx-legend-chart {
|
||||
align-self: flex-start;
|
||||
|
||||
/deep/ .legends {
|
||||
padding-left: 0;
|
||||
font-size: nb-theme(font-size-sm);
|
||||
}
|
||||
|
||||
/deep/ .legend {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
align-self: flex-end;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
import { Component, EventEmitter, Input, OnDestroy, Output } from '@angular/core';
|
||||
import { NbMediaBreakpoint, NbMediaBreakpointsService, NbThemeService } from '@nebular/theme';
|
||||
import { takeWhile } from 'rxjs/operators';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-chart-panel-header',
|
||||
styleUrls: ['./chart-panel-header.component.scss'],
|
||||
templateUrl: './chart-panel-header.component.html',
|
||||
})
|
||||
export class ChartPanelHeaderComponent implements OnDestroy {
|
||||
|
||||
private alive = true;
|
||||
|
||||
@Output() periodChange = new EventEmitter<string>();
|
||||
|
||||
@Input() type: string = 'week';
|
||||
|
||||
types: string[] = ['week', 'month', 'year'];
|
||||
chartLegend: {iconColor: string; title: string}[];
|
||||
breakpoint: NbMediaBreakpoint = { name: '', width: 0 };
|
||||
breakpoints: any;
|
||||
currentTheme: string;
|
||||
|
||||
constructor(private themeService: NbThemeService,
|
||||
private breakpointService: NbMediaBreakpointsService) {
|
||||
this.themeService.getJsTheme()
|
||||
.pipe(takeWhile(() => this.alive))
|
||||
.subscribe(theme => {
|
||||
const orderProfitLegend = theme.variables.orderProfitLegend;
|
||||
|
||||
this.currentTheme = theme.name;
|
||||
this.setLegendItems(orderProfitLegend);
|
||||
});
|
||||
|
||||
this.breakpoints = this.breakpointService.getBreakpointsMap();
|
||||
this.themeService.onMediaQueryChange()
|
||||
.pipe(takeWhile(() => this.alive))
|
||||
.subscribe(([oldValue, newValue]) => {
|
||||
this.breakpoint = newValue;
|
||||
});
|
||||
}
|
||||
|
||||
setLegendItems(orderProfitLegend) {
|
||||
this.chartLegend = [
|
||||
{
|
||||
iconColor: orderProfitLegend.firstItem,
|
||||
title: 'Payment',
|
||||
},
|
||||
{
|
||||
iconColor: orderProfitLegend.secondItem,
|
||||
title: 'Canceled',
|
||||
},
|
||||
{
|
||||
iconColor: orderProfitLegend.thirdItem,
|
||||
title: 'All orders',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
changePeriod(period: string): void {
|
||||
this.type = period;
|
||||
this.periodChange.emit(period);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.alive = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
@import '../../../../@theme/styles/themes';
|
||||
@import '~bootstrap/scss/mixins/breakpoints';
|
||||
@import '~@nebular/theme/styles/global/bootstrap/breakpoints';
|
||||
|
||||
@include nb-install-component() {
|
||||
.summary-container {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
background-color: nb-theme(chart-panel-summary-background-color);
|
||||
box-shadow: nb-theme(chart-panel-summary-box-shadow);
|
||||
justify-content: space-between;
|
||||
padding: 1.5rem 4rem 1rem;
|
||||
margin-bottom: 1rem;
|
||||
border:
|
||||
nb-theme(chart-panel-summary-border-width)
|
||||
solid
|
||||
nb-theme(chart-panel-summary-border-color);
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 2rem;
|
||||
color: nb-theme(color-fg-heading);
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
.value, .title {
|
||||
font-weight: nb-theme(font-weight-bold);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: nb-theme(font-size-sm);
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: nb-theme(font-size-xlg);
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(is) {
|
||||
.summary-container {
|
||||
padding-left: nb-theme(padding);
|
||||
padding-right: nb-theme(padding);
|
||||
}
|
||||
|
||||
.value {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import { Component, Input } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-chart-panel-summary',
|
||||
styleUrls: ['./chart-panel-summary.component.scss'],
|
||||
template: `
|
||||
<div class="summary-container">
|
||||
<div class="summory" *ngFor="let item of summary">
|
||||
<div class="title">{{ item.title }}</div>
|
||||
<div class="value">{{ item.value }}</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
export class ChartPanelSummaryComponent {
|
||||
@Input() summary: {title: string; value: number}[];
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<nb-card size="large">
|
||||
<nb-tabset fullWidth (changeTab)="changeTab($event)">
|
||||
<nb-tab tabTitle="Orders">
|
||||
<div class="chart-container">
|
||||
<ngx-chart-panel-summary [summary]="chartPanelSummary"></ngx-chart-panel-summary>
|
||||
<ngx-chart-panel-header [type]="period"
|
||||
(periodChange)="setPeriodAndGetChartData($event)">
|
||||
</ngx-chart-panel-header>
|
||||
<ngx-orders-chart #ordersChart [ordersChartData]="ordersChartData"></ngx-orders-chart>
|
||||
</div>
|
||||
</nb-tab>
|
||||
<nb-tab tabTitle="Profit" [lazyLoad]="true">
|
||||
<div class="chart-container">
|
||||
<ngx-chart-panel-summary [summary]="chartPanelSummary"></ngx-chart-panel-summary>
|
||||
<ngx-chart-panel-header [type]="period"
|
||||
(periodChange)="setPeriodAndGetChartData($event)">
|
||||
</ngx-chart-panel-header>
|
||||
<ngx-profit-chart #profitChart [profitChartData]="profitChartData"></ngx-profit-chart>
|
||||
</div>
|
||||
</nb-tab>
|
||||
</nb-tabset>
|
||||
</nb-card>
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
@import '../../../@theme/styles/themes';
|
||||
|
||||
$legend-all-orders-color: #00977e;
|
||||
$legend-payment-color: #6935ca;
|
||||
$legend-canceled-color: #3f4fda;
|
||||
|
||||
@include nb-install-component() {
|
||||
|
||||
/deep/ nb-tabset {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
|
||||
ul {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
nb-tab {
|
||||
flex: 1;
|
||||
padding-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
ngx-chart-panel-header, ngx-profit-chart, ngx-orders-chart {
|
||||
padding: 0 1.25rem;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
import { Component, OnDestroy, ViewChild } from '@angular/core';
|
||||
import { takeWhile } from 'rxjs/operators';
|
||||
|
||||
import { OrdersChartComponent } from './charts/orders-chart.component';
|
||||
import { ProfitChartComponent } from './charts/profit-chart.component';
|
||||
import { OrdersChart } from '../../../@core/data/orders-chart.service';
|
||||
import { ProfitChart } from '../../../@core/data/profit-chart.service';
|
||||
import { OrdersProfitChartService, OrderProfitChartSummary } from '../../../@core/data/orders-profit-chart.service';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-ecommerce-charts',
|
||||
styleUrls: ['./charts-panel.component.scss'],
|
||||
templateUrl: './charts-panel.component.html',
|
||||
})
|
||||
export class ECommerceChartsPanelComponent implements OnDestroy {
|
||||
|
||||
private alive = true;
|
||||
|
||||
chartPanelSummary: OrderProfitChartSummary[];
|
||||
period: string = 'week';
|
||||
ordersChartData: OrdersChart;
|
||||
profitChartData: ProfitChart;
|
||||
|
||||
@ViewChild('ordersChart') ordersChart: OrdersChartComponent;
|
||||
@ViewChild('profitChart') profitChart: ProfitChartComponent;
|
||||
|
||||
constructor(private ordersProfitChartService: OrdersProfitChartService) {
|
||||
this.ordersProfitChartService.getOrderProfitChartSummary()
|
||||
.pipe(takeWhile(() => this.alive))
|
||||
.subscribe((summary) => {
|
||||
this.chartPanelSummary = summary;
|
||||
});
|
||||
|
||||
this.getOrdersChartData(this.period);
|
||||
this.getProfitChartData(this.period);
|
||||
}
|
||||
|
||||
setPeriodAndGetChartData(value: string): void {
|
||||
if (this.period !== value) {
|
||||
this.period = value;
|
||||
}
|
||||
|
||||
this.getOrdersChartData(value);
|
||||
this.getProfitChartData(value);
|
||||
}
|
||||
|
||||
changeTab(selectedTab) {
|
||||
if (selectedTab.tabTitle === 'Profit') {
|
||||
this.profitChart.resizeChart();
|
||||
} else {
|
||||
this.ordersChart.resizeChart();
|
||||
}
|
||||
}
|
||||
|
||||
getOrdersChartData(period: string) {
|
||||
this.ordersProfitChartService.getOrdersChartData(period)
|
||||
.pipe(takeWhile(() => this.alive))
|
||||
.subscribe(ordersChartData => {
|
||||
this.ordersChartData = ordersChartData;
|
||||
});
|
||||
}
|
||||
|
||||
getProfitChartData(period: string) {
|
||||
this.ordersProfitChartService.getProfitChartData(period)
|
||||
.pipe(takeWhile(() => this.alive))
|
||||
.subscribe(profitChartData => {
|
||||
this.profitChartData = profitChartData;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.alive = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
@import '../../../../@theme/styles/themes';
|
||||
|
||||
@include nb-install-component() {
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
.echart {
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,296 @@
|
|||
import { AfterViewInit, Component, Input, OnChanges, OnDestroy } from '@angular/core';
|
||||
import { NbThemeService } from '@nebular/theme';
|
||||
import { delay, takeWhile } from 'rxjs/operators';
|
||||
|
||||
import { OrdersChart } from '../../../../@core/data/orders-chart.service';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-orders-chart',
|
||||
styleUrls: ['./charts-common.component.scss'],
|
||||
template: `
|
||||
<div echarts [options]="option" class="echart" (chartInit)="onChartInit($event)"></div>
|
||||
`,
|
||||
})
|
||||
export class OrdersChartComponent implements AfterViewInit, OnDestroy, OnChanges {
|
||||
|
||||
@Input()
|
||||
ordersChartData: OrdersChart;
|
||||
|
||||
private alive = true;
|
||||
|
||||
echartsIntance: any;
|
||||
option: any;
|
||||
|
||||
ngOnChanges(): void {
|
||||
if (this.option) {
|
||||
this.updateOrdersChartOptions(this.ordersChartData);
|
||||
}
|
||||
}
|
||||
|
||||
constructor(private theme: NbThemeService) {
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this.theme.getJsTheme()
|
||||
.pipe(
|
||||
takeWhile(() => this.alive),
|
||||
delay(1),
|
||||
)
|
||||
.subscribe(config => {
|
||||
const eTheme: any = config.variables.orders;
|
||||
|
||||
this.setOptions(eTheme);
|
||||
this.updateOrdersChartOptions(this.ordersChartData);
|
||||
});
|
||||
}
|
||||
|
||||
setOptions(eTheme) {
|
||||
this.option = {
|
||||
grid: {
|
||||
left: 40,
|
||||
top: 20,
|
||||
right: 0,
|
||||
bottom: 40,
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
axisPointer: {
|
||||
type: 'line',
|
||||
lineStyle: {
|
||||
color: eTheme.tooltipLineColor,
|
||||
width: eTheme.tooltipLineWidth,
|
||||
},
|
||||
},
|
||||
textStyle: {
|
||||
color: eTheme.tooltipTextColor,
|
||||
fontSize: eTheme.tooltipFontSize,
|
||||
fontWeight: eTheme.tooltipFontWeight,
|
||||
},
|
||||
position: 'top',
|
||||
backgroundColor: eTheme.tooltipBg,
|
||||
borderColor: eTheme.tooltipBorderColor,
|
||||
borderWidth: 3,
|
||||
formatter: (params) => {
|
||||
return Math.round(parseInt(params.value, 10));
|
||||
},
|
||||
extraCssText: eTheme.tooltipExtraCss,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
offset: 5,
|
||||
data: [],
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
axisLabel: {
|
||||
color: eTheme.axisTextColor,
|
||||
fontSize: eTheme.axisFontSize,
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: eTheme.axisLineColor,
|
||||
width: '2',
|
||||
},
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
boundaryGap: false,
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: eTheme.axisLineColor,
|
||||
width: '1',
|
||||
},
|
||||
},
|
||||
axisLabel: {
|
||||
color: eTheme.axisTextColor,
|
||||
fontSize: eTheme.axisFontSize,
|
||||
},
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
splitLine: {
|
||||
|
||||
lineStyle: {
|
||||
color: eTheme.yAxisSplitLine,
|
||||
width: '1',
|
||||
},
|
||||
},
|
||||
},
|
||||
series: [
|
||||
this.getFirstLine(eTheme),
|
||||
this.getSecondLine(eTheme),
|
||||
this.getThirdLine(eTheme),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
getFirstLine(eTheme) {
|
||||
return {
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbolSize: 20,
|
||||
itemStyle: {
|
||||
normal: {
|
||||
opacity: 0,
|
||||
},
|
||||
emphasis: {
|
||||
opacity: 0,
|
||||
},
|
||||
},
|
||||
lineStyle: {
|
||||
normal: {
|
||||
width: 0,
|
||||
},
|
||||
},
|
||||
areaStyle: {
|
||||
normal: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
|
||||
offset: 0,
|
||||
color: eTheme.firstAreaGradFrom,
|
||||
}, {
|
||||
offset: 1,
|
||||
color: eTheme.firstAreaGradTo,
|
||||
}]),
|
||||
opacity: 1,
|
||||
},
|
||||
},
|
||||
data: [],
|
||||
};
|
||||
}
|
||||
|
||||
getSecondLine(eTheme) {
|
||||
return {
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbolSize: 20,
|
||||
itemStyle: {
|
||||
normal: {
|
||||
opacity: 0,
|
||||
},
|
||||
emphasis: {
|
||||
color: '#ffffff',
|
||||
borderColor: eTheme.itemBorderColor,
|
||||
borderWidth: 2,
|
||||
opacity: 1,
|
||||
},
|
||||
},
|
||||
lineStyle: {
|
||||
normal: {
|
||||
width: eTheme.lineWidth,
|
||||
type: eTheme.lineStyle,
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
|
||||
offset: 0,
|
||||
color: eTheme.secondLineGradFrom,
|
||||
}, {
|
||||
offset: 1,
|
||||
color: eTheme.secondLineGradTo,
|
||||
}]),
|
||||
},
|
||||
},
|
||||
areaStyle: {
|
||||
normal: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
|
||||
offset: 0,
|
||||
color: eTheme.secondAreaGradFrom,
|
||||
}, {
|
||||
offset: 1,
|
||||
color: eTheme.secondAreaGradTo,
|
||||
}]),
|
||||
},
|
||||
},
|
||||
data: [],
|
||||
};
|
||||
}
|
||||
|
||||
getThirdLine(eTheme) {
|
||||
return {
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbolSize: 20,
|
||||
itemStyle: {
|
||||
normal: {
|
||||
opacity: 0,
|
||||
},
|
||||
emphasis: {
|
||||
color: '#ffffff',
|
||||
borderColor: eTheme.itemBorderColor,
|
||||
borderWidth: 2,
|
||||
opacity: 1,
|
||||
},
|
||||
},
|
||||
lineStyle: {
|
||||
normal: {
|
||||
width: eTheme.lineWidth,
|
||||
type: eTheme.lineStyle,
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
|
||||
offset: 0,
|
||||
color: eTheme.thirdLineGradFrom,
|
||||
}, {
|
||||
offset: 1,
|
||||
color: eTheme.thirdLineGradTo,
|
||||
}]),
|
||||
},
|
||||
},
|
||||
areaStyle: {
|
||||
normal: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
|
||||
offset: 0,
|
||||
color: eTheme.thirdAreaGradFrom,
|
||||
}, {
|
||||
offset: 1,
|
||||
color: eTheme.thirdAreaGradTo,
|
||||
}]),
|
||||
},
|
||||
},
|
||||
data: [],
|
||||
};
|
||||
}
|
||||
|
||||
updateOrdersChartOptions(ordersChartData: OrdersChart) {
|
||||
const options = this.option;
|
||||
const series = this.getNewSeries(options.series, ordersChartData.linesData);
|
||||
const xAxis = this.getNewXAxis(options.xAxis, ordersChartData.chartLabel);
|
||||
|
||||
this.option = {
|
||||
...options,
|
||||
xAxis,
|
||||
series,
|
||||
};
|
||||
}
|
||||
|
||||
getNewSeries(series, linesData: number[][]) {
|
||||
return series.map((line, index) => {
|
||||
return {
|
||||
...line,
|
||||
data: linesData[index],
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
getNewXAxis(xAxis, chartLabel: string[]) {
|
||||
return {
|
||||
...xAxis,
|
||||
data: chartLabel,
|
||||
};
|
||||
}
|
||||
|
||||
onChartInit(echarts) {
|
||||
this.echartsIntance = echarts;
|
||||
}
|
||||
|
||||
resizeChart() {
|
||||
if (this.echartsIntance) {
|
||||
// Fix recalculation chart size
|
||||
// TODO: investigate more deeply
|
||||
setTimeout(() => {
|
||||
this.echartsIntance.resize();
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.alive = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,193 @@
|
|||
import { AfterViewInit, Component, Input, OnChanges, OnDestroy } from '@angular/core';
|
||||
import { NbThemeService } from '@nebular/theme';
|
||||
import { takeWhile } from 'rxjs/operators';
|
||||
|
||||
import { ProfitChart } from '../../../../@core/data/profit-chart.service';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-profit-chart',
|
||||
styleUrls: ['./charts-common.component.scss'],
|
||||
template: `
|
||||
<div echarts [options]="options" class="echart" (chartInit)="onChartInit($event)"></div>
|
||||
`,
|
||||
})
|
||||
export class ProfitChartComponent implements AfterViewInit, OnDestroy, OnChanges {
|
||||
|
||||
@Input()
|
||||
profitChartData: ProfitChart;
|
||||
|
||||
private alive = true;
|
||||
|
||||
echartsIntance: any;
|
||||
options: any = {};
|
||||
|
||||
constructor(private theme: NbThemeService) {
|
||||
}
|
||||
|
||||
ngOnChanges(): void {
|
||||
if (this.echartsIntance) {
|
||||
this.updateProfitChartOptions(this.profitChartData);
|
||||
}
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.theme.getJsTheme()
|
||||
.pipe(takeWhile(() => this.alive))
|
||||
.subscribe(config => {
|
||||
const eTheme: any = config.variables.profit;
|
||||
|
||||
this.setOptions(eTheme);
|
||||
});
|
||||
}
|
||||
|
||||
setOptions(eTheme) {
|
||||
this.options = {
|
||||
backgroundColor: eTheme.bg,
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
shadowStyle: {
|
||||
color: 'rgba(0, 0, 0, 0.3)',
|
||||
},
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
data: this.profitChartData.chartLabel,
|
||||
axisTick: {
|
||||
alignWithLabel: true,
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: eTheme.axisLineColor,
|
||||
},
|
||||
},
|
||||
axisLabel: {
|
||||
color: eTheme.axisTextColor,
|
||||
fontSize: eTheme.axisFontSize,
|
||||
},
|
||||
},
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: eTheme.axisLineColor,
|
||||
},
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: eTheme.splitLineColor,
|
||||
},
|
||||
},
|
||||
axisLabel: {
|
||||
color: eTheme.axisTextColor,
|
||||
fontSize: eTheme.axisFontSize,
|
||||
},
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: 'Canceled',
|
||||
type: 'bar',
|
||||
barGap: 0,
|
||||
barWidth: '20%',
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
|
||||
offset: 0,
|
||||
color: eTheme.firstLineGradFrom,
|
||||
}, {
|
||||
offset: 1,
|
||||
color: eTheme.firstLineGradTo,
|
||||
}]),
|
||||
},
|
||||
},
|
||||
data: this.profitChartData.data[0],
|
||||
},
|
||||
{
|
||||
name: 'Payment',
|
||||
type: 'bar',
|
||||
barWidth: '20%',
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
|
||||
offset: 0,
|
||||
color: eTheme.secondLineGradFrom,
|
||||
}, {
|
||||
offset: 1,
|
||||
color: eTheme.secondLineGradTo,
|
||||
}]),
|
||||
},
|
||||
},
|
||||
data: this.profitChartData.data[1],
|
||||
},
|
||||
{
|
||||
name: 'All orders',
|
||||
type: 'bar',
|
||||
barWidth: '20%',
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
|
||||
offset: 0,
|
||||
color: eTheme.thirdLineGradFrom,
|
||||
}, {
|
||||
offset: 1,
|
||||
color: eTheme.thirdLineGradTo,
|
||||
}]),
|
||||
},
|
||||
},
|
||||
data: this.profitChartData.data[2],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
updateProfitChartOptions(profitChartData: ProfitChart) {
|
||||
const options = this.options;
|
||||
const series = this.getNewSeries(options.series, profitChartData.data);
|
||||
|
||||
this.echartsIntance.setOption({
|
||||
series: series,
|
||||
xAxis: {
|
||||
data: this.profitChartData.chartLabel,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getNewSeries(series, data: number[][]) {
|
||||
return series.map((line, index) => {
|
||||
return {
|
||||
...line,
|
||||
data: data[index],
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
onChartInit(echarts) {
|
||||
this.echartsIntance = echarts;
|
||||
}
|
||||
|
||||
resizeChart() {
|
||||
if (this.echartsIntance) {
|
||||
// Fix recalculation chart size
|
||||
// TODO: investigate more deeply
|
||||
setTimeout(() => {
|
||||
this.echartsIntance.resize();
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.alive = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
@import '../../../../@theme/styles/themes';
|
||||
@import '~bootstrap/scss/mixins/breakpoints';
|
||||
@import '~@nebular/theme/styles/global/bootstrap/breakpoints';
|
||||
|
||||
@include nb-install-component() {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
padding: nb-theme(card-padding);
|
||||
border-left:
|
||||
nb-theme(card-header-border-width)
|
||||
nb-theme(card-header-border-type)
|
||||
nb-theme(card-header-border-color);
|
||||
|
||||
.header {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-family: nb-theme(card-header-font-family);
|
||||
color: nb-theme(color-fg);
|
||||
}
|
||||
|
||||
.echart {
|
||||
height: 85%;
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
height: 50%;
|
||||
border-top:
|
||||
nb-theme(card-border-width)
|
||||
nb-theme(card-header-border-type)
|
||||
nb-theme(card-header-border-color);
|
||||
|
||||
.echart {
|
||||
height: 75%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,164 @@
|
|||
import { AfterViewInit, Component, Input, OnChanges, OnDestroy, SimpleChanges } from '@angular/core';
|
||||
import { NbThemeService } from '@nebular/theme';
|
||||
import { takeWhile } from 'rxjs/operators';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-country-orders-chart',
|
||||
styleUrls: ['./country-orders-chart.component.scss'],
|
||||
template: `
|
||||
<div class="header">
|
||||
<span class="title">Selected Country</span>
|
||||
<h2>{{countryName}}</h2>
|
||||
</div>
|
||||
<div echarts [options]="option" class="echart" (chartInit)="onChartInit($event)"></div>
|
||||
`,
|
||||
})
|
||||
export class CountryOrdersChartComponent implements AfterViewInit, OnDestroy, OnChanges {
|
||||
|
||||
@Input() countryName: string;
|
||||
@Input() data: number[];
|
||||
@Input() maxValue: number;
|
||||
@Input() labels: string[];
|
||||
|
||||
private alive = true;
|
||||
|
||||
option: any = {};
|
||||
echartsInstance;
|
||||
|
||||
constructor(private theme: NbThemeService) {
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes.data && !changes.data.isFirstChange()) {
|
||||
this.echartsInstance.setOption({
|
||||
series: [
|
||||
{
|
||||
data: this.data.map(v => this.maxValue),
|
||||
},
|
||||
{
|
||||
data: this.data,
|
||||
},
|
||||
{
|
||||
data: this.data,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.theme.getJsTheme()
|
||||
.pipe(takeWhile(() => this.alive))
|
||||
.subscribe(config => {
|
||||
const countriesTheme: any = config.variables.countryOrders;
|
||||
|
||||
this.option = Object.assign({}, {
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '3%',
|
||||
bottom: '3%',
|
||||
top: '3%',
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
axisLabel: {
|
||||
color: countriesTheme.chartAxisTextColor,
|
||||
fontSize: countriesTheme.chartAxisFontSize,
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: countriesTheme.chartAxisLineColor,
|
||||
width: '2',
|
||||
},
|
||||
},
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: countriesTheme.chartAxisSplitLine,
|
||||
width: '1',
|
||||
},
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
data: this.labels,
|
||||
axisLabel: {
|
||||
color: countriesTheme.chartAxisTextColor,
|
||||
fontSize: countriesTheme.chartAxisFontSize,
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: countriesTheme.chartAxisLineColor,
|
||||
width: '2',
|
||||
},
|
||||
},
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{ // For shadow
|
||||
type: 'bar',
|
||||
data: this.data.map(v => this.maxValue),
|
||||
cursor: 'default',
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: countriesTheme.chartInnerLineColor,
|
||||
},
|
||||
opacity: 1,
|
||||
},
|
||||
barWidth: '40%',
|
||||
barGap: '-100%',
|
||||
barCategoryGap: '30%',
|
||||
animation: false,
|
||||
z: 1,
|
||||
},
|
||||
{ // For bottom line
|
||||
type: 'bar',
|
||||
data: this.data,
|
||||
cursor: 'default',
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: countriesTheme.chartLineBottomShadowColor,
|
||||
},
|
||||
opacity: 1,
|
||||
},
|
||||
barWidth: '40%',
|
||||
barGap: '-100%',
|
||||
barCategoryGap: '30%',
|
||||
z: 2,
|
||||
},
|
||||
{
|
||||
type: 'bar',
|
||||
barWidth: '35%',
|
||||
data: this.data,
|
||||
cursor: 'default',
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: new echarts.graphic.LinearGradient(1, 0, 0, 0, [{
|
||||
offset: 0,
|
||||
color: countriesTheme.chartGradientFrom,
|
||||
}, {
|
||||
offset: 1,
|
||||
color: countriesTheme.chartGradientTo,
|
||||
}]),
|
||||
},
|
||||
},
|
||||
z: 3,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
onChartInit(ec) {
|
||||
this.echartsInstance = ec;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.alive = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
@import '../../../@theme/styles/themes';
|
||||
@import '~bootstrap/scss/mixins/breakpoints';
|
||||
@import '~@nebular/theme/styles/global/bootstrap/breakpoints';
|
||||
|
||||
@include nb-install-component() {
|
||||
|
||||
nb-card-body {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
nb-card-body {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
import { Component, OnDestroy } from '@angular/core';
|
||||
import { NbMediaBreakpoint, NbMediaBreakpointsService, NbThemeService } from '@nebular/theme';
|
||||
import { takeWhile } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-country-orders',
|
||||
styleUrls: ['./country-orders.component.scss'],
|
||||
template: `
|
||||
<nb-card [size]="breakpoint.width >= breakpoints.md ? 'medium' : 'xxlarge'">
|
||||
<nb-card-header>Country Orders Statistics</nb-card-header>
|
||||
<nb-card-body>
|
||||
<ngx-country-orders-map (select)="selectCountryById($event)"
|
||||
countryId="USA">
|
||||
</ngx-country-orders-map>
|
||||
<ngx-country-orders-chart [countryName]="countryName"
|
||||
[data]="countryData"
|
||||
[labels]="countriesCategories"
|
||||
maxValue="20">
|
||||
</ngx-country-orders-chart>
|
||||
</nb-card-body>
|
||||
</nb-card>
|
||||
`,
|
||||
})
|
||||
export class CountryOrdersComponent implements OnDestroy {
|
||||
|
||||
private alive = true;
|
||||
|
||||
private getRandomData(nPoints: number): number[] {
|
||||
return Array.from(Array(nPoints)).map(() => {
|
||||
return Math.round(Math.random() * 20);
|
||||
});
|
||||
}
|
||||
|
||||
countryName = '';
|
||||
countryData = [];
|
||||
countriesCategories = ['Sofas', 'Furniture', 'Lighting', 'Tables', 'Textiles'];
|
||||
breakpoint: NbMediaBreakpoint = { name: '', width: 0 };
|
||||
breakpoints: any;
|
||||
|
||||
constructor(private themeService: NbThemeService,
|
||||
private breakpointService: NbMediaBreakpointsService) {
|
||||
this.breakpoints = this.breakpointService.getBreakpointsMap();
|
||||
this.themeService.onMediaQueryChange()
|
||||
.pipe(takeWhile(() => this.alive))
|
||||
.subscribe(([oldValue, newValue]) => {
|
||||
this.breakpoint = newValue;
|
||||
});
|
||||
}
|
||||
|
||||
selectCountryById(countryName: string) {
|
||||
const nPoint = this.countriesCategories.length;
|
||||
|
||||
this.countryName = countryName;
|
||||
this.countryData = this.getRandomData(nPoint);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.alive = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
@import '../../../../@theme/styles/themes';
|
||||
@import '~bootstrap/scss/mixins/breakpoints';
|
||||
@import '~@nebular/theme/styles/global/bootstrap/breakpoints';
|
||||
|
||||
@include nb-install-component() {
|
||||
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: 60%;
|
||||
|
||||
.leaflet-container {
|
||||
height: 100%;
|
||||
background-color: nb-theme(layout-bg);
|
||||
|
||||
@include nb-for-theme(default) {
|
||||
background-color: nb-theme(color-white);
|
||||
}
|
||||
|
||||
@include nb-for-theme(corporate) {
|
||||
background-color: nb-theme(color-white);
|
||||
}
|
||||
}
|
||||
|
||||
/deep/ .leaflet-bar {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/deep/ .leaflet-control-zoom {
|
||||
border: none;
|
||||
|
||||
a {
|
||||
background-color: nb-theme(color-success);
|
||||
color: nb-theme(color-white);
|
||||
border-bottom: none;
|
||||
height: 2.5rem;
|
||||
|
||||
@include nb-for-theme(cosmic) {
|
||||
background-color: nb-theme(color-primary);
|
||||
}
|
||||
|
||||
@include nb-for-theme(corporate) {
|
||||
background-color: nb-theme(color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.leaflet-control-zoom-in {
|
||||
border-top-left-radius: nb-theme(btn-border-radius);
|
||||
border-top-right-radius: nb-theme(btn-border-radius);
|
||||
}
|
||||
|
||||
.leaflet-control-zoom-out {
|
||||
margin-top: 1px;
|
||||
border-bottom-left-radius: nb-theme(btn-border-radius);
|
||||
border-bottom-right-radius: nb-theme(btn-border-radius);
|
||||
}
|
||||
}
|
||||
|
||||
/deep/ .leaflet-control-attribution {
|
||||
background: transparent;
|
||||
|
||||
a {
|
||||
color: nb-theme(color-fg);
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
width: 100%;
|
||||
height: 50%;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
import { Component, EventEmitter, Input, OnDestroy, Output } from '@angular/core';
|
||||
|
||||
import * as L from 'leaflet';
|
||||
|
||||
import { CountryOrdersMapService } from './country-orders-map.service';
|
||||
import { NbThemeService } from '@nebular/theme';
|
||||
import { combineLatest } from 'rxjs';
|
||||
import { takeWhile } from 'rxjs/operators';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-country-orders-map',
|
||||
styleUrls: ['./country-orders-map.component.scss'],
|
||||
template: `
|
||||
<div leaflet [leafletOptions]="options" [leafletLayers]="layers" (leafletMapReady)="mapReady($event)"></div>
|
||||
`,
|
||||
})
|
||||
export class CountryOrdersMapComponent implements OnDestroy {
|
||||
|
||||
@Input() countryId: string;
|
||||
|
||||
@Output() select: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
layers = [];
|
||||
currentTheme: any;
|
||||
alive = true;
|
||||
selectedCountry;
|
||||
|
||||
options = {
|
||||
zoom: 2,
|
||||
minZoom: 2,
|
||||
maxZoom: 6,
|
||||
zoomControl: false,
|
||||
center: L.latLng({lat: 38.991709, lng: -76.886109}),
|
||||
maxBounds: new L.LatLngBounds(
|
||||
new L.LatLng(-89.98155760646617, -180),
|
||||
new L.LatLng(89.99346179538875, 180),
|
||||
),
|
||||
maxBoundsViscosity: 1.0,
|
||||
};
|
||||
|
||||
constructor(private ecMapService: CountryOrdersMapService,
|
||||
private theme: NbThemeService) {
|
||||
|
||||
combineLatest([
|
||||
this.ecMapService.getCords(),
|
||||
this.theme.getJsTheme(),
|
||||
])
|
||||
.pipe(takeWhile(() => this.alive))
|
||||
.subscribe(([cords, config]: [any, any]) => {
|
||||
this.currentTheme = config.variables.countryOrders;
|
||||
this.layers = [this.createGeoJsonLayer(cords)];
|
||||
this.selectFeature(this.findFeatureLayerByCountryId(this.countryId));
|
||||
});
|
||||
}
|
||||
|
||||
mapReady(map: L.Map) {
|
||||
map.addControl(L.control.zoom({position: 'bottomright'}));
|
||||
|
||||
// fix the map fully displaying, existing leaflet bag
|
||||
setTimeout(() => {
|
||||
map.invalidateSize();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
private createGeoJsonLayer(cords) {
|
||||
return L.geoJSON(
|
||||
cords as any,
|
||||
{
|
||||
style: () => ({
|
||||
weight: this.currentTheme.countryBorderWidth,
|
||||
fillColor: this.currentTheme.countryFillColor,
|
||||
fillOpacity: 1,
|
||||
color: this.currentTheme.countryBorderColor,
|
||||
opacity: 1,
|
||||
}),
|
||||
onEachFeature: (f, l) => {
|
||||
this.onEachFeature(f, l);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private onEachFeature(feature, layer) {
|
||||
layer.on({
|
||||
mouseover: (e) => this.highlightFeature(e.target),
|
||||
mouseout: (e) => this.moveout(e.target),
|
||||
click: (e) => this.selectFeature(e.target),
|
||||
});
|
||||
}
|
||||
|
||||
private highlightFeature(featureLayer) {
|
||||
if (featureLayer) {
|
||||
featureLayer.setStyle({
|
||||
weight: this.currentTheme.hoveredCountryBorderWidth,
|
||||
fillColor: this.currentTheme.hoveredCountryFillColor,
|
||||
color: this.currentTheme.hoveredCountryBorderColor,
|
||||
});
|
||||
|
||||
if (!L.Browser.ie && !L.Browser.opera12 && !L.Browser.edge) {
|
||||
featureLayer.bringToFront();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private moveout(featureLayer) {
|
||||
if (featureLayer !== this.selectedCountry) {
|
||||
this.resetHighlight(featureLayer);
|
||||
|
||||
// When countries have common border we should highlight selected country once again
|
||||
this.highlightFeature(this.selectedCountry);
|
||||
}
|
||||
}
|
||||
|
||||
private resetHighlight(featureLayer) {
|
||||
if (featureLayer) {
|
||||
const geoJsonLayer = this.layers[0];
|
||||
|
||||
geoJsonLayer.resetStyle(featureLayer);
|
||||
}
|
||||
}
|
||||
|
||||
private selectFeature(featureLayer) {
|
||||
if (featureLayer !== this.selectedCountry) {
|
||||
this.resetHighlight(this.selectedCountry);
|
||||
this.highlightFeature(featureLayer);
|
||||
this.selectedCountry = featureLayer;
|
||||
this.select.emit(featureLayer.feature.properties.name);
|
||||
}
|
||||
}
|
||||
|
||||
private findFeatureLayerByCountryId(id) {
|
||||
const layers = this.layers[0].getLayers();
|
||||
const featureLayer = layers.find(item => {
|
||||
return item.feature.id === id;
|
||||
});
|
||||
|
||||
return featureLayer ? featureLayer : null;
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.alive = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class CountryOrdersMapService {
|
||||
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
getCords(): Observable<any> {
|
||||
return this.http.get('./assets/leaflet-countries/countries.geo.json');
|
||||
}
|
||||
|
||||
}
|
||||
38
src/app/pages/e-commerce/e-commerce.component.html
Normal file
38
src/app/pages/e-commerce/e-commerce.component.html
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<div class="row">
|
||||
<div class="col-xxl-5">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<ngx-profit-card></ngx-profit-card>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<ngx-earning-card></ngx-earning-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ngx-traffic-reveal-card></ngx-traffic-reveal-card>
|
||||
</div>
|
||||
|
||||
<div class="col-xxl-7">
|
||||
<ngx-ecommerce-charts></ngx-ecommerce-charts>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xxl-9">
|
||||
<ngx-country-orders></ngx-country-orders>
|
||||
</div>
|
||||
|
||||
<div class="col-xxl-3">
|
||||
<ngx-progress-section></ngx-progress-section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xxl-9">
|
||||
<ngx-ecommerce-visitors-analytics></ngx-ecommerce-visitors-analytics>
|
||||
</div>
|
||||
|
||||
<div class="col-xxl-3">
|
||||
<ngx-user-activity></ngx-user-activity>
|
||||
</div>
|
||||
</div>
|
||||
8
src/app/pages/e-commerce/e-commerce.component.ts
Normal file
8
src/app/pages/e-commerce/e-commerce.component.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-ecommerce',
|
||||
templateUrl: './e-commerce.component.html',
|
||||
})
|
||||
export class ECommerceComponent {
|
||||
}
|
||||
95
src/app/pages/e-commerce/e-commerce.module.ts
Normal file
95
src/app/pages/e-commerce/e-commerce.module.ts
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { NgxEchartsModule } from 'ngx-echarts';
|
||||
import { NgxChartsModule } from '@swimlane/ngx-charts';
|
||||
|
||||
import { ThemeModule } from '../../@theme/theme.module';
|
||||
import { ECommerceComponent } from './e-commerce.component';
|
||||
import { ProfitCardComponent } from './profit-card/profit-card.component';
|
||||
import { ECommerceChartsPanelComponent } from './charts-panel/charts-panel.component';
|
||||
import { OrdersChartComponent } from './charts-panel/charts/orders-chart.component';
|
||||
import { ProfitChartComponent } from './charts-panel/charts/profit-chart.component';
|
||||
import { ChartPanelHeaderComponent } from './charts-panel/chart-panel-header/chart-panel-header.component';
|
||||
import { ChartPanelSummaryComponent } from './charts-panel/chart-panel-summary/chart-panel-summary.component';
|
||||
import { ChartModule } from 'angular2-chartjs';
|
||||
import { StatsCardBackComponent } from './profit-card/back-side/stats-card-back.component';
|
||||
import { StatsAreaChartComponent } from './profit-card/back-side/stats-area-chart.component';
|
||||
import { StatsBarAnimationChartComponent } from './profit-card/front-side/stats-bar-animation-chart.component';
|
||||
import { StatsCardFrontComponent } from './profit-card/front-side/stats-card-front.component';
|
||||
import { TrafficRevealCardComponent } from './traffic-reveal-card/traffic-reveal-card.component';
|
||||
import { TrafficBarComponent } from './traffic-reveal-card/front-side/traffic-bar/traffic-bar.component';
|
||||
import { TrafficFrontCardComponent } from './traffic-reveal-card/front-side/traffic-front-card.component';
|
||||
import { TrafficCardsHeaderComponent } from './traffic-reveal-card/traffic-cards-header/traffic-cards-header.component';
|
||||
import { TrafficBackCardComponent } from './traffic-reveal-card/back-side/traffic-back-card.component';
|
||||
import { TrafficBarChartComponent } from './traffic-reveal-card/back-side/traffic-bar-chart.component';
|
||||
import {
|
||||
ECommerceVisitorsAnalyticsComponent,
|
||||
} from './visitors-analytics/visitors-analytics.component';
|
||||
import {
|
||||
ECommerceVisitorsAnalyticsChartComponent,
|
||||
} from './visitors-analytics/visitors-analytics-chart/visitors-analytics-chart.component';
|
||||
import {
|
||||
ECommerceVisitorsStatisticsComponent,
|
||||
} from './visitors-analytics/visitors-statistics/visitors-statistics.component';
|
||||
import { ECommerceLegendChartComponent } from './legend-chart/legend-chart.component';
|
||||
import { ECommerceUserActivityComponent } from './user-activity/user-activity.component';
|
||||
import { ECommerceProgressSectionComponent } from './progress-section/progress-section.component';
|
||||
import { SlideOutComponent } from './slide-out/slide-out.component';
|
||||
|
||||
import { CountryOrdersComponent } from './country-orders/country-orders.component';
|
||||
import { CountryOrdersMapComponent } from './country-orders/map/country-orders-map.component';
|
||||
import { CountryOrdersMapService } from './country-orders/map/country-orders-map.service';
|
||||
import { LeafletModule } from '@asymmetrik/ngx-leaflet';
|
||||
import { CountryOrdersChartComponent } from './country-orders/chart/country-orders-chart.component';
|
||||
import { EarningCardComponent } from './earning-card/earning-card.component';
|
||||
import { EarningCardBackComponent } from './earning-card/back-side/earning-card-back.component';
|
||||
import { EarningPieChartComponent } from './earning-card/back-side/earning-pie-chart.component';
|
||||
import { EarningCardFrontComponent } from './earning-card/front-side/earning-card-front.component';
|
||||
import { EarningLiveUpdateChartComponent } from './earning-card/front-side/earning-live-update-chart.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
ThemeModule,
|
||||
ChartModule,
|
||||
NgxEchartsModule,
|
||||
NgxChartsModule,
|
||||
LeafletModule,
|
||||
],
|
||||
declarations: [
|
||||
ECommerceComponent,
|
||||
StatsCardFrontComponent,
|
||||
StatsAreaChartComponent,
|
||||
StatsBarAnimationChartComponent,
|
||||
ProfitCardComponent,
|
||||
ECommerceChartsPanelComponent,
|
||||
ChartPanelHeaderComponent,
|
||||
ChartPanelSummaryComponent,
|
||||
OrdersChartComponent,
|
||||
ProfitChartComponent,
|
||||
StatsCardBackComponent,
|
||||
TrafficRevealCardComponent,
|
||||
TrafficBarChartComponent,
|
||||
TrafficFrontCardComponent,
|
||||
TrafficBackCardComponent,
|
||||
TrafficBarComponent,
|
||||
TrafficCardsHeaderComponent,
|
||||
CountryOrdersComponent,
|
||||
CountryOrdersMapComponent,
|
||||
CountryOrdersChartComponent,
|
||||
ECommerceVisitorsAnalyticsComponent,
|
||||
ECommerceVisitorsAnalyticsChartComponent,
|
||||
ECommerceVisitorsStatisticsComponent,
|
||||
ECommerceLegendChartComponent,
|
||||
ECommerceUserActivityComponent,
|
||||
ECommerceProgressSectionComponent,
|
||||
SlideOutComponent,
|
||||
EarningCardComponent,
|
||||
EarningCardFrontComponent,
|
||||
EarningCardBackComponent,
|
||||
EarningPieChartComponent,
|
||||
EarningLiveUpdateChartComponent,
|
||||
],
|
||||
providers: [
|
||||
CountryOrdersMapService,
|
||||
],
|
||||
})
|
||||
export class ECommerceModule { }
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<nb-card-header>
|
||||
Earnings
|
||||
</nb-card-header>
|
||||
<nb-card-body>
|
||||
<div class="chart-info">
|
||||
<div class="title" [style.color]="color">{{ name }}</div>
|
||||
<div class="time-period">Last week</div>
|
||||
<div class="value">{{ value }}%</div>
|
||||
</div>
|
||||
<ngx-earning-pie-chart [values]="earningPieChartData"
|
||||
(selectPie)="changeChartInfo($event)"
|
||||
[defaultSelectedCurrency]="defaultSelectedCurrency">
|
||||
</ngx-earning-pie-chart>
|
||||
</nb-card-body>
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
@import '../../../../@theme/styles/themes';
|
||||
@import '~bootstrap/scss/mixins/breakpoints';
|
||||
@import '~@nebular/theme/styles/global/bootstrap/breakpoints';
|
||||
|
||||
@include nb-install-component() {
|
||||
nb-card-header {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
ngx-earning-pie-chart, .chart-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.chart-info {
|
||||
padding-top: 0.7rem;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: nb-theme(font-size-xlg);
|
||||
}
|
||||
|
||||
.time-period {
|
||||
margin-top: 1.5rem;
|
||||
color: nb-theme(color-fg);
|
||||
}
|
||||
|
||||
.value {
|
||||
margin-top: 0.2rem;
|
||||
font-size: 1.5rem;
|
||||
font-weight: nb-theme(font-weight-bold);
|
||||
color: nb-theme(color-fg-heading);
|
||||
}
|
||||
|
||||
.echart {
|
||||
position: absolute;
|
||||
width: calc(50% - #{nb-theme(card-padding)});
|
||||
height: calc(100% - 2 * #{nb-theme(card-padding)});
|
||||
}
|
||||
|
||||
@include media-breakpoint-between(xl, xl) {
|
||||
ngx-earning-pie-chart {
|
||||
flex: 2;
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-between(sm, sm) {
|
||||
ngx-earning-pie-chart {
|
||||
flex: 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
import { Component, OnDestroy } from '@angular/core';
|
||||
import { EarningService, PieChart } from '../../../../@core/data/earning.service';
|
||||
import { takeWhile } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-earning-card-back',
|
||||
styleUrls: ['./earning-card-back.component.scss'],
|
||||
templateUrl: './earning-card-back.component.html',
|
||||
})
|
||||
export class EarningCardBackComponent implements OnDestroy {
|
||||
private alive = true;
|
||||
|
||||
earningPieChartData: PieChart[];
|
||||
name: string;
|
||||
color: string;
|
||||
value: number;
|
||||
defaultSelectedCurrency: string = 'Bitcoin';
|
||||
|
||||
constructor(private earningService: EarningService ) {
|
||||
this.earningService.getEarningPieChartData()
|
||||
.pipe(takeWhile(() => this.alive))
|
||||
.subscribe((earningPieChartData) => {
|
||||
this.earningPieChartData = earningPieChartData;
|
||||
});
|
||||
}
|
||||
|
||||
changeChartInfo(pieData: {value: number; name: string; color: any}) {
|
||||
this.value = pieData.value;
|
||||
this.name = pieData.name;
|
||||
this.color = pieData.color;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.alive = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,209 @@
|
|||
import { AfterViewInit, Component, EventEmitter, Input, OnDestroy, Output } from '@angular/core';
|
||||
import { NbThemeService } from '@nebular/theme';
|
||||
import { delay, takeWhile } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-earning-pie-chart',
|
||||
styleUrls: ['./earning-card-back.component.scss'],
|
||||
template: `
|
||||
<div echarts
|
||||
class="echart"
|
||||
[options]="options"
|
||||
(chartInit)="onChartInit($event)"
|
||||
(chartClick)="onChartClick($event)">
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
export class EarningPieChartComponent implements AfterViewInit, OnDestroy {
|
||||
|
||||
@Output() selectPie = new EventEmitter<{value: number; name: string; color: string}>();
|
||||
@Input() values: {value: number; name: string; }[];
|
||||
@Input() defaultSelectedCurrency: string;
|
||||
|
||||
private alive = true;
|
||||
|
||||
options: any = {};
|
||||
echartsInstance;
|
||||
|
||||
constructor(private theme: NbThemeService) {
|
||||
}
|
||||
|
||||
onChartInit(ec) {
|
||||
this.echartsInstance = ec;
|
||||
}
|
||||
|
||||
onChartClick(event) {
|
||||
const pieData = {
|
||||
value: event.value,
|
||||
name: event.name,
|
||||
color: event.color.colorStops[0].color,
|
||||
};
|
||||
|
||||
this.emitSelectPie(pieData);
|
||||
}
|
||||
|
||||
emitSelectPie(pieData: {value: number; name: string; color: any}) {
|
||||
this.selectPie.emit(pieData);
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.theme.getJsTheme()
|
||||
.pipe(
|
||||
takeWhile(() => this.alive),
|
||||
delay(1),
|
||||
)
|
||||
.subscribe(config => {
|
||||
const variables = config.variables;
|
||||
|
||||
this.options = this.getOptions(variables);
|
||||
const defaultSelectedData =
|
||||
this.options.series[0].data.find((item) => item.name === this.defaultSelectedCurrency);
|
||||
const color = defaultSelectedData.itemStyle.normal.color.colorStops[0].color;
|
||||
const pieData = {
|
||||
value: defaultSelectedData.value,
|
||||
name: defaultSelectedData.name,
|
||||
color,
|
||||
};
|
||||
|
||||
this.emitSelectPie(pieData);
|
||||
});
|
||||
}
|
||||
|
||||
getOptions(variables) {
|
||||
const earningPie: any = variables.earningPie;
|
||||
|
||||
return {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: ' ',
|
||||
clockWise: true,
|
||||
hoverAnimation: false,
|
||||
type: 'pie',
|
||||
center: earningPie.center,
|
||||
radius: earningPie.radius,
|
||||
data: [
|
||||
{
|
||||
value: this.values[0].value,
|
||||
name: this.values[0].name,
|
||||
label: {
|
||||
normal: {
|
||||
position: 'center',
|
||||
formatter: '',
|
||||
textStyle: {
|
||||
fontSize: '22',
|
||||
fontFamily: variables.fontSecondary,
|
||||
fontWeight: '600',
|
||||
color: variables.fgHeading,
|
||||
},
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
show: false,
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{
|
||||
offset: 0,
|
||||
color: earningPie.firstPieGradientLeft,
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: earningPie.firstPieGradientRight,
|
||||
},
|
||||
]),
|
||||
shadowColor: earningPie.firstPieShadowColor,
|
||||
shadowBlur: 0,
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
value: this.values[1].value,
|
||||
name: this.values[1].name,
|
||||
label: {
|
||||
normal: {
|
||||
position: 'center',
|
||||
formatter: '',
|
||||
textStyle: {
|
||||
fontSize: '22',
|
||||
fontFamily: variables.fontSecondary,
|
||||
fontWeight: '600',
|
||||
color: variables.fgHeading,
|
||||
},
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
show: false,
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{
|
||||
offset: 0,
|
||||
color: earningPie.secondPieGradientLeft,
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: earningPie.secondPieGradientRight,
|
||||
},
|
||||
]),
|
||||
shadowColor: earningPie.secondPieShadowColor,
|
||||
shadowBlur: 0,
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
value: this.values[2].value,
|
||||
name: this.values[2].name,
|
||||
label: {
|
||||
normal: {
|
||||
position: 'center',
|
||||
formatter: '',
|
||||
textStyle: {
|
||||
fontSize: '22',
|
||||
fontFamily: variables.fontSecondary,
|
||||
fontWeight: '600',
|
||||
color: variables.fgHeading,
|
||||
},
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
show: false,
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{
|
||||
offset: 0,
|
||||
color: earningPie.thirdPieGradientLeft,
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: earningPie.thirdPieGradientRight,
|
||||
},
|
||||
]),
|
||||
shadowColor: earningPie.thirdPieShadowColor,
|
||||
shadowBlur: 0,
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.alive = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<nb-flip-card [showToggleButton]="false" [flipped]="flipped">
|
||||
<nb-card-front>
|
||||
<nb-card size="xsmall">
|
||||
<ngx-earning-card-front></ngx-earning-card-front>
|
||||
<i class="nb-arrow-right" (click)="toggleFlipView()"></i>
|
||||
</nb-card>
|
||||
</nb-card-front>
|
||||
<nb-card-back>
|
||||
<nb-card size="xsmall">
|
||||
<ngx-earning-card-back></ngx-earning-card-back>
|
||||
<i class="nb-arrow-right" (click)="toggleFlipView()"></i>
|
||||
</nb-card>
|
||||
</nb-card-back>
|
||||
</nb-flip-card>
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
@import '../../../@theme/styles/themes';
|
||||
|
||||
@include nb-install-component() {
|
||||
nb-card {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nb-arrow-right {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
@include nb-rtl(right, auto);
|
||||
@include nb-rtl(left, 0);
|
||||
padding: 1.5rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/deep/ .flipped {
|
||||
.back-container {
|
||||
.nb-arrow-right {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
}
|
||||
|
||||
.front-container {
|
||||
.nb-arrow-right {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ngx-earning-card-back, ngx-earning-card-front {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/deep/ nb-card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@include nb-rtl(flex-direction, row-reverse);
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
/deep/ nb-card-body {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-earning-card',
|
||||
styleUrls: ['./earning-card.component.scss'],
|
||||
templateUrl: './earning-card.component.html',
|
||||
})
|
||||
export class EarningCardComponent {
|
||||
|
||||
flipped = false;
|
||||
|
||||
toggleFlipView() {
|
||||
this.flipped = !this.flipped;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<nb-card-header>
|
||||
<div class="dropdown ghost-dropdown" ngbDropdown>
|
||||
<button type="button" ngbDropdownToggle class="btn"
|
||||
[ngClass]="{
|
||||
'btn-success': currentTheme === 'default',
|
||||
'btn-primary': currentTheme !== 'default'}">
|
||||
{{ selectedCurrency }}
|
||||
</button>
|
||||
<ul class="dropdown-menu" ngbDropdownMenu>
|
||||
<li class="dropdown-item" *ngFor="let currency of currencies" (click)="changeCurrency(currency)">{{ currency }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nb-card-header>
|
||||
<nb-card-body>
|
||||
<div class="chart-info">
|
||||
<div class="title">Daily Income</div>
|
||||
<div class="value">{{ earningLiveUpdateCardData.dailyIncome | ngxNumberWithCommas }}</div>
|
||||
<div class="delta"
|
||||
[class.up]="earningLiveUpdateCardData.delta.up"
|
||||
[class.down]="!earningLiveUpdateCardData.delta.up">
|
||||
{{ earningLiveUpdateCardData.delta.value }}%
|
||||
</div>
|
||||
</div>
|
||||
<ngx-earning-live-update-chart
|
||||
[liveUpdateChartData]="liveUpdateChartData">
|
||||
</ngx-earning-live-update-chart>
|
||||
</nb-card-body>
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
@import '../../../../@theme/styles/themes';
|
||||
@import '~@nebular/theme/styles/global/typography/typography';
|
||||
|
||||
@include nb-install-component() {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
|
||||
nb-card-header {
|
||||
flex-direction: row;
|
||||
padding-top: 0.45rem;
|
||||
padding-bottom: 0.45rem;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
min-width: 8.125rem;
|
||||
}
|
||||
|
||||
nb-card-body {
|
||||
padding: 1rem 0 0;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.chart-info {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0 nb-theme(card-padding);
|
||||
}
|
||||
|
||||
.title {
|
||||
color: nb-theme(color-fg-heading);
|
||||
}
|
||||
|
||||
.value {
|
||||
color: nb-theme(color-success);
|
||||
font-size: 1.5rem;
|
||||
|
||||
@include nb-for-theme(corporate) {
|
||||
color: nb-theme(color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.delta {
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
color: nb-theme(color-fg-heading);
|
||||
padding-left: 0.5rem;
|
||||
font-size: 1rem;
|
||||
top: 0;
|
||||
right: 0;
|
||||
@include nb-rtl(left, 1.25rem);
|
||||
@include nb-rtl(right, inherit);
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
content: '';
|
||||
right: 100%;
|
||||
border-left: 6px solid transparent;
|
||||
border-right: 6px solid transparent;
|
||||
}
|
||||
|
||||
&.down {
|
||||
&::before {
|
||||
bottom: 4px;
|
||||
border-top: 7px solid text-danger();
|
||||
}
|
||||
}
|
||||
|
||||
&.up {
|
||||
&::before {
|
||||
top: 4px;
|
||||
border-bottom: 7px solid text-success();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ngx-earning-live-update-chart {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/deep/ canvas {
|
||||
border-bottom-left-radius: nb-theme(card-border-radius);
|
||||
border-bottom-right-radius: nb-theme(card-border-radius);
|
||||
}
|
||||
|
||||
.echart {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@include nb-for-theme(corporate) {
|
||||
.delta {
|
||||
&.down {
|
||||
color: text-danger();
|
||||
|
||||
&::before {
|
||||
bottom: 4px;
|
||||
border-top: 7px solid text-danger();
|
||||
}
|
||||
}
|
||||
|
||||
&.up {
|
||||
color: text-primary();
|
||||
|
||||
&::before {
|
||||
top: 4px;
|
||||
border-bottom: 7px solid text-primary();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
|
||||
import { NbThemeService } from '@nebular/theme';
|
||||
import { interval } from 'rxjs';
|
||||
import { switchMap, takeWhile } from 'rxjs/operators';
|
||||
import { EarningService, LiveUpdateChart } from '../../../../@core/data/earning.service';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-earning-card-front',
|
||||
styleUrls: ['./earning-card-front.component.scss'],
|
||||
templateUrl: './earning-card-front.component.html',
|
||||
})
|
||||
export class EarningCardFrontComponent implements OnDestroy, OnInit {
|
||||
private alive = true;
|
||||
|
||||
@Input() selectedCurrency: string = 'Bitcoin';
|
||||
|
||||
intervalSubscription: Subscription;
|
||||
currencies: string[] = ['Bitcoin', 'Tether', 'Ethereum'];
|
||||
currentTheme: string;
|
||||
earningLiveUpdateCardData: LiveUpdateChart;
|
||||
liveUpdateChartData: { value: [string, number] }[];
|
||||
|
||||
constructor(private themeService: NbThemeService,
|
||||
private earningService: EarningService) {
|
||||
this.themeService.getJsTheme()
|
||||
.pipe(takeWhile(() => this.alive))
|
||||
.subscribe(theme => {
|
||||
this.currentTheme = theme.name;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.getEarningCardData(this.selectedCurrency);
|
||||
}
|
||||
|
||||
changeCurrency(currency) {
|
||||
if (this.selectedCurrency !== currency) {
|
||||
this.selectedCurrency = currency;
|
||||
|
||||
this.getEarningCardData(this.selectedCurrency);
|
||||
}
|
||||
}
|
||||
|
||||
private getEarningCardData(currency) {
|
||||
this.earningService.getEarningLiveUpdateCardData(currency)
|
||||
.pipe(takeWhile(() => this.alive))
|
||||
.subscribe((earningLiveUpdateCardData) => {
|
||||
this.earningLiveUpdateCardData = earningLiveUpdateCardData;
|
||||
this.liveUpdateChartData = earningLiveUpdateCardData.liveChart;
|
||||
|
||||
this.startReceivingLiveData(currency);
|
||||
});
|
||||
}
|
||||
|
||||
startReceivingLiveData(currency) {
|
||||
if (this.intervalSubscription) {
|
||||
this.intervalSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
this.intervalSubscription = interval(200)
|
||||
.pipe(
|
||||
takeWhile(() => this.alive),
|
||||
switchMap(() => this.earningService.generateRandomEarningData(currency)),
|
||||
)
|
||||
.subscribe((liveUpdateChartData) => {
|
||||
this.liveUpdateChartData = [...liveUpdateChartData];
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.alive = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
import { delay, takeWhile } from 'rxjs/operators';
|
||||
import { AfterViewInit, Component, Input, OnChanges, OnDestroy } from '@angular/core';
|
||||
import { NbThemeService } from '@nebular/theme';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-earning-live-update-chart',
|
||||
styleUrls: ['earning-card-front.component.scss'],
|
||||
template: `
|
||||
<div echarts
|
||||
class="echart"
|
||||
[options]="option"
|
||||
(chartInit)="onChartInit($event)"></div>
|
||||
`,
|
||||
})
|
||||
export class EarningLiveUpdateChartComponent implements AfterViewInit, OnDestroy, OnChanges {
|
||||
private alive = true;
|
||||
|
||||
@Input() liveUpdateChartData: { value: [string, number] }[];
|
||||
|
||||
option: any;
|
||||
echartsInstance;
|
||||
|
||||
constructor(private theme: NbThemeService) {
|
||||
}
|
||||
|
||||
ngOnChanges(): void {
|
||||
if (this.option) {
|
||||
this.updateChartOptions(this.liveUpdateChartData);
|
||||
}
|
||||
}
|
||||
|
||||
onChartInit(ec) {
|
||||
this.echartsInstance = ec;
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.theme.getJsTheme()
|
||||
.pipe(
|
||||
delay(1),
|
||||
takeWhile(() => this.alive),
|
||||
)
|
||||
.subscribe(config => {
|
||||
const earningLineTheme: any = config.variables.earningLine;
|
||||
|
||||
this.setChartOption(earningLineTheme);
|
||||
});
|
||||
}
|
||||
|
||||
setChartOption(earningLineTheme) {
|
||||
this.option = {
|
||||
grid: {
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'time',
|
||||
axisLine: {
|
||||
show: false,
|
||||
},
|
||||
axisLabel: {
|
||||
show: false,
|
||||
},
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
splitLine: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
boundaryGap: [0, '5%'],
|
||||
axisLine: {
|
||||
show: false,
|
||||
},
|
||||
axisLabel: {
|
||||
show: false,
|
||||
},
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
splitLine: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
textStyle: {
|
||||
color: earningLineTheme.tooltipTextColor,
|
||||
fontWeight: earningLineTheme.tooltipFontWeight,
|
||||
fontSize: earningLineTheme.tooltipFontSize,
|
||||
},
|
||||
position: 'top',
|
||||
backgroundColor: earningLineTheme.tooltipBg,
|
||||
borderColor: earningLineTheme.tooltipBorderColor,
|
||||
borderWidth: earningLineTheme.tooltipBorderWidth,
|
||||
formatter: params => `$ ${Math.round(parseInt(params.value[1], 10))}`,
|
||||
extraCssText: earningLineTheme.tooltipExtraCss,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'line',
|
||||
symbol: 'circle',
|
||||
sampling: 'average',
|
||||
itemStyle: {
|
||||
normal: {
|
||||
opacity: 0,
|
||||
},
|
||||
emphasis: {
|
||||
opacity: 0,
|
||||
},
|
||||
},
|
||||
lineStyle: {
|
||||
normal: {
|
||||
width: 0,
|
||||
},
|
||||
},
|
||||
areaStyle: {
|
||||
normal: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
|
||||
offset: 0,
|
||||
color: earningLineTheme.gradFrom,
|
||||
}, {
|
||||
offset: 1,
|
||||
color: earningLineTheme.gradTo,
|
||||
}]),
|
||||
opacity: 1,
|
||||
},
|
||||
},
|
||||
data: this.liveUpdateChartData,
|
||||
},
|
||||
],
|
||||
animation: true,
|
||||
};
|
||||
}
|
||||
|
||||
updateChartOptions(chartData: { value: [string, number] }[]) {
|
||||
this.echartsInstance.setOption({
|
||||
series: [{
|
||||
data: chartData,
|
||||
}],
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.alive = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
export enum NgxLegendItemColor {
|
||||
GREEN = 'green',
|
||||
PURPLE = 'purple',
|
||||
LIGHT_PURPLE = 'light-purple',
|
||||
BLUE = 'blue',
|
||||
YELLOW = 'yellow',
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<div class="legends">
|
||||
<div *ngFor="let legend of legendItems" class="legend">
|
||||
<div class="legend-item-color"
|
||||
[style.background]="legend.iconColor"></div>
|
||||
<div class="legend-title">{{ legend.title }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
@import '../../../@theme/styles/themes';
|
||||
@import '~bootstrap/scss/mixins/breakpoints';
|
||||
@import '~@nebular/theme/styles/global/bootstrap/breakpoints';
|
||||
|
||||
@include nb-install-component() {
|
||||
.legends {
|
||||
display: flex;
|
||||
@include nb-rtl(flex-direction, row-reverse);
|
||||
color: nb-theme(color-fg);
|
||||
padding: 0 0 0 2.85rem;
|
||||
}
|
||||
|
||||
.legend {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
margin-left: 4rem;
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.legend-item-color {
|
||||
min-width: 15px;
|
||||
min-height: 15px;
|
||||
border-radius: 0.2rem;
|
||||
}
|
||||
|
||||
.legend-title {
|
||||
padding: 0 0.75rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
.legend {
|
||||
margin-left: 1.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import { Component, Input } from '@angular/core';
|
||||
|
||||
import { NgxLegendItemColor } from './enum.legend-item-color';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-legend-chart',
|
||||
styleUrls: ['./legend-chart.component.scss'],
|
||||
templateUrl: './legend-chart.component.html',
|
||||
})
|
||||
export class ECommerceLegendChartComponent {
|
||||
|
||||
/**
|
||||
* Take an array of legend items
|
||||
* Available iconColor: 'green', 'purple', 'light-purple', 'blue', 'yellow'
|
||||
* @type {{iconColor: string; title: string}[]}
|
||||
*/
|
||||
@Input()
|
||||
legendItems: { iconColor: NgxLegendItemColor; title: string }[] = [];
|
||||
}
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
import { delay, takeWhile } from 'rxjs/operators';
|
||||
import { AfterViewInit, Component, OnDestroy } from '@angular/core';
|
||||
import { NbThemeService } from '@nebular/theme';
|
||||
|
||||
const points = [300, 520, 435, 530, 730, 620, 660, 860];
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-stats-ares-chart',
|
||||
styleUrls: ['stats-card-back.component.scss'],
|
||||
template: `
|
||||
<div echarts [options]="option" class="echart"></div>
|
||||
`,
|
||||
})
|
||||
export class StatsAreaChartComponent implements AfterViewInit, OnDestroy {
|
||||
|
||||
private alive = true;
|
||||
|
||||
option: any = {};
|
||||
|
||||
constructor(private theme: NbThemeService) {
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.theme.getJsTheme()
|
||||
.pipe(
|
||||
delay(1),
|
||||
takeWhile(() => this.alive),
|
||||
)
|
||||
.subscribe(config => {
|
||||
const trafficTheme: any = config.variables.traffic;
|
||||
|
||||
this.option = Object.assign({}, {
|
||||
grid: {
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: points,
|
||||
},
|
||||
yAxis: {
|
||||
boundaryGap: [0, '5%'],
|
||||
axisLine: {
|
||||
show: false,
|
||||
},
|
||||
axisLabel: {
|
||||
show: false,
|
||||
},
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: trafficTheme.colorBlack,
|
||||
opacity: 0.06,
|
||||
width: '1',
|
||||
},
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
textStyle: {
|
||||
color: trafficTheme.tooltipTextColor,
|
||||
fontWeight: trafficTheme.tooltipFontWeight,
|
||||
fontSize: 16,
|
||||
},
|
||||
position: 'top',
|
||||
backgroundColor: trafficTheme.tooltipBg,
|
||||
borderColor: trafficTheme.tooltipBorderColor,
|
||||
borderWidth: 3,
|
||||
formatter: '$ {c0}',
|
||||
extraCssText: trafficTheme.tooltipExtraCss,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'line',
|
||||
symbol: 'circle',
|
||||
symbolSize: 8,
|
||||
sampling: 'average',
|
||||
silent: true,
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: trafficTheme.shadowLineDarkBg,
|
||||
},
|
||||
emphasis: {
|
||||
color: 'rgba(0,0,0,0)',
|
||||
borderColor: 'rgba(0,0,0,0)',
|
||||
borderWidth: 0,
|
||||
},
|
||||
},
|
||||
lineStyle: {
|
||||
normal: {
|
||||
width: 2,
|
||||
color: trafficTheme.shadowLineDarkBg,
|
||||
},
|
||||
},
|
||||
data: points.map(p => p - 15),
|
||||
},
|
||||
{
|
||||
type: 'line',
|
||||
symbol: 'circle',
|
||||
symbolSize: 6,
|
||||
sampling: 'average',
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: trafficTheme.itemColor,
|
||||
borderColor: trafficTheme.itemBorderColor,
|
||||
borderWidth: 2,
|
||||
},
|
||||
emphasis: {
|
||||
color: 'white',
|
||||
borderColor: trafficTheme.itemEmphasisBorderColor,
|
||||
borderWidth: 2,
|
||||
},
|
||||
},
|
||||
lineStyle: {
|
||||
normal: {
|
||||
width: 2,
|
||||
color: trafficTheme.lineBg,
|
||||
shadowColor: trafficTheme.lineBg,
|
||||
shadowBlur: trafficTheme.lineShadowBlur,
|
||||
},
|
||||
},
|
||||
areaStyle: {
|
||||
normal: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
|
||||
offset: 0,
|
||||
color: trafficTheme.gradFrom,
|
||||
}, {
|
||||
offset: 1,
|
||||
color: trafficTheme.gradTo,
|
||||
}]),
|
||||
opacity: 1,
|
||||
},
|
||||
},
|
||||
data: points,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.alive = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<nb-card-header>
|
||||
<div class="header-container">
|
||||
<div class="icon">
|
||||
<i class="ion-social-usd"></i>
|
||||
</div>
|
||||
<span class="title">Profit</span>
|
||||
</div>
|
||||
</nb-card-header>
|
||||
<nb-card-body class="p-0">
|
||||
<div class="info">
|
||||
<div class="period">
|
||||
<span class="time-interval">Jun 1 - Jun 30</span>
|
||||
<div class="value">
|
||||
<span class="currency">$</span>
|
||||
300
|
||||
</div>
|
||||
</div>
|
||||
<div class="period latest">
|
||||
<span class="time-interval">Jul 1 - Jul 31</span>
|
||||
<div class="value">
|
||||
<span class="currency">$</span>
|
||||
860
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ngx-stats-ares-chart></ngx-stats-ares-chart>
|
||||
</nb-card-body>
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
@import '../../../../@theme/styles/themes';
|
||||
|
||||
@include nb-install-component() {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
|
||||
/deep/ nb-card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.header-container {
|
||||
display: flex;
|
||||
@include nb-rtl(flex-direction, row-reverse);
|
||||
}
|
||||
|
||||
.title {
|
||||
padding-left: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
/deep/ nb-card-body {
|
||||
overflow: hidden;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.info {
|
||||
padding: 0.75rem;
|
||||
padding-bottom: 0.5rem;
|
||||
display: flex;
|
||||
@include nb-rtl(flex-direction, row-reverse);
|
||||
}
|
||||
|
||||
.period {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
|
||||
&.latest {
|
||||
padding-left: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
.time-interval {
|
||||
color: nb-theme(color-fg);
|
||||
}
|
||||
|
||||
.value {
|
||||
font-family: nb-theme(font-secondary), serif;
|
||||
font-size: 1.25rem;
|
||||
font-weight: nb-theme(font-weight-bold);
|
||||
color: nb-theme(card-fg-heading);
|
||||
|
||||
@include nb-for-theme(default) {
|
||||
color: nb-theme(color-success);
|
||||
}
|
||||
|
||||
.currency {
|
||||
color: nb-theme(color-success);
|
||||
|
||||
@include nb-for-theme(corporate) {
|
||||
color: nb-theme(color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ngx-stats-ares-chart {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/deep/ canvas {
|
||||
border-bottom-left-radius: nb-theme(card-border-radius);
|
||||
border-bottom-right-radius: nb-theme(card-border-radius);
|
||||
}
|
||||
|
||||
.echart {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-stats-card-back',
|
||||
styleUrls: ['./stats-card-back.component.scss'],
|
||||
templateUrl: './stats-card-back.component.html',
|
||||
})
|
||||
export class StatsCardBackComponent {
|
||||
}
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
import { AfterViewInit, Component, Input, OnDestroy } from '@angular/core';
|
||||
import { NbThemeService } from '@nebular/theme';
|
||||
import { takeWhile } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-stats-bar-animation-chart',
|
||||
template: `
|
||||
<div echarts [options]="options" class="echart"></div>
|
||||
`,
|
||||
})
|
||||
export class StatsBarAnimationChartComponent implements AfterViewInit, OnDestroy {
|
||||
|
||||
private alive = true;
|
||||
|
||||
@Input() linesData: { firstLine: number[]; secondLine: number[] } = {
|
||||
firstLine: [],
|
||||
secondLine: [],
|
||||
};
|
||||
|
||||
options: any = {};
|
||||
|
||||
constructor(private theme: NbThemeService) {
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.theme.getJsTheme()
|
||||
.pipe(takeWhile(() => this.alive))
|
||||
.subscribe(config => {
|
||||
const profitBarAnimationEchart: any = config.variables.profitBarAnimationEchart;
|
||||
|
||||
this.setChartOption(profitBarAnimationEchart);
|
||||
});
|
||||
}
|
||||
|
||||
setChartOption(chartVariables) {
|
||||
this.options = {
|
||||
color: [
|
||||
chartVariables.firstAnimationBarColor,
|
||||
chartVariables.secondAnimationBarColor,
|
||||
],
|
||||
grid: {
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
legend: {
|
||||
data: ['transactions', 'orders'],
|
||||
borderWidth: 0,
|
||||
borderRadius: 0,
|
||||
itemWidth: 15,
|
||||
itemHeight: 15,
|
||||
textStyle: {
|
||||
color: chartVariables.textColor,
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
textStyle: {
|
||||
color: chartVariables.tooltipTextColor,
|
||||
fontWeight: chartVariables.tooltipFontWeight,
|
||||
fontSize: chartVariables.tooltipFontSize,
|
||||
},
|
||||
position: 'top',
|
||||
backgroundColor: chartVariables.tooltipBg,
|
||||
borderColor: chartVariables.tooltipBorderColor,
|
||||
borderWidth: chartVariables.tooltipBorderWidth,
|
||||
formatter: params => `$ ${Math.round(parseInt(params.value, 10))}`,
|
||||
extraCssText: chartVariables.tooltipExtraCss,
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
data: this.linesData.firstLine.map((_, index) => index),
|
||||
silent: false,
|
||||
axisLine: {
|
||||
show: false,
|
||||
},
|
||||
axisLabel: {
|
||||
show: false,
|
||||
},
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
axisLine: {
|
||||
show: false,
|
||||
},
|
||||
axisLabel: {
|
||||
show: false,
|
||||
},
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: chartVariables.splitLineStyleColor,
|
||||
opacity: chartVariables.splitLineStyleOpacity,
|
||||
width: chartVariables.splitLineStyleWidth,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: 'transactions',
|
||||
type: 'bar',
|
||||
data: this.linesData.firstLine,
|
||||
animationDelay: idx => idx * 10,
|
||||
},
|
||||
{
|
||||
name: 'orders',
|
||||
type: 'bar',
|
||||
data: this.linesData.secondLine,
|
||||
animationDelay: idx => idx * 10 + 100,
|
||||
},
|
||||
],
|
||||
animationEasing: 'elasticOut',
|
||||
animationDelayUpdate: idx => idx * 5,
|
||||
};
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.alive = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<nb-card-header>
|
||||
<div class="header-container">
|
||||
<div class="icon">
|
||||
<i class="ion-social-usd"></i>
|
||||
</div>
|
||||
<span class="title">Profit</span>
|
||||
</div>
|
||||
</nb-card-header>
|
||||
<nb-card-body>
|
||||
<ngx-stats-bar-animation-chart [linesData]="linesData"></ngx-stats-bar-animation-chart>
|
||||
</nb-card-body>
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
@import '../../../../@theme/styles/themes';
|
||||
|
||||
@include nb-install-component() {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
|
||||
/deep/ nb-card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.header-container {
|
||||
display: flex;
|
||||
@include nb-rtl(flex-direction, row-reverse);
|
||||
}
|
||||
|
||||
.title {
|
||||
padding-left: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
/deep/ nb-card-body {
|
||||
display: flex;
|
||||
position: relative;
|
||||
flex: 1;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
|
||||
.period {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.time-interval {
|
||||
font-size: nb-theme(font-size-sm);
|
||||
color: nb-theme(color-fg);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-family: nb-theme(font-secondary), serif;
|
||||
font-weight: nb-theme(font-weight-bold);
|
||||
color: nb-theme(card-fg-heading);
|
||||
|
||||
.currency {
|
||||
color: nb-theme(color-success);
|
||||
|
||||
@include nb-for-theme(corporate) {
|
||||
color: nb-theme(color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.start-period {
|
||||
.time-interval, .value {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.end-period {
|
||||
.time-interval, .value {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
ngx-stats-bar-animation-chart {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
|
||||
.echart {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { ProfitBarAnimationChartService } from '../../../../@core/data/profit-bar-animation-chart.service';
|
||||
import { takeWhile } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-stats-card-front',
|
||||
styleUrls: ['./stats-card-front.component.scss'],
|
||||
templateUrl: './stats-card-front.component.html',
|
||||
})
|
||||
export class StatsCardFrontComponent {
|
||||
|
||||
private alive = true;
|
||||
|
||||
linesData: { firstLine: number[]; secondLine: number[] };
|
||||
|
||||
constructor(private profitBarAnimationChartService: ProfitBarAnimationChartService) {
|
||||
this.profitBarAnimationChartService.getChartData()
|
||||
.pipe(takeWhile(() => this.alive))
|
||||
.subscribe((linesData) => {
|
||||
this.linesData = linesData;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<nb-flip-card [showToggleButton]="false" [flipped]="flipped">
|
||||
<nb-card-front>
|
||||
<nb-card size="xsmall">
|
||||
<ngx-stats-card-front></ngx-stats-card-front>
|
||||
<i class="nb-arrow-right" (click)="toggleView()"></i>
|
||||
</nb-card>
|
||||
</nb-card-front>
|
||||
<nb-card-back>
|
||||
<nb-card size="xsmall">
|
||||
<ngx-stats-card-back></ngx-stats-card-back>
|
||||
<i class="nb-arrow-right" (click)="toggleView()"></i>
|
||||
</nb-card>
|
||||
</nb-card-back>
|
||||
</nb-flip-card>
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
@import '../../../@theme/styles/themes';
|
||||
|
||||
@include nb-install-component() {
|
||||
.nb-arrow-right {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
@include nb-rtl(right, auto);
|
||||
@include nb-rtl(left, 0);
|
||||
padding: 1.5rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
::ng-deep .flipped {
|
||||
.back-container {
|
||||
.nb-arrow-right {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
}
|
||||
|
||||
.front-container {
|
||||
.nb-arrow-right {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-profit-card',
|
||||
styleUrls: ['./profit-card.component.scss'],
|
||||
templateUrl: './profit-card.component.html',
|
||||
})
|
||||
export class ProfitCardComponent {
|
||||
|
||||
flipped = false;
|
||||
|
||||
toggleView() {
|
||||
this.flipped = !this.flipped;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<nb-card size="medium">
|
||||
<nb-card-body>
|
||||
<div class="progress-info" *ngFor="let item of progressInfoData">
|
||||
<div class="title">{{ item.title }}</div>
|
||||
<div class="value">{{ item.value | ngxNumberWithCommas }}</div>
|
||||
<nb-progress-bar [value]="item.activeProgress"></nb-progress-bar>
|
||||
<div class="description">
|
||||
<bdi>{{ item.description }}</bdi>
|
||||
</div>
|
||||
</div>
|
||||
</nb-card-body>
|
||||
</nb-card>
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
@import '../../../@theme/styles/themes';
|
||||
|
||||
$shadow-green: #00977e;
|
||||
|
||||
@include nb-install-component() {
|
||||
.progress-info {
|
||||
color: nb-theme(color-fg-heading);
|
||||
margin-top: 2.5rem;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
font-family: nb-theme(font-secondary);
|
||||
font-size: nb-theme(font-size-lg);
|
||||
font-weight: nb-theme(font-weight-bold);
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 3rem;
|
||||
font-weight: nb-theme(font-weight-light);
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.description {
|
||||
color: nb-theme(color-fg);
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
/deep/ nb-progress-bar {
|
||||
margin-top: 0.2rem;
|
||||
|
||||
.progress-container {
|
||||
height: 0.8rem;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.progress-value {
|
||||
height: 0.6rem;
|
||||
background: nb-theme(progress-bar-background);
|
||||
|
||||
@include nb-for-theme(cosmic) {
|
||||
box-shadow: 0 0.2rem $shadow-green;
|
||||
}
|
||||
|
||||
@include nb-for-theme(corporate) {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-progress-section',
|
||||
styleUrls: ['./progress-section.component.scss'],
|
||||
templateUrl: './progress-section.component.html',
|
||||
})
|
||||
export class ECommerceProgressSectionComponent {
|
||||
progressInfoData = [
|
||||
{
|
||||
title: 'Today’s Profit',
|
||||
value: 572900,
|
||||
activeProgress: 70,
|
||||
description: 'Better than last week (70%)',
|
||||
},
|
||||
{
|
||||
title: 'New Orders',
|
||||
value: 6378,
|
||||
activeProgress: 30,
|
||||
description: 'Better than last week (30%)',
|
||||
},
|
||||
{
|
||||
title: 'New Comments',
|
||||
value: 200,
|
||||
activeProgress: 55,
|
||||
description: 'Better than last week (55%)',
|
||||
},
|
||||
];
|
||||
}
|
||||
11
src/app/pages/e-commerce/slide-out/slide-out.component.html
Normal file
11
src/app/pages/e-commerce/slide-out/slide-out.component.html
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<i class="show-hide-toggle"
|
||||
[class.nb-arrow-thin-left]="!showVisitorsStatistics"
|
||||
[class.nb-arrow-thin-right]="showVisitorsStatistics"
|
||||
(click)="toggleStatistics()"></i>
|
||||
<div class="slide-out-container"
|
||||
[class.expanded]="showVisitorsStatistics"
|
||||
[class.collapsed]="!showVisitorsStatistics">
|
||||
<div class="content-wrapper">
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
</div>
|
||||
113
src/app/pages/e-commerce/slide-out/slide-out.component.scss
Normal file
113
src/app/pages/e-commerce/slide-out/slide-out.component.scss
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
@import '../../../@theme/styles/themes';
|
||||
@import '~bootstrap/scss/mixins/breakpoints';
|
||||
@import '~@nebular/theme/styles/global/bootstrap/breakpoints';
|
||||
|
||||
@include nb-install-component() {
|
||||
$slide-out-container-width: nb-theme(slide-out-container-width);
|
||||
|
||||
// toggle button
|
||||
.show-hide-toggle {
|
||||
position: absolute;
|
||||
display: block;
|
||||
font-size: 2rem;
|
||||
font-weight: nb-theme(font-weight-bold);
|
||||
top: 1.5rem;
|
||||
right: 1.5rem;
|
||||
cursor: pointer;
|
||||
color: nb-theme(color-fg);
|
||||
background-color: transparent;
|
||||
z-index: 2;
|
||||
@include nb-rtl(right, auto);
|
||||
@include nb-rtl(left, 1.5rem);
|
||||
}
|
||||
// toggle button
|
||||
|
||||
.slide-out-container {
|
||||
position: absolute;
|
||||
padding: 1.5rem;
|
||||
width: $slide-out-container-width;
|
||||
}
|
||||
|
||||
.slide-out-container,
|
||||
.slide-out-container::before {
|
||||
display: block;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
overflow: hidden;
|
||||
transition: all 0.5s ease-out;
|
||||
}
|
||||
|
||||
.slide-out-container::before {
|
||||
content: '';
|
||||
right: 0;
|
||||
@include nb-rtl(right, auto);
|
||||
@include nb-rtl(left, 0);
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
background: nb-theme(slide-out-background);
|
||||
box-shadow: nb-theme(slide-out-shadow-color);
|
||||
@include nb-rtl(box-shadow, nb-theme(slide-out-shadow-color-rtl));
|
||||
filter: blur(3px);
|
||||
opacity: 0.9;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.slide-out-container.collapsed {
|
||||
left: calc(100% - 6rem);
|
||||
@include nb-rtl(left, auto);
|
||||
@include nb-rtl(right, calc(100% - 6rem));
|
||||
}
|
||||
|
||||
.slide-out-container.expanded {
|
||||
left: calc(100% - #{$slide-out-container-width});
|
||||
@include nb-rtl(left, auto);
|
||||
@include nb-rtl(right, calc(100% - #{$slide-out-container-width}));
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
margin: 0 6rem;
|
||||
transition: all 0.5s ease-out;
|
||||
}
|
||||
|
||||
.expanded .content-wrapper {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
$slide-out-container-width: 50%;
|
||||
|
||||
.slide-out-container {
|
||||
width: $slide-out-container-width;
|
||||
}
|
||||
|
||||
.slide-out-container.expanded {
|
||||
left: calc(100% - #{$slide-out-container-width});
|
||||
@include nb-rtl(right, calc(100% - #{$slide-out-container-width}));
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(is) {
|
||||
$slide-out-container-width: 100%;
|
||||
|
||||
.show-hide-toggle {
|
||||
right: 0.5rem;
|
||||
}
|
||||
|
||||
.slide-out-container {
|
||||
width: $slide-out-container-width;
|
||||
}
|
||||
|
||||
.slide-out-container.collapsed {
|
||||
left: calc(100% - 3rem);
|
||||
@include nb-rtl(right, calc(100% - 3rem));
|
||||
}
|
||||
|
||||
.slide-out-container.expanded {
|
||||
left: calc(100% - #{$slide-out-container-width});
|
||||
@include nb-rtl(right, calc(100% - #{$slide-out-container-width}));
|
||||
}
|
||||
}
|
||||
}
|
||||
15
src/app/pages/e-commerce/slide-out/slide-out.component.ts
Normal file
15
src/app/pages/e-commerce/slide-out/slide-out.component.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { Component, Input } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-slide-out',
|
||||
styleUrls: ['./slide-out.component.scss'],
|
||||
templateUrl: './slide-out.component.html',
|
||||
})
|
||||
export class SlideOutComponent {
|
||||
|
||||
@Input() showVisitorsStatistics: boolean = false;
|
||||
|
||||
toggleStatistics() {
|
||||
this.showVisitorsStatistics = !this.showVisitorsStatistics;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<nb-card-body>
|
||||
<ngx-traffic-bar-chart [data]="trafficBarData.data"
|
||||
[labels]="trafficBarData.labels"
|
||||
[formatter]="trafficBarData.formatter">
|
||||
</ngx-traffic-bar-chart>
|
||||
</nb-card-body>
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
@import '../../../../@theme/styles/themes';
|
||||
|
||||
@include nb-install-component() {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
|
||||
nb-card-body {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
ngx-traffic-bar-chart {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/deep/ canvas {
|
||||
border-bottom-left-radius: nb-theme(card-border-radius);
|
||||
border-bottom-right-radius: nb-theme(card-border-radius);
|
||||
}
|
||||
|
||||
.echart {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
import { Component, Input, OnDestroy } from '@angular/core';
|
||||
import { NbThemeService } from '@nebular/theme';
|
||||
import { takeWhile } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-traffic-back-card',
|
||||
styleUrls: ['./traffic-back-card.component.scss'],
|
||||
templateUrl: './traffic-back-card.component.html',
|
||||
})
|
||||
export class TrafficBackCardComponent implements OnDestroy {
|
||||
|
||||
private alive = true;
|
||||
|
||||
@Input() trafficBarData: any;
|
||||
|
||||
currentTheme: string;
|
||||
|
||||
constructor(private themeService: NbThemeService) {
|
||||
this.themeService.getJsTheme()
|
||||
.pipe(takeWhile(() => this.alive))
|
||||
.subscribe(theme => {
|
||||
this.currentTheme = theme.name;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.alive = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
import { AfterViewInit, Component, Input, OnChanges, OnDestroy, SimpleChanges } from '@angular/core';
|
||||
import { NbThemeService } from '@nebular/theme';
|
||||
import { takeWhile } from 'rxjs/operators';
|
||||
|
||||
declare const echarts: any;
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-traffic-bar-chart',
|
||||
styleUrls: ['traffic-back-card.component.scss'],
|
||||
template: `
|
||||
<div echarts [options]="option" class="echart" (chartInit)="onChartInit($event)"></div>
|
||||
`,
|
||||
})
|
||||
export class TrafficBarChartComponent implements AfterViewInit, OnDestroy, OnChanges {
|
||||
|
||||
@Input() data: number[];
|
||||
@Input() labels: string[];
|
||||
@Input() formatter: string;
|
||||
|
||||
private alive = true;
|
||||
|
||||
option: any = {};
|
||||
echartsInstance;
|
||||
|
||||
constructor(private theme: NbThemeService) {
|
||||
}
|
||||
|
||||
onChartInit(ec) {
|
||||
this.echartsInstance = ec;
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (!changes.data.isFirstChange() && !changes.labels.isFirstChange()) {
|
||||
this.echartsInstance.setOption({
|
||||
series: [{
|
||||
data: this.data,
|
||||
}],
|
||||
xAxis: {
|
||||
data: this.labels,
|
||||
},
|
||||
tooltip: {
|
||||
formatter: this.formatter,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.theme.getJsTheme()
|
||||
.pipe(takeWhile(() => this.alive))
|
||||
.subscribe(config => {
|
||||
const trafficTheme: any = config.variables.trafficBarEchart;
|
||||
|
||||
this.option = Object.assign({}, {
|
||||
grid: {
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
type : 'category',
|
||||
data : this.labels,
|
||||
axisLabel: {
|
||||
color: trafficTheme.axisTextColor,
|
||||
fontSize: trafficTheme.axisFontSize,
|
||||
},
|
||||
axisLine: {
|
||||
show: false,
|
||||
},
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
show: false,
|
||||
axisLine: {
|
||||
show: false,
|
||||
},
|
||||
axisLabel: {
|
||||
show: false,
|
||||
},
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
boundaryGap: [0, '5%'],
|
||||
},
|
||||
tooltip: {
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
textStyle: {
|
||||
color: trafficTheme.tooltipTextColor,
|
||||
fontWeight: trafficTheme.tooltipFontWeight,
|
||||
fontSize: 16,
|
||||
},
|
||||
position: 'top',
|
||||
backgroundColor: trafficTheme.tooltipBg,
|
||||
borderColor: trafficTheme.tooltipBorderColor,
|
||||
borderWidth: 3,
|
||||
formatter: this.formatter,
|
||||
extraCssText: trafficTheme.tooltipExtraCss,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'bar',
|
||||
barWidth: '40%',
|
||||
data: this.data,
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
|
||||
offset: 0,
|
||||
color: trafficTheme.gradientFrom,
|
||||
}, {
|
||||
offset: 1,
|
||||
color: trafficTheme.gradientTo,
|
||||
}]),
|
||||
opacity: 1,
|
||||
shadowColor: trafficTheme.gradientFrom,
|
||||
shadowBlur: trafficTheme.shadowBlur,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.alive = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<div class="traffic-bar">
|
||||
<div class="period-from">{{ barData.prevDate }}</div>
|
||||
<div class="vertical-progress-bar">
|
||||
<div class="progress-line"
|
||||
[style.height.%]="barData.prevValue">
|
||||
</div>
|
||||
</div>
|
||||
<div class="vertical-progress-bar">
|
||||
<div class="progress-line"
|
||||
[style.height.%]="barData.nextValue"
|
||||
[class.success]="successDelta"
|
||||
[class.failure]="!successDelta">
|
||||
</div>
|
||||
</div>
|
||||
<div class="period-to">{{ barData.nextDate }}</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
@import '../../../../../@theme/styles/themes';
|
||||
@import '~@nebular/theme/styles/global/bootstrap/hero-buttons';
|
||||
@import '~@nebular/theme/styles/global/typography/typography';
|
||||
|
||||
$traffic-bar-background-color: #d0d8e3;
|
||||
|
||||
@include nb-install-component() {
|
||||
height: 100%;
|
||||
|
||||
.traffic-bar {
|
||||
display: flex;
|
||||
align-self: stretch;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
|
||||
> * {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.period-from, .period-to {
|
||||
align-self: flex-end;
|
||||
font-size: 1rem;
|
||||
line-height: 0.8;
|
||||
}
|
||||
|
||||
.period-from {
|
||||
position: absolute;
|
||||
right: 100%;
|
||||
@include nb-rtl(right, inherit);
|
||||
@include nb-rtl(left, 100%);
|
||||
bottom: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.vertical-progress-bar {
|
||||
height: 100%;
|
||||
width: 0.7rem;
|
||||
transform: rotate(180deg);
|
||||
|
||||
.progress-line {
|
||||
width: 100%;
|
||||
background-color: $traffic-bar-background-color;
|
||||
|
||||
&.success {
|
||||
background-color: text-success();
|
||||
|
||||
@include nb-for-theme(corporate) {
|
||||
background-color: text-primary();
|
||||
}
|
||||
}
|
||||
|
||||
&.failure {
|
||||
background-color: text-danger();
|
||||
}
|
||||
}
|
||||
|
||||
@include nb-for-theme(cosmic) {
|
||||
.progress-line {
|
||||
background: linear-gradient(180deg, #a054fe 0%, #7a58ff 100%);
|
||||
|
||||
&.success {
|
||||
background: linear-gradient(0deg, #00caba 0%, #00d77f 100%);
|
||||
}
|
||||
|
||||
&.failure {
|
||||
background: linear-gradient(180deg, #ff38ab 0%, #ff386c 100%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import { Component, Input } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-traffic-bar',
|
||||
styleUrls: ['./traffic-bar.component.scss'],
|
||||
templateUrl: './traffic-bar.component.html',
|
||||
})
|
||||
export class TrafficBarComponent {
|
||||
|
||||
@Input() barData: { prevDate: string; prevValue: number; nextDate: string; nextValue: number };
|
||||
@Input() successDelta: boolean;
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<nb-card-body>
|
||||
<ul class="traffic-list">
|
||||
<li *ngFor="let item of frontCardData; trackBy: trackByDate">
|
||||
<div class="date">{{ item.date }}</div>
|
||||
<div class="value">{{ item.value }}</div>
|
||||
<div class="delta"
|
||||
[class.up]="item.delta.up"
|
||||
[class.down]="!item.delta.up">
|
||||
{{ item.delta.value }}%
|
||||
</div>
|
||||
<ngx-traffic-bar [barData]="item.comparison"
|
||||
[successDelta]="item.delta.up">
|
||||
</ngx-traffic-bar>
|
||||
</li>
|
||||
</ul>
|
||||
</nb-card-body>
|
||||
|
|
@ -0,0 +1,158 @@
|
|||
@import '../../../../@theme/styles/themes';
|
||||
@import '~@nebular/theme/components/card/card.component.theme';
|
||||
@import '~@nebular/theme/styles/global/typography/typography';
|
||||
@import '~bootstrap/scss/mixins/breakpoints';
|
||||
@import '~@nebular/theme/styles/global/bootstrap/breakpoints';
|
||||
|
||||
@include nb-install-component() {
|
||||
overflow: auto;
|
||||
|
||||
nb-card-body {
|
||||
padding: 0;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.traffic-list {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 4.5rem;
|
||||
font-size: 1.25rem;
|
||||
position: relative;
|
||||
color: nb-theme(color-fg);
|
||||
padding: nb-theme(card-padding);
|
||||
border-bottom:
|
||||
nb-theme(list-item-border-width)
|
||||
nb-theme(card-header-border-type)
|
||||
nb-theme(separator);
|
||||
|
||||
&:hover {
|
||||
background-color: nb-theme(layout-bg);
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
content: '';
|
||||
height: 100%;
|
||||
width: 6px;
|
||||
left: 0;
|
||||
top: 0;
|
||||
background-color: nb-theme(color-success);
|
||||
border-radius: nb-theme(radius);
|
||||
}
|
||||
}
|
||||
|
||||
> * {
|
||||
flex: 2;
|
||||
}
|
||||
|
||||
ngx-traffic-bar {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.date {
|
||||
@include nb-for-theme(corporate) {
|
||||
color: nb-theme(color-fg-heading);
|
||||
}
|
||||
}
|
||||
|
||||
.value {
|
||||
color: nb-theme(color-fg-heading);
|
||||
}
|
||||
|
||||
.delta {
|
||||
display: flex;
|
||||
padding-left: 0.5rem;
|
||||
font-size: 1rem;
|
||||
align-items: center;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
right: 100%;
|
||||
margin-right: 0.7rem;
|
||||
@include nb-rtl(margin-left, 0.7rem);
|
||||
@include nb-rtl(margin-right, 0);
|
||||
border-left: 5px solid transparent;
|
||||
border-right: 5px solid transparent;
|
||||
}
|
||||
|
||||
&.down {
|
||||
color: text-danger();
|
||||
|
||||
&::before {
|
||||
bottom: 5px;
|
||||
border-top: 6px solid text-danger();
|
||||
}
|
||||
}
|
||||
|
||||
&.up {
|
||||
color: text-success();
|
||||
|
||||
&::before {
|
||||
top: 5px;
|
||||
border-bottom: 6px solid text-success();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include nb-for-theme(cosmic) {
|
||||
.traffic-list li {
|
||||
&:hover {
|
||||
&::before {
|
||||
$color-top: nb-theme(btn-success-bg);
|
||||
$color-bottom: btn-hero-success-left-color();
|
||||
|
||||
background-image: linear-gradient(to top, $color-top, $color-bottom);
|
||||
box-shadow: 0 0 16px -2px btn-hero-success-middle-color();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include nb-for-theme(corporate) {
|
||||
.traffic-list li {
|
||||
border-color: nb-theme(tabs-separator);
|
||||
|
||||
&:first-child {
|
||||
border-top:
|
||||
nb-theme(list-item-border-width)
|
||||
nb-theme(card-header-border-type)
|
||||
nb-theme(separator);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&::before {
|
||||
background-color: nb-theme(color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.date {
|
||||
color: nb-theme(color-fg-heading);
|
||||
}
|
||||
|
||||
.delta {
|
||||
&.up {
|
||||
color: text-primary();
|
||||
|
||||
&::before {
|
||||
border-bottom-color: text-primary();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(is) {
|
||||
ngx-traffic-bar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import { Component, Input, OnDestroy } from '@angular/core';
|
||||
import { NbThemeService } from '@nebular/theme';
|
||||
import { takeWhile } from 'rxjs/operators';
|
||||
|
||||
import { TrafficList } from '../../../../@core/data/traffic-list.service';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-traffic-front-card',
|
||||
styleUrls: ['./traffic-front-card.component.scss'],
|
||||
templateUrl: './traffic-front-card.component.html',
|
||||
})
|
||||
export class TrafficFrontCardComponent implements OnDestroy {
|
||||
|
||||
private alive = true;
|
||||
|
||||
@Input() frontCardData: TrafficList;
|
||||
|
||||
currentTheme: string;
|
||||
|
||||
constructor(private themeService: NbThemeService) {
|
||||
this.themeService.getJsTheme()
|
||||
.pipe(takeWhile(() => this.alive))
|
||||
.subscribe(theme => {
|
||||
this.currentTheme = theme.name;
|
||||
});
|
||||
}
|
||||
|
||||
trackByDate(_, item) {
|
||||
return item.date;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.alive = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<nb-card-header>
|
||||
<span>Traffic</span>
|
||||
<div class="dropdown ghost-dropdown" ngbDropdown>
|
||||
<button type="button" class="btn btn-sm" ngbDropdownToggle
|
||||
[ngClass]="{
|
||||
'btn-success': currentTheme === 'default',
|
||||
'btn-primary': currentTheme !== 'default'}">
|
||||
{{ type }}
|
||||
</button>
|
||||
<ul ngbDropdownMenu class="dropdown-menu">
|
||||
<li class="dropdown-item" *ngFor="let period of types" (click)="changePeriod(period)">
|
||||
{{ period }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nb-card-header>
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
@import '../../../../@theme/styles/themes';
|
||||
|
||||
@include nb-install-component() {
|
||||
nb-card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0.675rem 4rem 0.5rem 1.25rem;
|
||||
@include nb-rtl(padding-right, 1.25rem);
|
||||
@include nb-rtl(padding-left, 4rem);
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
min-width: 120px;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
import { Component, EventEmitter, Input, OnDestroy, Output } from '@angular/core';
|
||||
import { NbThemeService } from '@nebular/theme';
|
||||
import { takeWhile } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-traffic-cards-header',
|
||||
styleUrls: ['./traffic-cards-header.component.scss'],
|
||||
templateUrl: './traffic-cards-header.component.html',
|
||||
})
|
||||
export class TrafficCardsHeaderComponent implements OnDestroy {
|
||||
private alive = true;
|
||||
|
||||
@Output() periodChange = new EventEmitter<string>();
|
||||
|
||||
@Input() type: string = 'week';
|
||||
|
||||
types: string[] = ['week', 'month', 'year'];
|
||||
currentTheme: string;
|
||||
|
||||
constructor(private themeService: NbThemeService) {
|
||||
this.themeService.getJsTheme()
|
||||
.pipe(takeWhile(() => this.alive))
|
||||
.subscribe(theme => {
|
||||
this.currentTheme = theme.name;
|
||||
});
|
||||
}
|
||||
|
||||
changePeriod(period: string): void {
|
||||
this.type = period;
|
||||
this.periodChange.emit(period);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.alive = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<nb-reveal-card [showToggleButton]="false" [revealed]="revealed">
|
||||
<nb-card-front>
|
||||
<nb-card size="small">
|
||||
<ngx-traffic-cards-header (periodChange)="setPeriod($event)"></ngx-traffic-cards-header>
|
||||
<ngx-traffic-front-card [frontCardData]="trafficListData"></ngx-traffic-front-card>
|
||||
<i class="nb-arrow-up" (click)="toggleView()"></i>
|
||||
</nb-card>
|
||||
</nb-card-front>
|
||||
<nb-card-back>
|
||||
<nb-card size="small">
|
||||
<ngx-traffic-cards-header (periodChange)="setPeriod($event)"></ngx-traffic-cards-header>
|
||||
<ngx-traffic-back-card [trafficBarData]="trafficBarData"></ngx-traffic-back-card>
|
||||
<i class="nb-arrow-down" (click)="toggleView()"></i>
|
||||
</nb-card>>
|
||||
</nb-card-back>
|
||||
</nb-reveal-card>
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
@import '../../../@theme/styles/themes';
|
||||
|
||||
@include nb-install-component() {
|
||||
.nb-arrow-up, .nb-arrow-down {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
@include nb-rtl(right, auto);
|
||||
@include nb-rtl(left, 0);
|
||||
padding: 1.5rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
nb-card-back {
|
||||
/deep/ nb-card-header {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
import { Component, OnDestroy } from '@angular/core';
|
||||
import { TrafficList, TrafficListService } from '../../../@core/data/traffic-list.service';
|
||||
import { takeWhile } from 'rxjs/operators';
|
||||
import { TrafficBar, TrafficBarService } from '../../../@core/data/traffic-bar.service';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-traffic-reveal-card',
|
||||
styleUrls: ['./traffic-reveal-card.component.scss'],
|
||||
templateUrl: './traffic-reveal-card.component.html',
|
||||
})
|
||||
export class TrafficRevealCardComponent implements OnDestroy {
|
||||
|
||||
private alive = true;
|
||||
|
||||
trafficBarData: TrafficBar;
|
||||
trafficListData: TrafficList;
|
||||
revealed = false;
|
||||
period: string = 'week';
|
||||
|
||||
constructor(private trafficListService: TrafficListService,
|
||||
private trafficBarService: TrafficBarService) {
|
||||
this.getTrafficFrontCardData(this.period);
|
||||
this.getTrafficBackCardData(this.period);
|
||||
}
|
||||
|
||||
toggleView() {
|
||||
this.revealed = !this.revealed;
|
||||
}
|
||||
|
||||
setPeriod(value: string): void {
|
||||
this.getTrafficFrontCardData(value);
|
||||
this.getTrafficBackCardData(value);
|
||||
}
|
||||
|
||||
getTrafficBackCardData(period: string) {
|
||||
this.trafficBarService.getTrafficBarData(period)
|
||||
.pipe(takeWhile(() => this.alive ))
|
||||
.subscribe(trafficBarData => {
|
||||
this.trafficBarData = trafficBarData;
|
||||
});
|
||||
}
|
||||
|
||||
getTrafficFrontCardData(period: string) {
|
||||
this.trafficListService.getTrafficListData(period)
|
||||
.pipe(takeWhile(() => this.alive ))
|
||||
.subscribe(trafficListData => {
|
||||
this.trafficListData = trafficListData;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.alive = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
@import '../../../@theme/styles/themes';
|
||||
@import '~@nebular/theme/styles/global/typography/typography';
|
||||
|
||||
@include nb-install-component() {
|
||||
nb-card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border: none;
|
||||
}
|
||||
|
||||
nb-card-body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.user-activity-list {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.user-activity-list li {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
position: relative;
|
||||
color: nb-theme(color-fg);
|
||||
padding: nb-theme(card-padding);
|
||||
border-bottom:
|
||||
nb-theme(list-item-border-width)
|
||||
nb-theme(card-header-border-type)
|
||||
nb-theme(separator);
|
||||
|
||||
&:first-child {
|
||||
border-top:
|
||||
nb-theme(list-item-border-width)
|
||||
nb-theme(card-header-border-type)
|
||||
nb-theme(separator);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: nb-theme(layout-bg);
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
content: '';
|
||||
height: 100%;
|
||||
width: 6px;
|
||||
left: 0;
|
||||
top: 0;
|
||||
background-color: nb-theme(color-success);
|
||||
border-radius: nb-theme(radius);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.visited-date,
|
||||
.value,
|
||||
.title {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.visited-date {
|
||||
color: nb-theme(color-fg-heading);
|
||||
|
||||
@include nb-for-theme(cosmic) {
|
||||
color: nb-theme(color-fg);
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.value {
|
||||
margin-top: 0.5rem;
|
||||
color: nb-theme(color-success);
|
||||
}
|
||||
|
||||
.delta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
right: 100%;
|
||||
margin-right: 0.7rem;
|
||||
@include nb-rtl(margin-right, 0);
|
||||
@include nb-rtl(margin-left, 0.7rem);
|
||||
border-left: 6px solid transparent;
|
||||
border-right: 6px solid transparent;
|
||||
}
|
||||
|
||||
&.down {
|
||||
color: text-danger();
|
||||
|
||||
&::before {
|
||||
border-top: 6px solid text-danger();
|
||||
}
|
||||
}
|
||||
|
||||
&.up {
|
||||
color: text-success();
|
||||
|
||||
&::before {
|
||||
border-bottom: 6px solid text-success();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include nb-for-theme(cosmic) {
|
||||
.user-activity-list li {
|
||||
&:hover {
|
||||
&::before {
|
||||
$color-top: nb-theme(btn-success-bg);
|
||||
$color-bottom: btn-hero-success-left-color();
|
||||
|
||||
background-image: linear-gradient(to top, $color-top, $color-bottom);
|
||||
box-shadow: 0 0 16px -2px btn-hero-success-middle-color();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.delta {
|
||||
&.down, &.up {
|
||||
color: nb-theme(color-fg-heading);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include nb-for-theme(corporate) {
|
||||
.user-activity-list li {
|
||||
border-color: nb-theme(tabs-separator);
|
||||
|
||||
&:first-child {
|
||||
border-color: nb-theme(tabs-separator);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&::before {
|
||||
background-color: nb-theme(color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.visited-pages-count {
|
||||
.value {
|
||||
color: nb-theme(color-fg-heading);
|
||||
}
|
||||
}
|
||||
|
||||
.delta {
|
||||
&.up {
|
||||
color: text-primary();
|
||||
|
||||
&::before {
|
||||
border-bottom-color: text-primary();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { NbThemeService } from '@nebular/theme';
|
||||
import { takeWhile } from 'rxjs/operators';
|
||||
|
||||
import { UserActivityService, UserActive } from '../../../@core/data/user-activity.service';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-user-activity',
|
||||
styleUrls: ['./user-activity.component.scss'],
|
||||
template: `
|
||||
<nb-card size="medium">
|
||||
<nb-card-header>
|
||||
<span>User Activity</span>
|
||||
<div class="dropdown ghost-dropdown" ngbDropdown>
|
||||
<button type="button" class="btn btn-sm" ngbDropdownToggle
|
||||
[ngClass]="{ 'btn-success': currentTheme == 'default', 'btn-primary': currentTheme != 'default'}">
|
||||
{{ type }}
|
||||
</button>
|
||||
<ul ngbDropdownMenu class="dropdown-menu">
|
||||
<li class="dropdown-item"
|
||||
*ngFor="let t of types"
|
||||
(click)="getUserActivity(t); type = t">
|
||||
{{ t }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nb-card-header>
|
||||
<nb-card-body>
|
||||
<ul class="user-activity-list">
|
||||
<li *ngFor="let item of userActivity">
|
||||
<div class="visited-date">
|
||||
{{ item.date }}
|
||||
</div>
|
||||
<div class="visited-pages-count">
|
||||
<div class="title">Pages Visit</div>
|
||||
<div class="value">{{ item.pagesVisitCount }}</div>
|
||||
</div>
|
||||
<div class="visited-percentages">
|
||||
<div class="title">New visits, %</div>
|
||||
<div class="delta value"
|
||||
[class.up]="item.deltaUp"
|
||||
[class.down]="!item.deltaUp">
|
||||
{{ item.newVisits }}%
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</nb-card-body>
|
||||
</nb-card>
|
||||
`,
|
||||
})
|
||||
export class ECommerceUserActivityComponent implements OnDestroy, OnInit {
|
||||
|
||||
private alive = true;
|
||||
|
||||
userActivity: UserActive[] = [];
|
||||
type = 'month';
|
||||
types = ['week', 'month', 'year'];
|
||||
currentTheme: string;
|
||||
|
||||
constructor(private themeService: NbThemeService,
|
||||
private userActivityService: UserActivityService) {
|
||||
this.themeService.getJsTheme()
|
||||
.pipe(takeWhile(() => this.alive))
|
||||
.subscribe(theme => {
|
||||
this.currentTheme = theme.name;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.getUserActivity(this.type);
|
||||
}
|
||||
|
||||
getUserActivity(period: string) {
|
||||
this.userActivityService.getUserActivityData(period)
|
||||
.subscribe(userActivityData => {
|
||||
this.userActivity = userActivityData;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.alive = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
@import '../../../../@theme/styles/themes';
|
||||
|
||||
@include nb-install-component() {
|
||||
display: block;
|
||||
height: 290px;
|
||||
width: 100%;
|
||||
|
||||
.echart {
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,241 @@
|
|||
import { delay, takeWhile } from 'rxjs/operators';
|
||||
import { AfterViewInit, Component, OnDestroy } from '@angular/core';
|
||||
import { NbThemeService } from '@nebular/theme';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-visitors-analytics-chart',
|
||||
styleUrls: ['./visitors-analytics-chart.component.scss'],
|
||||
template: `
|
||||
<div echarts [options]="option" class="echart"></div>
|
||||
`,
|
||||
})
|
||||
export class ECommerceVisitorsAnalyticsChartComponent implements AfterViewInit, OnDestroy {
|
||||
|
||||
private alive = true;
|
||||
private innerLinePoints: number[] = [
|
||||
94, 188, 225, 244, 253, 254, 249, 235, 208,
|
||||
173, 141, 118, 105, 97, 94, 96, 104, 121, 147,
|
||||
183, 224, 265, 302, 333, 358, 375, 388, 395,
|
||||
400, 400, 397, 390, 377, 360, 338, 310, 278,
|
||||
241, 204, 166, 130, 98, 71, 49, 32, 20, 13, 9,
|
||||
];
|
||||
private outerLinePoints: number[] = [
|
||||
85, 71, 59, 50, 45, 42, 41, 44 , 58, 88,
|
||||
136 , 199, 267, 326, 367, 391, 400, 397,
|
||||
376, 319, 200, 104, 60, 41, 36, 37, 44,
|
||||
55, 74, 100 , 131, 159, 180, 193, 199, 200,
|
||||
195, 184, 164, 135, 103, 73, 50, 33, 22, 15, 11,
|
||||
];
|
||||
private months: string[] = [
|
||||
'Jan', 'Feb', 'Mar',
|
||||
'Apr', 'May', 'Jun',
|
||||
'Jul', 'Aug', 'Sep',
|
||||
'Oct', 'Nov', 'Dec',
|
||||
];
|
||||
|
||||
option: any;
|
||||
data: Array<any>;
|
||||
themeSubscription: any;
|
||||
|
||||
constructor(private theme: NbThemeService) {
|
||||
const outerLinePointsLength = this.outerLinePoints.length;
|
||||
const monthsLength = this.months.length;
|
||||
|
||||
this.data = this.outerLinePoints.map((p, index) => {
|
||||
const monthIndex = Math.round(index / 4);
|
||||
const label = (index % Math.round(outerLinePointsLength / monthsLength) === 0)
|
||||
? this.months[monthIndex]
|
||||
: '';
|
||||
|
||||
return {
|
||||
label,
|
||||
value: p,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this.theme.getJsTheme()
|
||||
.pipe(
|
||||
delay(1),
|
||||
takeWhile(() => this.alive),
|
||||
)
|
||||
.subscribe(config => {
|
||||
const eTheme: any = config.variables.visitors;
|
||||
|
||||
this.setOptions(eTheme);
|
||||
});
|
||||
}
|
||||
|
||||
setOptions(eTheme) {
|
||||
this.option = {
|
||||
grid: {
|
||||
left: 40,
|
||||
top: 20,
|
||||
right: 0,
|
||||
bottom: 60,
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'line',
|
||||
lineStyle: {
|
||||
color: eTheme.tooltipLineColor,
|
||||
width: eTheme.tooltipLineWidth,
|
||||
},
|
||||
},
|
||||
textStyle: {
|
||||
color: eTheme.tooltipTextColor,
|
||||
fontSize: 20,
|
||||
fontWeight: eTheme.tooltipFontWeight,
|
||||
},
|
||||
position: 'top',
|
||||
backgroundColor: eTheme.tooltipBg,
|
||||
borderColor: eTheme.tooltipBorderColor,
|
||||
borderWidth: 3,
|
||||
formatter: (params) => {
|
||||
return Math.round(parseInt(params[0].value, 10));
|
||||
},
|
||||
extraCssText: eTheme.tooltipExtraCss,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
offset: 25,
|
||||
data: this.data.map(i => i.label),
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
axisLabel: {
|
||||
color: eTheme.axisTextColor,
|
||||
fontSize: eTheme.axisFontSize,
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: eTheme.axisLineColor,
|
||||
width: '2',
|
||||
},
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
boundaryGap: false,
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: eTheme.axisLineColor,
|
||||
width: '1',
|
||||
},
|
||||
},
|
||||
axisLabel: {
|
||||
color: eTheme.axisTextColor,
|
||||
fontSize: eTheme.axisFontSize,
|
||||
},
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
splitLine: {
|
||||
|
||||
lineStyle: {
|
||||
color: eTheme.yAxisSplitLine,
|
||||
width: '1',
|
||||
},
|
||||
},
|
||||
},
|
||||
series: [
|
||||
this.getInnerLine(eTheme),
|
||||
this.getOuterLine(eTheme),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
getOuterLine(eTheme) {
|
||||
return {
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbolSize: 20,
|
||||
itemStyle: {
|
||||
normal: {
|
||||
opacity: 0,
|
||||
},
|
||||
emphasis: {
|
||||
color: '#ffffff',
|
||||
borderColor: eTheme.itemBorderColor,
|
||||
borderWidth: 2,
|
||||
opacity: 1,
|
||||
},
|
||||
},
|
||||
lineStyle: {
|
||||
normal: {
|
||||
width: eTheme.lineWidth,
|
||||
type: eTheme.lineStyle,
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
|
||||
offset: 0,
|
||||
color: eTheme.lineGradFrom,
|
||||
}, {
|
||||
offset: 1,
|
||||
color: eTheme.lineGradTo,
|
||||
}]),
|
||||
shadowColor: eTheme.lineShadow,
|
||||
shadowBlur: 6,
|
||||
shadowOffsetY: 12,
|
||||
},
|
||||
},
|
||||
areaStyle: {
|
||||
normal: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
|
||||
offset: 0,
|
||||
color: eTheme.areaGradFrom,
|
||||
}, {
|
||||
offset: 1,
|
||||
color: eTheme.areaGradTo,
|
||||
}]),
|
||||
},
|
||||
},
|
||||
data: this.data.map(i => i.value),
|
||||
};
|
||||
}
|
||||
|
||||
getInnerLine(eTheme) {
|
||||
return {
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbolSize: 20,
|
||||
tooltip: {
|
||||
show: false,
|
||||
extraCssText: '',
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
opacity: 0,
|
||||
},
|
||||
emphasis: {
|
||||
opacity: 0,
|
||||
},
|
||||
},
|
||||
lineStyle: {
|
||||
normal: {
|
||||
width: eTheme.innerLineWidth,
|
||||
type: eTheme.innerLineStyle,
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1),
|
||||
},
|
||||
},
|
||||
areaStyle: {
|
||||
normal: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
|
||||
offset: 0,
|
||||
color: eTheme.innerAreaGradFrom,
|
||||
}, {
|
||||
offset: 1,
|
||||
color: eTheme.innerAreaGradTo,
|
||||
}]),
|
||||
opacity: 1,
|
||||
},
|
||||
},
|
||||
data: this.innerLinePoints,
|
||||
};
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.alive = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<nb-card size="medium">
|
||||
<nb-card-header>
|
||||
<div class="title">Visitors Analytics</div>
|
||||
<div class="sub-title">Consumption</div>
|
||||
</nb-card-header>
|
||||
<nb-card-body>
|
||||
<div class="chart-container">
|
||||
<div class="chart-header">
|
||||
<ngx-legend-chart [legendItems]="chartLegend"></ngx-legend-chart>
|
||||
</div>
|
||||
<ngx-visitors-analytics-chart></ngx-visitors-analytics-chart>
|
||||
</div>
|
||||
</nb-card-body>
|
||||
|
||||
<ngx-slide-out [showVisitorsStatistics]="true">
|
||||
<ngx-visitors-statistics></ngx-visitors-statistics>
|
||||
</ngx-slide-out>
|
||||
</nb-card>
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
@import '../../../@theme/styles/themes';
|
||||
@import '~bootstrap/scss/mixins/breakpoints';
|
||||
@import '~@nebular/theme/styles/global/bootstrap/breakpoints';
|
||||
|
||||
@include nb-install-component() {
|
||||
position: relative;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
|
||||
nb-card {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
nb-card-header {
|
||||
border-bottom: none;
|
||||
|
||||
.title {
|
||||
font-size: 1.5rem;
|
||||
font-weight: nb-theme(font-weight-bold);
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
color: nb-theme(color-fg);
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.chart-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 2.125rem;
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(is) {
|
||||
ngx-legend-chart {
|
||||
/deep/ .legends {
|
||||
padding-left: 0;
|
||||
font-size: nb-theme(font-size-sm);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
import { Component, OnDestroy } from '@angular/core';
|
||||
import { takeWhile } from 'rxjs/operators';
|
||||
import { NbThemeService } from '@nebular/theme';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-ecommerce-visitors-analytics',
|
||||
styleUrls: ['./visitors-analytics.component.scss'],
|
||||
templateUrl: './visitors-analytics.component.html',
|
||||
})
|
||||
export class ECommerceVisitorsAnalyticsComponent implements OnDestroy {
|
||||
private alive = true;
|
||||
|
||||
chartLegend: {iconColor: string; title: string}[];
|
||||
|
||||
constructor(private themeService: NbThemeService) {
|
||||
this.themeService.getJsTheme()
|
||||
.pipe(takeWhile(() => this.alive))
|
||||
.subscribe(theme => {
|
||||
this.setLegendItems(theme.variables.visitorsLegend);
|
||||
});
|
||||
}
|
||||
|
||||
setLegendItems(visitorsLegend): void {
|
||||
this.chartLegend = [
|
||||
{
|
||||
iconColor: visitorsLegend.firstIcon,
|
||||
title: 'Unique Visitors',
|
||||
},
|
||||
{
|
||||
iconColor: visitorsLegend.secondIcon,
|
||||
title: 'Page Views',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.alive = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<div class="visitors-statistics">
|
||||
<div class="statistics-header">
|
||||
<div class="visitors-value">1,100</div>
|
||||
<div class="visitors-title">New Visitors</div>
|
||||
</div>
|
||||
<div class="statistics-chart">
|
||||
<div echarts [options]="option" class="echart"></div>
|
||||
</div>
|
||||
<div class="statistics-footer">
|
||||
<div class="chart-values">
|
||||
<span class="chart-value">25%</span>
|
||||
<span class="chart-value">75%</span>
|
||||
</div>
|
||||
<ngx-legend-chart class="visitors-statistics-legend" [legendItems]="chartLegend"></ngx-legend-chart>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
@import '../../../../@theme/styles/themes';
|
||||
|
||||
@include nb-install-component() {
|
||||
|
||||
.visitors-value {
|
||||
font-size: 3rem;
|
||||
font-weight: nb-theme(font-weight-light);
|
||||
color: nb-theme(color-fg-heading);
|
||||
line-height: 0.8;
|
||||
}
|
||||
|
||||
.visitors-title {
|
||||
margin-top: 1rem;
|
||||
font-size: 1.25rem;
|
||||
color: nb-theme(color-fg);
|
||||
}
|
||||
|
||||
.visitors-statistics {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.statistics-chart {
|
||||
margin-top: 3.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
width: 100%;
|
||||
|
||||
.echart {
|
||||
display: block;
|
||||
height: 190px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.chart-values {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.chart-value {
|
||||
color: nb-theme(color-fg-heading);
|
||||
font-size: 2rem;
|
||||
font-weight: nb-theme(font-weight-bold);
|
||||
margin-bottom: 1rem;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
// legend start
|
||||
.visitors-statistics-legend {
|
||||
/deep/ .legends {
|
||||
padding: 0;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
/deep/ .legend {
|
||||
flex: 1;
|
||||
margin-left: 0;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
// legend end
|
||||
}
|
||||
|
|
@ -0,0 +1,196 @@
|
|||
import { AfterViewInit, Component, OnDestroy } from '@angular/core';
|
||||
import { NbThemeService } from '@nebular/theme';
|
||||
import { delay, takeWhile } from 'rxjs/operators';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-visitors-statistics',
|
||||
styleUrls: ['./visitors-statistics.component.scss'],
|
||||
templateUrl: './visitors-statistics.component.html',
|
||||
})
|
||||
export class ECommerceVisitorsStatisticsComponent implements AfterViewInit, OnDestroy {
|
||||
|
||||
private alive = true;
|
||||
private value = 75;
|
||||
|
||||
option: any = {};
|
||||
chartLegend: {iconColor: string; title: string}[];
|
||||
|
||||
constructor(private theme: NbThemeService) {
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.theme.getJsTheme()
|
||||
.pipe(
|
||||
takeWhile(() => this.alive),
|
||||
delay(1),
|
||||
)
|
||||
.subscribe(config => {
|
||||
const variables: any = config.variables;
|
||||
const visitorsPieLegend: any = config.variables.visitorsPieLegend;
|
||||
|
||||
this.setOptions(variables);
|
||||
this.setLegendItems(visitorsPieLegend);
|
||||
});
|
||||
}
|
||||
|
||||
setLegendItems(visitorsPieLegend) {
|
||||
this.chartLegend = [
|
||||
{
|
||||
iconColor: visitorsPieLegend.firstSection,
|
||||
title: 'New Visitors',
|
||||
},
|
||||
{
|
||||
iconColor: visitorsPieLegend.secondSection,
|
||||
title: 'Return Visitors',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
setOptions(variables) {
|
||||
const visitorsPie: any = variables.visitorsPie;
|
||||
|
||||
this.option = {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: ' ',
|
||||
clockWise: true,
|
||||
hoverAnimation: false,
|
||||
type: 'pie',
|
||||
center: ['50%', '50%'],
|
||||
radius: visitorsPie.firstPieRadius,
|
||||
data: [
|
||||
{
|
||||
value: this.value,
|
||||
name: ' ',
|
||||
label: {
|
||||
normal: {
|
||||
position: 'center',
|
||||
formatter: '',
|
||||
textStyle: {
|
||||
fontSize: '22',
|
||||
fontFamily: variables.fontSecondary,
|
||||
fontWeight: '600',
|
||||
color: variables.fgHeading,
|
||||
},
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
show: false,
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{
|
||||
offset: 0,
|
||||
color: visitorsPie.firstPieGradientLeft,
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: visitorsPie.firstPieGradientRight,
|
||||
},
|
||||
]),
|
||||
shadowColor: visitorsPie.firstPieShadowColor,
|
||||
shadowBlur: 0,
|
||||
shadowOffsetX: 0,
|
||||
shadowOffsetY: 3,
|
||||
},
|
||||
},
|
||||
hoverAnimation: false,
|
||||
},
|
||||
{
|
||||
value: 100 - this.value,
|
||||
name: ' ',
|
||||
tooltip: {
|
||||
show: false,
|
||||
},
|
||||
label: {
|
||||
normal: {
|
||||
position: 'inner',
|
||||
},
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: variables.layoutBg,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: ' ',
|
||||
clockWise: true,
|
||||
hoverAnimation: false,
|
||||
type: 'pie',
|
||||
center: ['50%', '50%'],
|
||||
radius: visitorsPie.secondPieRadius,
|
||||
data: [
|
||||
{
|
||||
value: this.value,
|
||||
name: ' ',
|
||||
label: {
|
||||
normal: {
|
||||
position: 'center',
|
||||
formatter: '',
|
||||
textStyle: {
|
||||
fontSize: '22',
|
||||
fontFamily: variables.fontSecondary,
|
||||
fontWeight: '600',
|
||||
color: variables.fgHeading,
|
||||
},
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
show: false,
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1),
|
||||
},
|
||||
},
|
||||
hoverAnimation: false,
|
||||
},
|
||||
{
|
||||
value: 100 - this.value,
|
||||
name: ' ',
|
||||
tooltip: {
|
||||
show: false,
|
||||
},
|
||||
label: {
|
||||
normal: {
|
||||
position: 'inner',
|
||||
},
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{
|
||||
offset: 0,
|
||||
color: visitorsPie.secondPieGradientLeft,
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: visitorsPie.secondPieGradientRight,
|
||||
},
|
||||
]),
|
||||
shadowColor: visitorsPie.secondPieShadowColor,
|
||||
shadowBlur: 0,
|
||||
shadowOffsetX: visitorsPie.shadowOffsetX,
|
||||
shadowOffsetY: visitorsPie.shadowOffsetY,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.alive = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,11 +2,16 @@ import { NbMenuItem } from '@nebular/theme';
|
|||
|
||||
export const MENU_ITEMS: NbMenuItem[] = [
|
||||
{
|
||||
title: 'Dashboard',
|
||||
icon: 'nb-home',
|
||||
title: 'E-commerce',
|
||||
icon: 'nb-e-commerce',
|
||||
link: '/pages/dashboard',
|
||||
home: true,
|
||||
},
|
||||
{
|
||||
title: 'IoT Dashboard',
|
||||
icon: 'nb-home',
|
||||
link: '/pages/iot-dashboard',
|
||||
},
|
||||
{
|
||||
title: 'FEATURES',
|
||||
group: true,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { NgModule } from '@angular/core';
|
|||
|
||||
import { PagesComponent } from './pages.component';
|
||||
import { DashboardComponent } from './dashboard/dashboard.component';
|
||||
import { ECommerceComponent } from './e-commerce/e-commerce.component';
|
||||
import { NotFoundComponent } from './miscellaneous/not-found/not-found.component';
|
||||
|
||||
const routes: Routes = [{
|
||||
|
|
@ -10,6 +11,9 @@ const routes: Routes = [{
|
|||
component: PagesComponent,
|
||||
children: [{
|
||||
path: 'dashboard',
|
||||
component: ECommerceComponent,
|
||||
}, {
|
||||
path: 'iot-dashboard',
|
||||
component: DashboardComponent,
|
||||
}, {
|
||||
path: 'ui-features',
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { NgModule } from '@angular/core';
|
|||
|
||||
import { PagesComponent } from './pages.component';
|
||||
import { DashboardModule } from './dashboard/dashboard.module';
|
||||
import { ECommerceModule } from './e-commerce/e-commerce.module';
|
||||
import { PagesRoutingModule } from './pages-routing.module';
|
||||
import { ThemeModule } from '../@theme/theme.module';
|
||||
import { MiscellaneousModule } from './miscellaneous/miscellaneous.module';
|
||||
|
|
@ -15,6 +16,7 @@ const PAGES_COMPONENTS = [
|
|||
PagesRoutingModule,
|
||||
ThemeModule,
|
||||
DashboardModule,
|
||||
ECommerceModule,
|
||||
MiscellaneousModule,
|
||||
],
|
||||
declarations: [
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ export class IconsComponent {
|
|||
|
||||
icons = {
|
||||
|
||||
nebular: ['nb-alert', 'nb-angle-double-left', 'nb-arrow-down', 'nb-arrow-dropdown', 'nb-arrow-dropleft',
|
||||
nebular: ['nb-alert', 'nb-angle-double-left', 'nb-angle-double-right',
|
||||
'nb-arrow-down', 'nb-arrow-dropdown', 'nb-arrow-dropleft',
|
||||
'nb-arrow-dropright', 'nb-arrow-dropup', 'nb-arrow-left', 'nb-arrow-retweet', 'nb-arrow-right',
|
||||
'nb-arrow-thin-down', 'nb-arrow-thin-left', 'nb-arrow-thin-right', 'nb-arrow-thin-up',
|
||||
'nb-arrow-up', 'nb-audio', 'nb-bar-chart', 'nb-checkmark', 'nb-chevron-down',
|
||||
|
|
@ -25,7 +26,8 @@ export class IconsComponent {
|
|||
'nb-rainy', 'nb-roller-shades', 'nb-search', 'nb-shuffle', 'nb-skip-backward',
|
||||
'nb-skip-backward-outline', 'nb-skip-forward', 'nb-skip-forward-outline', 'nb-snowy-circled',
|
||||
'nb-square', 'nb-square-outline', 'nb-star', 'nb-sunny', 'nb-sunny-circled', 'nb-tables', 'nb-title',
|
||||
'nb-trash', 'nb-volume-high', 'nb-volume-mute', 'nb-drop', 'nb-drops', 'nb-info'],
|
||||
'nb-trash', 'nb-volume-high', 'nb-volume-mute', 'nb-drop', 'nb-drops', 'nb-info', 'nb-expand', 'nb-collapse',
|
||||
'nb-e-commerce'],
|
||||
|
||||
ionicons: [
|
||||
'ion-ionic', 'ion-arrow-right-b', 'ion-arrow-down-b', 'ion-arrow-left-b', 'ion-arrow-up-c', 'ion-arrow-right-c',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue