mirror of
https://github.com/valitydev/fraudbusters-ui.git
synced 2024-11-06 00:25:17 +00:00
Add analytics
This commit is contained in:
parent
6c70c8144f
commit
73c5e9319b
@ -50,7 +50,7 @@ module.exports = {
|
||||
'src/app/sections/notifications/notifications.component.ts',
|
||||
'src/app/sections/load/fraud-uploader/dnd.directive.ts',
|
||||
'src/app/sections/groups/services/remove-reference/remove-reference.service.ts',
|
||||
'src/app/shared/components/testing-data-set-list/services/data-set/testing-data-set.service.ts',
|
||||
'src/app/shared/components/testing-data-set-list/services/data-set/testing-analytics.service.ts',
|
||||
],
|
||||
overrides: [
|
||||
{
|
||||
|
@ -39,7 +39,7 @@
|
||||
},
|
||||
{
|
||||
"name": "lists",
|
||||
"x-displayName": "Lists"
|
||||
"x-displayName": "Payments lists"
|
||||
},
|
||||
{
|
||||
"name": "emulation",
|
||||
@ -60,6 +60,22 @@
|
||||
{
|
||||
"name": "historical-data",
|
||||
"x-displayName": "Historical data"
|
||||
},
|
||||
{
|
||||
"name": "notification-template",
|
||||
"x-displayName": "Notification template"
|
||||
},
|
||||
{
|
||||
"name": "data-sets",
|
||||
"x-displayName": "Data sets"
|
||||
},
|
||||
{
|
||||
"name": "analytics",
|
||||
"x-displayName": "Analytics"
|
||||
},
|
||||
{
|
||||
"name": "dictionary",
|
||||
"x-displayName": "Dictionary"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
@ -2701,6 +2717,318 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/analytics/fraud-payments/count": {
|
||||
"get": {
|
||||
"summary": "Получить количество платежей с подозрением на мошенничество",
|
||||
"operationId": "getFraudPaymentsCount",
|
||||
"tags": ["analytics"],
|
||||
"parameters": [
|
||||
{
|
||||
"$ref": "#/components/parameters/fromTime"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/parameters/toTime"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/parameters/currency"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/parameters/merchantId"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/parameters/shopId"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Attempted fraud-payments count",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/CountResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
"403": {
|
||||
"description": "Forbidden"
|
||||
},
|
||||
"404": {
|
||||
"description": "Not Found"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/analytics/fraud-payments/blocked/count": {
|
||||
"get": {
|
||||
"summary": "Получить количество заблокированных мошеннических платежей",
|
||||
"operationId": "getBlockedFraudPaymentsCount",
|
||||
"tags": ["analytics"],
|
||||
"parameters": [
|
||||
{
|
||||
"$ref": "#/components/parameters/fromTime"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/parameters/toTime"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/parameters/currency"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/parameters/merchantId"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/parameters/shopId"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Blocked fraud-payments count",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/CountResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
"403": {
|
||||
"description": "Forbidden"
|
||||
},
|
||||
"404": {
|
||||
"description": "Not Found"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/analytics/fraud-payments/blocked/sum": {
|
||||
"get": {
|
||||
"summary": "Получить общую сумму заблокированных мошеннических платежей",
|
||||
"operationId": "getBlockedFraudPaymentsSum",
|
||||
"tags": ["analytics"],
|
||||
"parameters": [
|
||||
{
|
||||
"$ref": "#/components/parameters/fromTime"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/parameters/toTime"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/parameters/currency"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/parameters/merchantId"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/parameters/shopId"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Blocked fraud-payments sum",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SumResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
"403": {
|
||||
"description": "Forbidden"
|
||||
},
|
||||
"404": {
|
||||
"description": "Not Found"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/analytics/fraud-payments/blocked/count/ratio": {
|
||||
"get": {
|
||||
"summary": "Получить соотношение количества заблокированных платежей к общему количеству мошеннических платежей",
|
||||
"operationId": "getBlockedFraudPaymentsCountRatio",
|
||||
"tags": ["analytics"],
|
||||
"parameters": [
|
||||
{
|
||||
"$ref": "#/components/parameters/fromTime"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/parameters/toTime"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/parameters/currency"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/parameters/merchantId"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/parameters/shopId"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Blocked fraud-payments count ratio",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/RatioResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
"403": {
|
||||
"description": "Forbidden"
|
||||
},
|
||||
"404": {
|
||||
"description": "Not Found"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/analytics/fraud-payments/scores/split-count/ratio": {
|
||||
"get": {
|
||||
"summary": "Получить соотношение количества платежей по различными оценкам риска операции к общему числу мошеннических \nплатежей в разрезе временного интервала\n",
|
||||
"operationId": "getFraudPaymentsScoreSplitCountRatio",
|
||||
"tags": ["analytics"],
|
||||
"parameters": [
|
||||
{
|
||||
"$ref": "#/components/parameters/fromTime"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/parameters/toTime"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/parameters/currency"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/parameters/splitUnit"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/parameters/merchantId"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/parameters/shopId"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Split fraud-payments risc score count ratio",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SplitRiskScoreCountRatioResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
"403": {
|
||||
"description": "Forbidden"
|
||||
},
|
||||
"404": {
|
||||
"description": "Not Found"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/analytics/fraud-payments/results/summary": {
|
||||
"get": {
|
||||
"summary": "Получить статистику по результатам проверок платежей",
|
||||
"operationId": "getFraudPaymentsResultsSummary",
|
||||
"tags": ["analytics"],
|
||||
"parameters": [
|
||||
{
|
||||
"$ref": "#/components/parameters/fromTime"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/parameters/toTime"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/parameters/currency"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/parameters/merchantId"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/parameters/shopId"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Fraud-payments results summary",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/FraudResultListSummaryResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
"403": {
|
||||
"description": "Forbidden"
|
||||
},
|
||||
"404": {
|
||||
"description": "Not Found"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/dictionaries/currencies": {
|
||||
"get": {
|
||||
"summary": "Получить список денежных единиц",
|
||||
"operationId": "getCurrencies",
|
||||
"tags": ["dictionary"],
|
||||
"parameters": [
|
||||
{
|
||||
"$ref": "#/components/parameters/fromTime"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/parameters/toTime"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/parameters/shopId"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "List of currencies",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ListResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
"403": {
|
||||
"description": "Forbidden"
|
||||
},
|
||||
"404": {
|
||||
"description": "Not Found"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"servers": [
|
||||
@ -3804,6 +4132,139 @@
|
||||
"$ref": "#/components/schemas/MerchantInfo"
|
||||
}
|
||||
}
|
||||
},
|
||||
"CountResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"count": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
}
|
||||
}
|
||||
},
|
||||
"SumResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sum": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
}
|
||||
}
|
||||
},
|
||||
"RatioResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ratio": {
|
||||
"type": "number",
|
||||
"format": "float"
|
||||
}
|
||||
}
|
||||
},
|
||||
"SplitUnit": {
|
||||
"description": "Единица времени сегмента разбиения",
|
||||
"type": "string",
|
||||
"enum": ["minute", "hour", "day", "week", "month", "year"]
|
||||
},
|
||||
"OffsetCountRatio": {
|
||||
"type": "object",
|
||||
"allOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["countRatio", "offset"],
|
||||
"properties": {
|
||||
"countRatio": {
|
||||
"description": "Соотношение платежей c данной оценкой к общему количеству платежей",
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"minimum": 0
|
||||
},
|
||||
"offset": {
|
||||
"description": "Номер интервала в списке",
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"minimum": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"RiscScoreOffsetCountRatio": {
|
||||
"type": "object",
|
||||
"required": ["score", "offsetCountRatio"],
|
||||
"properties": {
|
||||
"score": {
|
||||
"description": "Оценка платежа",
|
||||
"type": "string"
|
||||
},
|
||||
"offsetCountRatio": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/OffsetCountRatio"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"SplitRiskScoreCountRatioResponse": {
|
||||
"type": "object",
|
||||
"required": ["splitUnit", "offsetCountRatios"],
|
||||
"properties": {
|
||||
"splitUnit": {
|
||||
"$ref": "#/components/schemas/SplitUnit"
|
||||
},
|
||||
"offsetCountRatios": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/RiscScoreOffsetCountRatio"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Summary": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"count": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"sum": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"ratio": {
|
||||
"type": "number",
|
||||
"format": "float"
|
||||
}
|
||||
}
|
||||
},
|
||||
"FraudResultSummary": {
|
||||
"type": "object",
|
||||
"required": ["status", "summary"],
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"checkedRule": {
|
||||
"type": "string"
|
||||
},
|
||||
"template": {
|
||||
"type": "string"
|
||||
},
|
||||
"summary": {
|
||||
"$ref": "#/components/schemas/Summary"
|
||||
}
|
||||
}
|
||||
},
|
||||
"FraudResultListSummaryResponse": {
|
||||
"type": "object",
|
||||
"required": ["result"],
|
||||
"properties": {
|
||||
"result": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/FraudResultSummary"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": {
|
||||
@ -3876,6 +4337,59 @@
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"fromTime": {
|
||||
"name": "fromTime",
|
||||
"in": "query",
|
||||
"description": "Начало временного отрезка",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"toTime": {
|
||||
"name": "toTime",
|
||||
"in": "query",
|
||||
"description": "Конец временного отрезка",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"currency": {
|
||||
"name": "currency",
|
||||
"in": "query",
|
||||
"description": "Денежные единицы",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"merchantId": {
|
||||
"name": "merchantId",
|
||||
"in": "query",
|
||||
"description": "Идентификатор мерчанта",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"shopId": {
|
||||
"name": "shopId",
|
||||
"in": "query",
|
||||
"description": "Идентификатор магазина",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"splitUnit": {
|
||||
"name": "splitUnit",
|
||||
"in": "query",
|
||||
"description": "Единица времени сегмента разбиения",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"enum": ["minute", "hour", "day", "week", "month", "year"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
16952
package-lock.json
generated
16952
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -39,6 +39,7 @@
|
||||
"@angular/platform-browser-dynamic": "~10.1.3",
|
||||
"@angular/router": "~10.1.3",
|
||||
"angular-file": "^3.1.2",
|
||||
"apexcharts": "^3.35.3",
|
||||
"coerce-property": "^0.3.2",
|
||||
"concurrently": "^7.0.0",
|
||||
"eslint": "^8.10.0",
|
||||
@ -93,4 +94,4 @@
|
||||
"lint-staged": {
|
||||
"*.{html,js,ts,css,scss,md,json,prettierrc,svg,huskyrc}": "prettier --write"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
8
src/app/api/payments/analytics/analytics.module.ts
Normal file
8
src/app/api/payments/analytics/analytics.module.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { AnalyticsService } from './analytics.service';
|
||||
|
||||
@NgModule({
|
||||
providers: [AnalyticsService],
|
||||
})
|
||||
export class AnalyticsModule {}
|
24
src/app/api/payments/analytics/analytics.service.ts
Normal file
24
src/app/api/payments/analytics/analytics.service.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { ConfigService } from '../../../config';
|
||||
import { filterParameters } from '../../../shared/utils/filter-params';
|
||||
import { ListResponse } from '../../fb-management/swagger-codegen/model/listResponse';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { SearchAnalyticsCurrenciesParams } from '../../../sections/analytics/components/search/search-currencies-params';
|
||||
|
||||
@Injectable()
|
||||
export class AnalyticsService {
|
||||
private readonly fbAnalyticsDictionaries = `${this.configService.fbManagementEndpoint}/dictionaries`;
|
||||
|
||||
constructor(private http: HttpClient, private configService: ConfigService) {}
|
||||
|
||||
getCurrencies(params: SearchAnalyticsCurrenciesParams): Observable<string[]> {
|
||||
return this.http
|
||||
.get<ListResponse>(`${this.fbAnalyticsDictionaries}/currencies`, {
|
||||
params: filterParameters(params),
|
||||
})
|
||||
.pipe(map((response: ListResponse) => response.result));
|
||||
}
|
||||
}
|
2
src/app/api/payments/analytics/index.ts
Normal file
2
src/app/api/payments/analytics/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './analytics.module';
|
||||
export * from './analytics.service';
|
@ -7,7 +7,7 @@ import { RouterModule } from '@angular/router';
|
||||
[
|
||||
{
|
||||
path: '',
|
||||
redirectTo: 'emulation/template',
|
||||
redirectTo: 'analytics',
|
||||
pathMatch: 'full',
|
||||
},
|
||||
],
|
||||
|
33
src/app/sections/analytics/analytics-routing.module.ts
Normal file
33
src/app/sections/analytics/analytics-routing.module.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
import { AuthGuard, Roles } from '../../auth';
|
||||
import { AnalyticsComponent } from './analytics.component';
|
||||
import { BaseAnalyticsComponent } from './components/base/base-analytics.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild([
|
||||
{
|
||||
path: '',
|
||||
component: AnalyticsComponent,
|
||||
canActivate: [AuthGuard],
|
||||
data: { roles: [Roles.FraudOfficer] },
|
||||
children: [
|
||||
{
|
||||
path: 'base',
|
||||
component: BaseAnalyticsComponent,
|
||||
canActivate: [AuthGuard],
|
||||
data: { roles: [Roles.FraudOfficer] },
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
redirectTo: 'base',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class AnalyticsRoutingModule {}
|
16
src/app/sections/analytics/analytics.component.html
Normal file
16
src/app/sections/analytics/analytics.component.html
Normal file
@ -0,0 +1,16 @@
|
||||
<div class="mat-headline">Analytics</div>
|
||||
<div fxLayout="column" fxLayoutGap="8px">
|
||||
<nav mat-tab-nav-bar>
|
||||
<a
|
||||
mat-tab-link
|
||||
*ngFor="let link of links"
|
||||
[routerLink]="link.path"
|
||||
routerLinkActive
|
||||
#rla="routerLinkActive"
|
||||
[active]="rla.isActive || hasActiveFragments(link.otherActiveUrlFragments)"
|
||||
>
|
||||
{{ link.name }}
|
||||
</a>
|
||||
</nav>
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
26
src/app/sections/analytics/analytics.component.ts
Normal file
26
src/app/sections/analytics/analytics.component.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { hasActiveFragments } from '../../shared/utils/has-active-fragments';
|
||||
import { LAYOUT_GAP_S } from '../../tokens';
|
||||
|
||||
@Component({
|
||||
templateUrl: 'analytics.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class AnalyticsComponent {
|
||||
links = [
|
||||
{
|
||||
path: 'base',
|
||||
name: 'Base',
|
||||
otherActiveUrlFragments: [],
|
||||
},
|
||||
];
|
||||
|
||||
constructor(private router: Router, @Inject(LAYOUT_GAP_S) public layoutGapS: string) {}
|
||||
|
||||
hasActiveFragments(fragments: string[]): boolean {
|
||||
const ulrFragments = this.router.url.split('/');
|
||||
return hasActiveFragments(fragments, ulrFragments);
|
||||
}
|
||||
}
|
74
src/app/sections/analytics/analytics.module.ts
Normal file
74
src/app/sections/analytics/analytics.module.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FlexModule } from '@angular/flex-layout';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
||||
import { MatSortModule } from '@angular/material/sort';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { MatTabsModule } from '@angular/material/tabs';
|
||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||
import { ConfirmActionDialogModule } from '../../shared/components/confirm-action-dialog';
|
||||
import { EmptySearchResultModule } from '../../shared/components/empty-search-result';
|
||||
import { ShowMorePanelModule } from '../../shared/components/show-more-panel';
|
||||
import { SharedPipesModule } from '../../shared/pipes';
|
||||
import { AnalyticsRoutingModule } from './analytics-routing.module';
|
||||
import { AnalyticsComponent } from './analytics.component';
|
||||
import { BaseAnalyticsComponent } from './components/base/base-analytics.component';
|
||||
import { BaseAnalyticsSearchComponent } from './components/search/base-analytics-search.component';
|
||||
import { MatDatepickerModule } from '@angular/material/datepicker';
|
||||
import { MatNativeDateModule } from '@angular/material/core';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import {
|
||||
NgxMatDatetimePickerModule,
|
||||
NgxMatNativeDateModule,
|
||||
NgxMatTimepickerModule,
|
||||
} from '@angular-material-components/datetime-picker';
|
||||
import { SearchFieldService } from '../../shared/services/utils/search-field.service';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { AnalyticsService } from '../../api/payments/analytics';
|
||||
import { FbInfoCardModule } from '../../shared/components/fb-info-card';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
AnalyticsRoutingModule,
|
||||
MatTabsModule,
|
||||
CommonModule,
|
||||
MatCardModule,
|
||||
MatTableModule,
|
||||
MatSortModule,
|
||||
MatButtonModule,
|
||||
MatIconModule,
|
||||
MatToolbarModule,
|
||||
ConfirmActionDialogModule,
|
||||
MatMenuModule,
|
||||
EmptySearchResultModule,
|
||||
SharedPipesModule,
|
||||
FlexModule,
|
||||
MatSnackBarModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
ReactiveFormsModule,
|
||||
MatProgressSpinnerModule,
|
||||
MatProgressBarModule,
|
||||
ShowMorePanelModule,
|
||||
MatDatepickerModule,
|
||||
MatNativeDateModule,
|
||||
MatTooltipModule,
|
||||
NgxMatTimepickerModule,
|
||||
NgxMatNativeDateModule,
|
||||
NgxMatDatetimePickerModule,
|
||||
MatSelectModule,
|
||||
FbInfoCardModule,
|
||||
],
|
||||
declarations: [AnalyticsComponent, BaseAnalyticsComponent, BaseAnalyticsSearchComponent],
|
||||
providers: [AnalyticsService],
|
||||
})
|
||||
export class AnalyticsModule {}
|
@ -0,0 +1,31 @@
|
||||
<div fxLayout="column" [fxLayoutGap]="layoutGapM">
|
||||
<div fxLayout fxLayoutAlign="space-between center">
|
||||
<fb-base-analytics-search (valueChanges)="search($event)"></fb-base-analytics-search>
|
||||
</div>
|
||||
<div fxLayout="row" fxlayoutgap="10px grid" fxLayoutAlign="space-between center" [fxLayoutGap]="layoutGapM">
|
||||
<fb-info-card
|
||||
fxFlex="25"
|
||||
[headerText]="'Attempted payments'"
|
||||
[value]="'30 430 000'"
|
||||
[type]="'success'"
|
||||
></fb-info-card>
|
||||
<fb-info-card fxFlex="25" [headerText]="'Blocked payments'" [value]="'507'" [type]="'error'"></fb-info-card>
|
||||
<fb-info-card
|
||||
fxFlex="25"
|
||||
[headerText]="'Block rates'"
|
||||
[value]="'0.24'"
|
||||
[units]="'%'"
|
||||
[type]="'error'"
|
||||
></fb-info-card>
|
||||
<fb-info-card
|
||||
fxFlex="25"
|
||||
[headerText]="'Block sum'"
|
||||
[value]="'30 430'"
|
||||
[units]="'$'"
|
||||
[type]="'error'"
|
||||
></fb-info-card>
|
||||
</div>
|
||||
<div fxLayout="column" fxLayoutAlign="space-between center" [fxLayoutGap]="layoutGapM">
|
||||
<mat-card fxLayout fxFlex="100" fxLayoutAlign="center"> </mat-card>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,18 @@
|
||||
import { ChangeDetectionStrategy, Component, Inject, OnInit } from '@angular/core';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { Router } from '@angular/router';
|
||||
import { LAYOUT_GAP_M } from '../../../../tokens';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import { AnalyticsService } from '../../../../api/payments/analytics';
|
||||
|
||||
@Component({
|
||||
templateUrl: 'base-analytics.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class BaseAnalyticsComponent implements OnInit {
|
||||
constructor(@Inject(LAYOUT_GAP_M) public layoutGapM: string) {}
|
||||
|
||||
search($event) {}
|
||||
|
||||
ngOnInit(): void {}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
<form fxflexfill [formGroup]="form">
|
||||
<div fxLayoutGap="10px grid" fxFlexFill fxLayout.lt-sm="column" class="row-margin">
|
||||
<mat-form-field appearance="outline">
|
||||
<input matInput placeholder="merchant" formControlName="partyId" autocomplete="false" />
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline">
|
||||
<input matInput placeholder="shop" formControlName="shopId" autocomplete="false" />
|
||||
</mat-form-field>
|
||||
<mat-form-field class="multi-select" appearance="outline" *ngIf="currencies$ | async as currencies">
|
||||
<mat-select formControlName="types" multiple>
|
||||
<mat-option *ngFor="let name of currencies" value="{{ name }}">{{ name }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field class="multi-select" appearance="outline" *ngIf="currencies$ | async as currencies">
|
||||
<mat-select formControlName="times" multiple>
|
||||
<mat-option *ngFor="let name of currencies" value="{{ name }}">{{ name }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</form>
|
@ -0,0 +1,44 @@
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { debounceTime, map, take } from 'rxjs/operators';
|
||||
import { removeEmptyProperties } from '../../../../shared/utils/remove-empty-properties';
|
||||
import { AnalyticsService } from '../../../../api/payments/analytics';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'fb-base-analytics-search',
|
||||
templateUrl: 'base-analytics-search.component.html',
|
||||
styleUrls: ['search.component.scss'],
|
||||
})
|
||||
export class BaseAnalyticsSearchComponent {
|
||||
@Output() valueChanges: EventEmitter<string> = new EventEmitter();
|
||||
|
||||
currencies$: Observable<string[]>;
|
||||
|
||||
form: FormGroup = this.fb.group({
|
||||
partyId: '.*',
|
||||
shopId: '.*',
|
||||
types: [],
|
||||
times: '',
|
||||
});
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private fb: FormBuilder,
|
||||
private analyticsService: AnalyticsService
|
||||
) {
|
||||
this.form.valueChanges.pipe(debounceTime(600), map(removeEmptyProperties)).subscribe((v) => {
|
||||
const params = Object.create(v);
|
||||
params.partyId = v.partyId;
|
||||
params.shopId = v.shopId;
|
||||
params.types = v.types;
|
||||
params.times = v.times;
|
||||
this.router.navigate([location.pathname], { queryParams: params });
|
||||
this.valueChanges.emit(v);
|
||||
});
|
||||
this.route.queryParams.pipe(take(1)).subscribe((v) => this.form.patchValue(v));
|
||||
this.currencies$ = this.analyticsService.getCurrencies({});
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
export interface SearchAnalyticsCurrenciesParams {
|
||||
from?: string;
|
||||
to?: string;
|
||||
shopId?: string;
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
::ng-deep {
|
||||
form {
|
||||
.mat-form-field:not(.mat-form-field-appearance-legacy) .mat-form-field-prefix .mat-icon-button,
|
||||
.mat-form-field:not(.mat-form-field-appearance-legacy) .mat-form-field-suffix .mat-icon-button {
|
||||
margin-left: -30px;
|
||||
font-size: revert;
|
||||
height: 1.5em;
|
||||
width: 1.5em;
|
||||
}
|
||||
|
||||
.mat-form-field:not(.mat-form-field-appearance-legacy)
|
||||
.mat-form-field-prefix
|
||||
.mat-datepicker-toggle-default-icon,
|
||||
.mat-form-field:not(.mat-form-field-appearance-legacy)
|
||||
.mat-form-field-suffix
|
||||
.mat-datepicker-toggle-default-icon {
|
||||
display: inherit;
|
||||
}
|
||||
|
||||
mat-form-field.date-picker {
|
||||
div {
|
||||
.mat-form-field-flex {
|
||||
padding: 0;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mat-form-field-wrapper {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.mat-form-field-appearance-outline .mat-form-field-flex {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
div.row-margin {
|
||||
margin: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
1
src/app/sections/analytics/index.ts
Normal file
1
src/app/sections/analytics/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './analytics.module';
|
@ -7,6 +7,10 @@ const ROUTES: Routes = [
|
||||
redirectTo: 'emulation/template',
|
||||
pathMatch: 'full',
|
||||
},
|
||||
{
|
||||
path: 'analytics',
|
||||
loadChildren: () => import('./analytics').then((m) => m.AnalyticsModule),
|
||||
},
|
||||
{
|
||||
path: 'templates',
|
||||
loadChildren: () => import('./templates').then((m) => m.TemplatesModule),
|
||||
|
35
src/app/shared/components/charts/_charts-theme.scss
Normal file
35
src/app/shared/components/charts/_charts-theme.scss
Normal file
@ -0,0 +1,35 @@
|
||||
@use '@angular/material' as mat;
|
||||
@import '../../../styles/utils/shadow';
|
||||
|
||||
@mixin dsh-charts-theme($theme) {
|
||||
$foreground: map-get($theme, foreground);
|
||||
$background: map-get($theme, background);
|
||||
|
||||
.apexcharts {
|
||||
&-text {
|
||||
fill: mat.get-color-from-palette($foreground, text);
|
||||
}
|
||||
|
||||
&-legend-text {
|
||||
color: mat.get-color-from-palette($foreground, text) !important;
|
||||
}
|
||||
|
||||
&-tooltip {
|
||||
@include dsh-shadow($theme);
|
||||
background-color: mat.get-color-from-palette($background, card);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin dsh-charts-typography($config) {
|
||||
.apexcharts-text,
|
||||
.apexcharts-legend-text {
|
||||
line-height: 20px;
|
||||
|
||||
font: {
|
||||
family: mat.font-family($config, caption) !important;
|
||||
size: mat.font-size($config, caption) !important;
|
||||
weight: mat.font-weight($config, caption) !important;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
@import '../../../styles/utils/shadow';
|
||||
@import '../charts-theme';
|
||||
|
||||
@mixin dsh-bar-chart-theme($theme) {
|
||||
$foreground: map-get($theme, foreground);
|
||||
|
||||
@include dsh-charts-theme($theme);
|
||||
|
||||
.dsh-bar-chart-tooltip {
|
||||
&-title {
|
||||
color: map-get($foreground, light-text);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
<apx-chart
|
||||
[chart]="config.chart"
|
||||
[series]="series"
|
||||
[dataLabels]="config.dataLabels"
|
||||
[plotOptions]="config.plotOptions"
|
||||
[xaxis]="config.xaxis"
|
||||
[yaxis]="config.yaxis"
|
||||
[legend]="config.legend"
|
||||
[fill]="config.fill"
|
||||
[tooltip]="config.tooltip"
|
||||
[colors]="colors"
|
||||
[states]="config.states"
|
||||
></apx-chart>
|
@ -0,0 +1,28 @@
|
||||
$tooltip-round-size: 15px;
|
||||
$tooltip-round-margin: 5px;
|
||||
$tooltip-padding: 10px;
|
||||
$tooltip-item-padding: 4px 0;
|
||||
$tooltip-border-radius: 4px;
|
||||
|
||||
:host ::ng-deep {
|
||||
.apexcharts-tooltip {
|
||||
border: none !important;
|
||||
border-radius: $tooltip-border-radius;
|
||||
padding: $tooltip-padding;
|
||||
}
|
||||
|
||||
.dsh-bar-chart-tooltip {
|
||||
&-round {
|
||||
margin-right: $tooltip-round-margin;
|
||||
height: $tooltip-round-size;
|
||||
width: $tooltip-round-size;
|
||||
border-radius: $tooltip-round-size;
|
||||
}
|
||||
|
||||
&-container {
|
||||
padding: $tooltip-item-padding;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
|
||||
import cloneDeep from 'lodash-es/cloneDeep';
|
||||
import { ApexAxisChartSeries } from 'ng-apexcharts/lib/model/apex-types';
|
||||
|
||||
import { DEFAULT_CONFIG } from './default-config';
|
||||
|
||||
@Component({
|
||||
selector: 'dsh-bar-chart',
|
||||
templateUrl: 'bar-chart.component.html',
|
||||
styleUrls: ['bar-chart.component.scss'],
|
||||
})
|
||||
export class BarChartComponent implements OnChanges {
|
||||
@Input()
|
||||
series?: ApexAxisChartSeries;
|
||||
|
||||
@Input()
|
||||
colors?: string[];
|
||||
|
||||
@Input()
|
||||
height?: number;
|
||||
|
||||
config = cloneDeep(DEFAULT_CONFIG);
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (changes.height && changes.height.currentValue !== changes.height.previousValue) {
|
||||
this.config.chart.height = this.height;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { NgApexchartsModule } from 'ng-apexcharts';
|
||||
|
||||
import { BarChartComponent } from './bar-chart.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [BarChartComponent],
|
||||
exports: [BarChartComponent],
|
||||
imports: [CommonModule, NgApexchartsModule],
|
||||
})
|
||||
export class BarChartModule {}
|
29
src/app/shared/components/charts/bar-chart/custom-tooltip.ts
Normal file
29
src/app/shared/components/charts/bar-chart/custom-tooltip.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { formatNumber } from '@angular/common';
|
||||
import { locale } from 'moment';
|
||||
|
||||
const getGetTooltipTitle = (x: string | null): string =>
|
||||
x.includes('hide') || x.includes('show') ? x.split('#')[0] : x;
|
||||
|
||||
export const customTooltip = ({ series, dataPointIndex, w }) => {
|
||||
let values = '';
|
||||
for (let i = 0; i < series.length; i += 1) {
|
||||
const formattedAmount = formatNumber(series[i][dataPointIndex], locale());
|
||||
const tooltipValue = w.globals.initialSeries[i].name
|
||||
? `${w.globals.seriesNames[i]} - ${formattedAmount}`
|
||||
: formattedAmount;
|
||||
values += `
|
||||
<div class="dsh-bar-chart-tooltip-container">
|
||||
<div class="dsh-bar-chart-tooltip-round mat-caption" style="background-color: ${w.globals.colors[i]}"></div>
|
||||
${tooltipValue}
|
||||
</div>`;
|
||||
}
|
||||
const x = getValueX(w.config.series, dataPointIndex);
|
||||
const tooltipTitle = getGetTooltipTitle(x);
|
||||
return `
|
||||
<div class="dsh-bar-chart-tooltip-title mat-caption">${tooltipTitle}</div>
|
||||
${values}
|
||||
`;
|
||||
};
|
||||
|
||||
const getValueX = (series: any[], index: number): string =>
|
||||
series.reduce((acc, curr) => (acc ? acc : curr.data.length ? curr.data[index].x : acc), null);
|
65
src/app/shared/components/charts/bar-chart/default-config.ts
Normal file
65
src/app/shared/components/charts/bar-chart/default-config.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import { ApexOptions } from 'ng-apexcharts/lib/model/apex-types';
|
||||
|
||||
import { DEFAULT_ANIMATION } from '@dsh/components/charts/default-animation';
|
||||
import { formatAmount } from '@dsh/components/charts/format-amount';
|
||||
|
||||
import { DEFAULT_LEGEND } from '../default-legend';
|
||||
import { DEFAULT_STATES } from '../default-states';
|
||||
import { customTooltip } from './custom-tooltip';
|
||||
|
||||
const COLUMN_WIDTH = '30%';
|
||||
|
||||
export const DEFAULT_CONFIG: ApexOptions = {
|
||||
chart: {
|
||||
type: 'bar',
|
||||
stacked: true,
|
||||
height: 300,
|
||||
toolbar: {
|
||||
show: false,
|
||||
},
|
||||
animations: DEFAULT_ANIMATION,
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false,
|
||||
},
|
||||
legend: DEFAULT_LEGEND,
|
||||
fill: {
|
||||
opacity: 1,
|
||||
},
|
||||
tooltip: {
|
||||
custom: customTooltip,
|
||||
},
|
||||
plotOptions: {
|
||||
bar: {
|
||||
horizontal: false,
|
||||
columnWidth: COLUMN_WIDTH,
|
||||
},
|
||||
},
|
||||
xaxis: {
|
||||
type: 'category',
|
||||
labels: {
|
||||
offsetY: -5,
|
||||
rotate: 0,
|
||||
formatter(value: string): string {
|
||||
const splitted = typeof value === 'string' ? value.split('#') : '';
|
||||
return splitted[1] === 'hide' ? '' : splitted[0];
|
||||
},
|
||||
},
|
||||
axisTicks: {
|
||||
show: false,
|
||||
},
|
||||
axisBorder: {
|
||||
show: false,
|
||||
},
|
||||
crosshairs: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
yaxis: {
|
||||
forceNiceScale: true,
|
||||
labels: {
|
||||
formatter: formatAmount,
|
||||
},
|
||||
},
|
||||
states: DEFAULT_STATES,
|
||||
};
|
1
src/app/shared/components/charts/bar-chart/index.ts
Normal file
1
src/app/shared/components/charts/bar-chart/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './bar-chart.module';
|
15
src/app/shared/components/charts/default-animation.ts
Normal file
15
src/app/shared/components/charts/default-animation.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { ApexChart } from 'ng-apexcharts';
|
||||
|
||||
export const DEFAULT_ANIMATION: ApexChart['animations'] = {
|
||||
enabled: true,
|
||||
easing: 'easeinout',
|
||||
speed: 500,
|
||||
animateGradually: {
|
||||
enabled: true,
|
||||
delay: 150,
|
||||
},
|
||||
dynamicAnimation: {
|
||||
enabled: true,
|
||||
speed: 350,
|
||||
},
|
||||
};
|
22
src/app/shared/components/charts/default-legend.ts
Normal file
22
src/app/shared/components/charts/default-legend.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { ApexLegend } from 'ng-apexcharts';
|
||||
|
||||
const LEGEND_ROUND_SIZE = 15;
|
||||
const VERTICAL_MARGIN = 4;
|
||||
const HORIZONTAL_MARGIN = 5;
|
||||
|
||||
export const DEFAULT_LEGEND: ApexLegend = {
|
||||
position: 'bottom',
|
||||
horizontalAlign: 'center',
|
||||
markers: {
|
||||
width: LEGEND_ROUND_SIZE,
|
||||
height: LEGEND_ROUND_SIZE,
|
||||
radius: LEGEND_ROUND_SIZE,
|
||||
},
|
||||
itemMargin: {
|
||||
vertical: VERTICAL_MARGIN,
|
||||
horizontal: HORIZONTAL_MARGIN,
|
||||
},
|
||||
onItemHover: {
|
||||
highlightDataSeries: false,
|
||||
},
|
||||
};
|
14
src/app/shared/components/charts/default-states.ts
Normal file
14
src/app/shared/components/charts/default-states.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { ApexStates } from 'ng-apexcharts';
|
||||
|
||||
export const DEFAULT_STATES: ApexStates = {
|
||||
hover: {
|
||||
filter: {
|
||||
type: 'none',
|
||||
},
|
||||
},
|
||||
active: {
|
||||
filter: {
|
||||
type: 'none',
|
||||
},
|
||||
},
|
||||
};
|
@ -0,0 +1,15 @@
|
||||
@import '../../../../styles/utils/shadow';
|
||||
@import '../charts-theme';
|
||||
|
||||
@mixin dsh-donut-chart-theme($theme) {
|
||||
$foreground: map-get($theme, foreground);
|
||||
$background: map-get($theme, background);
|
||||
|
||||
@include dsh-charts-theme($theme);
|
||||
|
||||
.dsh-donut-chart-tooltip {
|
||||
&-title {
|
||||
color: map-get($foreground, light-text);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
import { ApexOptions } from 'ng-apexcharts/lib/model/apex-types';
|
||||
|
||||
import { DEFAULT_ANIMATION } from '@dsh/components/charts/default-animation';
|
||||
|
||||
import { DEFAULT_LEGEND } from '../default-legend';
|
||||
import { DEFAULT_STATES } from '../default-states';
|
||||
|
||||
const INNER_DONUT_RADIUS = '92';
|
||||
|
||||
export const DEFAULT_CONFIG: ApexOptions = {
|
||||
chart: {
|
||||
type: 'donut',
|
||||
width: '100%',
|
||||
toolbar: {
|
||||
show: false,
|
||||
},
|
||||
animations: DEFAULT_ANIMATION,
|
||||
events: {},
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false,
|
||||
},
|
||||
legend: {
|
||||
...DEFAULT_LEGEND,
|
||||
formatter: (seriesName, opts) => {
|
||||
return `${seriesName} - ${opts.w.globals.series[opts.seriesIndex].toFixed(2)}%`;
|
||||
},
|
||||
showForNullSeries: true,
|
||||
},
|
||||
plotOptions: {
|
||||
pie: {
|
||||
expandOnClick: false,
|
||||
donut: {
|
||||
size: INNER_DONUT_RADIUS,
|
||||
labels: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
enabled: false,
|
||||
},
|
||||
states: DEFAULT_STATES,
|
||||
};
|
@ -0,0 +1,12 @@
|
||||
<apx-chart
|
||||
[chart]="config.chart"
|
||||
[dataLabels]="config.dataLabels"
|
||||
[legend]="config.legend"
|
||||
[grid]="config.grid"
|
||||
[tooltip]="config.tooltip"
|
||||
[plotOptions]="config.plotOptions"
|
||||
[states]="config.states"
|
||||
[series]="series"
|
||||
[labels]="labels"
|
||||
[colors]="colors"
|
||||
></apx-chart>
|
@ -0,0 +1,6 @@
|
||||
:host ::ng-deep {
|
||||
.apexcharts-legend-series,
|
||||
.apexcharts-legend-marker {
|
||||
cursor: default !important;
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
import cloneDeep from 'lodash-es/cloneDeep';
|
||||
import { ApexAxisChartSeries } from 'ng-apexcharts/lib/model/apex-types';
|
||||
|
||||
import { DEFAULT_CONFIG } from './default-config';
|
||||
|
||||
@Component({
|
||||
selector: 'dsh-donut-chart',
|
||||
templateUrl: 'donut-chart.component.html',
|
||||
styleUrls: ['donut-chart.component.scss'],
|
||||
})
|
||||
export class DonutChartComponent implements OnInit {
|
||||
@Input()
|
||||
series: ApexAxisChartSeries;
|
||||
|
||||
@Input()
|
||||
labels: string[];
|
||||
|
||||
@Input()
|
||||
colors?: string[];
|
||||
|
||||
@Output()
|
||||
dataSelect = new EventEmitter<number>();
|
||||
|
||||
config = cloneDeep(DEFAULT_CONFIG);
|
||||
|
||||
ngOnInit() {
|
||||
this.config.chart.events.dataPointSelection = this.updateDataPointSelection;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
updateDataPointSelection = (_: any, __: any, options?: any) => this.dataSelect.emit(options.dataPointIndex);
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { NgApexchartsModule } from 'ng-apexcharts';
|
||||
|
||||
import { DonutChartComponent } from './donut-chart.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [DonutChartComponent],
|
||||
exports: [DonutChartComponent],
|
||||
imports: [CommonModule, NgApexchartsModule],
|
||||
})
|
||||
export class DonutChartModule {}
|
1
src/app/shared/components/charts/donut-chart/index.ts
Normal file
1
src/app/shared/components/charts/donut-chart/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './donut-chart.module';
|
22
src/app/shared/components/charts/format-amount.ts
Normal file
22
src/app/shared/components/charts/format-amount.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { formatNumber } from '@angular/common';
|
||||
import { locale } from 'moment';
|
||||
|
||||
export const formatAmount = (num: number): string => {
|
||||
for (const amountUnit of AMOUNT_UNITS) {
|
||||
if (num >= amountUnit.amount) {
|
||||
return formatNumber(num / amountUnit.amount, locale(), '1.0-2') + amountUnit.unit;
|
||||
}
|
||||
}
|
||||
return formatNumber(num, locale(), '1.0-2');
|
||||
};
|
||||
|
||||
interface AmountUnit {
|
||||
amount: number;
|
||||
unit: '' | 'K' | 'M' | 'B';
|
||||
}
|
||||
|
||||
const AMOUNT_UNITS: AmountUnit[] = [
|
||||
{ amount: 1000000000, unit: 'B' },
|
||||
{ amount: 1000000, unit: 'M' },
|
||||
{ amount: 1000, unit: 'K' },
|
||||
];
|
2
src/app/shared/components/charts/index.ts
Normal file
2
src/app/shared/components/charts/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './bar-chart';
|
||||
export * from './donut-chart';
|
@ -0,0 +1,6 @@
|
||||
<mat-card fxLayoutAlign="center">
|
||||
<mat-card-content>
|
||||
<h4 fxLayoutAlign="center">{{ headerText }}</h4>
|
||||
<h1 [class]="type" fxLayoutAlign="center">{{ value }} {{ units }}</h1>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
@ -0,0 +1,7 @@
|
||||
.error {
|
||||
color: #cf1c1d;
|
||||
}
|
||||
|
||||
.success {
|
||||
color: #1ab152;
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'fb-info-card',
|
||||
templateUrl: 'fb-info-card.component.html',
|
||||
styleUrls: ['fb-info-card.component.scss'],
|
||||
})
|
||||
export class FbInfoCardComponent {
|
||||
@Input() headerText: string;
|
||||
@Input() value: string;
|
||||
@Input() units = '';
|
||||
@Input() type: string;
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FlexModule } from '@angular/flex-layout';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
|
||||
import { FbInfoCardComponent } from './fb-info-card.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [FbInfoCardComponent],
|
||||
exports: [FbInfoCardComponent],
|
||||
imports: [MatCardModule, FlexModule, CommonModule],
|
||||
})
|
||||
export class FbInfoCardModule {}
|
1
src/app/shared/components/fb-info-card/index.ts
Normal file
1
src/app/shared/components/fb-info-card/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './fb-info-card.module';
|
@ -3,6 +3,12 @@ import { NavItem } from './nav-item';
|
||||
|
||||
export class MenuModel {
|
||||
public static navItems: NavItem[] = [
|
||||
{
|
||||
displayName: 'Analytics',
|
||||
iconName: 'pie_chart',
|
||||
route: 'analytics',
|
||||
roles: [Roles.FraudOfficer, Roles.FraudMonitoring],
|
||||
},
|
||||
{
|
||||
displayName: 'Templates',
|
||||
iconName: 'business',
|
||||
|
14
src/app/styles/utils/_shadow.scss
Normal file
14
src/app/styles/utils/_shadow.scss
Normal file
@ -0,0 +1,14 @@
|
||||
@use '@angular/material' as mat;
|
||||
|
||||
@function get-shadow($color, $opacity) {
|
||||
$color: rgba($color, $opacity);
|
||||
|
||||
@return '0px 4px 8px #{$color}';
|
||||
}
|
||||
|
||||
@mixin dsh-shadow($theme) {
|
||||
$primary: map-get($theme, primary);
|
||||
$shadow-color: mat.get-color-from-palette($primary, default);
|
||||
|
||||
box-shadow: #{get-shadow($shadow-color, 0.12)} !important;
|
||||
}
|
Loading…
Reference in New Issue
Block a user