mirror of
https://github.com/valitydev/control-center.git
synced 2024-11-06 02:25:17 +00:00
TD-882: New party page (#341)
This commit is contained in:
parent
fc1335e8e0
commit
d7741ae064
8
package-lock.json
generated
8
package-lock.json
generated
@ -25,7 +25,7 @@
|
||||
"@vality/fistful-proto": "2.0.1-6600be9.0",
|
||||
"@vality/machinegun-proto": "1.0.0",
|
||||
"@vality/magista-proto": "2.0.2-28d11b9.0",
|
||||
"@vality/ng-core": "^17.2.1-pr-57-3adeb57.0",
|
||||
"@vality/ng-core": "17.2.1-pr-57-943cc8a.0",
|
||||
"@vality/payout-manager-proto": "2.0.1-eb4091a.0",
|
||||
"@vality/repairer-proto": "2.0.2-07b73e9.0",
|
||||
"@vality/thrift-ts": "2.4.1-8ad5123.0",
|
||||
@ -6454,9 +6454,9 @@
|
||||
"integrity": "sha512-BsDy5ejotfTtUlwuoX3kz+PYJ5NSTW6m5ZRGv+p5HaKXSjR7tserPdv0q133Wp4T+sg0ED0Qr9Peqsrn+9XlDQ=="
|
||||
},
|
||||
"node_modules/@vality/ng-core": {
|
||||
"version": "17.2.1-pr-57-3adeb57.0",
|
||||
"resolved": "https://registry.npmjs.org/@vality/ng-core/-/ng-core-17.2.1-pr-57-3adeb57.0.tgz",
|
||||
"integrity": "sha512-h8/U6pUrJDMTXUj2HL9xC6KjlVa1kNj20DcI79gaoYmh//4L/U+y6vF75ieOeZW8MUFDD9WcVGfrwsRFWOntqQ==",
|
||||
"version": "17.2.1-pr-57-943cc8a.0",
|
||||
"resolved": "https://registry.npmjs.org/@vality/ng-core/-/ng-core-17.2.1-pr-57-943cc8a.0.tgz",
|
||||
"integrity": "sha512-o7WVumfCORuG+NgzKCyEH4E5UKiF2eESHvqzJRe3GSXFkUD69NWnxx0qmGNIelR4jqy1O/JXd1bEuqWg6xobLA==",
|
||||
"dependencies": {
|
||||
"@angular/material-date-fns-adapter": "^17.2.0",
|
||||
"@ng-matero/extensions": "^17.1.0",
|
||||
|
@ -33,7 +33,7 @@
|
||||
"@vality/fistful-proto": "2.0.1-6600be9.0",
|
||||
"@vality/machinegun-proto": "1.0.0",
|
||||
"@vality/magista-proto": "2.0.2-28d11b9.0",
|
||||
"@vality/ng-core": "^17.2.1-pr-57-3adeb57.0",
|
||||
"@vality/ng-core": "17.2.1-pr-57-943cc8a.0",
|
||||
"@vality/payout-manager-proto": "2.0.1-eb4091a.0",
|
||||
"@vality/repairer-proto": "2.0.2-07b73e9.0",
|
||||
"@vality/thrift-ts": "2.4.1-8ad5123.0",
|
||||
|
@ -2,17 +2,7 @@
|
||||
|
||||
<mat-sidenav-container autosize class="container">
|
||||
<mat-sidenav fixedInViewport="true" fixedTopGap="64" mode="side" opened role="navigation">
|
||||
<mat-nav-list>
|
||||
<ng-container *ngFor="let group of menuItemsGroups$ | async; let i = index">
|
||||
<mat-divider *ngIf="i !== 0"></mat-divider>
|
||||
<mat-list-item
|
||||
*ngFor="let item of group"
|
||||
[routerLink]="item.route"
|
||||
[routerLinkActive]="['active']"
|
||||
>{{ item.name }}
|
||||
</mat-list-item>
|
||||
</ng-container>
|
||||
</mat-nav-list>
|
||||
<v-nav [links]="links$ | async" exact style="display: block"></v-nav>
|
||||
</mat-sidenav>
|
||||
<mat-sidenav-content class="content" role="main">
|
||||
<router-outlet></router-outlet>
|
||||
|
@ -1,10 +1,11 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Link } from '@vality/ng-core';
|
||||
import { KeycloakService } from 'keycloak-angular';
|
||||
import sortBy from 'lodash-es/sortBy';
|
||||
import { from, Observable } from 'rxjs';
|
||||
import { shareReplay, map } from 'rxjs/operators';
|
||||
import { shareReplay, map, startWith } from 'rxjs/operators';
|
||||
|
||||
import { AppAuthGuardService } from '@cc/app/shared/services';
|
||||
import { AppAuthGuardService, Services } from '@cc/app/shared/services';
|
||||
|
||||
import { environment } from '../environments/environment';
|
||||
|
||||
@ -28,9 +29,8 @@ import { SidenavInfoService } from './shared/components/sidenav-info';
|
||||
styleUrls: ['./app.component.scss'],
|
||||
})
|
||||
export class AppComponent {
|
||||
menuItemsGroups$: Observable<{ name: string; route: string }[][]> = from(
|
||||
this.keycloakService.loadUserProfile(),
|
||||
).pipe(
|
||||
links$: Observable<Link[][]> = from(this.keycloakService.loadUserProfile()).pipe(
|
||||
startWith(null),
|
||||
map(() => this.getMenuItemsGroups()),
|
||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||
);
|
||||
@ -58,80 +58,80 @@ export class AppComponent {
|
||||
}
|
||||
|
||||
private getMenuItemsGroups() {
|
||||
const menuItems = [
|
||||
const menuItems: (Link & { services: Services[] })[][] = [
|
||||
[
|
||||
{
|
||||
name: 'Domain config',
|
||||
route: '/domain',
|
||||
label: 'Domain config',
|
||||
url: '/domain',
|
||||
services: DOMAIN_ROUTING_CONFIG.services,
|
||||
},
|
||||
{
|
||||
name: 'Terminals',
|
||||
route: '/terminals',
|
||||
label: 'Terminals',
|
||||
url: '/terminals',
|
||||
services: TERMINALS_ROUTING_CONFIG.services,
|
||||
},
|
||||
{
|
||||
name: 'Repairing',
|
||||
route: '/repairing',
|
||||
label: 'Repairing',
|
||||
url: '/repairing',
|
||||
services: REPAIRING_ROUTING_CONFIG.services,
|
||||
},
|
||||
{
|
||||
name: 'Sources',
|
||||
route: '/sources',
|
||||
label: 'Sources',
|
||||
url: '/sources',
|
||||
services: SOURCES_ROUTING_CONFIG.services,
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
name: 'Merchants',
|
||||
route: '/parties',
|
||||
label: 'Merchants',
|
||||
url: '/parties',
|
||||
services: PARTIES_ROUTING_CONFIG.services,
|
||||
},
|
||||
{
|
||||
name: 'Shops',
|
||||
route: '/shops',
|
||||
label: 'Shops',
|
||||
url: '/shops',
|
||||
services: SHOPS_ROUTING_CONFIG.services,
|
||||
},
|
||||
{
|
||||
name: 'Wallets',
|
||||
route: '/wallets',
|
||||
label: 'Wallets',
|
||||
url: '/wallets',
|
||||
services: WALLETS_ROUTING_CONFIG.services,
|
||||
},
|
||||
{
|
||||
name: 'Claims',
|
||||
route: '/claims',
|
||||
label: 'Claims',
|
||||
url: '/claims',
|
||||
services: CLAIMS_ROUTING_CONFIG.services,
|
||||
},
|
||||
],
|
||||
sortBy(
|
||||
[
|
||||
{
|
||||
name: 'Payments',
|
||||
route: '/payments',
|
||||
label: 'Payments',
|
||||
url: '/payments',
|
||||
services: PAYMENTS_ROUTING_CONFIG.services,
|
||||
},
|
||||
{
|
||||
name: 'Payouts',
|
||||
route: '/payouts',
|
||||
label: 'Payouts',
|
||||
url: '/payouts',
|
||||
services: PAYOUTS_ROUTING_CONFIG.services,
|
||||
},
|
||||
{
|
||||
name: 'Chargebacks',
|
||||
route: '/chargebacks',
|
||||
label: 'Chargebacks',
|
||||
url: '/chargebacks',
|
||||
services: WALLETS_ROUTING_CONFIG.services,
|
||||
},
|
||||
{
|
||||
name: 'Deposits',
|
||||
route: '/deposits',
|
||||
label: 'Deposits',
|
||||
url: '/deposits',
|
||||
services: DEPOSITS_ROUTING_CONFIG.services,
|
||||
},
|
||||
{
|
||||
name: 'Withdrawals',
|
||||
route: '/withdrawals',
|
||||
label: 'Withdrawals',
|
||||
url: '/withdrawals',
|
||||
services: WITHDRAWALS_ROUTING_CONFIG.services,
|
||||
},
|
||||
],
|
||||
'name',
|
||||
'label',
|
||||
),
|
||||
];
|
||||
return menuItems.map((group) =>
|
||||
|
@ -13,7 +13,7 @@ import { MatToolbarModule } from '@angular/material/toolbar';
|
||||
import { BrowserModule, DomSanitizer } from '@angular/platform-browser';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { InputMaskModule } from '@ngneat/input-mask';
|
||||
import { QUERY_PARAMS_SERIALIZERS } from '@vality/ng-core';
|
||||
import { NavComponent, QUERY_PARAMS_SERIALIZERS } from '@vality/ng-core';
|
||||
import { MonacoEditorModule } from 'ngx-monaco-editor-v2';
|
||||
|
||||
import { KeycloakTokenInfoModule } from '@cc/app/shared/services';
|
||||
@ -72,6 +72,7 @@ export let AppInjector: Injector;
|
||||
SectionsModule,
|
||||
SidenavInfoComponent,
|
||||
ToolbarComponent,
|
||||
NavComponent,
|
||||
MonacoEditorModule.forRoot(),
|
||||
// TODO: hack for metadata datetime 😡
|
||||
MatDatepickerModule,
|
||||
|
@ -1,6 +1,27 @@
|
||||
<mat-toolbar class="toolbar" color="primary">
|
||||
<span>Control Center</span><span class="spacer"></span><span>{{ username$ | async }}</span>
|
||||
<button [matMenuTriggerFor]="userMenu" mat-icon-button><mat-icon>more_vert</mat-icon></button>
|
||||
<div class="logo">Control Center</div>
|
||||
<div class="search">
|
||||
<cc-merchant-field
|
||||
[formControl]="partyIdControl"
|
||||
appearance="outline"
|
||||
hint=""
|
||||
label="Search merchant"
|
||||
size="small"
|
||||
></cc-merchant-field>
|
||||
<mat-icon
|
||||
*ngIf="partyIdControl.value"
|
||||
[cdkCopyToClipboard]="partyIdControl.value"
|
||||
class="copy"
|
||||
(cdkCopyToClipboardCopied)="copyNotify($event)"
|
||||
>content_copy</mat-icon
|
||||
>
|
||||
</div>
|
||||
<div class="user">
|
||||
<span>{{ username$ | async }}</span>
|
||||
<button [matMenuTriggerFor]="userMenu" mat-icon-button>
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</mat-toolbar>
|
||||
|
||||
<mat-menu #userMenu="matMenu"><button mat-menu-item (click)="logout()">Logout</button></mat-menu>
|
||||
|
@ -1,8 +1,52 @@
|
||||
.toolbar {
|
||||
position: fixed;
|
||||
z-index: 2;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
|
||||
& > * {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.spacer {
|
||||
flex: 1 1 auto;
|
||||
.search {
|
||||
align-self: end;
|
||||
margin-bottom: -9px;
|
||||
justify-self: center;
|
||||
position: relative;
|
||||
|
||||
.copy {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: -291px;
|
||||
top: 8px;
|
||||
}
|
||||
|
||||
::ng-deep .mdc-floating-label {
|
||||
pointer-events: none !important;
|
||||
}
|
||||
|
||||
::ng-deep & > * {
|
||||
min-width: 280px;
|
||||
}
|
||||
|
||||
::ng-deep * {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
::ng-deep .mdc-notched-outline * {
|
||||
border-color: rgba(255, 255, 255, 0.5) !important;
|
||||
}
|
||||
|
||||
::ng-deep .ng-spinner-loader {
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
border-left-color: #fff;
|
||||
}
|
||||
|
||||
--mtx-select-enabled-arrow-color: #fff;
|
||||
}
|
||||
|
||||
.user {
|
||||
justify-self: end;
|
||||
}
|
||||
|
@ -1,29 +1,92 @@
|
||||
import { CdkCopyToClipboard } from '@angular/cdk/clipboard';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, DestroyRef, OnInit } from '@angular/core';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { FormsModule, ReactiveFormsModule, FormControl } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||
import { Router } from '@angular/router';
|
||||
import { UrlService } from '@vality/ng-core';
|
||||
import { KeycloakService } from 'keycloak-angular';
|
||||
import { from } from 'rxjs';
|
||||
import { map, shareReplay } from 'rxjs/operators';
|
||||
import { map, shareReplay, distinctUntilChanged } from 'rxjs/operators';
|
||||
|
||||
import { MerchantFieldModule } from '../../../shared/components/merchant-field';
|
||||
|
||||
@Component({
|
||||
selector: 'cc-toolbar',
|
||||
standalone: true,
|
||||
imports: [MatButtonModule, MatIconModule, MatMenuModule, MatToolbarModule, CommonModule],
|
||||
imports: [
|
||||
MatButtonModule,
|
||||
MatIconModule,
|
||||
MatMenuModule,
|
||||
MatToolbarModule,
|
||||
CommonModule,
|
||||
MerchantFieldModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
CdkCopyToClipboard,
|
||||
],
|
||||
templateUrl: './toolbar.component.html',
|
||||
styleUrl: './toolbar.component.scss',
|
||||
})
|
||||
export class ToolbarComponent {
|
||||
export class ToolbarComponent implements OnInit {
|
||||
username$ = from(this.keycloakService.loadUserProfile()).pipe(
|
||||
map(() => this.keycloakService.getUsername()),
|
||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||
);
|
||||
partyIdControl = new FormControl<string>(this.getPartyId());
|
||||
|
||||
constructor(private keycloakService: KeycloakService) {}
|
||||
constructor(
|
||||
private keycloakService: KeycloakService,
|
||||
private router: Router,
|
||||
private urlService: UrlService,
|
||||
private destroyRef: DestroyRef,
|
||||
private snackBar: MatSnackBar,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.partyIdControl.valueChanges
|
||||
.pipe(distinctUntilChanged(), takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe((partyId) => {
|
||||
if (partyId) {
|
||||
if (this.getPartyId() !== partyId) {
|
||||
void this.router.navigate([`/party/${partyId}`]);
|
||||
}
|
||||
} else {
|
||||
void this.router.navigate([`/parties`]);
|
||||
}
|
||||
});
|
||||
this.urlService.path$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((path) => {
|
||||
const partyId = this.getPartyId(path);
|
||||
if (partyId) {
|
||||
if (partyId !== this.partyIdControl.value) {
|
||||
this.partyIdControl.setValue(partyId, { emitEvent: false });
|
||||
}
|
||||
} else if (this.partyIdControl.value) {
|
||||
this.partyIdControl.setValue(null, { emitEvent: false });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
logout() {
|
||||
void this.keycloakService.logout();
|
||||
}
|
||||
|
||||
copyNotify(res: boolean) {
|
||||
this.snackBar.open(
|
||||
res
|
||||
? `Party Id #${this.partyIdControl.value} copied`
|
||||
: `Party Id not copied, select and copy yourself: ${this.partyIdControl.value}`,
|
||||
'OK',
|
||||
{ duration: res ? 2_000 : 60_000 },
|
||||
);
|
||||
}
|
||||
|
||||
private getPartyId(path: string[] = this.urlService.path) {
|
||||
return path[0] === 'party' && path[1] ? path[1] : null;
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,4 @@
|
||||
<cc-page-layout
|
||||
[path]="[
|
||||
{ label: 'Merchants', link: '/parties' },
|
||||
{
|
||||
label: (party$ | async)?.contact_info?.email,
|
||||
tooltip: (party$ | async)?.id,
|
||||
link: ['/party', (party$ | async)?.id]
|
||||
},
|
||||
{
|
||||
label: 'Merchant claims',
|
||||
link: '/claims',
|
||||
queryParams: { party_id: '"' + (party$ | async)?.id + '"' }
|
||||
}
|
||||
]"
|
||||
description="#{{ (claim$ | async)?.id }}"
|
||||
title="Claim"
|
||||
>
|
||||
<cc-page-layout description="#{{ (claim$ | async)?.id }}" title="Claim">
|
||||
<cc-page-layout-actions *ngIf="claim$ | async as claim">
|
||||
<cc-status [color]="statusColor[claim.status | ccUnionKey]">{{
|
||||
claim.status | ccUnionKey | keyTitle | titlecase
|
||||
|
@ -10,7 +10,7 @@ import { ROUTING_CONFIG } from './routing-config';
|
||||
imports: [
|
||||
RouterModule.forChild([
|
||||
{
|
||||
path: 'claims',
|
||||
path: '',
|
||||
component: ClaimsComponent,
|
||||
canActivate: [AppAuthGuardService],
|
||||
data: ROUTING_CONFIG,
|
||||
|
@ -1,5 +1,5 @@
|
||||
<v-table
|
||||
[columns]="columns"
|
||||
[columns]="columns()"
|
||||
[data]="data"
|
||||
[hasMore]="hasMore"
|
||||
[progress]="isLoading"
|
||||
|
@ -1,11 +1,19 @@
|
||||
import { Component, Input, Output, EventEmitter } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
Input,
|
||||
Output,
|
||||
EventEmitter,
|
||||
booleanAttribute,
|
||||
input,
|
||||
computed,
|
||||
} from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { Claim, ClaimStatus } from '@vality/domain-proto/claim_management';
|
||||
import { Column, LoadOptions, TagColumn, createOperationColumn } from '@vality/ng-core';
|
||||
import isObject from 'lodash-es/isObject';
|
||||
import startCase from 'lodash-es/startCase';
|
||||
|
||||
import { getUnionKey } from '../../../../utils';
|
||||
import { PartiesStoreService } from '../../../api/payment-processing';
|
||||
import { createPartyColumn } from '../../../shared';
|
||||
|
||||
@Component({
|
||||
@ -17,11 +25,17 @@ export class ClaimsTableComponent {
|
||||
@Input() data!: Claim[];
|
||||
@Input() isLoading?: boolean | null;
|
||||
@Input() hasMore?: boolean | null;
|
||||
noParty = input(false, { transform: booleanAttribute });
|
||||
|
||||
@Output() update = new EventEmitter<LoadOptions>();
|
||||
@Output() more = new EventEmitter<void>();
|
||||
|
||||
columns: Column<Claim>[] = [
|
||||
columns = computed<Column<Claim>[]>(() =>
|
||||
this.sourceColumns.filter(
|
||||
(c) => (isObject(c) && c?.field !== 'party_id') || !this.noParty(),
|
||||
),
|
||||
);
|
||||
private sourceColumns: Column<Claim>[] = [
|
||||
{ field: 'id', link: (d) => this.getClaimLink(d.party_id, d.id) },
|
||||
createPartyColumn('party_id'),
|
||||
{
|
||||
@ -51,10 +65,7 @@ export class ClaimsTableComponent {
|
||||
]),
|
||||
];
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private partiesStoreService: PartiesStoreService,
|
||||
) {}
|
||||
constructor(private router: Router) {}
|
||||
|
||||
navigateToClaim(partyId: string, claimID: number) {
|
||||
void this.router.navigate([this.getClaimLink(partyId, claimID)]);
|
||||
|
@ -4,6 +4,10 @@
|
||||
></cc-page-layout-actions>
|
||||
<v-filters #filters [active]="active">
|
||||
<ng-template [formGroup]="filtersForm">
|
||||
<cc-merchant-field
|
||||
*ngIf="!(party$ | async)"
|
||||
formControlName="party_id"
|
||||
></cc-merchant-field>
|
||||
<mat-form-field>
|
||||
<input
|
||||
autocomplete="off"
|
||||
@ -20,13 +24,13 @@
|
||||
}}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<cc-merchant-field formControlName="party_id"></cc-merchant-field>
|
||||
</ng-template>
|
||||
</v-filters>
|
||||
<cc-claims-table
|
||||
[data]="claims$ | async"
|
||||
[hasMore]="hasMore$ | async"
|
||||
[isLoading]="isLoading$ | async"
|
||||
[noParty]="!!(party$ | async)"
|
||||
(more)="more()"
|
||||
(update)="load($event)"
|
||||
>
|
||||
|
@ -4,15 +4,17 @@ import { NonNullableFormBuilder } from '@angular/forms';
|
||||
import { PartyID } from '@vality/domain-proto/domain';
|
||||
import { DialogService, LoadOptions, QueryParamsService, clean } from '@vality/ng-core';
|
||||
import { debounceTime } from 'rxjs';
|
||||
import { startWith } from 'rxjs/operators';
|
||||
import { startWith, take } from 'rxjs/operators';
|
||||
|
||||
import { CLAIM_STATUSES } from '../../api/claim-management';
|
||||
import { PartyStoreService } from '../party';
|
||||
|
||||
import { CreateClaimDialogComponent } from './components/create-claim-dialog/create-claim-dialog.component';
|
||||
import { FetchClaimsService } from './fetch-claims.service';
|
||||
|
||||
@Component({
|
||||
templateUrl: './claims.component.html',
|
||||
providers: [PartyStoreService],
|
||||
})
|
||||
export class ClaimsComponent implements OnInit {
|
||||
isLoading$ = this.fetchClaimsService.isLoading$;
|
||||
@ -25,6 +27,7 @@ export class ClaimsComponent implements OnInit {
|
||||
statuses: [[] as string[]],
|
||||
});
|
||||
active = 0;
|
||||
party$ = this.partyStoreService.party$;
|
||||
|
||||
private selectedPartyId: PartyID;
|
||||
|
||||
@ -34,6 +37,7 @@ export class ClaimsComponent implements OnInit {
|
||||
private fb: NonNullableFormBuilder,
|
||||
private qp: QueryParamsService<ClaimsComponent['filtersForm']['value']>,
|
||||
private destroyRef: DestroyRef,
|
||||
private partyStoreService: PartyStoreService,
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
@ -48,11 +52,17 @@ export class ClaimsComponent implements OnInit {
|
||||
load(options?: LoadOptions): void {
|
||||
const filters = clean(this.filtersForm.value);
|
||||
void this.qp.set(filters);
|
||||
this.fetchClaimsService.load(
|
||||
{ ...filters, statuses: filters.statuses?.map((status) => ({ [status]: {} })) || [] },
|
||||
options,
|
||||
);
|
||||
this.active = Object.keys(filters).length;
|
||||
this.partyStoreService.party$.pipe(take(1)).subscribe((p) => {
|
||||
this.fetchClaimsService.load(
|
||||
clean({
|
||||
party_id: p ? p.id : undefined,
|
||||
...filters,
|
||||
statuses: filters.statuses?.map((status) => ({ [status]: {} })) || [],
|
||||
}),
|
||||
options,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
more(): void {
|
||||
|
1
src/app/sections/claims/index.ts
Normal file
1
src/app/sections/claims/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './claims.module';
|
@ -13,12 +13,7 @@ import { ROUTING_CONFIG } from './routing-config';
|
||||
path: '',
|
||||
canActivate: [AppAuthGuardService],
|
||||
data: ROUTING_CONFIG,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: DomainInfoComponent,
|
||||
},
|
||||
],
|
||||
component: DomainInfoComponent,
|
||||
},
|
||||
]),
|
||||
],
|
||||
|
@ -1,9 +1,8 @@
|
||||
<div style="display: flex; flex-direction: column; gap: 24px">
|
||||
<div class="mat-headline-4 mat-no-margin">Shops</div>
|
||||
<cc-page-layout title="Shops">
|
||||
<cc-shops-table
|
||||
[progress]="progress$ | async"
|
||||
[shops]="shopsParty$ | async"
|
||||
noPartyColumn
|
||||
(update)="update()"
|
||||
></cc-shops-table>
|
||||
</div>
|
||||
</cc-page-layout>
|
||||
|
@ -3,6 +3,7 @@ import { NgModule } from '@angular/core';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { InputFieldModule } from '@vality/ng-core';
|
||||
|
||||
import { PageLayoutModule } from '../../shared';
|
||||
import { ShopsTableComponent } from '../../shared/components/shops-table';
|
||||
|
||||
import { PartyShopsRoutingModule } from './party-shops-routing.module';
|
||||
@ -16,6 +17,7 @@ import { PartyShopsComponent } from './party-shops.component';
|
||||
ReactiveFormsModule,
|
||||
InputFieldModule,
|
||||
ShopsTableComponent,
|
||||
PageLayoutModule,
|
||||
],
|
||||
declarations: [PartyShopsComponent],
|
||||
})
|
||||
|
2
src/app/sections/party/index.ts
Normal file
2
src/app/sections/party/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './party-store.service';
|
||||
export * from './party.module';
|
@ -25,7 +25,28 @@ import { ROUTING_CONFIG } from './routing-config';
|
||||
loadChildren: () =>
|
||||
import('../routing-rules').then((m) => m.RoutingRulesModule),
|
||||
},
|
||||
{ path: '', redirectTo: 'shops', pathMatch: 'full' },
|
||||
{
|
||||
path: 'claims',
|
||||
loadChildren: () => import('../claims').then((m) => m.ClaimsModule),
|
||||
},
|
||||
{
|
||||
path: 'claim',
|
||||
redirectTo: 'claims',
|
||||
},
|
||||
{
|
||||
path: 'wallets',
|
||||
loadChildren: () =>
|
||||
import('../wallets/wallets.module').then((m) => m.WalletsModule),
|
||||
},
|
||||
{
|
||||
path: 'wallet',
|
||||
redirectTo: 'wallets',
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
redirectTo: 'shops',
|
||||
pathMatch: 'full',
|
||||
},
|
||||
],
|
||||
},
|
||||
]),
|
||||
|
44
src/app/sections/party/party-store.service.ts
Normal file
44
src/app/sections/party/party-store.service.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { Party } from '@vality/domain-proto/domain';
|
||||
import { NotifyLogService } from '@vality/ng-core';
|
||||
import { EMPTY, Observable, of } from 'rxjs';
|
||||
import {
|
||||
startWith,
|
||||
switchMap,
|
||||
catchError,
|
||||
shareReplay,
|
||||
distinctUntilChanged,
|
||||
} from 'rxjs/operators';
|
||||
|
||||
import { PartyManagementService } from '../../api/payment-processing';
|
||||
|
||||
@Injectable()
|
||||
export class PartyStoreService {
|
||||
party$: Observable<Party | Pick<Party, 'id'> | null> = this.route.params.pipe(
|
||||
startWith(this.route.snapshot.params),
|
||||
switchMap(({ partyID }) =>
|
||||
partyID
|
||||
? this.partyManagementService.Get(partyID).pipe(
|
||||
catchError((err) => {
|
||||
this.log.error(err);
|
||||
return EMPTY;
|
||||
}),
|
||||
)
|
||||
: of(null),
|
||||
),
|
||||
startWith(this.partyId ? { id: this.partyId } : null),
|
||||
distinctUntilChanged(),
|
||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||
);
|
||||
|
||||
private get partyId() {
|
||||
return this.route.snapshot.params.partyID;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private partyManagementService: PartyManagementService,
|
||||
private log: NotifyLogService,
|
||||
) {}
|
||||
}
|
@ -1,24 +1,35 @@
|
||||
<div class="party-container" style="display: flex; flex-direction: column; gap: 24px">
|
||||
<div style="display: flex; place-content: stretch space-between">
|
||||
<h3 class="mat-body-1">
|
||||
{{ (party$ | async)?.contact_info?.email }}
|
||||
</h3>
|
||||
<h4 class="mat-secondary-text mat-body-1">{{ (party$ | async)?.id }}</h4>
|
||||
<mat-toolbar *ngIf="party$ | async as party" style="height: 48px; padding: 0 24px">
|
||||
<div style="display: flex; gap: 8px; align-items: center">
|
||||
<!-- <div class="mat-body-2 mat-no-margin">-->
|
||||
<!-- {{ party?.contact_info?.email }}-->
|
||||
<!-- </div>-->
|
||||
<v-tag
|
||||
*ngIf="party?.blocking"
|
||||
[color]="(party?.blocking | ccUnionKey) === 'blocked' ? 'warn' : 'success'"
|
||||
style="margin-top: 8px"
|
||||
>{{ party?.blocking | ccUnionKey | titlecase }}</v-tag
|
||||
>
|
||||
<v-tag
|
||||
*ngIf="party?.suspension"
|
||||
[color]="(party?.suspension | ccUnionKey) === 'suspended' ? 'warn' : 'success'"
|
||||
style="margin-top: 8px"
|
||||
>{{ party?.suspension | ccUnionKey | titlecase }}</v-tag
|
||||
>
|
||||
</div>
|
||||
</mat-toolbar>
|
||||
|
||||
<div style="display: flex; flex-direction: column; gap: 24px">
|
||||
<nav mat-tab-nav-bar>
|
||||
<a
|
||||
#rla="routerLinkActive"
|
||||
*ngFor="let link of links"
|
||||
[active]="rla.isActive || (activeLinkByFragment$ | async) === link"
|
||||
[routerLink]="link.url"
|
||||
mat-tab-link
|
||||
routerLinkActive
|
||||
>
|
||||
{{ link.name }}
|
||||
</a>
|
||||
</nav>
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
</div>
|
||||
<mat-sidenav-container autosize>
|
||||
<mat-sidenav-content style="overflow: unset"
|
||||
><router-outlet></router-outlet
|
||||
></mat-sidenav-content>
|
||||
<mat-sidenav
|
||||
[fixedTopGap]="64 + 48"
|
||||
[opened]="!(sidenavInfoService.opened$ | async)"
|
||||
fixedInViewport="true"
|
||||
mode="side"
|
||||
position="end"
|
||||
style="background: transparent; border: none; padding: 24px 0 24px 0"
|
||||
>
|
||||
<v-nav [links]="links" type="secondary"></v-nav
|
||||
></mat-sidenav>
|
||||
</mat-sidenav-container>
|
||||
|
@ -1,7 +0,0 @@
|
||||
.party-container {
|
||||
margin: 24px;
|
||||
}
|
||||
|
||||
router-outlet {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
@ -1,89 +1,57 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
|
||||
import { NotifyLogService } from '@vality/ng-core';
|
||||
import { EMPTY } from 'rxjs';
|
||||
import { catchError, filter, map, shareReplay, startWith, switchMap } from 'rxjs/operators';
|
||||
import { Link } from '@vality/ng-core';
|
||||
|
||||
import { AppAuthGuardService } from '@cc/app/shared/services';
|
||||
import { AppAuthGuardService, Services } from '@cc/app/shared/services';
|
||||
|
||||
import { PartyManagementService } from '../../api/payment-processing';
|
||||
import { ROUTING_CONFIG as SHOPS_ROUTING_CONFIG } from '../party-shops/routing-config';
|
||||
import { SidenavInfoService } from '../../shared/components/sidenav-info';
|
||||
import { ROUTING_CONFIG as CLAIMS_CONFIG } from '../claims/routing-config';
|
||||
import { ROUTING_CONFIG as RULESET_ROUTING_CONFIG } from '../routing-rules/party-routing-ruleset/routing-config';
|
||||
import { SHOPS_ROUTING_CONFIG } from '../shops';
|
||||
import { ROUTING_CONFIG as WALLETS_ROUTING_CONFIG } from '../wallets/routing-config';
|
||||
|
||||
import { PartyStoreService } from './party-store.service';
|
||||
|
||||
interface PartyLink extends Link {
|
||||
services?: Services[];
|
||||
}
|
||||
|
||||
@Component({
|
||||
templateUrl: 'party.component.html',
|
||||
styleUrls: ['party.component.scss'],
|
||||
providers: [PartyStoreService],
|
||||
})
|
||||
export class PartyComponent {
|
||||
links = this.getLinks();
|
||||
activeLinkByFragment$ = this.router.events.pipe(
|
||||
filter((e) => e instanceof NavigationEnd),
|
||||
startWith(undefined),
|
||||
map(() => this.findLinkWithMaxActiveFragments()),
|
||||
shareReplay(1),
|
||||
);
|
||||
party$ = this.route.params.pipe(
|
||||
startWith(this.route.snapshot.params),
|
||||
switchMap(({ partyID }) => this.partyManagementService.Get(partyID)),
|
||||
catchError((err) => {
|
||||
this.log.error(err);
|
||||
return EMPTY;
|
||||
}),
|
||||
startWith({ id: this.route.snapshot.params.partyID }),
|
||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||
);
|
||||
links: PartyLink[] = [
|
||||
{
|
||||
label: 'Shops',
|
||||
url: 'shops',
|
||||
services: SHOPS_ROUTING_CONFIG.services,
|
||||
},
|
||||
{
|
||||
label: 'Wallets',
|
||||
url: 'wallets',
|
||||
services: WALLETS_ROUTING_CONFIG.services,
|
||||
},
|
||||
{
|
||||
label: 'Claims',
|
||||
url: 'claims',
|
||||
services: CLAIMS_CONFIG.services,
|
||||
},
|
||||
{
|
||||
label: 'Payment Routing Rules',
|
||||
url: 'routing-rules/payment',
|
||||
services: RULESET_ROUTING_CONFIG.services,
|
||||
},
|
||||
{
|
||||
label: 'Withdrawal Routing Rules',
|
||||
url: 'routing-rules/withdrawal',
|
||||
services: RULESET_ROUTING_CONFIG.services,
|
||||
},
|
||||
].filter((item) => this.appAuthGuardService.userHasSomeServiceMethods(item.services));
|
||||
party$ = this.partyStoreService.party$;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private appAuthGuardService: AppAuthGuardService,
|
||||
private partyManagementService: PartyManagementService,
|
||||
private log: NotifyLogService,
|
||||
protected sidenavInfoService: SidenavInfoService,
|
||||
private partyStoreService: PartyStoreService,
|
||||
) {}
|
||||
|
||||
private getLinks() {
|
||||
const links = [
|
||||
{
|
||||
name: 'Shops',
|
||||
url: 'shops',
|
||||
otherActiveUrlFragments: ['shop'],
|
||||
services: SHOPS_ROUTING_CONFIG.services,
|
||||
},
|
||||
{
|
||||
name: 'Payment Routing Rules',
|
||||
url: 'routing-rules/payment',
|
||||
services: RULESET_ROUTING_CONFIG.services,
|
||||
},
|
||||
{
|
||||
name: 'Withdrawal Routing Rules',
|
||||
url: 'routing-rules/withdrawal',
|
||||
services: RULESET_ROUTING_CONFIG.services,
|
||||
},
|
||||
];
|
||||
return links.filter((item) =>
|
||||
this.appAuthGuardService.userHasSomeServiceMethods(item.services),
|
||||
);
|
||||
}
|
||||
|
||||
private activeFragments(fragments: string[]): number {
|
||||
if (fragments?.length) {
|
||||
const ulrFragments = this.router.url.split('/');
|
||||
if (
|
||||
ulrFragments.filter((fragment) => fragments.includes(fragment)).length ===
|
||||
fragments.length
|
||||
) {
|
||||
return fragments.length;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private findLinkWithMaxActiveFragments() {
|
||||
return this.links.reduce(([maxLink, maxActiveFragments], link) => {
|
||||
const activeFragments = this.activeFragments(link.otherActiveUrlFragments);
|
||||
return maxActiveFragments > activeFragments
|
||||
? [maxLink, maxActiveFragments]
|
||||
: [link, activeFragments];
|
||||
}, [])?.[0];
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,29 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatSidenavModule } from '@angular/material/sidenav';
|
||||
import { MatTabsModule } from '@angular/material/tabs';
|
||||
import { MatToolbar } from '@angular/material/toolbar';
|
||||
import { NavComponent, TagModule } from '@vality/ng-core';
|
||||
|
||||
import { PageLayoutModule } from '../../shared';
|
||||
import { PageLayoutModule, ThriftPipesModule } from '../../shared';
|
||||
|
||||
import { PartyRouting } from './party-routing.module';
|
||||
import { PartyComponent } from './party.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [PartyRouting, CommonModule, MatTabsModule, MatButtonModule, PageLayoutModule],
|
||||
imports: [
|
||||
PartyRouting,
|
||||
CommonModule,
|
||||
MatTabsModule,
|
||||
MatButtonModule,
|
||||
PageLayoutModule,
|
||||
NavComponent,
|
||||
MatSidenavModule,
|
||||
MatToolbar,
|
||||
TagModule,
|
||||
ThriftPipesModule,
|
||||
],
|
||||
declarations: [PartyComponent],
|
||||
})
|
||||
export class PartyModule {}
|
||||
|
@ -32,6 +32,11 @@ import { ROUTING_CONFIG } from './routing-config';
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
redirectTo: 'payment',
|
||||
pathMatch: 'prefix',
|
||||
},
|
||||
]),
|
||||
],
|
||||
})
|
||||
|
@ -1,13 +1,14 @@
|
||||
<div style="display: flex; flex-direction: column; gap: 24px">
|
||||
<div style="display: flex; place-content: center space-between; align-items: center; gap: 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-page-layout
|
||||
[progress]="isLoading$ | async"
|
||||
[title]="(routingRulesTypeService.routingRulesType$ | async | titlecase) + ' Routing Rules'"
|
||||
>
|
||||
<cc-page-layout-actions>
|
||||
<button color="primary" mat-raised-button (click)="attachNewRuleset()">Add</button>
|
||||
</cc-page-layout-actions>
|
||||
<cc-routing-rules-list
|
||||
[data]="data$ | async"
|
||||
[displayedColumns]="displayedColumns"
|
||||
[progress]="isLoading$ | async"
|
||||
(toDetails)="navigateToPartyRuleset($event.parentRefId, $event.delegateIdx)"
|
||||
></cc-routing-rules-list>
|
||||
</div>
|
||||
</cc-page-layout>
|
||||
|
@ -1,14 +1,14 @@
|
||||
import { ChangeDetectionStrategy, Component, DestroyRef } from '@angular/core';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { DialogService } from '@vality/ng-core';
|
||||
import { DialogService, NotifyLogService } from '@vality/ng-core';
|
||||
import { first, map } from 'rxjs/operators';
|
||||
|
||||
import { DomainStoreService } from '@cc/app/api/domain-config';
|
||||
import { RoutingRulesType } from '@cc/app/sections/routing-rules/types/routing-rules-type';
|
||||
import { NotificationErrorService } from '@cc/app/shared/services/notification-error';
|
||||
|
||||
import { handleError } from '../../../../utils/operators/handle-error';
|
||||
import { RoutingRulesTypeService } from '../routing-rules-type.service';
|
||||
import { RoutingRulesService } from '../services/routing-rules';
|
||||
|
||||
import { AttachNewRulesetDialogComponent } from './attach-new-ruleset-dialog';
|
||||
@ -18,7 +18,7 @@ import { PartyDelegateRulesetsService } from './party-delegate-rulesets.service'
|
||||
selector: 'cc-party-delegate-rulesets',
|
||||
templateUrl: 'party-delegate-rulesets.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
providers: [PartyDelegateRulesetsService],
|
||||
providers: [PartyDelegateRulesetsService, RoutingRulesTypeService],
|
||||
})
|
||||
export class PartyDelegateRulesetsComponent {
|
||||
displayedColumns = [
|
||||
@ -60,9 +60,10 @@ export class PartyDelegateRulesetsComponent {
|
||||
private router: Router,
|
||||
private dialogService: DialogService,
|
||||
private domainStoreService: DomainStoreService,
|
||||
private notificationErrorService: NotificationErrorService,
|
||||
private log: NotifyLogService,
|
||||
private route: ActivatedRoute,
|
||||
private destroyRef: DestroyRef,
|
||||
protected routingRulesTypeService: RoutingRulesTypeService,
|
||||
) {}
|
||||
|
||||
attachNewRuleset() {
|
||||
@ -72,10 +73,7 @@ export class PartyDelegateRulesetsComponent {
|
||||
type: this.route.snapshot.params.type,
|
||||
})
|
||||
.afterClosed()
|
||||
.pipe(
|
||||
handleError(this.notificationErrorService.error),
|
||||
takeUntilDestroyed(this.destroyRef),
|
||||
)
|
||||
.pipe(handleError(this.log.error), takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
|
@ -17,9 +17,9 @@ import { DialogModule } from '@vality/ng-core';
|
||||
|
||||
import { DetailsItemModule } from '@cc/components/details-item';
|
||||
|
||||
import { PageLayoutModule } from '../../../shared';
|
||||
import { ChangeTargetDialogModule } from '../change-target-dialog';
|
||||
import { RoutingRulesListModule } from '../routing-rules-list';
|
||||
import { RoutingRulesetHeaderModule } from '../routing-ruleset-header';
|
||||
import { TargetRulesetFormModule } from '../target-ruleset-form';
|
||||
|
||||
import { AttachNewRulesetDialogComponent } from './attach-new-ruleset-dialog';
|
||||
@ -31,7 +31,6 @@ const EXPORTED_DECLARATIONS = [PartyDelegateRulesetsComponent, AttachNewRulesetD
|
||||
@NgModule({
|
||||
imports: [
|
||||
PartyDelegateRulesetsRoutingModule,
|
||||
RoutingRulesetHeaderModule,
|
||||
MatButtonModule,
|
||||
CommonModule,
|
||||
RouterModule,
|
||||
@ -51,6 +50,7 @@ const EXPORTED_DECLARATIONS = [PartyDelegateRulesetsComponent, AttachNewRulesetD
|
||||
TargetRulesetFormModule,
|
||||
RoutingRulesListModule,
|
||||
DialogModule,
|
||||
PageLayoutModule,
|
||||
],
|
||||
declarations: EXPORTED_DECLARATIONS,
|
||||
exports: EXPORTED_DECLARATIONS,
|
||||
|
@ -1,39 +1,30 @@
|
||||
<ng-container *ngIf="isLoading$ | async; else loaded">
|
||||
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
|
||||
</ng-container>
|
||||
<ng-template #loaded>
|
||||
<div *ngIf="partyRuleset$ | async as partyRuleset; else emptyPartyDelegate" class="content">
|
||||
<cc-routing-ruleset-header
|
||||
[backTo]="
|
||||
'party/' + (partyID$ | async) + '/routing-rules/' + (routingRulesType$ | async)
|
||||
"
|
||||
[refID]="partyRuleset.ref.id"
|
||||
(add)="addPartyRule()"
|
||||
>
|
||||
Party routing rules
|
||||
</cc-routing-ruleset-header>
|
||||
|
||||
<cc-routing-rules-list
|
||||
*ngIf="(routingRulesType$ | async) === 'payment'"
|
||||
[data]="shopsData$ | async"
|
||||
[displayedColumns]="shopsDisplayedColumns"
|
||||
(toDetails)="navigateToDelegate($event.parentRefId, $event.delegateIdx)"
|
||||
></cc-routing-rules-list>
|
||||
|
||||
<cc-routing-rules-list
|
||||
*ngIf="(routingRulesType$ | async) === 'withdrawal'"
|
||||
[data]="walletsData$ | async"
|
||||
[displayedColumns]="walletsDisplayedColumns"
|
||||
(toDetails)="navigateToDelegate($event.parentRefId, $event.delegateIdx)"
|
||||
></cc-routing-rules-list>
|
||||
</div>
|
||||
|
||||
<ng-template #emptyPartyDelegate>
|
||||
<div class="empty-party-delegate">
|
||||
<div class="mat-display-1">Routing rules not found</div>
|
||||
<button class="init" color="primary" mat-raised-button (click)="initialize()">
|
||||
Initialize
|
||||
</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
<cc-page-layout
|
||||
[id]="(partyRuleset$ | async)?.ref?.id"
|
||||
[progress]="isLoading$ | async"
|
||||
[upLink]="[
|
||||
'/party/' +
|
||||
(partyID$ | async) +
|
||||
'/routing-rules/' +
|
||||
(routingRulesTypeService.routingRulesType$ | async)
|
||||
]"
|
||||
title="Party Routing Rules"
|
||||
(idLinkClick)="openRefId()"
|
||||
>
|
||||
<cc-page-layout-actions>
|
||||
<button [disabled]="isLoading$ | async" color="primary" mat-raised-button (click)="add()">
|
||||
{{ (partyRuleset$ | async) ? 'Add' : 'Init' }}
|
||||
</button>
|
||||
</cc-page-layout-actions>
|
||||
<cc-routing-rules-list
|
||||
*ngIf="(routingRulesTypeService.routingRulesType$ | async) === 'payment'"
|
||||
[data]="shopsData$ | async"
|
||||
[displayedColumns]="shopsDisplayedColumns"
|
||||
(toDetails)="navigateToDelegate($event.parentRefId, $event.delegateIdx)"
|
||||
></cc-routing-rules-list>
|
||||
<cc-routing-rules-list
|
||||
*ngIf="(routingRulesTypeService.routingRulesType$ | async) === 'withdrawal'"
|
||||
[data]="walletsData$ | async"
|
||||
[displayedColumns]="walletsDisplayedColumns"
|
||||
(toDetails)="navigateToDelegate($event.parentRefId, $event.delegateIdx)"
|
||||
></cc-routing-rules-list>
|
||||
</cc-page-layout>
|
||||
|
@ -1,9 +1,3 @@
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.empty-party-delegate {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -2,12 +2,14 @@ import { Component, DestroyRef } from '@angular/core';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { DialogService, DialogResponseStatus } from '@vality/ng-core';
|
||||
import { combineLatest, Observable } from 'rxjs';
|
||||
import { filter, map, pluck, shareReplay, startWith, switchMap, take } from 'rxjs/operators';
|
||||
import { combineLatest } from 'rxjs';
|
||||
import { filter, map, shareReplay, startWith, switchMap, take } from 'rxjs/operators';
|
||||
|
||||
import { DomainStoreService } from '@cc/app/api/domain-config';
|
||||
|
||||
import { RoutingRulesType } from '../types/routing-rules-type';
|
||||
import { SidenavInfoService } from '../../../shared/components/sidenav-info';
|
||||
import { DomainObjectCardComponent } from '../../../shared/components/thrift-api-crud';
|
||||
import { RoutingRulesTypeService } from '../routing-rules-type.service';
|
||||
|
||||
import { AddPartyRoutingRuleDialogComponent } from './add-party-routing-rule-dialog';
|
||||
import { InitializeRoutingRulesDialogComponent } from './initialize-routing-rules-dialog';
|
||||
@ -17,15 +19,11 @@ import { PartyRoutingRulesetService } from './party-routing-ruleset.service';
|
||||
selector: 'cc-party-routing-ruleset',
|
||||
templateUrl: 'party-routing-ruleset.component.html',
|
||||
styleUrls: ['party-routing-ruleset.component.scss'],
|
||||
providers: [PartyRoutingRulesetService],
|
||||
providers: [PartyRoutingRulesetService, RoutingRulesTypeService],
|
||||
})
|
||||
export class PartyRoutingRulesetComponent {
|
||||
partyRuleset$ = this.partyRoutingRulesetService.partyRuleset$;
|
||||
partyID$ = this.partyRoutingRulesetService.partyID$;
|
||||
routingRulesType$ = this.route.params.pipe(
|
||||
startWith(this.route.snapshot.params),
|
||||
pluck('type'),
|
||||
) as Observable<RoutingRulesType>;
|
||||
isLoading$ = this.domainStoreService.isLoading$;
|
||||
|
||||
shopsDisplayedColumns = [
|
||||
@ -60,6 +58,7 @@ export class PartyRoutingRulesetComponent {
|
||||
};
|
||||
}),
|
||||
),
|
||||
startWith([]),
|
||||
takeUntilDestroyed(this.destroyRef),
|
||||
shareReplay(1),
|
||||
);
|
||||
@ -87,6 +86,7 @@ export class PartyRoutingRulesetComponent {
|
||||
};
|
||||
}),
|
||||
),
|
||||
startWith([]),
|
||||
takeUntilDestroyed(this.destroyRef),
|
||||
shareReplay(1),
|
||||
);
|
||||
@ -98,9 +98,45 @@ export class PartyRoutingRulesetComponent {
|
||||
private route: ActivatedRoute,
|
||||
private domainStoreService: DomainStoreService,
|
||||
private destroyRef: DestroyRef,
|
||||
private sidenavInfoService: SidenavInfoService,
|
||||
protected routingRulesTypeService: RoutingRulesTypeService,
|
||||
) {}
|
||||
|
||||
initialize() {
|
||||
add() {
|
||||
this.partyRuleset$.pipe(take(1)).subscribe((partyRuleset) => {
|
||||
if (partyRuleset) {
|
||||
this.addPartyRule();
|
||||
} else {
|
||||
this.initialize();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
navigateToDelegate(parentRefId: number, delegateIdx: number) {
|
||||
this.partyRoutingRulesetService.partyRuleset$
|
||||
.pipe(take(1), takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe((ruleset) =>
|
||||
this.router.navigate([
|
||||
'party',
|
||||
this.route.snapshot.params.partyID,
|
||||
'routing-rules',
|
||||
this.route.snapshot.params.type,
|
||||
parentRefId,
|
||||
'delegate',
|
||||
ruleset?.data?.decisions?.delegates?.[delegateIdx]?.ruleset?.id,
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
openRefId() {
|
||||
this.partyRuleset$.pipe(take(1), filter(Boolean)).subscribe(({ ref }) => {
|
||||
this.sidenavInfoService.toggle(DomainObjectCardComponent, {
|
||||
ref: { routing_rules: { id: Number(ref.id) } },
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private initialize() {
|
||||
combineLatest([
|
||||
this.partyRoutingRulesetService.partyID$,
|
||||
this.partyRoutingRulesetService.refID$,
|
||||
@ -121,12 +157,12 @@ export class PartyRoutingRulesetComponent {
|
||||
});
|
||||
}
|
||||
|
||||
addPartyRule() {
|
||||
private addPartyRule() {
|
||||
combineLatest([
|
||||
this.partyRoutingRulesetService.refID$,
|
||||
this.partyRoutingRulesetService.shops$,
|
||||
this.partyRoutingRulesetService.wallets$,
|
||||
this.routingRulesType$,
|
||||
this.routingRulesTypeService.routingRulesType$,
|
||||
this.partyRoutingRulesetService.partyID$,
|
||||
])
|
||||
.pipe(
|
||||
@ -152,20 +188,4 @@ export class PartyRoutingRulesetComponent {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
navigateToDelegate(parentRefId: number, delegateIdx: number) {
|
||||
this.partyRoutingRulesetService.partyRuleset$
|
||||
.pipe(take(1), takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe((ruleset) =>
|
||||
this.router.navigate([
|
||||
'party',
|
||||
this.route.snapshot.params.partyID,
|
||||
'routing-rules',
|
||||
this.route.snapshot.params.type,
|
||||
parentRefId,
|
||||
'delegate',
|
||||
ruleset?.data?.decisions?.delegates?.[delegateIdx]?.ruleset?.id,
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -16,9 +16,9 @@ import { MatRadioModule } from '@angular/material/radio';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
import { PageLayoutModule } from '../../../shared';
|
||||
import { ChangeTargetDialogModule } from '../change-target-dialog';
|
||||
import { RoutingRulesListModule } from '../routing-rules-list';
|
||||
import { RoutingRulesetHeaderModule } from '../routing-ruleset-header';
|
||||
|
||||
import { AddPartyRoutingRuleDialogModule } from './add-party-routing-rule-dialog';
|
||||
import { InitializeRoutingRulesDialogModule } from './initialize-routing-rules-dialog';
|
||||
@ -43,12 +43,12 @@ import { PartyRoutingRulesetComponent } from './party-routing-ruleset.component'
|
||||
MatSelectModule,
|
||||
MatRadioModule,
|
||||
MatExpansionModule,
|
||||
RoutingRulesetHeaderModule,
|
||||
AddPartyRoutingRuleDialogModule,
|
||||
InitializeRoutingRulesDialogModule,
|
||||
MatProgressBarModule,
|
||||
ChangeTargetDialogModule,
|
||||
RoutingRulesListModule,
|
||||
PageLayoutModule,
|
||||
],
|
||||
declarations: [PartyRoutingRulesetComponent],
|
||||
})
|
||||
|
18
src/app/sections/routing-rules/routing-rules-type.service.ts
Normal file
18
src/app/sections/routing-rules/routing-rules-type.service.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
import { startWith, map } from 'rxjs/operators';
|
||||
|
||||
import { getEnumValues } from '../../../utils';
|
||||
|
||||
import { RoutingRulesType } from './types/routing-rules-type';
|
||||
|
||||
@Injectable()
|
||||
export class RoutingRulesTypeService {
|
||||
routingRulesType$ = this.route.params.pipe(
|
||||
startWith(this.route.snapshot.params),
|
||||
map((p) => (getEnumValues(RoutingRulesType).includes(p.type) ? p.type : null)),
|
||||
) as Observable<RoutingRulesType>;
|
||||
|
||||
constructor(private route: ActivatedRoute) {}
|
||||
}
|
@ -1 +0,0 @@
|
||||
export * from './routing-ruleset-header.module';
|
@ -1,24 +0,0 @@
|
||||
<div style="display: flex; flex-direction: column; gap: 8px">
|
||||
<div style="display: flex; place-content: center space-between; align-items: center; gap: 8px">
|
||||
<div
|
||||
class="mat-headline-4 title"
|
||||
style="display: flex; place-content: center flex-start; align-items: center; gap: 8px"
|
||||
>
|
||||
<mat-icon *ngIf="backTo" class="back" (click)="navigateBack()">arrow_back</mat-icon>
|
||||
<div><ng-content></ng-content></div>
|
||||
</div>
|
||||
<button mat-button (click)="add.emit($event)">Add rule</button>
|
||||
</div>
|
||||
<div *ngIf="description" class="mat-h3 cc-routing-rules-caption">
|
||||
{{ description }}
|
||||
</div>
|
||||
<div class="mat-caption cc-routing-rules-caption">
|
||||
<span
|
||||
class="mat-secondary-text"
|
||||
style="cursor: pointer; text-decoration: underline"
|
||||
(click)="openRule()"
|
||||
>
|
||||
Ruleset ref ID: {{ refID }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
@ -1,7 +0,0 @@
|
||||
.title {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.back {
|
||||
cursor: pointer;
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { SidenavInfoService } from '../../../shared/components/sidenav-info';
|
||||
import { DomainObjectCardComponent } from '../../../shared/components/thrift-api-crud';
|
||||
|
||||
@Component({
|
||||
selector: 'cc-routing-ruleset-header',
|
||||
templateUrl: 'routing-ruleset-header.component.html',
|
||||
styleUrls: ['routing-ruleset-header.component.scss'],
|
||||
})
|
||||
export class RoutingRulesetHeaderComponent {
|
||||
@Input() refID: string;
|
||||
@Input() description?: string;
|
||||
@Input() backTo?: string;
|
||||
|
||||
@Output() add = new EventEmitter();
|
||||
|
||||
get queryParams() {
|
||||
return {
|
||||
types: JSON.stringify(['RoutingRulesObject']),
|
||||
sidenav: JSON.stringify({
|
||||
id: 'domainObject',
|
||||
inputs: { ref: { routing_rules: { id: this.refID } } },
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private sidenavInfoService: SidenavInfoService,
|
||||
) {}
|
||||
|
||||
navigateBack() {
|
||||
void this.router.navigate([this.backTo]);
|
||||
}
|
||||
|
||||
openRule() {
|
||||
this.sidenavInfoService.toggle(DomainObjectCardComponent, {
|
||||
ref: { routing_rules: { id: Number(this.refID) } },
|
||||
});
|
||||
}
|
||||
}
|
@ -1,14 +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 { RouterLink } from '@angular/router';
|
||||
|
||||
import { RoutingRulesetHeaderComponent } from './routing-ruleset-header.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, MatIconModule, MatButtonModule, RouterLink],
|
||||
declarations: [RoutingRulesetHeaderComponent],
|
||||
exports: [RoutingRulesetHeaderComponent],
|
||||
})
|
||||
export class RoutingRulesetHeaderModule {}
|
@ -1,20 +1,19 @@
|
||||
<div style="display: flex; flex-direction: column; gap: 24px">
|
||||
<cc-routing-ruleset-header
|
||||
[backTo]="
|
||||
'party/' +
|
||||
<cc-page-layout
|
||||
[id]="(shopRuleset$ | async)?.ref?.id"
|
||||
[title]="((routingRulesType$ | async) === 'payment' ? 'Shop' : 'Wallet') + ' Routing Rules'"
|
||||
[upLink]="[
|
||||
'/party/' +
|
||||
(partyID$ | async) +
|
||||
'/routing-rules/' +
|
||||
(routingRulesType$ | async) +
|
||||
'/' +
|
||||
(partyRulesetRefID$ | async)
|
||||
"
|
||||
[description]="(shop$ | async)?.details?.name"
|
||||
[refID]="(shopRuleset$ | async)?.ref?.id"
|
||||
(add)="addShopRule()"
|
||||
>
|
||||
Routing rules
|
||||
</cc-routing-ruleset-header>
|
||||
|
||||
]"
|
||||
(idLinkClick)="openRefId()"
|
||||
>
|
||||
<cc-page-layout-actions>
|
||||
<button color="primary" mat-raised-button (click)="addShopRule()">Add</button>
|
||||
</cc-page-layout-actions>
|
||||
<v-table
|
||||
[columns]="columns"
|
||||
[data]="(candidates$ | async) || []"
|
||||
@ -26,4 +25,4 @@
|
||||
sortOnFront
|
||||
(rowDropped)="drop($event)"
|
||||
></v-table>
|
||||
</div>
|
||||
</cc-page-layout>
|
||||
|
@ -13,11 +13,14 @@ import {
|
||||
} from '@vality/ng-core';
|
||||
import cloneDeep from 'lodash-es/cloneDeep';
|
||||
import { Observable, combineLatest, filter } from 'rxjs';
|
||||
import { first, map, switchMap, withLatestFrom } from 'rxjs/operators';
|
||||
import { first, map, switchMap, withLatestFrom, take } from 'rxjs/operators';
|
||||
|
||||
import { DomainStoreService } from '@cc/app/api/domain-config';
|
||||
import { RoutingRulesType } from '@cc/app/sections/routing-rules/types/routing-rules-type';
|
||||
import { DomainThriftFormDialogComponent } from '@cc/app/shared/components/thrift-api-crud';
|
||||
import {
|
||||
DomainThriftFormDialogComponent,
|
||||
DomainObjectCardComponent,
|
||||
} from '@cc/app/shared/components/thrift-api-crud';
|
||||
|
||||
import { objectToJSON } from '../../../../utils';
|
||||
import { createPredicateColumn } from '../../../shared';
|
||||
@ -285,4 +288,12 @@ export class RoutingRulesetComponent {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
openRefId() {
|
||||
this.shopRuleset$.pipe(take(1), filter(Boolean)).subscribe(({ ref }) => {
|
||||
this.sidenavInfoService.toggle(DomainObjectCardComponent, {
|
||||
ref: { routing_rules: { id: Number(ref.id) } },
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -19,8 +19,8 @@ import { TableModule, DialogModule } from '@vality/ng-core';
|
||||
|
||||
import { DomainThriftViewerComponent } from '@cc/app/shared/components/thrift-api-crud';
|
||||
|
||||
import { PageLayoutModule } from '../../../shared';
|
||||
import { ThriftViewerModule } from '../../../shared/components/thrift-viewer';
|
||||
import { RoutingRulesetHeaderModule } from '../routing-ruleset-header';
|
||||
|
||||
import { ChangeCandidatesPrioritiesDialogComponent } from './components/change-candidates-priorities-dialog/change-candidates-priorities-dialog.component';
|
||||
import { RoutingRulesetRoutingModule } from './routing-ruleset-routing.module';
|
||||
@ -44,12 +44,12 @@ import { RoutingRulesetComponent } from './routing-ruleset.component';
|
||||
MatSelectModule,
|
||||
MatRadioModule,
|
||||
MatExpansionModule,
|
||||
RoutingRulesetHeaderModule,
|
||||
MatAutocompleteModule,
|
||||
TableModule,
|
||||
DomainThriftViewerComponent,
|
||||
ThriftViewerModule,
|
||||
DialogModule,
|
||||
PageLayoutModule,
|
||||
],
|
||||
declarations: [RoutingRulesetComponent, ChangeCandidatesPrioritiesDialogComponent],
|
||||
})
|
||||
|
@ -15,6 +15,10 @@ import { SearchPartiesComponent } from './search-parties.component';
|
||||
canActivate: [AppAuthGuardService],
|
||||
data: ROUTING_CONFIG,
|
||||
},
|
||||
{
|
||||
path: 'party',
|
||||
redirectTo: 'parties',
|
||||
},
|
||||
]),
|
||||
],
|
||||
exports: [RouterModule],
|
||||
|
@ -11,8 +11,8 @@ const ROUTES: Routes = [
|
||||
loadChildren: () => import('./party/party.module').then((m) => m.PartyModule),
|
||||
},
|
||||
{
|
||||
path: 'party',
|
||||
loadChildren: () => import('./party/party.module').then((m) => m.PartyModule),
|
||||
path: 'claims',
|
||||
loadChildren: () => import('./claims').then((m) => m.ClaimsModule),
|
||||
},
|
||||
{
|
||||
path: 'party/:partyID',
|
||||
|
@ -12,19 +12,22 @@
|
||||
</cc-page-layout-actions>
|
||||
<v-filters *ngIf="isFilterControl.value" [active]="active" merge (clear)="filtersForm.reset()">
|
||||
<ng-template [formGroup]="filtersForm">
|
||||
<cc-merchant-field formControlName="party_id"></cc-merchant-field>
|
||||
<cc-merchant-field
|
||||
*ngIf="!(party$ | async)"
|
||||
formControlName="party_id"
|
||||
></cc-merchant-field>
|
||||
<v-list-field formControlName="wallet_id" label="Wallet IDs"></v-list-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Identity ID</mat-label>
|
||||
<input formControlName="identity_id" matInput />
|
||||
</mat-form-field>
|
||||
<cc-currency-field formControlName="currency_code"></cc-currency-field>
|
||||
<v-list-field formControlName="wallet_id" label="Wallet IDs"></v-list-field>
|
||||
</ng-template>
|
||||
</v-filters>
|
||||
|
||||
<v-table
|
||||
*ngIf="isFilterControl.value"
|
||||
[columns]="filterColumns"
|
||||
[columns]="filterColumns$ | async"
|
||||
[data]="filterWallets$ | async"
|
||||
[hasMore]="filterHasMore$ | async"
|
||||
[progress]="filtersLoading$ | async"
|
||||
|
@ -1,4 +1,12 @@
|
||||
import { Component, OnInit, Inject, ViewChild, DestroyRef } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
Inject,
|
||||
ViewChild,
|
||||
DestroyRef,
|
||||
Injector,
|
||||
runInInjectionContext,
|
||||
} from '@angular/core';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { FormBuilder, FormControl } from '@angular/forms';
|
||||
import { SearchWalletHit } from '@vality/deanonimus-proto/internal/deanonimus';
|
||||
@ -17,7 +25,7 @@ import {
|
||||
} from '@vality/ng-core';
|
||||
import isNil from 'lodash-es/isNil';
|
||||
import { of } from 'rxjs';
|
||||
import { map, shareReplay, catchError, debounceTime } from 'rxjs/operators';
|
||||
import { map, shareReplay, catchError, debounceTime, take } from 'rxjs/operators';
|
||||
import { MemoizeExpiring } from 'typescript-memoize';
|
||||
|
||||
import { WalletParams } from '@cc/app/api/fistful-stat/query-dsl/types/wallet';
|
||||
@ -26,6 +34,7 @@ import { ManagementService } from '@cc/app/api/wallet';
|
||||
import { IdentityManagementService } from '../../api/identity';
|
||||
import { createCurrencyColumn, createPartyColumn } from '../../shared';
|
||||
import { DEBOUNCE_TIME_MS } from '../../tokens';
|
||||
import { PartyStoreService } from '../party';
|
||||
|
||||
import { FetchWalletsTextService } from './fetch-wallets-text.service';
|
||||
import { FetchWalletsService } from './fetch-wallets.service';
|
||||
@ -33,7 +42,7 @@ import { FetchWalletsService } from './fetch-wallets.service';
|
||||
@Component({
|
||||
selector: 'cc-wallets',
|
||||
templateUrl: './wallets.component.html',
|
||||
providers: [FetchWalletsService, FetchWalletsTextService],
|
||||
providers: [FetchWalletsService, FetchWalletsTextService, PartyStoreService],
|
||||
})
|
||||
export class WalletsComponent implements OnInit {
|
||||
isFilterControl = new FormControl(1);
|
||||
@ -45,43 +54,56 @@ export class WalletsComponent implements OnInit {
|
||||
fullTextSearchWallets$ = this.fetchWalletsTextService.result$;
|
||||
fullTextSearchLoading$ = this.fetchWalletsTextService.isLoading$;
|
||||
|
||||
filterColumns: Column<StatWallet>[] = [
|
||||
{ field: 'id' },
|
||||
{ field: 'name' },
|
||||
'currency_symbolic_code',
|
||||
'identity_id',
|
||||
{ field: 'created_at', type: 'datetime' },
|
||||
createCurrencyColumn<StatWallet>(
|
||||
'balance',
|
||||
(d) => this.getBalance(d.id).pipe(map((b) => b.current)),
|
||||
(d) => this.getBalance(d.id).pipe(map((b) => b.currency.symbolic_code)),
|
||||
{ lazy: true },
|
||||
filterColumns$ = this.partyStoreService.party$.pipe(
|
||||
map((party) =>
|
||||
runInInjectionContext(this.injector, () => [
|
||||
{ field: 'id' },
|
||||
{ field: 'name' },
|
||||
'currency_symbolic_code',
|
||||
'identity_id',
|
||||
{ field: 'created_at', type: 'datetime' },
|
||||
createCurrencyColumn<StatWallet>(
|
||||
'balance',
|
||||
(d) => this.getBalance(d.id).pipe(map((b) => b.current)),
|
||||
(d) => this.getBalance(d.id).pipe(map((b) => b.currency.symbolic_code)),
|
||||
{ lazy: true },
|
||||
),
|
||||
createCurrencyColumn<StatWallet>(
|
||||
'hold',
|
||||
(d) => this.getBalance(d.id).pipe(map((b) => b.current - b.expected_min)),
|
||||
(d) => this.getBalance(d.id).pipe(map((b) => b.currency.symbolic_code)),
|
||||
{ lazy: true },
|
||||
),
|
||||
createCurrencyColumn<StatWallet>(
|
||||
'expected_min',
|
||||
(d) => this.getBalance(d.id).pipe(map((b) => b.expected_min)),
|
||||
(d) => this.getBalance(d.id).pipe(map((b) => b.currency.symbolic_code)),
|
||||
{ lazy: true },
|
||||
),
|
||||
{
|
||||
field: 'contract_id',
|
||||
formatter: (d) =>
|
||||
this.getIdentity(d.identity_id).pipe(
|
||||
map((identity) => identity.contract_id),
|
||||
),
|
||||
lazy: true,
|
||||
},
|
||||
...(party
|
||||
? []
|
||||
: [
|
||||
createPartyColumn<StatWallet>(
|
||||
'party',
|
||||
(d) =>
|
||||
this.getIdentity(d.identity_id).pipe(
|
||||
map((identity) => identity.party_id),
|
||||
),
|
||||
undefined,
|
||||
{ lazy: true },
|
||||
),
|
||||
]),
|
||||
]),
|
||||
),
|
||||
createCurrencyColumn<StatWallet>(
|
||||
'hold',
|
||||
(d) => this.getBalance(d.id).pipe(map((b) => b.current - b.expected_min)),
|
||||
(d) => this.getBalance(d.id).pipe(map((b) => b.currency.symbolic_code)),
|
||||
{ lazy: true },
|
||||
),
|
||||
createCurrencyColumn<StatWallet>(
|
||||
'expected_min',
|
||||
(d) => this.getBalance(d.id).pipe(map((b) => b.expected_min)),
|
||||
(d) => this.getBalance(d.id).pipe(map((b) => b.currency.symbolic_code)),
|
||||
{ lazy: true },
|
||||
),
|
||||
{
|
||||
field: 'contract_id',
|
||||
formatter: (d) =>
|
||||
this.getIdentity(d.identity_id).pipe(map((identity) => identity.contract_id)),
|
||||
lazy: true,
|
||||
},
|
||||
createPartyColumn(
|
||||
'party',
|
||||
(d) => this.getIdentity(d.identity_id).pipe(map((identity) => identity.party_id)),
|
||||
undefined,
|
||||
{ lazy: true },
|
||||
),
|
||||
];
|
||||
);
|
||||
fullTextSearchColumns: Column<SearchWalletHit>[] = [
|
||||
{ field: 'wallet.id' },
|
||||
{ field: 'wallet.name' },
|
||||
@ -118,6 +140,7 @@ export class WalletsComponent implements OnInit {
|
||||
active = 0;
|
||||
@ViewChild(FiltersComponent) filters!: FiltersComponent;
|
||||
typeQp = this.qp.createNamespace<{ isFilter: boolean }>('type');
|
||||
party$ = this.partyStoreService.party$;
|
||||
|
||||
constructor(
|
||||
private fetchWalletsService: FetchWalletsService,
|
||||
@ -129,6 +152,8 @@ export class WalletsComponent implements OnInit {
|
||||
@Inject(DEBOUNCE_TIME_MS) private debounceTimeMs: number,
|
||||
private destroyRef: DestroyRef,
|
||||
private identityManagementService: IdentityManagementService,
|
||||
private partyStoreService: PartyStoreService,
|
||||
private injector: Injector,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@ -152,8 +177,16 @@ export class WalletsComponent implements OnInit {
|
||||
|
||||
filterSearch(opts?: UpdateOptions) {
|
||||
const props = clean(this.filtersForm.value);
|
||||
this.fetchWalletsService.load(props, opts);
|
||||
this.active = countProps(props);
|
||||
this.partyStoreService.party$.pipe(take(1)).subscribe((p) => {
|
||||
this.fetchWalletsService.load(
|
||||
clean({
|
||||
party_id: p ? p.id : undefined,
|
||||
...props,
|
||||
}),
|
||||
opts,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
filterMore() {
|
||||
|
@ -1,9 +1,12 @@
|
||||
<v-select-field
|
||||
[appearance]="appearance"
|
||||
[formControl]="control"
|
||||
[hint]="hint"
|
||||
[label]="label || 'Merchant'"
|
||||
[options]="options$ | async"
|
||||
[progress]="!!(progress$ | async)"
|
||||
[required]="required"
|
||||
[size]="size"
|
||||
externalSearch
|
||||
(searchChange)="this.searchChange$.next($event)"
|
||||
></v-select-field>
|
||||
|
@ -6,9 +6,17 @@ import {
|
||||
NotifyLogService,
|
||||
FormControlSuperclass,
|
||||
createControlProviders,
|
||||
getValueChanges,
|
||||
} from '@vality/ng-core';
|
||||
import { BehaviorSubject, Observable, of, ReplaySubject, Subject } from 'rxjs';
|
||||
import { catchError, debounceTime, map, switchMap, tap, startWith } from 'rxjs/operators';
|
||||
import { BehaviorSubject, Observable, of, ReplaySubject, Subject, merge } from 'rxjs';
|
||||
import {
|
||||
catchError,
|
||||
debounceTime,
|
||||
map,
|
||||
switchMap,
|
||||
tap,
|
||||
distinctUntilChanged,
|
||||
} from 'rxjs/operators';
|
||||
|
||||
import { DeanonimusService } from '@cc/app/api/deanonimus';
|
||||
|
||||
@ -23,6 +31,9 @@ export class MerchantFieldComponent
|
||||
{
|
||||
@Input() label: string;
|
||||
@Input({ transform: booleanAttribute }) required: boolean;
|
||||
@Input() size?: string;
|
||||
@Input() appearance?: string;
|
||||
@Input() hint?: string;
|
||||
|
||||
options$ = new ReplaySubject<Option<PartyID>[]>(1);
|
||||
searchChange$ = new Subject<string>();
|
||||
@ -37,9 +48,9 @@ export class MerchantFieldComponent
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.searchChange$
|
||||
merge(getValueChanges(this.control), this.searchChange$)
|
||||
.pipe(
|
||||
startWith(this.control.value),
|
||||
distinctUntilChanged(),
|
||||
tap(() => {
|
||||
this.options$.next([]);
|
||||
this.progress$.next(true);
|
||||
|
@ -1,42 +1,60 @@
|
||||
<div class="header">
|
||||
<h1
|
||||
class="mat-headline-4 mat-no-margin"
|
||||
style="display: flex; place-content: center flex-start; align-items: center; gap: 4px"
|
||||
>
|
||||
<button *ngIf="isBackAvailable" mat-icon-button (click)="back()">
|
||||
<mat-icon>arrow_back</mat-icon>
|
||||
</button>
|
||||
<div>
|
||||
{{ title }}
|
||||
<span class="mat-secondary-text">{{ description }}</span>
|
||||
<div [ngClass]="{ wrapper__offset: !noOffset }" class="wrapper">
|
||||
<div *ngIf="title" style="display: flex; flex-direction: column; gap: 8px">
|
||||
<div *ngIf="(path$ | async)?.length > 1" class="mat-caption mat-secondary-text">
|
||||
<ng-container *ngFor="let p of path$ | async; let index = index">
|
||||
<span
|
||||
[ngClass]="{ 'mat-link': index !== (path$ | async).length - 1 }"
|
||||
[routerLink]="p.url"
|
||||
[title]="p.url"
|
||||
>{{ p.label }}</span
|
||||
>
|
||||
<span *ngIf="index !== (path$ | async).length - 1"> / </span>
|
||||
</ng-container>
|
||||
</div>
|
||||
</h1>
|
||||
<div class="mat-headline-4 mat-no-margin">
|
||||
<ng-content select="cc-page-layout-actions"></ng-content>
|
||||
<div class="header">
|
||||
<h1
|
||||
class="mat-headline-4 mat-no-margin"
|
||||
style="
|
||||
display: flex;
|
||||
place-content: center flex-start;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
"
|
||||
>
|
||||
<button *ngIf="isBackAvailable()" mat-icon-button (click)="back()">
|
||||
<mat-icon>{{ upLink() ? 'arrow_upward' : 'arrow_back' }}</mat-icon>
|
||||
</button>
|
||||
<div>
|
||||
{{ title }}
|
||||
<span class="mat-secondary-text"
|
||||
>{{ description }}
|
||||
<ng-container *ngIf="id">
|
||||
<span
|
||||
*ngIf="idLink() || idLinkClick.observed; else idText"
|
||||
[routerLink]="idLink()"
|
||||
class="mat-action"
|
||||
(click)="idLinkClick.emit($event)"
|
||||
><ng-container *ngTemplateOutlet="idText"></ng-container
|
||||
></span>
|
||||
<ng-template #idText>{{ id ? '#' + id : '' }}</ng-template>
|
||||
</ng-container>
|
||||
</span>
|
||||
</div>
|
||||
</h1>
|
||||
<div class="mat-headline-4 mat-no-margin">
|
||||
<ng-content select="cc-page-layout-actions"></ng-content>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
*ngIf="progress; else content"
|
||||
style="display: flex; place-content: center; align-items: center"
|
||||
>
|
||||
<mat-spinner diameter="64"></mat-spinner>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="path?.length" class="mat-h3 mat-secondary-text" style="display: flex; gap: 4px">
|
||||
<ng-container *ngFor="let pathPart of path; let idx = index">
|
||||
<div [matTooltip]="pathPart.tooltip">
|
||||
<a
|
||||
*ngIf="pathPart.link; else onlyLabel"
|
||||
[queryParams]="pathPart?.queryParams"
|
||||
[routerLink]="pathPart.link"
|
||||
class="mat-secondary-text"
|
||||
>{{ pathPart.label }}</a
|
||||
>
|
||||
<ng-template #onlyLabel>{{ pathPart.label }}</ng-template>
|
||||
</div>
|
||||
<div *ngIf="idx !== path.length - 1">/</div></ng-container
|
||||
>
|
||||
</div>
|
||||
|
||||
<div
|
||||
*ngIf="progress; else content"
|
||||
style="display: flex; place-content: center; align-items: center"
|
||||
>
|
||||
<mat-spinner diameter="64"></mat-spinner>
|
||||
</div>
|
||||
<ng-template #content>
|
||||
<ng-content></ng-content>
|
||||
</ng-template>
|
||||
|
@ -1,7 +1,10 @@
|
||||
:host {
|
||||
.wrapper {
|
||||
&__offset {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
display: grid;
|
||||
gap: 24px;
|
||||
padding: 24px;
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
|
@ -1,6 +1,17 @@
|
||||
import { Location } from '@angular/common';
|
||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
||||
import { Params } from '@angular/router';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
Input,
|
||||
booleanAttribute,
|
||||
input,
|
||||
computed,
|
||||
Output,
|
||||
EventEmitter,
|
||||
} from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { UrlService } from '@vality/ng-core';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'cc-page-layout',
|
||||
@ -11,21 +22,49 @@ import { Params } from '@angular/router';
|
||||
export class PageLayoutComponent {
|
||||
@Input() title!: string;
|
||||
@Input() description?: string;
|
||||
@Input() id?: string;
|
||||
@Input() progress?: boolean;
|
||||
@Input() path?: {
|
||||
label: string;
|
||||
link?: unknown[] | string | null | undefined;
|
||||
queryParams?: Params | null;
|
||||
tooltip?: string;
|
||||
}[];
|
||||
@Input({ transform: booleanAttribute }) noOffset = false;
|
||||
|
||||
// 1 and 2 is default history length
|
||||
isBackAvailable =
|
||||
window.history.length > 2 && window.location.pathname.split('/').slice(1).length > 1;
|
||||
@Output() idLinkClick = new EventEmitter<MouseEvent>();
|
||||
|
||||
constructor(private location: Location) {}
|
||||
backLink = input<unknown[]>();
|
||||
upLink = input<unknown[]>();
|
||||
idLink = input<unknown[]>();
|
||||
|
||||
isBackAvailable = computed(
|
||||
() =>
|
||||
this.backLink() ||
|
||||
this.upLink() ||
|
||||
// 1 and 2 is default history length
|
||||
(window.history.length > 2 && window.location.pathname.split('/').slice(1).length > 1),
|
||||
);
|
||||
|
||||
path$ = this.urlService.path$.pipe(
|
||||
map((path) => {
|
||||
return path
|
||||
.reduce(
|
||||
(acc, p) => {
|
||||
acc.push({ url: [...(acc.at(-1)?.url || ['']), p], label: p });
|
||||
return acc;
|
||||
},
|
||||
[] as { url: string[]; label: string }[],
|
||||
)
|
||||
.map((v) => ({ ...v, url: v.url.join('/') }));
|
||||
}),
|
||||
);
|
||||
|
||||
constructor(
|
||||
private location: Location,
|
||||
private router: Router,
|
||||
private urlService: UrlService,
|
||||
) {}
|
||||
|
||||
back() {
|
||||
this.location.back();
|
||||
if (this.backLink() || this.upLink()) {
|
||||
void this.router.navigate(this.backLink() || this.upLink());
|
||||
} else {
|
||||
this.location.back();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
import { Injectable, Type, Inject, Optional } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import {
|
||||
QueryParamsService,
|
||||
QueryParamsNamespace,
|
||||
getPossiblyAsyncObservable,
|
||||
PossiblyAsync,
|
||||
UrlService,
|
||||
} from '@vality/ng-core';
|
||||
import isEqual from 'lodash-es/isEqual';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { filter, startWith, map, distinctUntilChanged } from 'rxjs/operators';
|
||||
import { filter, map } from 'rxjs/operators';
|
||||
|
||||
import { SIDENAV_INFO_COMPONENTS, SidenavInfoComponents } from './tokens';
|
||||
|
||||
@ -24,7 +24,7 @@ export class SidenavInfoService {
|
||||
private qp!: QueryParamsNamespace<{ id?: string; inputs?: Record<PropertyKey, unknown> }>;
|
||||
|
||||
constructor(
|
||||
router: Router,
|
||||
urlService: UrlService,
|
||||
private qps: QueryParamsService,
|
||||
@Optional()
|
||||
@Inject(SIDENAV_INFO_COMPONENTS)
|
||||
@ -33,17 +33,9 @@ export class SidenavInfoService {
|
||||
if (!this.sidenavInfoComponents) {
|
||||
this.sidenavInfoComponents = sidenavInfoComponents ?? {};
|
||||
}
|
||||
router.events
|
||||
.pipe(
|
||||
startWith(null),
|
||||
filter(() => router.navigated),
|
||||
map(() => router.url?.split('?', 1)[0].split('#', 1)[0]),
|
||||
distinctUntilChanged(),
|
||||
filter(() => !!this.component$.value),
|
||||
)
|
||||
.subscribe(() => {
|
||||
this.close();
|
||||
});
|
||||
urlService.url$.pipe(filter(() => !!this.component$.value)).subscribe(() => {
|
||||
this.close();
|
||||
});
|
||||
this.qp = this.qps.createNamespace('sidenav');
|
||||
this.qp.params$
|
||||
.pipe(
|
||||
|
Loading…
Reference in New Issue
Block a user