mirror of
https://github.com/valitydev/control-center.git
synced 2024-11-06 02:25:17 +00:00
IMP-84: Add duplicate shop delegates, new parties, shops table (#273)
This commit is contained in:
parent
908f6de4ac
commit
bf8c021c9d
8
package-lock.json
generated
8
package-lock.json
generated
@ -29,7 +29,7 @@
|
||||
"@vality/dominant-cache-proto": "2.0.0",
|
||||
"@vality/fistful-proto": "2.0.1-ed97a0e.0",
|
||||
"@vality/magista-proto": "2.0.2-37b81e6.0",
|
||||
"@vality/ng-core": "16.2.1-pr-40-adb1f52.0",
|
||||
"@vality/ng-core": "16.2.1-pr-40-bdc62db.0",
|
||||
"@vality/payout-manager-proto": "2.0.0",
|
||||
"@vality/repairer-proto": "2.0.2-f5e3b7a.0",
|
||||
"@vality/thrift-ts": "2.4.1-8ad5123.0",
|
||||
@ -6000,9 +6000,9 @@
|
||||
"integrity": "sha512-gJizpTWuB74L+XuJ+dUaxAwJDkycdnuVwrXWIl/NKcS7++/zgrgTpw+tM5/Te3rWqkkCnSxC1SK0C4aPbbtifg=="
|
||||
},
|
||||
"node_modules/@vality/ng-core": {
|
||||
"version": "16.2.1-pr-40-adb1f52.0",
|
||||
"resolved": "https://registry.npmjs.org/@vality/ng-core/-/ng-core-16.2.1-pr-40-adb1f52.0.tgz",
|
||||
"integrity": "sha512-dePHY8wWDE/lDthKkSibBrBQHTAbs1G8nHFiAnAR3Ntt6MOuWcti2qy9i9AxQ9nSN6o7yTzqS8S+GcbaVUYnpA==",
|
||||
"version": "16.2.1-pr-40-bdc62db.0",
|
||||
"resolved": "https://registry.npmjs.org/@vality/ng-core/-/ng-core-16.2.1-pr-40-bdc62db.0.tgz",
|
||||
"integrity": "sha512-hw7ljBUP1X1WYt6ZEQZ2E+y0Udy7G5pUNWL1wUIMmexaXr5H21NTuRZRgy9u3zfxrYfNnLmmI1XZgLfZry/gkA==",
|
||||
"dependencies": {
|
||||
"@ng-matero/extensions": "^16.0.0",
|
||||
"@s-libs/js-core": "^16.0.0",
|
||||
|
@ -37,7 +37,7 @@
|
||||
"@vality/dominant-cache-proto": "2.0.0",
|
||||
"@vality/fistful-proto": "2.0.1-ed97a0e.0",
|
||||
"@vality/magista-proto": "2.0.2-37b81e6.0",
|
||||
"@vality/ng-core": "16.2.1-pr-40-adb1f52.0",
|
||||
"@vality/ng-core": "16.2.1-pr-40-bdc62db.0",
|
||||
"@vality/payout-manager-proto": "2.0.0",
|
||||
"@vality/repairer-proto": "2.0.2-f5e3b7a.0",
|
||||
"@vality/thrift-ts": "2.4.1-8ad5123.0",
|
||||
|
@ -1,4 +1,26 @@
|
||||
<div fxLayout="column" fxLayoutGap="24px">
|
||||
<div class="mat-headline-4">Merchant's shops</div>
|
||||
<cc-shops-table [shops]="shops$ | async"></cc-shops-table>
|
||||
<div fxLayout="column" fxLayoutGap="24px">
|
||||
<mat-card>
|
||||
<mat-card-content>
|
||||
<v-input-field
|
||||
[formControl]="filterControl"
|
||||
fxFlex="100"
|
||||
label="Filter"
|
||||
></v-input-field>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
<v-table [columns]="columns" [data]="shops$ | async" noActions></v-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-template #shopTpl>
|
||||
<cc-domain-thrift-viewer [value]="selectedShop" type="Shop"></cc-domain-thrift-viewer>
|
||||
</ng-template>
|
||||
<ng-template #contractTpl>
|
||||
<cc-domain-thrift-viewer
|
||||
[progress]="!!(progress$ | async)"
|
||||
[value]="getContract(selectedShop.id) | async"
|
||||
type="Contract"
|
||||
></cc-domain-thrift-viewer>
|
||||
</ng-template>
|
||||
|
@ -1,4 +1,24 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, TemplateRef, ViewChild } from '@angular/core';
|
||||
import { FormControl } from '@angular/forms';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { Shop } from '@vality/domain-proto/domain';
|
||||
import {
|
||||
Column,
|
||||
ConfirmDialogComponent,
|
||||
createOperationColumn,
|
||||
DialogResponseStatus,
|
||||
DialogService,
|
||||
NotifyLogService,
|
||||
progressTo,
|
||||
} from '@vality/ng-core';
|
||||
import startCase from 'lodash-es/startCase';
|
||||
import { BehaviorSubject, combineLatest, map, switchMap } from 'rxjs';
|
||||
import { debounceTime, filter, shareReplay, startWith } from 'rxjs/operators';
|
||||
import { Memoize } from 'typescript-memoize';
|
||||
|
||||
import { PartyManagementService } from '@cc/app/api/payment-processing';
|
||||
import { SidenavInfoService } from '@cc/app/shared/components/sidenav-info';
|
||||
import { getUnionKey } from '@cc/utils';
|
||||
|
||||
import { PartyShopsService } from './party-shops.service';
|
||||
|
||||
@ -8,6 +28,163 @@ import { PartyShopsService } from './party-shops.service';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class PartyShopsComponent {
|
||||
shops$ = this.partyShopsService.shops$;
|
||||
constructor(private partyShopsService: PartyShopsService) {}
|
||||
@ViewChild('shopTpl') shopTpl: TemplateRef<unknown>;
|
||||
@ViewChild('contractTpl') contractTpl: TemplateRef<unknown>;
|
||||
|
||||
filterControl = new FormControl('');
|
||||
shops$ = combineLatest([
|
||||
this.partyShopsService.shops$,
|
||||
this.filterControl.valueChanges.pipe(
|
||||
startWith(this.filterControl.value),
|
||||
debounceTime(200),
|
||||
),
|
||||
]).pipe(
|
||||
map(([shops, searchStr]) =>
|
||||
shops.filter((s) => JSON.stringify(s).toLowerCase().includes(searchStr.toLowerCase())),
|
||||
),
|
||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||
);
|
||||
selectedShop?: Shop;
|
||||
columns: Column<Shop>[] = [
|
||||
{
|
||||
field: 'details.name',
|
||||
description: 'id',
|
||||
pinned: 'left',
|
||||
click: (d) => {
|
||||
this.selectedShop = d;
|
||||
this.sidenavInfoService.toggle(this.shopTpl, d.details.name || `Shop #${d.id}`, d);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'contract_id',
|
||||
header: 'Contract',
|
||||
click: (d) => {
|
||||
this.selectedShop = d;
|
||||
this.sidenavInfoService.toggle(this.contractTpl, `Contract #${d.id}`, d);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'details.description',
|
||||
},
|
||||
{
|
||||
field: 'location.url',
|
||||
},
|
||||
{
|
||||
field: 'account.currency.symbolic_code',
|
||||
header: 'Currency',
|
||||
},
|
||||
{
|
||||
field: 'blocking',
|
||||
type: 'tag',
|
||||
formatter: (shop) => getUnionKey(shop.blocking),
|
||||
typeParameters: {
|
||||
label: (shop) => startCase(getUnionKey(shop.blocking)),
|
||||
tags: {
|
||||
blocked: { color: 'warn' },
|
||||
unblocked: { color: 'success' },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'suspension',
|
||||
type: 'tag',
|
||||
formatter: (shop) => getUnionKey(shop.suspension),
|
||||
typeParameters: {
|
||||
label: (shop) => startCase(getUnionKey(shop.suspension)),
|
||||
tags: {
|
||||
suspended: { color: 'warn' },
|
||||
active: { color: 'success' },
|
||||
},
|
||||
},
|
||||
},
|
||||
createOperationColumn([
|
||||
{
|
||||
label: (shop) =>
|
||||
getUnionKey(shop.suspension) === 'suspended' ? 'Activate' : 'Suspend',
|
||||
click: (shop) => {
|
||||
this.toggleSuspension(shop);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: (shop) => (getUnionKey(shop.blocking) === 'blocked' ? 'Unblock' : 'Block'),
|
||||
click: (shop) => {
|
||||
this.toggleBlocking(shop);
|
||||
},
|
||||
},
|
||||
]),
|
||||
];
|
||||
progress$ = new BehaviorSubject(0);
|
||||
|
||||
constructor(
|
||||
private partyShopsService: PartyShopsService,
|
||||
private route: ActivatedRoute,
|
||||
private sidenavInfoService: SidenavInfoService,
|
||||
private partyManagementService: PartyManagementService,
|
||||
private dialogService: DialogService,
|
||||
private log: NotifyLogService,
|
||||
) {}
|
||||
|
||||
@Memoize()
|
||||
getContract(shopID: string) {
|
||||
return this.partyManagementService
|
||||
.GetShopContract(this.route.snapshot.params.partyID, shopID)
|
||||
.pipe(
|
||||
progressTo(this.progress$),
|
||||
map((c) => c.contract),
|
||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||
);
|
||||
}
|
||||
|
||||
toggleBlocking(shop: Shop) {
|
||||
const partyID = this.route.snapshot.params.partyID;
|
||||
this.dialogService
|
||||
.open(ConfirmDialogComponent, {
|
||||
title: getUnionKey(shop.blocking) === 'unblocked' ? 'Block shop' : 'Unblock shop',
|
||||
hasReason: true,
|
||||
})
|
||||
.afterClosed()
|
||||
.pipe(
|
||||
filter((r) => r.status === DialogResponseStatus.Success),
|
||||
switchMap((r) =>
|
||||
getUnionKey(shop.blocking) === 'unblocked'
|
||||
? this.partyManagementService.BlockShop(partyID, shop.id, r.data.reason)
|
||||
: this.partyManagementService.UnblockShop(partyID, shop.id, r.data.reason),
|
||||
),
|
||||
)
|
||||
.subscribe({
|
||||
next: () => {
|
||||
this.partyShopsService.reload();
|
||||
this.log.success();
|
||||
},
|
||||
error: (err) => {
|
||||
this.log.error(err);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
toggleSuspension(shop: Shop) {
|
||||
const partyID = this.route.snapshot.params.partyID;
|
||||
this.dialogService
|
||||
.open(ConfirmDialogComponent, {
|
||||
title: getUnionKey(shop.suspension) === 'active' ? 'Suspend shop' : 'Activate shop',
|
||||
})
|
||||
.afterClosed()
|
||||
.pipe(
|
||||
filter((r) => r.status === DialogResponseStatus.Success),
|
||||
switchMap(() =>
|
||||
getUnionKey(shop.suspension) === 'active'
|
||||
? this.partyManagementService.SuspendShop(partyID, shop.id)
|
||||
: this.partyManagementService.ActivateShop(partyID, shop.id),
|
||||
),
|
||||
)
|
||||
.subscribe({
|
||||
next: () => {
|
||||
this.partyShopsService.reload();
|
||||
this.log.success();
|
||||
},
|
||||
error: (err) => {
|
||||
this.log.error(err);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,33 +1,26 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FlexModule } from '@angular/flex-layout';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
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 { MatPaginatorModule } from '@angular/material/paginator';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { InputFieldModule, TableModule } from '@vality/ng-core';
|
||||
|
||||
import { DomainThriftViewerComponent } from '@cc/app/shared/components/thrift-api-crud';
|
||||
|
||||
import { PartyShopsRoutingModule } from './party-shops-routing.module';
|
||||
import { PartyShopsComponent } from './party-shops.component';
|
||||
import { ShopsTableComponent } from './shops-table/shops-table.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
PartyShopsRoutingModule,
|
||||
CommonModule,
|
||||
FlexModule,
|
||||
MatPaginatorModule,
|
||||
MatTableModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatButtonModule,
|
||||
MatCardModule,
|
||||
MatIconModule,
|
||||
MatMenuModule,
|
||||
TableModule,
|
||||
ReactiveFormsModule,
|
||||
InputFieldModule,
|
||||
DomainThriftViewerComponent,
|
||||
],
|
||||
declarations: [PartyShopsComponent, ShopsTableComponent],
|
||||
declarations: [PartyShopsComponent],
|
||||
})
|
||||
export class PartyShopsModule {}
|
||||
|
@ -1,20 +1,25 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { Party } from '@vality/domain-proto/domain';
|
||||
import { defer, Observable } from 'rxjs';
|
||||
import { map, pluck, shareReplay, switchMap } from 'rxjs/operators';
|
||||
import { defer, merge, Observable, Subject } from 'rxjs';
|
||||
import { map, shareReplay, switchMap } from 'rxjs/operators';
|
||||
|
||||
import { PartyManagementService } from '@cc/app/api/payment-processing';
|
||||
|
||||
@Injectable()
|
||||
export class PartyShopsService {
|
||||
shops$ = defer(() => this.party$).pipe(
|
||||
pluck('shops'),
|
||||
map((p) => p.shops),
|
||||
map((shops) => Array.from(shops.values())),
|
||||
);
|
||||
|
||||
private party$: Observable<Party> = this.route.params.pipe(
|
||||
pluck('partyID'),
|
||||
private reload$ = new Subject<void>();
|
||||
|
||||
private party$: Observable<Party> = merge(
|
||||
this.route.params,
|
||||
this.reload$.pipe(map(() => this.route.snapshot.params)),
|
||||
).pipe(
|
||||
map((p) => p.partyID),
|
||||
switchMap((partyID) => this.partyManagementService.Get(partyID)),
|
||||
shareReplay(1),
|
||||
);
|
||||
@ -23,4 +28,8 @@ export class PartyShopsService {
|
||||
private partyManagementService: PartyManagementService,
|
||||
private route: ActivatedRoute,
|
||||
) {}
|
||||
|
||||
reload() {
|
||||
this.reload$.next();
|
||||
}
|
||||
}
|
||||
|
@ -1,51 +0,0 @@
|
||||
<div fxLayout="column" fxLayoutGap="24px">
|
||||
<mat-card>
|
||||
<mat-card-content>
|
||||
<mat-form-field fxFlex="25">
|
||||
<input
|
||||
matInput
|
||||
placeholder="Filter"
|
||||
type="text"
|
||||
(keyup)="applyFilter($event.target.value)"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
||||
<mat-card>
|
||||
<mat-card-content fxLayout="column">
|
||||
<table [dataSource]="dataSource" fxFlex mat-table>
|
||||
<ng-container matColumnDef="id">
|
||||
<th *matHeaderCellDef mat-header-cell>Shop ID</th>
|
||||
<td *matCellDef="let shop" mat-cell>{{ shop.id }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="name">
|
||||
<th *matHeaderCellDef mat-header-cell>Name</th>
|
||||
<td *matCellDef="let shop" mat-cell>{{ shop.details.name }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="url">
|
||||
<th *matHeaderCellDef mat-header-cell>Url</th>
|
||||
<td *matCellDef="let shop" mat-cell>{{ shop.location.url }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="actions">
|
||||
<th *matHeaderCellDef class="action-cell" mat-header-cell></th>
|
||||
<td *matCellDef="let shop" class="action-cell" mat-cell>
|
||||
<button [matMenuTriggerFor]="menu" mat-icon-button>
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
<mat-menu #menu="matMenu">
|
||||
<button mat-menu-item (click)="navigateToShop(shop.id)">Details</button>
|
||||
</mat-menu>
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr *matHeaderRowDef="displayedColumns" mat-header-row></tr>
|
||||
<tr *matRowDef="let shop; columns: displayedColumns" mat-row></tr>
|
||||
</table>
|
||||
|
||||
<mat-paginator
|
||||
[pageSizeOptions]="[10, 20, 50, 100, 250, 500]"
|
||||
showFirstLastButtons
|
||||
></mat-paginator>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
@ -1,11 +0,0 @@
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.action-cell {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
import { Component, Input, OnChanges, SimpleChanges, ViewChild } from '@angular/core';
|
||||
import { MatPaginator } from '@angular/material/paginator';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { Shop } from '@vality/domain-proto/domain';
|
||||
import { pluck } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'cc-shops-table',
|
||||
templateUrl: 'shops-table.component.html',
|
||||
styleUrls: ['shops-table.component.scss'],
|
||||
})
|
||||
export class ShopsTableComponent implements OnChanges {
|
||||
@Input() shops: Shop[];
|
||||
@ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
|
||||
|
||||
dataSource: MatTableDataSource<Shop> = new MatTableDataSource();
|
||||
displayedColumns = ['id', 'name', 'url', 'actions'];
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private route: ActivatedRoute,
|
||||
) {}
|
||||
|
||||
ngOnChanges({ shops }: SimpleChanges) {
|
||||
if (shops.currentValue) {
|
||||
this.dataSource.data = shops.currentValue;
|
||||
this.dataSource.filterPredicate = (shop: Shop, filter: string) =>
|
||||
JSON.stringify(shop).toLowerCase().includes(filter);
|
||||
this.dataSource.paginator = this.paginator;
|
||||
}
|
||||
}
|
||||
|
||||
applyFilter(filterValue: string) {
|
||||
this.dataSource.filter = filterValue.trim().toLowerCase();
|
||||
}
|
||||
|
||||
navigateToShop(shopID: string) {
|
||||
this.route.params.pipe(pluck('partyID')).subscribe((partyID: string) => {
|
||||
void this.router.navigate([`/party/${partyID}/shop/${shopID}`]);
|
||||
});
|
||||
}
|
||||
}
|
@ -20,11 +20,6 @@ import { ROUTING_CONFIG } from './routing-config';
|
||||
loadChildren: () =>
|
||||
import('../party-shops').then((m) => m.PartyShopsModule),
|
||||
},
|
||||
{
|
||||
path: 'shop/:shopID',
|
||||
loadChildren: () =>
|
||||
import('../shop-details').then((m) => m.ShopDetailsModule),
|
||||
},
|
||||
{
|
||||
path: 'routing-rules',
|
||||
loadChildren: () =>
|
||||
|
@ -1,19 +1,13 @@
|
||||
<ng-container *ngIf="isLoading$ | async; else loaded">
|
||||
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
|
||||
</ng-container>
|
||||
<ng-template #loaded>
|
||||
<div [fxLayoutGap]="(data$ | async)?.length ? '24px' : '64px'" fxLayout="column">
|
||||
<div fxLayout fxLayoutAlign="space-between center" fxLayoutGap="8px">
|
||||
<div class="mat-headline-4 mat-no-margin">Party delegate rulesets</div>
|
||||
<button color="primary" mat-button (click)="attachNewRuleset()">
|
||||
Attach new ruleset
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<cc-routing-rules-list
|
||||
[data]="data$ | async"
|
||||
[displayedColumns]="displayedColumns"
|
||||
(toDetails)="navigateToPartyRuleset($event.parentRefId, $event.delegateIdx)"
|
||||
></cc-routing-rules-list>
|
||||
<div fxLayout="column" fxLayoutGap="24px">
|
||||
<div fxLayout fxLayoutAlign="space-between center" fxLayoutGap="8px">
|
||||
<div class="mat-headline-4 mat-no-margin">Party delegate rulesets</div>
|
||||
<button color="primary" mat-button (click)="attachNewRuleset()">Attach new ruleset</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<cc-routing-rules-list
|
||||
[data]="data$ | async"
|
||||
[displayedColumns]="displayedColumns"
|
||||
[progress]="isLoading$ | async"
|
||||
(toDetails)="navigateToPartyRuleset($event.parentRefId, $event.delegateIdx)"
|
||||
></cc-routing-rules-list>
|
||||
</div>
|
||||
|
@ -23,9 +23,9 @@ import { PartyDelegateRulesetsService } from './party-delegate-rulesets.service'
|
||||
})
|
||||
export class PartyDelegateRulesetsComponent {
|
||||
displayedColumns = [
|
||||
{ key: 'partyDelegate', name: 'Party delegate' },
|
||||
{ key: 'paymentInstitution', name: 'Payment institution' },
|
||||
{ key: 'mainRuleset', name: 'Main ruleset' },
|
||||
{ key: 'partyDelegate', name: 'Party delegate' },
|
||||
];
|
||||
isLoading$ = this.domainStoreService.isLoading$;
|
||||
data$ = this.partyDelegateRulesetsService.getDelegatesWithPaymentInstitution().pipe(
|
||||
|
@ -5,7 +5,7 @@
|
||||
<div
|
||||
*ngIf="partyRuleset$ | async as partyRuleset; else emptyPartyDelegate"
|
||||
fxLayout="column"
|
||||
fxLayoutGap="64px"
|
||||
fxLayoutGap="24px"
|
||||
>
|
||||
<cc-routing-ruleset-header
|
||||
[backTo]="
|
||||
@ -18,14 +18,14 @@
|
||||
</cc-routing-ruleset-header>
|
||||
|
||||
<cc-routing-rules-list
|
||||
*ngIf="(shopsData$ | async)?.length || !(walletsData$ | async)?.length"
|
||||
*ngIf="(routingRulesType$ | async) === 'payment'"
|
||||
[data]="shopsData$ | async"
|
||||
[displayedColumns]="shopsDisplayedColumns"
|
||||
(toDetails)="navigateToDelegate($event.parentRefId, $event.delegateIdx)"
|
||||
></cc-routing-rules-list>
|
||||
|
||||
<cc-routing-rules-list
|
||||
*ngIf="(walletsData$ | async)?.length"
|
||||
*ngIf="(routingRulesType$ | async) === 'withdrawal'"
|
||||
[data]="walletsData$ | async"
|
||||
[displayedColumns]="walletsDisplayedColumns"
|
||||
(toDetails)="navigateToDelegate($event.parentRefId, $event.delegateIdx)"
|
||||
|
@ -30,14 +30,17 @@ export class PartyRoutingRulesetComponent {
|
||||
isLoading$ = this.domainStoreService.isLoading$;
|
||||
|
||||
shopsDisplayedColumns = [
|
||||
{ key: 'shop', name: 'Shop' },
|
||||
{ key: 'id', name: 'Delegate (Ruleset Ref ID)' },
|
||||
{ key: 'shop', name: 'Shop' },
|
||||
];
|
||||
walletsDisplayedColumns = [
|
||||
{ key: 'wallet', name: 'Wallet' },
|
||||
{ key: 'id', name: 'Delegate (Ruleset Ref ID)' },
|
||||
{ key: 'wallet', name: 'Wallet' },
|
||||
];
|
||||
shopsData$ = combineLatest([this.partyRuleset$, this.partyRoutingRulesetService.shops$]).pipe(
|
||||
shopsData$ = combineLatest([
|
||||
this.partyRuleset$,
|
||||
this.partyRoutingRulesetService.shops$.pipe(startWith([])),
|
||||
]).pipe(
|
||||
filter(([r]) => !!r),
|
||||
map(([ruleset, shops]) =>
|
||||
ruleset.data.decisions.delegates
|
||||
@ -63,7 +66,7 @@ export class PartyRoutingRulesetComponent {
|
||||
);
|
||||
walletsData$ = combineLatest([
|
||||
this.partyRuleset$,
|
||||
this.partyRoutingRulesetService.wallets$,
|
||||
this.partyRoutingRulesetService.wallets$.pipe(startWith([])),
|
||||
]).pipe(
|
||||
filter(([r]) => !!r),
|
||||
map(([ruleset, wallets]) =>
|
||||
|
@ -1,70 +1 @@
|
||||
<mat-card *ngIf="(dataSource$ | async)?.data?.length; else empty">
|
||||
<mat-card-content fxLayout="column">
|
||||
<table [dataSource]="dataSource$ | async" mat-table>
|
||||
<ng-container *ngFor="let column of displayedColumns" [matColumnDef]="column.key">
|
||||
<th *matHeaderCellDef mat-header-cell>{{ column.name }}</th>
|
||||
<td *matCellDef="let element" mat-cell>
|
||||
<div
|
||||
*ngIf="
|
||||
element[column.key]?.text || element[column.key]?.caption;
|
||||
else onlyBody
|
||||
"
|
||||
fxLayout="column"
|
||||
>
|
||||
<div class="mat-body-1">{{ element[column.key]?.text || ' ' }}</div>
|
||||
<div class="mat-caption">
|
||||
{{ element[column.key]?.caption || ' ' }}
|
||||
</div>
|
||||
</div>
|
||||
<ng-template #onlyBody>
|
||||
{{ element[column.key] }}
|
||||
</ng-template>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="actions">
|
||||
<th *matHeaderCellDef mat-header-cell width="1px"></th>
|
||||
<td *matCellDef="let element" mat-cell>
|
||||
<button [matMenuTriggerFor]="menu" mat-icon-button>
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
<mat-menu #menu="matMenu">
|
||||
<button
|
||||
mat-menu-item
|
||||
(click)="
|
||||
toDetails.emit({
|
||||
parentRefId: element?.parentRefId,
|
||||
delegateIdx: element?.delegateIdx
|
||||
})
|
||||
"
|
||||
>
|
||||
Details
|
||||
</button>
|
||||
<button mat-menu-item (click)="changeDelegateRuleset(element)">
|
||||
Change delegate ruleset
|
||||
</button>
|
||||
<button mat-menu-item (click)="changeTarget(element)">
|
||||
Change main ruleset
|
||||
</button>
|
||||
<button mat-menu-item (click)="cloneDelegateRuleset(element)">
|
||||
Clone delegate ruleset
|
||||
</button>
|
||||
<button mat-menu-item (click)="delete(element)">Delete</button>
|
||||
</mat-menu>
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr *matHeaderRowDef="allDisplayedColumns" mat-header-row></tr>
|
||||
<tr *matRowDef="let row; columns: allDisplayedColumns" mat-row></tr>
|
||||
</table>
|
||||
|
||||
<mat-paginator
|
||||
[pageSizeOptions]="[10, 20, 50, 100, 250, 500]"
|
||||
showFirstLastButtons
|
||||
></mat-paginator>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
||||
<ng-template #empty>
|
||||
<div class="mat-display-1" fxLayout="column" fxLayoutAlign=" center">
|
||||
Routing rules not found
|
||||
</div>
|
||||
</ng-template>
|
||||
<v-table [columns]="columns" [data]="data" [progress]="progress" noActions></v-table>
|
||||
|
@ -1,34 +0,0 @@
|
||||
import { Component, DebugElement } from '@angular/core';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
|
||||
import { RoutingRulesListComponent } from './routing-rules-list.component';
|
||||
|
||||
@Component({
|
||||
selector: 'cc-host',
|
||||
template: `<dsh-routing-rules-list></dsh-routing-rules-list>`,
|
||||
})
|
||||
class HostComponent {}
|
||||
|
||||
describe('RoutingRulesListComponent', () => {
|
||||
let fixture: ComponentFixture<HostComponent>;
|
||||
let debugElement: DebugElement;
|
||||
let component: RoutingRulesListComponent;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [],
|
||||
declarations: [HostComponent, RoutingRulesListComponent],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(HostComponent);
|
||||
debugElement = fixture.debugElement.query(By.directive(RoutingRulesListComponent));
|
||||
component = debugElement.componentInstance;
|
||||
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -4,15 +4,20 @@ import {
|
||||
EventEmitter,
|
||||
Input,
|
||||
Output,
|
||||
ViewChild,
|
||||
OnChanges,
|
||||
} from '@angular/core';
|
||||
import { MatPaginator } from '@angular/material/paginator';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
|
||||
import { DialogResponseStatus, DialogService, ConfirmDialogComponent } from '@vality/ng-core';
|
||||
import { combineLatest, defer, ReplaySubject } from 'rxjs';
|
||||
import { filter, map, shareReplay, startWith, switchMap } from 'rxjs/operators';
|
||||
import {
|
||||
DialogResponseStatus,
|
||||
DialogService,
|
||||
ConfirmDialogComponent,
|
||||
Column,
|
||||
createOperationColumn,
|
||||
ComponentChanges,
|
||||
} from '@vality/ng-core';
|
||||
import { coerceBoolean } from 'coerce-property';
|
||||
import { filter, switchMap } from 'rxjs/operators';
|
||||
|
||||
import { NotificationErrorService } from '@cc/app/shared/services/notification-error';
|
||||
|
||||
@ -36,47 +41,15 @@ export class RoutingRulesListComponent<
|
||||
T extends { [N in PropertyKey]: unknown } & DelegateId = {
|
||||
[N in PropertyKey]: unknown;
|
||||
} & DelegateId,
|
||||
> {
|
||||
> implements OnChanges
|
||||
{
|
||||
@Input() data: T[];
|
||||
@Input() displayedColumns: { key: keyof T; name: string }[];
|
||||
|
||||
@Input() set data(data: T[]) {
|
||||
this.data$.next(data);
|
||||
}
|
||||
@Input() @coerceBoolean progress: boolean | '' = false;
|
||||
|
||||
@Output() toDetails = new EventEmitter<DelegateId>();
|
||||
|
||||
@ViewChild(MatPaginator) set paginator(paginator: MatPaginator) {
|
||||
this.paginator$.next(paginator);
|
||||
}
|
||||
|
||||
dataSource$ = combineLatest([
|
||||
defer(() => this.data$),
|
||||
defer(() => this.paginator$).pipe(startWith(null)),
|
||||
]).pipe(
|
||||
map(([d, paginator]) => {
|
||||
const data = new MatTableDataSource(d);
|
||||
data.paginator = paginator;
|
||||
return data;
|
||||
}),
|
||||
shareReplay(1),
|
||||
);
|
||||
|
||||
get allDisplayedColumns() {
|
||||
if (!this.displayedColumns) {
|
||||
return [];
|
||||
}
|
||||
return this.displayedColumns
|
||||
.concat([
|
||||
{
|
||||
key: 'actions',
|
||||
name: 'Actions',
|
||||
},
|
||||
])
|
||||
.map(({ key }) => key);
|
||||
}
|
||||
|
||||
private data$ = new ReplaySubject<T[]>(1);
|
||||
private paginator$ = new ReplaySubject<MatPaginator>(1);
|
||||
columns: Column<T>[] = [];
|
||||
|
||||
constructor(
|
||||
private dialogService: DialogService,
|
||||
@ -85,6 +58,61 @@ export class RoutingRulesListComponent<
|
||||
private route: ActivatedRoute,
|
||||
) {}
|
||||
|
||||
ngOnChanges(changes: ComponentChanges<RoutingRulesListComponent<T>>) {
|
||||
if (changes.displayedColumns) {
|
||||
this.columns = [
|
||||
...this.displayedColumns.map(
|
||||
(c, idx): Column<T> => ({
|
||||
field: `${c.key as string}.text`,
|
||||
formatter:
|
||||
idx === 0
|
||||
? (d) => {
|
||||
const v = d?.[c.key] as { caption: string; text: string };
|
||||
return v?.text || `#${v?.caption}`;
|
||||
}
|
||||
: undefined,
|
||||
click:
|
||||
idx === 0
|
||||
? (d) =>
|
||||
this.toDetails.emit({
|
||||
parentRefId: d?.parentRefId,
|
||||
delegateIdx: d?.delegateIdx,
|
||||
})
|
||||
: undefined,
|
||||
header: c.name,
|
||||
description: `${c.key as string}.caption`,
|
||||
}),
|
||||
),
|
||||
createOperationColumn([
|
||||
{
|
||||
label: 'Details',
|
||||
click: (d) =>
|
||||
this.toDetails.emit({
|
||||
parentRefId: d?.parentRefId,
|
||||
delegateIdx: d?.delegateIdx,
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: 'Change delegate ruleset',
|
||||
click: (d) => this.changeDelegateRuleset(d),
|
||||
},
|
||||
{
|
||||
label: 'Change main ruleset',
|
||||
click: (d) => this.changeTarget(d),
|
||||
},
|
||||
{
|
||||
label: 'Clone delegate ruleset',
|
||||
click: (d) => this.cloneDelegateRuleset(d),
|
||||
},
|
||||
{
|
||||
label: 'Delete',
|
||||
click: (d) => this.delete(d),
|
||||
},
|
||||
]),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
changeDelegateRuleset(delegateId: DelegateId) {
|
||||
this.dialogService
|
||||
.open(ChangeDelegateRulesetDialogComponent, {
|
||||
|
@ -6,7 +6,7 @@ import { MatCardModule } from '@angular/material/card';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { MatPaginatorModule } from '@angular/material/paginator';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { TableModule } from '@vality/ng-core';
|
||||
|
||||
import { RoutingRulesListComponent } from './routing-rules-list.component';
|
||||
|
||||
@ -15,12 +15,11 @@ import { RoutingRulesListComponent } from './routing-rules-list.component';
|
||||
CommonModule,
|
||||
MatMenuModule,
|
||||
MatCardModule,
|
||||
MatTableModule,
|
||||
MatPaginatorModule,
|
||||
MatTableModule,
|
||||
MatIconModule,
|
||||
FlexLayoutModule,
|
||||
MatButtonModule,
|
||||
TableModule,
|
||||
],
|
||||
declarations: [RoutingRulesListComponent],
|
||||
exports: [RoutingRulesListComponent],
|
||||
|
@ -57,7 +57,7 @@ export class RoutingRulesetComponent {
|
||||
field: 'candidate',
|
||||
description: 'description',
|
||||
sortable: true,
|
||||
formatter: (d) => this.getCandidateIdx(d).pipe(map((idx) => `${idx + 1}`)),
|
||||
formatter: (d) => this.getCandidateIdx(d).pipe(map((idx) => `#${idx + 1}`)),
|
||||
click: (d) => {
|
||||
this.getCandidateIdx(d)
|
||||
.pipe(untilDestroyed(this))
|
||||
@ -129,6 +129,16 @@ export class RoutingRulesetComponent {
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Duplicate',
|
||||
click: (d) => {
|
||||
this.getCandidateIdx(d)
|
||||
.pipe(untilDestroyed(this))
|
||||
.subscribe((idx) => {
|
||||
void this.duplicateShopRule(idx);
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Remove',
|
||||
click: (d) => {
|
||||
@ -176,7 +186,7 @@ export class RoutingRulesetComponent {
|
||||
next: (res) => {
|
||||
if (res.status === DialogResponseStatus.Success) {
|
||||
this.domainStoreService.forceReload();
|
||||
this.log.successOperation('update', 'Routing rule');
|
||||
this.log.successOperation('create', 'Routing rule');
|
||||
}
|
||||
},
|
||||
error: (err) => {
|
||||
@ -217,6 +227,38 @@ export class RoutingRulesetComponent {
|
||||
});
|
||||
}
|
||||
|
||||
duplicateShopRule(idx: number) {
|
||||
this.routingRulesetService.refID$
|
||||
.pipe(
|
||||
first(),
|
||||
switchMap((refId) => this.routingRulesService.getShopCandidate(refId, idx)),
|
||||
withLatestFrom(this.routingRulesetService.refID$),
|
||||
switchMap(([shopCandidate, refId]) =>
|
||||
this.dialog
|
||||
.open(DomainThriftFormDialogComponent<RoutingCandidate>, {
|
||||
type: 'RoutingCandidate',
|
||||
title: 'Add shop routing candidate',
|
||||
object: shopCandidate,
|
||||
actionType: 'create',
|
||||
action: (params) => this.routingRulesService.addShopRule(refId, params),
|
||||
})
|
||||
.afterClosed(),
|
||||
),
|
||||
)
|
||||
.pipe(untilDestroyed(this))
|
||||
.subscribe({
|
||||
next: (res) => {
|
||||
if (res.status === DialogResponseStatus.Success) {
|
||||
this.domainStoreService.forceReload();
|
||||
this.log.successOperation('create', 'Routing rule');
|
||||
}
|
||||
},
|
||||
error: (err) => {
|
||||
this.log.error(err);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
removeShopRule(idx: number) {
|
||||
this.routingRulesetService.removeShopRule(idx);
|
||||
}
|
||||
|
@ -1 +0,0 @@
|
||||
export * from './parties-table.module';
|
@ -1,36 +0,0 @@
|
||||
<table *ngIf="displayedColumns" [dataSource]="parties" mat-table>
|
||||
<ng-container matColumnDef="email">
|
||||
<th *matHeaderCellDef mat-header-cell>Email</th>
|
||||
<td *matCellDef="let party" mat-cell>
|
||||
{{ party.email }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="id">
|
||||
<th *matHeaderCellDef mat-header-cell>ID</th>
|
||||
<td *matCellDef="let party" mat-cell>
|
||||
{{ party.id }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
<th *matHeaderCellDef class="action-cell" mat-header-cell></th>
|
||||
<td *matCellDef="let party" class="action-cell" mat-cell>
|
||||
<button [matMenuTriggerFor]="menu" mat-icon-button>
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
<mat-menu #menu="matMenu">
|
||||
<button
|
||||
*ngFor="let action of partyActions"
|
||||
mat-menu-item
|
||||
(click)="menuItemSelected(action, party.id)"
|
||||
>
|
||||
{{ action | ccPartyActions }}
|
||||
</button>
|
||||
</mat-menu>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr *matHeaderRowDef="displayedColumns" mat-header-row></tr>
|
||||
<tr *matRowDef="let row; columns: displayedColumns" mat-row></tr>
|
||||
</table>
|
@ -1,7 +0,0 @@
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.action-cell {
|
||||
width: 8px;
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { Party, PartyID } from '@vality/domain-proto/domain';
|
||||
|
||||
import { PartyActions } from './party-actions';
|
||||
import { PartyMenuItemEvent } from './party-menu-item-event';
|
||||
|
||||
@Component({
|
||||
selector: 'cc-parties-table',
|
||||
templateUrl: 'parties-table.component.html',
|
||||
styleUrls: ['parties-table.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class PartiesTableComponent {
|
||||
@Input()
|
||||
parties: Party[];
|
||||
|
||||
@Output()
|
||||
menuItemSelected$: EventEmitter<PartyMenuItemEvent> = new EventEmitter();
|
||||
|
||||
partyActions = Object.keys(PartyActions);
|
||||
displayedColumns = ['email', 'id', 'actions'];
|
||||
|
||||
menuItemSelected(action: string, partyID: PartyID): void {
|
||||
switch (action) {
|
||||
case PartyActions.NavigateToParty:
|
||||
this.menuItemSelected$.emit({ action, partyID });
|
||||
break;
|
||||
default:
|
||||
console.error('Wrong party action type.');
|
||||
}
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
|
||||
import { PartiesTableComponent } from './parties-table.component';
|
||||
import { PartyActionsPipe } from './party-actions.pipe';
|
||||
|
||||
@NgModule({
|
||||
exports: [PartiesTableComponent],
|
||||
declarations: [PartiesTableComponent, PartyActionsPipe],
|
||||
imports: [MatTableModule, MatMenuModule, MatButtonModule, MatIconModule, CommonModule],
|
||||
})
|
||||
export class PartiesTableModule {}
|
@ -1,16 +0,0 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
import { PartyActions } from './party-actions';
|
||||
|
||||
const PARTY_ACTION_NAMES: { [N in PartyActions]: string } = {
|
||||
[PartyActions.NavigateToParty]: 'Merchant details',
|
||||
};
|
||||
|
||||
@Pipe({
|
||||
name: 'ccPartyActions',
|
||||
})
|
||||
export class PartyActionsPipe implements PipeTransform {
|
||||
transform(action: string): string {
|
||||
return PARTY_ACTION_NAMES[action] || action;
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
export enum PartyActions {
|
||||
NavigateToParty = 'NavigateToParty',
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
import { PartyID } from '@vality/domain-proto/domain';
|
||||
|
||||
import { PartyActions } from './party-actions';
|
||||
|
||||
export interface PartyMenuItemEvent {
|
||||
action: PartyActions;
|
||||
partyID: PartyID;
|
||||
}
|
@ -6,18 +6,12 @@
|
||||
(searchParamsChanged$)="searchParamsUpdated($event)"
|
||||
></cc-parties-search-filters>
|
||||
</mat-card-content>
|
||||
<mat-card-footer *ngIf="inProgress$ | async">
|
||||
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
|
||||
</mat-card-footer>
|
||||
</mat-card>
|
||||
|
||||
<ng-container *ngIf="parties$ | async as parties">
|
||||
<cc-empty-search-result *ngIf="parties.length === 0"></cc-empty-search-result>
|
||||
<mat-card *ngIf="parties.length > 0" fxLayout="column" fxLayoutGap="16px">
|
||||
<cc-parties-table
|
||||
[parties]="parties"
|
||||
(menuItemSelected$)="partyMenuItemSelected($event)"
|
||||
></cc-parties-table>
|
||||
</mat-card>
|
||||
</ng-container>
|
||||
<v-table
|
||||
[columns]="columns"
|
||||
[data]="parties$ | async"
|
||||
[progress]="inProgress$ | async"
|
||||
noActions
|
||||
></v-table>
|
||||
</cc-page-layout>
|
||||
|
@ -1,11 +1,14 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { Party } from '@vality/deanonimus-proto/deanonimus';
|
||||
import { Column, createOperationColumn } from '@vality/ng-core';
|
||||
import startCase from 'lodash-es/startCase';
|
||||
|
||||
import { FetchPartiesService } from '@cc/app/shared/services/fetch-parties.service';
|
||||
|
||||
import { getUnionKey } from '../../../utils';
|
||||
|
||||
import { PartiesSearchFiltersParams } from './parties-search-filters';
|
||||
import { PartyActions } from './parties-table/party-actions';
|
||||
import { PartyMenuItemEvent } from './parties-table/party-menu-item-event';
|
||||
import { SearchPartiesService } from './search-parties.service';
|
||||
|
||||
@Component({
|
||||
@ -17,6 +20,50 @@ export class SearchPartiesComponent {
|
||||
initSearchParams$ = this.partiesService.data$;
|
||||
inProgress$ = this.fetchPartiesService.inProgress$;
|
||||
parties$ = this.fetchPartiesService.parties$;
|
||||
columns: Column<Party>[] = [
|
||||
{
|
||||
field: 'email',
|
||||
description: 'id',
|
||||
pinned: 'left',
|
||||
link: (party) => `/party/${party.id}`,
|
||||
},
|
||||
{
|
||||
field: 'blocking',
|
||||
type: 'tag',
|
||||
formatter: (party) => getUnionKey(party.blocking),
|
||||
typeParameters: {
|
||||
label: (party) => startCase(getUnionKey(party.blocking)),
|
||||
tags: {
|
||||
blocked: { color: 'warn' },
|
||||
unblocked: { color: 'success' },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'suspension',
|
||||
type: 'tag',
|
||||
formatter: (party) => getUnionKey(party.suspension),
|
||||
typeParameters: {
|
||||
label: (party) => startCase(getUnionKey(party.suspension)),
|
||||
tags: {
|
||||
suspended: { color: 'warn' },
|
||||
active: { color: 'success' },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'shops',
|
||||
formatter: (party) => party.shops.size,
|
||||
},
|
||||
createOperationColumn([
|
||||
{
|
||||
label: 'Details',
|
||||
click: (party) => {
|
||||
this.router.navigate([`/party/${party.id}`]);
|
||||
},
|
||||
},
|
||||
]),
|
||||
];
|
||||
|
||||
constructor(
|
||||
private partiesService: SearchPartiesService,
|
||||
@ -28,11 +75,4 @@ export class SearchPartiesComponent {
|
||||
this.partiesService.preserve($event);
|
||||
this.fetchPartiesService.searchParties($event.text);
|
||||
}
|
||||
|
||||
partyMenuItemSelected(event: PartyMenuItemEvent) {
|
||||
switch (event.action) {
|
||||
case PartyActions.NavigateToParty:
|
||||
void this.router.navigate([`/party/${event.partyID}`]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,13 +3,13 @@ import { NgModule } from '@angular/core';
|
||||
import { FlexModule } from '@angular/flex-layout';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
||||
import { TableModule } from '@vality/ng-core';
|
||||
|
||||
import { EmptySearchResultModule } from '@cc/components/empty-search-result';
|
||||
|
||||
import { PageLayoutModule } from '../../shared';
|
||||
|
||||
import { PartiesSearchFiltersModule } from './parties-search-filters';
|
||||
import { PartiesTableModule } from './parties-table';
|
||||
import { SearchPartiesRoutingModule } from './search-parties-routing.module';
|
||||
import { SearchPartiesComponent } from './search-parties.component';
|
||||
|
||||
@ -19,11 +19,11 @@ import { SearchPartiesComponent } from './search-parties.component';
|
||||
FlexModule,
|
||||
MatCardModule,
|
||||
PartiesSearchFiltersModule,
|
||||
PartiesTableModule,
|
||||
CommonModule,
|
||||
EmptySearchResultModule,
|
||||
MatProgressBarModule,
|
||||
PageLayoutModule,
|
||||
TableModule,
|
||||
],
|
||||
declarations: [SearchPartiesComponent],
|
||||
})
|
||||
|
@ -1 +0,0 @@
|
||||
export * from './shop-details.module';
|
@ -1,5 +0,0 @@
|
||||
import { Services, RoutingConfig } from '@cc/app/shared/services';
|
||||
|
||||
export const ROUTING_CONFIG: RoutingConfig = {
|
||||
services: [Services.PartyManagement],
|
||||
};
|
@ -1,61 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Shop } from '@vality/domain-proto/domain';
|
||||
import { PartyID, ShopID } from '@vality/domain-proto/payment_processing';
|
||||
import { BehaviorSubject, Observable, defer, EMPTY, merge } from 'rxjs';
|
||||
import { catchError, shareReplay, switchMap } from 'rxjs/operators';
|
||||
|
||||
import { PartyManagementService } from '@cc/app/api/payment-processing';
|
||||
import { NotificationErrorService } from '@cc/app/shared/services/notification-error';
|
||||
import { inProgressFrom, progressTo } from '@cc/utils';
|
||||
|
||||
@Injectable()
|
||||
export class FetchShopService {
|
||||
shop$: Observable<Shop> = defer(() => this.getShop$).pipe(
|
||||
switchMap(({ partyID, shopID }) =>
|
||||
this.partyManagementService.GetShop(partyID, shopID).pipe(
|
||||
progressTo(this.progress$),
|
||||
catchError((err) => {
|
||||
this.notificationErrorService.error(
|
||||
err,
|
||||
'An error occurred while fetching shop',
|
||||
);
|
||||
return EMPTY;
|
||||
}),
|
||||
),
|
||||
),
|
||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||
);
|
||||
contract$ = defer(() => this.getShop$).pipe(
|
||||
switchMap(({ partyID, shopID }) =>
|
||||
this.partyManagementService.GetShopContract(partyID, shopID).pipe(
|
||||
progressTo(this.progress$),
|
||||
catchError((err) => {
|
||||
this.notificationErrorService.error(
|
||||
err,
|
||||
'An error occurred while fetching shop contract',
|
||||
);
|
||||
return EMPTY;
|
||||
}),
|
||||
),
|
||||
),
|
||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||
);
|
||||
|
||||
inProgress$ = inProgressFrom(() => this.progress$, merge(this.shop$, this.contract$));
|
||||
|
||||
private getShop$ = new BehaviorSubject<{ partyID: PartyID; shopID: ShopID }>(null);
|
||||
private progress$ = new BehaviorSubject(0);
|
||||
|
||||
constructor(
|
||||
private partyManagementService: PartyManagementService,
|
||||
private notificationErrorService: NotificationErrorService,
|
||||
) {}
|
||||
|
||||
getShop(partyID: PartyID, shopID: ShopID) {
|
||||
this.getShop$.next({ shopID, partyID });
|
||||
}
|
||||
|
||||
reload() {
|
||||
if (this.getShop$.value) this.getShop$.next(this.getShop$.value);
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
import { AppAuthGuardService } from '@cc/app/shared/services';
|
||||
|
||||
import { ROUTING_CONFIG } from './routing-config';
|
||||
import { ShopDetailsComponent } from './shop-details.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild([
|
||||
{
|
||||
path: '',
|
||||
component: ShopDetailsComponent,
|
||||
canActivate: [AppAuthGuardService],
|
||||
data: ROUTING_CONFIG,
|
||||
},
|
||||
]),
|
||||
],
|
||||
})
|
||||
export class ShopDetailsRoutingModule {}
|
@ -1,42 +0,0 @@
|
||||
<div fxLayout="column" fxLayoutGap="24px">
|
||||
<cc-headline>Shop details</cc-headline>
|
||||
<mat-card>
|
||||
<mat-card-content>
|
||||
<cc-json-viewer
|
||||
*ngIf="shop$ | async as shop"
|
||||
[extensions]="extensions$ | async"
|
||||
[metadata]="metadata$ | async"
|
||||
[value]="shop"
|
||||
namespace="domain"
|
||||
type="Shop"
|
||||
></cc-json-viewer>
|
||||
<div *ngIf="inProgress$ | async" fxLayout fxLayoutAlign="center center">
|
||||
<mat-spinner diameter="64"></mat-spinner>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
<mat-card>
|
||||
<mat-card-title>Contract</mat-card-title>
|
||||
<mat-card-content>
|
||||
<cc-json-viewer
|
||||
*ngIf="contract$ | async as contract"
|
||||
[extensions]="extensions$ | async"
|
||||
[metadata]="metadata$ | async"
|
||||
[value]="contract"
|
||||
namespace="domain"
|
||||
type="Contract"
|
||||
></cc-json-viewer>
|
||||
<div *ngIf="inProgress$ | async" fxLayout fxLayoutAlign="center center">
|
||||
<mat-spinner diameter="64"></mat-spinner>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
<v-actions *ngIf="shop$ | async as shop">
|
||||
<button mat-raised-button (click)="toggleSuspension()">
|
||||
{{ (shop.suspension | ccUnionKey) === 'active' ? 'Suspend' : 'Activate' }}
|
||||
</button>
|
||||
<button color="warn" mat-raised-button (click)="toggleBlocking()">
|
||||
{{ (shop.blocking | ccUnionKey) === 'unblocked' ? 'Block' : 'Unblock' }}
|
||||
</button>
|
||||
</v-actions>
|
||||
</div>
|
@ -1,112 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { UntilDestroy } from '@ngneat/until-destroy';
|
||||
import { DialogService, DialogResponseStatus, ConfirmDialogComponent } from '@vality/ng-core';
|
||||
import { combineLatest, switchMap, from } from 'rxjs';
|
||||
import { pluck, filter, withLatestFrom, first, map } from 'rxjs/operators';
|
||||
|
||||
import { DomainMetadataViewExtensionsService } from '@cc/app/shared/components/thrift-api-crud/domain/domain-thrift-viewer/services/domain-metadata-view-extensions';
|
||||
import { NotificationErrorService } from '@cc/app/shared/services/notification-error';
|
||||
|
||||
import { getUnionKey } from '../../../utils';
|
||||
import { PartyManagementService } from '../../api/payment-processing';
|
||||
import { NotificationService } from '../../shared/services/notification';
|
||||
|
||||
import { FetchShopService } from './services/fetch-shop.service';
|
||||
|
||||
@UntilDestroy()
|
||||
@Component({
|
||||
templateUrl: 'shop-details.component.html',
|
||||
providers: [FetchShopService],
|
||||
})
|
||||
export class ShopDetailsComponent {
|
||||
partyID$ = this.route.params.pipe(pluck('partyID'));
|
||||
shopID$ = this.route.params.pipe(pluck('shopID'));
|
||||
|
||||
shop$ = this.fetchShopService.shop$;
|
||||
contract$ = this.fetchShopService.contract$.pipe(map((c) => c?.contract));
|
||||
inProgress$ = this.fetchShopService.inProgress$;
|
||||
metadata$ = from(import('@vality/domain-proto/metadata.json').then((m) => m.default));
|
||||
extensions$ = this.domainMetadataViewExtensionsService.extensions$;
|
||||
|
||||
constructor(
|
||||
private fetchShopService: FetchShopService,
|
||||
private route: ActivatedRoute,
|
||||
private partyManagementService: PartyManagementService,
|
||||
private dialogService: DialogService,
|
||||
private notificationErrorService: NotificationErrorService,
|
||||
private notificationService: NotificationService,
|
||||
private domainMetadataViewExtensionsService: DomainMetadataViewExtensionsService,
|
||||
) {
|
||||
combineLatest([this.partyID$, this.shopID$]).subscribe(([partyID, shopID]) => {
|
||||
this.fetchShopService.getShop(partyID, shopID);
|
||||
});
|
||||
}
|
||||
|
||||
toggleBlocking() {
|
||||
this.shop$
|
||||
.pipe(
|
||||
first(),
|
||||
switchMap((shop) =>
|
||||
this.dialogService
|
||||
.open(ConfirmDialogComponent, {
|
||||
title:
|
||||
getUnionKey(shop.blocking) === 'unblocked'
|
||||
? 'Block shop'
|
||||
: 'Unblock shop',
|
||||
hasReason: true,
|
||||
})
|
||||
.afterClosed(),
|
||||
),
|
||||
filter((r) => r.status === DialogResponseStatus.Success),
|
||||
withLatestFrom(this.shop$, this.partyID$),
|
||||
switchMap(([{ data }, shop, partyID]) =>
|
||||
getUnionKey(shop.blocking) === 'unblocked'
|
||||
? this.partyManagementService.BlockShop(partyID, shop.id, data.reason)
|
||||
: this.partyManagementService.UnblockShop(partyID, shop.id, data.reason),
|
||||
),
|
||||
)
|
||||
.subscribe({
|
||||
next: () => {
|
||||
this.fetchShopService.reload();
|
||||
this.notificationService.success();
|
||||
},
|
||||
error: (err) => {
|
||||
this.notificationErrorService.error(err);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
toggleSuspension() {
|
||||
this.shop$
|
||||
.pipe(
|
||||
first(),
|
||||
switchMap((shop) =>
|
||||
this.dialogService
|
||||
.open(ConfirmDialogComponent, {
|
||||
title:
|
||||
getUnionKey(shop.suspension) === 'active'
|
||||
? 'Suspend shop'
|
||||
: 'Activate shop',
|
||||
})
|
||||
.afterClosed(),
|
||||
),
|
||||
filter((r) => r.status === DialogResponseStatus.Success),
|
||||
withLatestFrom(this.shop$, this.partyID$),
|
||||
switchMap(([, shop, partyID]) =>
|
||||
getUnionKey(shop.suspension) === 'active'
|
||||
? this.partyManagementService.SuspendShop(partyID, shop.id)
|
||||
: this.partyManagementService.ActivateShop(partyID, shop.id),
|
||||
),
|
||||
)
|
||||
.subscribe({
|
||||
next: () => {
|
||||
this.fetchShopService.reload();
|
||||
this.notificationService.success();
|
||||
},
|
||||
error: (err) => {
|
||||
this.notificationErrorService.error(err);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FlexModule } from '@angular/flex-layout';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { ActionsModule } from '@vality/ng-core';
|
||||
|
||||
import { JsonViewerModule } from '@cc/app/shared/components/json-viewer';
|
||||
import { HeadlineModule } from '@cc/components/headline';
|
||||
|
||||
import { ThriftPipesModule } from '../../shared';
|
||||
|
||||
import { ShopDetailsRoutingModule } from './shop-details-routing.module';
|
||||
import { ShopDetailsComponent } from './shop-details.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
ShopDetailsRoutingModule,
|
||||
HeadlineModule,
|
||||
FlexModule,
|
||||
MatCardModule,
|
||||
CommonModule,
|
||||
MatProgressSpinnerModule,
|
||||
ActionsModule,
|
||||
MatButtonModule,
|
||||
ThriftPipesModule,
|
||||
JsonViewerModule,
|
||||
],
|
||||
declarations: [ShopDetailsComponent],
|
||||
})
|
||||
export class ShopDetailsModule {}
|
@ -1,4 +1,4 @@
|
||||
<v-dialog [title]="dialogData.title">
|
||||
<v-dialog [title]="dialogData.title" fullSize>
|
||||
<cc-domain-thrift-form
|
||||
[formControl]="control"
|
||||
[namespace]="dialogData.namespace"
|
||||
@ -7,7 +7,7 @@
|
||||
|
||||
<v-dialog-actions>
|
||||
<button [disabled]="control.invalid" color="primary" mat-button (click)="upsert()">
|
||||
{{ isUpdate ? 'Update' : 'Add' }}
|
||||
{{ actionType === 'update' ? 'Update' : 'Add' }}
|
||||
</button>
|
||||
</v-dialog-actions>
|
||||
</v-dialog>
|
||||
|
@ -2,7 +2,7 @@ import { Component, Injector } from '@angular/core';
|
||||
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
|
||||
import { DialogSuperclass, DialogModule } from '@vality/ng-core';
|
||||
import { DialogSuperclass, DialogModule, DEFAULT_DIALOG_CONFIG } from '@vality/ng-core';
|
||||
import { ValueType } from '@vality/thrift-ts';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
@ -22,13 +22,19 @@ export class DomainThriftFormDialogComponent<T = unknown, R = unknown> extends D
|
||||
action: (object: T) => Observable<R>;
|
||||
namespace?: string;
|
||||
object?: T;
|
||||
actionType?: 'create' | 'update';
|
||||
},
|
||||
{ object?: T; result?: R; error?: unknown }
|
||||
> {
|
||||
static defaultDialogConfig = DEFAULT_DIALOG_CONFIG.large;
|
||||
|
||||
control = this.fb.control(this.dialogData.object ?? null);
|
||||
|
||||
get isUpdate() {
|
||||
return this.dialogData.object !== undefined;
|
||||
get actionType() {
|
||||
return (
|
||||
this.dialogData.actionType ??
|
||||
(this.dialogData.object !== undefined ? 'update' : 'create')
|
||||
);
|
||||
}
|
||||
|
||||
constructor(
|
||||
|
@ -1,7 +1,7 @@
|
||||
<cc-metadata-form
|
||||
<cc-thrift-editor
|
||||
[extensions]="extensions$ | async"
|
||||
[formControl]="control"
|
||||
[metadata]="metadata$ | async"
|
||||
[namespace]="namespace ?? defaultNamespace"
|
||||
[type]="type"
|
||||
></cc-metadata-form>
|
||||
></cc-thrift-editor>
|
||||
|
@ -6,6 +6,7 @@ import { createControlProviders, getImportValue } from '@vality/ng-core';
|
||||
|
||||
import { DomainMetadataFormExtensionsService } from '../../../../services';
|
||||
import { MetadataFormModule } from '../../../metadata-form';
|
||||
import { ThriftEditorModule } from '../../../thrift-editor';
|
||||
import { BaseThriftFormSuperclass } from '../../thrift-forms/utils/thrift-form-superclass';
|
||||
|
||||
@Component({
|
||||
@ -13,7 +14,7 @@ import { BaseThriftFormSuperclass } from '../../thrift-forms/utils/thrift-form-s
|
||||
selector: 'cc-domain-thrift-form',
|
||||
templateUrl: './domain-thrift-form.component.html',
|
||||
providers: createControlProviders(() => DomainThriftFormComponent),
|
||||
imports: [CommonModule, ReactiveFormsModule, MetadataFormModule],
|
||||
imports: [CommonModule, ReactiveFormsModule, MetadataFormModule, ThriftEditorModule],
|
||||
})
|
||||
export class DomainThriftFormComponent extends BaseThriftFormSuperclass {
|
||||
metadata$ = getImportValue<ThriftAstMetadata[]>(import('@vality/domain-proto/metadata.json'));
|
||||
|
@ -1,6 +1,7 @@
|
||||
<cc-thrift-viewer
|
||||
[extensions]="extensions$ | async"
|
||||
[metadata]="metadata$ | async"
|
||||
[progress]="progress"
|
||||
[type]="type"
|
||||
[value]="value"
|
||||
namespace="domain"
|
||||
|
@ -3,6 +3,7 @@ import { Component, Input } from '@angular/core';
|
||||
import { ThriftAstMetadata } from '@vality/domain-proto';
|
||||
import { getImportValue } from '@vality/ng-core';
|
||||
import { ValueType } from '@vality/thrift-ts';
|
||||
import { coerceBoolean } from 'coerce-property';
|
||||
|
||||
import { ThriftViewerModule } from '../../../thrift-viewer';
|
||||
|
||||
@ -17,6 +18,7 @@ import { DomainMetadataViewExtensionsService } from './services/domain-metadata-
|
||||
export class DomainThriftViewerComponent<T> {
|
||||
@Input() value: T;
|
||||
@Input() type: ValueType;
|
||||
@Input() @coerceBoolean progress: boolean | '' = false;
|
||||
// @Input() extensions?: MetadataViewExtension[];
|
||||
|
||||
metadata$ = getImportValue<ThriftAstMetadata[]>(import('@vality/domain-proto/metadata.json'));
|
||||
|
@ -24,7 +24,7 @@ export enum EditorKind {
|
||||
providers: createControlProviders(() => ThriftEditorComponent),
|
||||
})
|
||||
export class ThriftEditorComponent<T> extends ValidatedFormControlSuperclass<T> {
|
||||
@Input() kind: EditorKind = EditorKind.Editor;
|
||||
@Input() kind: EditorKind = EditorKind.Form;
|
||||
|
||||
@Input() defaultValue?: T;
|
||||
|
||||
|
@ -1,32 +1,35 @@
|
||||
<div class="wrapper" gdColumns="1fr" gdGap="8px" gdRows="1fr auto">
|
||||
<cc-monaco-diff-editor
|
||||
*ngIf="isDiff; else standard"
|
||||
[modified]="comparedFile$ | async"
|
||||
[options]="{ renderSideBySide: true, readOnly: true }"
|
||||
[original]="valueFile$ | async"
|
||||
class="monaco-editor"
|
||||
></cc-monaco-diff-editor>
|
||||
<ng-template #standard>
|
||||
<cc-json-viewer
|
||||
*ngIf="kind === 'component'"
|
||||
[extensions]="extensions"
|
||||
[metadata]="metadata"
|
||||
[namespace]="namespace"
|
||||
[type]="type"
|
||||
[value]="value"
|
||||
></cc-json-viewer>
|
||||
<cc-monaco-editor
|
||||
*ngIf="kind === 'editor'"
|
||||
[file]="valueFile$ | async"
|
||||
[options]="{ readOnly: true }"
|
||||
<div *ngIf="progress; else loaded" fxLayoutAlign="center"><mat-spinner></mat-spinner></div>
|
||||
<ng-template #loaded>
|
||||
<div class="wrapper" gdColumns="1fr" gdGap="8px" gdRows="1fr auto">
|
||||
<cc-monaco-diff-editor
|
||||
*ngIf="isDiff; else standard"
|
||||
[modified]="comparedFile$ | async"
|
||||
[options]="{ renderSideBySide: true, readOnly: true }"
|
||||
[original]="valueFile$ | async"
|
||||
class="monaco-editor"
|
||||
>
|
||||
</cc-monaco-editor>
|
||||
<div class="actions" fxLayout fxLayoutAlign="end center" fxLayoutGap="2px">
|
||||
<button color="primary" mat-icon-button (click)="toggleKind()">
|
||||
<mat-icon *ngIf="kind === 'component'">code_blocks</mat-icon>
|
||||
<mat-icon *ngIf="kind === 'editor'">view_comfy_alt</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
></cc-monaco-diff-editor>
|
||||
<ng-template #standard>
|
||||
<cc-json-viewer
|
||||
*ngIf="kind === 'component'"
|
||||
[extensions]="extensions"
|
||||
[metadata]="metadata"
|
||||
[namespace]="namespace"
|
||||
[type]="type"
|
||||
[value]="value"
|
||||
></cc-json-viewer>
|
||||
<cc-monaco-editor
|
||||
*ngIf="kind === 'editor'"
|
||||
[file]="valueFile$ | async"
|
||||
[options]="{ readOnly: true }"
|
||||
class="monaco-editor"
|
||||
>
|
||||
</cc-monaco-editor>
|
||||
<div class="actions" fxLayout fxLayoutAlign="end center" fxLayoutGap="2px">
|
||||
<button color="primary" mat-icon-button (click)="toggleKind()">
|
||||
<mat-icon *ngIf="kind === 'component'">code_blocks</mat-icon>
|
||||
<mat-icon *ngIf="kind === 'editor'">view_comfy_alt</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
@ -3,6 +3,7 @@ import { UntilDestroy } from '@ngneat/until-destroy';
|
||||
import { ThriftAstMetadata } from '@vality/domain-proto';
|
||||
import { ComponentChanges } from '@vality/ng-core';
|
||||
import { ValueType } from '@vality/thrift-ts';
|
||||
import { coerceBoolean } from 'coerce-property';
|
||||
import { ReplaySubject } from 'rxjs';
|
||||
|
||||
import { MetadataViewExtension } from '@cc/app/shared/components/json-viewer';
|
||||
@ -26,6 +27,7 @@ export class ThriftViewerComponent<T> implements OnChanges {
|
||||
@Input() kind: ViewerKind = ViewerKind.Component;
|
||||
@Input() value: T;
|
||||
@Input() compared?: T;
|
||||
@Input() @coerceBoolean progress: boolean | '' = false;
|
||||
|
||||
@Input() metadata: ThriftAstMetadata[];
|
||||
@Input() namespace: string;
|
||||
|
@ -5,6 +5,7 @@ import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatButtonToggleModule } from '@angular/material/button-toggle';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
|
||||
import { MetadataFormModule } from '@cc/app/shared/components/metadata-form';
|
||||
import { MonacoEditorModule } from '@cc/components/monaco-editor';
|
||||
@ -27,6 +28,7 @@ import { ThriftViewerComponent } from './thrift-viewer.component';
|
||||
FlexModule,
|
||||
JsonViewerModule,
|
||||
GridModule,
|
||||
MatProgressSpinnerModule,
|
||||
],
|
||||
})
|
||||
export class ThriftViewerModule {}
|
||||
|
Loading…
Reference in New Issue
Block a user