mirror of
https://github.com/valitydev/control-center.git
synced 2024-11-06 02:25:17 +00:00
IMP-57: Wallet balance (#211)
This commit is contained in:
parent
fafe19bdfd
commit
77708832d8
14
package-lock.json
generated
14
package-lock.json
generated
@ -30,7 +30,7 @@
|
||||
"@s-libs/ng-core": "15.0.0",
|
||||
"@s-libs/rxjs-core": "15.0.0",
|
||||
"@vality/deanonimus-proto": "2.0.1-2a3d5ad.0",
|
||||
"@vality/domain-proto": "2.0.1-698c7d2.0",
|
||||
"@vality/domain-proto": "2.0.1-e12c03c.0",
|
||||
"@vality/dominant-cache-proto": "2.0.1-99f38c9.0",
|
||||
"@vality/fistful-proto": "2.0.1-4ff4ea3.0",
|
||||
"@vality/magista-proto": "2.0.1-cf0eff8.0",
|
||||
@ -5202,9 +5202,9 @@
|
||||
"integrity": "sha512-p/kR6o1mTWatvzpltDbSzJjRiA75ph8N5CZm5FTa+5ZgGer9pSN4sZhRGJj/iIfZkXZuVNZp9kisS+4+QKx13A=="
|
||||
},
|
||||
"node_modules/@vality/domain-proto": {
|
||||
"version": "2.0.1-698c7d2.0",
|
||||
"resolved": "https://registry.npmjs.org/@vality/domain-proto/-/domain-proto-2.0.1-698c7d2.0.tgz",
|
||||
"integrity": "sha512-tt9AU+haIySRsP8H3dz54XXp6eZSbuR0ME3lRZfFOZbsx6NadkHEJt4eVtEFYxc5V76gBw4uxtxJDfUz/UDOrA=="
|
||||
"version": "2.0.1-e12c03c.0",
|
||||
"resolved": "https://registry.npmjs.org/@vality/domain-proto/-/domain-proto-2.0.1-e12c03c.0.tgz",
|
||||
"integrity": "sha512-xWUtBeHMRkHIfNZP85XJSvVmBu+T5YXAELvnk6TKhpoRorseYKvcmIUA9Rsz4preQrUclZHngSjd23FSGRZn8Q=="
|
||||
},
|
||||
"node_modules/@vality/dominant-cache-proto": {
|
||||
"version": "2.0.1-99f38c9.0",
|
||||
@ -22956,9 +22956,9 @@
|
||||
"integrity": "sha512-p/kR6o1mTWatvzpltDbSzJjRiA75ph8N5CZm5FTa+5ZgGer9pSN4sZhRGJj/iIfZkXZuVNZp9kisS+4+QKx13A=="
|
||||
},
|
||||
"@vality/domain-proto": {
|
||||
"version": "2.0.1-698c7d2.0",
|
||||
"resolved": "https://registry.npmjs.org/@vality/domain-proto/-/domain-proto-2.0.1-698c7d2.0.tgz",
|
||||
"integrity": "sha512-tt9AU+haIySRsP8H3dz54XXp6eZSbuR0ME3lRZfFOZbsx6NadkHEJt4eVtEFYxc5V76gBw4uxtxJDfUz/UDOrA=="
|
||||
"version": "2.0.1-e12c03c.0",
|
||||
"resolved": "https://registry.npmjs.org/@vality/domain-proto/-/domain-proto-2.0.1-e12c03c.0.tgz",
|
||||
"integrity": "sha512-xWUtBeHMRkHIfNZP85XJSvVmBu+T5YXAELvnk6TKhpoRorseYKvcmIUA9Rsz4preQrUclZHngSjd23FSGRZn8Q=="
|
||||
},
|
||||
"@vality/dominant-cache-proto": {
|
||||
"version": "2.0.1-99f38c9.0",
|
||||
|
@ -44,7 +44,7 @@
|
||||
"@s-libs/ng-core": "15.0.0",
|
||||
"@s-libs/rxjs-core": "15.0.0",
|
||||
"@vality/deanonimus-proto": "2.0.1-2a3d5ad.0",
|
||||
"@vality/domain-proto": "2.0.1-698c7d2.0",
|
||||
"@vality/domain-proto": "2.0.1-e12c03c.0",
|
||||
"@vality/dominant-cache-proto": "2.0.1-99f38c9.0",
|
||||
"@vality/fistful-proto": "2.0.1-4ff4ea3.0",
|
||||
"@vality/magista-proto": "2.0.1-cf0eff8.0",
|
||||
|
43
src/app/api/accounter/accounter.service.ts
Normal file
43
src/app/api/accounter/accounter.service.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
accounter_AccounterCodegenClient,
|
||||
ThriftAstMetadata,
|
||||
accounter_Accounter,
|
||||
} from '@vality/domain-proto';
|
||||
import { Account } from '@vality/domain-proto/internal/accounter';
|
||||
import { AccountID } from '@vality/domain-proto/internal/domain';
|
||||
import { combineLatest, from, map, Observable, switchMap } from 'rxjs';
|
||||
|
||||
import { KeycloakTokenInfoService, toWachterHeaders } from '@cc/app/shared/services';
|
||||
import { environment } from '@cc/environments/environment';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AccounterService {
|
||||
private client$: Observable<accounter_AccounterCodegenClient>;
|
||||
|
||||
constructor(private keycloakTokenInfoService: KeycloakTokenInfoService) {
|
||||
const headers$ = this.keycloakTokenInfoService.decoded$.pipe(
|
||||
map(toWachterHeaders('Accounter'))
|
||||
);
|
||||
const metadata$ = from(
|
||||
import('@vality/domain-proto/metadata.json').then(
|
||||
(m) => m.default as ThriftAstMetadata[]
|
||||
)
|
||||
);
|
||||
this.client$ = combineLatest([metadata$, headers$]).pipe(
|
||||
switchMap(([metadata, headers]) =>
|
||||
accounter_Accounter({
|
||||
metadata,
|
||||
headers,
|
||||
logging: environment.logging.requests,
|
||||
path: '/wachter',
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
GetAccountByID(id: AccountID): Observable<Account> {
|
||||
return this.client$.pipe(switchMap((c) => c.GetAccountByID(id)));
|
||||
}
|
||||
}
|
1
src/app/api/accounter/index.ts
Normal file
1
src/app/api/accounter/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './accounter.service';
|
@ -5,4 +5,5 @@ export interface WalletParams extends PagedBaseParameters {
|
||||
party_id?: string;
|
||||
identity_id?: string;
|
||||
currency_code?: string;
|
||||
wallet_id?: string[];
|
||||
}
|
||||
|
@ -73,9 +73,9 @@ export class RepairingComponent implements OnInit {
|
||||
createDatetimeFormattedColumn('created_at'),
|
||||
createDescriptionFormattedColumn<Machine>(
|
||||
'provider',
|
||||
(data) => data.provider_id,
|
||||
(data) =>
|
||||
providers.find((p) => String(p.ref.id) === data.provider_id)?.data?.name,
|
||||
(data) => data.provider_id
|
||||
providers.find((p) => String(p.ref.id) === data.provider_id)?.data?.name
|
||||
),
|
||||
createTooltipTemplateGridColumn(
|
||||
{
|
||||
|
@ -2,7 +2,7 @@
|
||||
<h1 class="cc-display-1">Wallets</h1>
|
||||
|
||||
<mat-card [formGroup]="filters">
|
||||
<mat-card-content gdColumns="1fr 1fr 1fr" gdGap="16px">
|
||||
<mat-card-content gdColumns="1fr 1fr 1fr 1fr" gdGap="16px">
|
||||
<cc-merchant-field formControlName="party_id"></cc-merchant-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Identity ID</mat-label>
|
||||
@ -12,10 +12,16 @@
|
||||
<mat-label>Currency Code</mat-label>
|
||||
<input formControlName="currency_code" matInput />
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Wallet IDs</mat-label>
|
||||
<input formControlName="wallet_id" matInput />
|
||||
<mat-hint>id0,id1</mat-hint>
|
||||
</mat-form-field>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
||||
<cc-simple-table
|
||||
[cellTemplate]="{ balance: balanceTpl }"
|
||||
[columns]="columns"
|
||||
[data]="wallets$ | async"
|
||||
[hasMore]="hasMore$ | async"
|
||||
@ -23,5 +29,26 @@
|
||||
(fetchMore)="fetchMore()"
|
||||
(size)="search($event)"
|
||||
(update)="search($event.size)"
|
||||
></cc-simple-table>
|
||||
>
|
||||
<ng-template #balanceTpl let-col="colDef" let-index="index" let-row>
|
||||
<ng-container *ngIf="{ visible: false } as balance">
|
||||
<button
|
||||
*ngIf="!balance.visible; else balanceTpl"
|
||||
class="cell-button"
|
||||
mat-icon-button
|
||||
(click)="balance.visible = true"
|
||||
>
|
||||
<mat-icon>sync</mat-icon>
|
||||
</button>
|
||||
<ng-template #balanceTpl>
|
||||
<ng-container *ngIf="getBalance(row.id) | async as account; else spinner">
|
||||
{{ account.own_amount | amountCurrency : account.currency_sym_code }}
|
||||
</ng-container>
|
||||
<ng-template #spinner>
|
||||
<button [loading]="true" class="cell-button" mat-icon-button></button>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
</cc-simple-table>
|
||||
</div>
|
||||
|
8
src/app/sections/wallets/wallets.component.scss
Normal file
8
src/app/sections/wallets/wallets.component.scss
Normal file
@ -0,0 +1,8 @@
|
||||
:host {
|
||||
display: block;
|
||||
padding: 24px 16px;
|
||||
}
|
||||
|
||||
.cell-button {
|
||||
margin: -4px 0;
|
||||
}
|
@ -1,11 +1,17 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormBuilder } from '@angular/forms';
|
||||
import { untilDestroyed, UntilDestroy } from '@ngneat/until-destroy';
|
||||
import { clean } from '@vality/ng-core';
|
||||
import { startWith, map } from 'rxjs/operators';
|
||||
import { StatWallet } from '@vality/fistful-proto/internal/fistful_stat';
|
||||
import { clean, splitIds } from '@vality/ng-core';
|
||||
import { of } from 'rxjs';
|
||||
import { startWith, map, shareReplay, switchMap, catchError } from 'rxjs/operators';
|
||||
import { Memoize } from 'typescript-memoize';
|
||||
|
||||
import { AccounterService } from '@cc/app/api/accounter';
|
||||
import { WalletParams } from '@cc/app/api/fistful-stat/query-dsl/types/wallet';
|
||||
import { ManagementService } from '@cc/app/api/wallet';
|
||||
import { QueryParamsService } from '@cc/app/shared/services';
|
||||
import { NotificationErrorService } from '@cc/app/shared/services/notification-error';
|
||||
import {
|
||||
createDatetimeFormattedColumn,
|
||||
createDescriptionFormattedColumn,
|
||||
@ -18,57 +24,71 @@ import { FetchWalletsService } from './fetch-wallets.service';
|
||||
@Component({
|
||||
selector: 'cc-wallets',
|
||||
templateUrl: './wallets.component.html',
|
||||
styles: [
|
||||
`
|
||||
:host {
|
||||
display: block;
|
||||
padding: 24px 16px;
|
||||
}
|
||||
`,
|
||||
],
|
||||
providers: [FetchWalletsService],
|
||||
styleUrls: ['wallets.component.scss'],
|
||||
})
|
||||
export class WalletsComponent implements OnInit {
|
||||
wallets$ = this.fetchWalletsService.searchResult$;
|
||||
inProgress$ = this.fetchWalletsService.doAction$;
|
||||
hasMore$ = this.fetchWalletsService.hasMore$;
|
||||
columns = createGridColumns([
|
||||
columns = createGridColumns<StatWallet>([
|
||||
createDescriptionFormattedColumn('name', 'id'),
|
||||
'currency_symbolic_code',
|
||||
'identity_id',
|
||||
createDatetimeFormattedColumn('created_at'),
|
||||
'balance',
|
||||
]);
|
||||
filters = this.fb.group<WalletParams>({
|
||||
party_id: null,
|
||||
identity_id: null,
|
||||
currency_code: null,
|
||||
wallet_id: null,
|
||||
...this.qp.params,
|
||||
});
|
||||
|
||||
test$ = this.getBalance('294');
|
||||
|
||||
constructor(
|
||||
private fetchWalletsService: FetchWalletsService,
|
||||
private qp: QueryParamsService<WalletParams>,
|
||||
private fb: FormBuilder
|
||||
private fb: FormBuilder,
|
||||
private walletManagementService: ManagementService,
|
||||
private accounterService: AccounterService,
|
||||
private errorService: NotificationErrorService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.filters.valueChanges
|
||||
.pipe(
|
||||
startWith(this.filters.value),
|
||||
map((v) => ({ ...v, wallet_id: splitIds(v.wallet_id) })),
|
||||
map((v) => clean(v)),
|
||||
untilDestroyed(this)
|
||||
)
|
||||
.subscribe((value) => {
|
||||
void this.qp.set(value);
|
||||
this.fetchWalletsService.search(value);
|
||||
this.search();
|
||||
});
|
||||
}
|
||||
|
||||
search(size: number) {
|
||||
this.fetchWalletsService.search(clean(this.filters.value), size);
|
||||
search(size?: number) {
|
||||
const { wallet_id, ...v } = this.filters.value;
|
||||
this.fetchWalletsService.search(clean({ ...v, wallet_id: splitIds(wallet_id) }), size);
|
||||
}
|
||||
|
||||
fetchMore() {
|
||||
this.fetchWalletsService.fetchMore();
|
||||
}
|
||||
|
||||
@Memoize()
|
||||
getBalance(walletId: string) {
|
||||
return this.walletManagementService.Get(walletId, {}).pipe(
|
||||
switchMap((wallet) => this.accounterService.GetAccountByID(Number(wallet.account.id))),
|
||||
catchError((err) => {
|
||||
this.errorService.error(err);
|
||||
return of({});
|
||||
}),
|
||||
shareReplay({ refCount: true, bufferSize: 1 })
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -2,11 +2,15 @@ import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FlexModule, GridModule } from '@angular/flex-layout';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { MtxButtonModule } from '@ng-matero/extensions/button';
|
||||
|
||||
import { AmountCurrencyPipe } from '@cc/app/shared';
|
||||
import { MerchantFieldModule } from '@cc/app/shared/components/merchant-field';
|
||||
import { MetadataFormModule } from '@cc/app/shared/components/metadata-form';
|
||||
import { EmptySearchResultModule } from '@cc/components/empty-search-result';
|
||||
@ -32,6 +36,10 @@ import { WalletsComponent } from './wallets.component';
|
||||
MerchantFieldModule,
|
||||
GridModule,
|
||||
SimpleTableModule,
|
||||
MatButtonModule,
|
||||
MatIconModule,
|
||||
MtxButtonModule,
|
||||
AmountCurrencyPipe,
|
||||
],
|
||||
declarations: [WalletsComponent],
|
||||
})
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { formatCurrency, getCurrencySymbol } from '@angular/common';
|
||||
import { Pipe, Inject, LOCALE_ID, DEFAULT_CURRENCY_CODE, PipeTransform } from '@angular/core';
|
||||
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
|
||||
import isNil from 'lodash-es/isNil';
|
||||
import { ReplaySubject, combineLatest } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
@ -34,6 +35,8 @@ export class AmountCurrencyPipe implements PipeTransform {
|
||||
combineLatest([this.domainStoreService.getObjects('currency'), this.params$])
|
||||
.pipe(
|
||||
map(([currencies, { amount, currencyCode, format }]) => {
|
||||
if (isNil(amount)) return '?';
|
||||
if (!currencyCode) return String(amount);
|
||||
const exponent = currencies.find((c) => c.data.symbolic_code === currencyCode)
|
||||
.data.exponent;
|
||||
return formatCurrency(
|
||||
|
@ -13,7 +13,8 @@ export type GridColumn<T> =
|
||||
> & {
|
||||
_data?: any;
|
||||
})
|
||||
| keyof T;
|
||||
| keyof T
|
||||
| string;
|
||||
|
||||
export function createGridColumn<T>(col: GridColumn<T>) {
|
||||
if (!isObject(col))
|
||||
|
Loading…
Reference in New Issue
Block a user