mirror of
https://github.com/valitydev/control-center.git
synced 2024-11-06 02:25:17 +00:00
TD-869: New deposit details page. New create revert dialog (#334)
This commit is contained in:
parent
7e5ac50b8f
commit
1b060558bb
8
package-lock.json
generated
8
package-lock.json
generated
@ -25,7 +25,7 @@
|
|||||||
"@vality/fistful-proto": "2.0.1-8ecf2b7.0",
|
"@vality/fistful-proto": "2.0.1-8ecf2b7.0",
|
||||||
"@vality/machinegun-proto": "1.0.0",
|
"@vality/machinegun-proto": "1.0.0",
|
||||||
"@vality/magista-proto": "2.0.2-28d11b9.0",
|
"@vality/magista-proto": "2.0.2-28d11b9.0",
|
||||||
"@vality/ng-core": "^17.2.1-pr-57-0134d85.0",
|
"@vality/ng-core": "^17.2.1-pr-57-3adeb57.0",
|
||||||
"@vality/payout-manager-proto": "2.0.1-eb4091a.0",
|
"@vality/payout-manager-proto": "2.0.1-eb4091a.0",
|
||||||
"@vality/repairer-proto": "2.0.2-07b73e9.0",
|
"@vality/repairer-proto": "2.0.2-07b73e9.0",
|
||||||
"@vality/thrift-ts": "2.4.1-8ad5123.0",
|
"@vality/thrift-ts": "2.4.1-8ad5123.0",
|
||||||
@ -6454,9 +6454,9 @@
|
|||||||
"integrity": "sha512-BsDy5ejotfTtUlwuoX3kz+PYJ5NSTW6m5ZRGv+p5HaKXSjR7tserPdv0q133Wp4T+sg0ED0Qr9Peqsrn+9XlDQ=="
|
"integrity": "sha512-BsDy5ejotfTtUlwuoX3kz+PYJ5NSTW6m5ZRGv+p5HaKXSjR7tserPdv0q133Wp4T+sg0ED0Qr9Peqsrn+9XlDQ=="
|
||||||
},
|
},
|
||||||
"node_modules/@vality/ng-core": {
|
"node_modules/@vality/ng-core": {
|
||||||
"version": "17.2.1-pr-57-0134d85.0",
|
"version": "17.2.1-pr-57-3adeb57.0",
|
||||||
"resolved": "https://registry.npmjs.org/@vality/ng-core/-/ng-core-17.2.1-pr-57-0134d85.0.tgz",
|
"resolved": "https://registry.npmjs.org/@vality/ng-core/-/ng-core-17.2.1-pr-57-3adeb57.0.tgz",
|
||||||
"integrity": "sha512-0wlLzf2+smFYVYqu/iEptyeKrasRsxWRd2hNwqG+cd0eiIwxNWyBMaI0+FuWS141CtodSgF8Ab8CAG2ceD+vkw==",
|
"integrity": "sha512-h8/U6pUrJDMTXUj2HL9xC6KjlVa1kNj20DcI79gaoYmh//4L/U+y6vF75ieOeZW8MUFDD9WcVGfrwsRFWOntqQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/material-date-fns-adapter": "^17.2.0",
|
"@angular/material-date-fns-adapter": "^17.2.0",
|
||||||
"@ng-matero/extensions": "^17.1.0",
|
"@ng-matero/extensions": "^17.1.0",
|
||||||
|
@ -33,7 +33,7 @@
|
|||||||
"@vality/fistful-proto": "2.0.1-8ecf2b7.0",
|
"@vality/fistful-proto": "2.0.1-8ecf2b7.0",
|
||||||
"@vality/machinegun-proto": "1.0.0",
|
"@vality/machinegun-proto": "1.0.0",
|
||||||
"@vality/magista-proto": "2.0.2-28d11b9.0",
|
"@vality/magista-proto": "2.0.2-28d11b9.0",
|
||||||
"@vality/ng-core": "^17.2.1-pr-57-0134d85.0",
|
"@vality/ng-core": "^17.2.1-pr-57-3adeb57.0",
|
||||||
"@vality/payout-manager-proto": "2.0.1-eb4091a.0",
|
"@vality/payout-manager-proto": "2.0.1-eb4091a.0",
|
||||||
"@vality/repairer-proto": "2.0.2-07b73e9.0",
|
"@vality/repairer-proto": "2.0.2-07b73e9.0",
|
||||||
"@vality/thrift-ts": "2.4.1-8ad5123.0",
|
"@vality/thrift-ts": "2.4.1-8ad5123.0",
|
||||||
|
@ -1,7 +1,14 @@
|
|||||||
<cc-page-layout title="Deposit details">
|
<cc-page-layout [progress]="isLoading$ | async" title="Deposit details">
|
||||||
<cc-deposit-main-info
|
<mat-card *ngIf="deposit$ | async">
|
||||||
*ngIf="deposit$ | async as deposit"
|
<mat-card-content>
|
||||||
[deposit]="deposit"
|
<cc-thrift-viewer
|
||||||
></cc-deposit-main-info>
|
[extensions]="extensions$ | async"
|
||||||
|
[metadata]="metadata$ | async"
|
||||||
|
[value]="deposit$ | async"
|
||||||
|
namespace="fistful_stat"
|
||||||
|
type="StatDeposit"
|
||||||
|
></cc-thrift-viewer>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
<cc-reverts *ngIf="deposit$ | async as deposit" [deposit]="deposit"></cc-reverts>
|
<cc-reverts *ngIf="deposit$ | async as deposit" [deposit]="deposit"></cc-reverts>
|
||||||
</cc-page-layout>
|
</cc-page-layout>
|
||||||
|
@ -1,6 +1,22 @@
|
|||||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
import { formatDate } from '@angular/common';
|
||||||
|
import { ChangeDetectionStrategy, Component, OnInit, Inject, LOCALE_ID } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { pluck, take } from 'rxjs/operators';
|
import { DepositStatus, RevertStatus } from '@vality/fistful-proto/fistful_stat';
|
||||||
|
import { Timestamp } from '@vality/fistful-proto/internal/base';
|
||||||
|
import { formatCurrency, getImportValue } from '@vality/ng-core';
|
||||||
|
import startCase from 'lodash-es/startCase';
|
||||||
|
import { of, Observable } from 'rxjs';
|
||||||
|
import { take, map } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { getUnionKey, getUnionValue } from '../../../utils';
|
||||||
|
import { ManagementService } from '../../api/wallet';
|
||||||
|
import {
|
||||||
|
MetadataViewExtension,
|
||||||
|
MetadataViewExtensionResult,
|
||||||
|
} from '../../shared/components/json-viewer';
|
||||||
|
import { isTypeWithAliases } from '../../shared/components/metadata-form';
|
||||||
|
import { AmountCurrencyService } from '../../shared/services';
|
||||||
|
import { FetchSourcesService } from '../sources';
|
||||||
|
|
||||||
import { ReceiveDepositService } from './services/receive-deposit/receive-deposit.service';
|
import { ReceiveDepositService } from './services/receive-deposit/receive-deposit.service';
|
||||||
|
|
||||||
@ -11,15 +27,114 @@ import { ReceiveDepositService } from './services/receive-deposit/receive-deposi
|
|||||||
})
|
})
|
||||||
export class DepositDetailsComponent implements OnInit {
|
export class DepositDetailsComponent implements OnInit {
|
||||||
deposit$ = this.fetchDepositService.deposit$;
|
deposit$ = this.fetchDepositService.deposit$;
|
||||||
|
isLoading$ = this.fetchDepositService.isLoading$;
|
||||||
|
metadata$ = getImportValue(import('@vality/fistful-proto/metadata.json'));
|
||||||
|
extensions$: Observable<MetadataViewExtension[]> = this.fetchDepositService.deposit$.pipe(
|
||||||
|
map((deposit) => [
|
||||||
|
{
|
||||||
|
determinant: (d) => of(isTypeWithAliases(d, 'Timestamp', 'base')),
|
||||||
|
extension: (_, value: Timestamp) =>
|
||||||
|
of({ value: formatDate(value, 'dd.MM.yyyy HH:mm:ss', 'en') }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
determinant: (d) =>
|
||||||
|
of(isTypeWithAliases(d, 'CurrencySymbolicCode', 'fistful_stat')),
|
||||||
|
extension: () => of({ hidden: true }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
determinant: (d) => of(isTypeWithAliases(d, 'Amount', 'base')),
|
||||||
|
extension: (_, amount: number) =>
|
||||||
|
this.amountCurrencyService.getCurrency(deposit.currency_symbolic_code).pipe(
|
||||||
|
map((c) => ({
|
||||||
|
value: formatCurrency(
|
||||||
|
amount,
|
||||||
|
c.data.symbolic_code,
|
||||||
|
'long',
|
||||||
|
this._locale,
|
||||||
|
c.data.exponent,
|
||||||
|
),
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
determinant: (d) => of(isTypeWithAliases(d, 'DepositStatus', 'fistful_stat')),
|
||||||
|
extension: (_, status: DepositStatus) =>
|
||||||
|
of({
|
||||||
|
value: startCase(getUnionKey(status)),
|
||||||
|
tooltip: Object.keys(getUnionValue(status)).length
|
||||||
|
? getUnionValue(status)
|
||||||
|
: undefined,
|
||||||
|
tag: true,
|
||||||
|
color: (
|
||||||
|
{
|
||||||
|
failed: 'warn',
|
||||||
|
pending: 'pending',
|
||||||
|
succeeded: 'success',
|
||||||
|
} as const
|
||||||
|
)[getUnionKey(status)],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
determinant: (d) => of(isTypeWithAliases(d, 'RevertStatus', 'fistful_stat')),
|
||||||
|
extension: (_, status: RevertStatus, viewValue: string) =>
|
||||||
|
of({
|
||||||
|
value: startCase(viewValue),
|
||||||
|
tag: true,
|
||||||
|
color: (
|
||||||
|
{
|
||||||
|
[1]: 'pending',
|
||||||
|
[2]: 'success',
|
||||||
|
} as const
|
||||||
|
)[status],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
determinant: (d) => of(isTypeWithAliases(d, 'WalletID', 'fistful_stat')),
|
||||||
|
extension: (_, id: string) =>
|
||||||
|
this.walletManagementService.Get(id, {}).pipe(
|
||||||
|
map(
|
||||||
|
(wallet): MetadataViewExtensionResult => ({
|
||||||
|
value: wallet.name,
|
||||||
|
tooltip: wallet.id,
|
||||||
|
link: [
|
||||||
|
['/wallets'],
|
||||||
|
{ queryParams: { wallet_id: JSON.stringify([wallet.id]) } },
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
determinant: (d) => of(isTypeWithAliases(d, 'SourceID', 'fistful_stat')),
|
||||||
|
extension: (_, id: string) =>
|
||||||
|
this.fetchSourcesService.sources$.pipe(
|
||||||
|
map((sources) => sources.find((s) => s.id === id)),
|
||||||
|
map(
|
||||||
|
(source): MetadataViewExtensionResult => ({
|
||||||
|
value: source.name,
|
||||||
|
tooltip: source.id,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private fetchDepositService: ReceiveDepositService,
|
private fetchDepositService: ReceiveDepositService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
|
@Inject(LOCALE_ID) private _locale: string,
|
||||||
|
private amountCurrencyService: AmountCurrencyService,
|
||||||
|
private walletManagementService: ManagementService,
|
||||||
|
private fetchSourcesService: FetchSourcesService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.route.params
|
this.route.params
|
||||||
.pipe(take(1), pluck('depositID'))
|
.pipe(
|
||||||
|
take(1),
|
||||||
|
map((p) => p.depositID),
|
||||||
|
)
|
||||||
.subscribe((depositID) => this.fetchDepositService.receiveDeposit(depositID));
|
.subscribe((depositID) => this.fetchDepositService.receiveDeposit(depositID));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,9 +9,10 @@ import { StatusModule, PageLayoutModule } from '@cc/app/shared/components';
|
|||||||
import { DetailsItemModule } from '@cc/components/details-item';
|
import { DetailsItemModule } from '@cc/components/details-item';
|
||||||
import { HeadlineModule } from '@cc/components/headline';
|
import { HeadlineModule } from '@cc/components/headline';
|
||||||
|
|
||||||
|
import { ThriftViewerModule } from '../../shared/components/thrift-viewer';
|
||||||
|
|
||||||
import { DepositDetailsRoutingModule } from './deposit-details-routing.module';
|
import { DepositDetailsRoutingModule } from './deposit-details-routing.module';
|
||||||
import { DepositDetailsComponent } from './deposit-details.component';
|
import { DepositDetailsComponent } from './deposit-details.component';
|
||||||
import { DepositMainInfoModule } from './deposit-main-info/deposit-main-info.module';
|
|
||||||
import { RevertsModule } from './reverts/reverts.module';
|
import { RevertsModule } from './reverts/reverts.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@ -25,9 +26,9 @@ import { RevertsModule } from './reverts/reverts.module';
|
|||||||
MatProgressSpinnerModule,
|
MatProgressSpinnerModule,
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
MatDialogModule,
|
MatDialogModule,
|
||||||
DepositMainInfoModule,
|
|
||||||
RevertsModule,
|
RevertsModule,
|
||||||
PageLayoutModule,
|
PageLayoutModule,
|
||||||
|
ThriftViewerModule,
|
||||||
],
|
],
|
||||||
declarations: [DepositDetailsComponent],
|
declarations: [DepositDetailsComponent],
|
||||||
})
|
})
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
<mat-card>
|
|
||||||
<mat-card-content style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 16px">
|
|
||||||
<cc-details-item style="grid-column: span 2" title="ID">{{ deposit.id }}</cc-details-item>
|
|
||||||
<cc-details-item title="Status">
|
|
||||||
<cc-status [color]="deposit.status | toStatus | toDepositColor">{{
|
|
||||||
deposit.status | toStatus
|
|
||||||
}}</cc-status>
|
|
||||||
</cc-details-item>
|
|
||||||
<cc-details-item title="Amount"
|
|
||||||
>{{ deposit.amount | ccThriftInt64 | ccFormatAmount }}
|
|
||||||
{{ deposit.currency_symbolic_code | ccCurrency }}</cc-details-item
|
|
||||||
>
|
|
||||||
<cc-details-item title="Fee"
|
|
||||||
>{{ deposit.fee | ccThriftInt64 | ccFormatAmount }}
|
|
||||||
{{ deposit.currency_symbolic_code | ccCurrency }}</cc-details-item
|
|
||||||
>
|
|
||||||
<cc-details-item title="Created At">{{
|
|
||||||
deposit.created_at | date: 'dd.MM.yyyy HH:mm:ss'
|
|
||||||
}}</cc-details-item>
|
|
||||||
<cc-details-item title="Source ID">{{ deposit.source_id }}</cc-details-item>
|
|
||||||
<cc-details-item title="Identity ID">{{ deposit.identity_id }}</cc-details-item>
|
|
||||||
<cc-details-item title="Destination">
|
|
||||||
<cc-wallet-info [walletID]="deposit.destination_id"></cc-wallet-info>
|
|
||||||
</cc-details-item>
|
|
||||||
</mat-card-content>
|
|
||||||
</mat-card>
|
|
@ -1,12 +0,0 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
|
||||||
import { StatDeposit } from '@vality/fistful-proto/fistful_stat';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'cc-deposit-main-info',
|
|
||||||
templateUrl: 'deposit-main-info.component.html',
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
})
|
|
||||||
export class DepositMainInfoComponent {
|
|
||||||
@Input()
|
|
||||||
deposit: StatDeposit;
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
import { CommonModule } from '@angular/common';
|
|
||||||
import { NgModule } from '@angular/core';
|
|
||||||
import { MatCardModule } from '@angular/material/card';
|
|
||||||
|
|
||||||
import { StatusModule } from '@cc/app/shared/components';
|
|
||||||
import { WalletInfoModule } from '@cc/app/shared/components/wallet-info';
|
|
||||||
import { CommonPipesModule, ThriftPipesModule } from '@cc/app/shared/pipes';
|
|
||||||
import { DetailsItemModule } from '@cc/components/details-item';
|
|
||||||
|
|
||||||
import { DepositMainInfoComponent } from './deposit-main-info.component';
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [
|
|
||||||
CommonModule,
|
|
||||||
MatCardModule,
|
|
||||||
DetailsItemModule,
|
|
||||||
StatusModule,
|
|
||||||
ThriftPipesModule,
|
|
||||||
CommonPipesModule,
|
|
||||||
WalletInfoModule,
|
|
||||||
],
|
|
||||||
declarations: [DepositMainInfoComponent],
|
|
||||||
exports: [DepositMainInfoComponent],
|
|
||||||
})
|
|
||||||
export class DepositMainInfoModule {}
|
|
@ -1,44 +1,15 @@
|
|||||||
<v-dialog [progress]="!!(progress$ | async)" title="Create revert">
|
<v-dialog [progress]="!!(progress$ | async)" title="Create revert">
|
||||||
<div *ngIf="form" [formGroup]="form" style="display: flex; flex-direction: column; gap: 16px">
|
<cc-fistful-thrift-form
|
||||||
<div style="display: flex; gap: 24px">
|
[extensions]="extensions"
|
||||||
<mat-form-field style="flex: 1">
|
[formControl]="control"
|
||||||
<input
|
namespace="deposit_revert"
|
||||||
formControlName="amount"
|
noChangeKind
|
||||||
matInput
|
type="RevertParams"
|
||||||
placeholder="Amount"
|
></cc-fistful-thrift-form>
|
||||||
required
|
|
||||||
type="number"
|
|
||||||
/>
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field style="flex: 1">
|
|
||||||
<input
|
|
||||||
formControlName="currency"
|
|
||||||
matInput
|
|
||||||
placeholder="Currency"
|
|
||||||
readonly
|
|
||||||
required
|
|
||||||
type="text"
|
|
||||||
/>
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
<div style="display: flex; gap: 24px">
|
|
||||||
<mat-form-field style="flex: 1">
|
|
||||||
<input formControlName="reason" matInput placeholder="Reason" type="text" />
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field style="flex: 1">
|
|
||||||
<input
|
|
||||||
formControlName="externalID"
|
|
||||||
matInput
|
|
||||||
placeholder="External ID"
|
|
||||||
type="text"
|
|
||||||
/>
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<v-dialog-actions>
|
<v-dialog-actions>
|
||||||
<button
|
<button
|
||||||
[disabled]="!!(progress$ | async) || form.invalid"
|
[disabled]="!!(progress$ | async) || control.invalid"
|
||||||
color="primary"
|
color="primary"
|
||||||
mat-button
|
mat-button
|
||||||
(click)="createRevert()"
|
(click)="createRevert()"
|
||||||
@ -47,3 +18,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</v-dialog-actions>
|
</v-dialog-actions>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
|
|
||||||
|
<ng-template #cashTemplate let-cashControl="control">
|
||||||
|
<cc-cash-field [formControl]="cashControl"></cc-cash-field>
|
||||||
|
</ng-template>
|
||||||
|
@ -1,12 +1,25 @@
|
|||||||
import { ChangeDetectionStrategy, Component, DestroyRef } from '@angular/core';
|
import {
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
Component,
|
||||||
|
DestroyRef,
|
||||||
|
viewChild,
|
||||||
|
TemplateRef,
|
||||||
|
} from '@angular/core';
|
||||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
import { Validators, NonNullableFormBuilder } from '@angular/forms';
|
import { FormControl } from '@angular/forms';
|
||||||
|
import { DepositParams } from '@vality/fistful-proto/deposit';
|
||||||
import { Revert } from '@vality/fistful-proto/internal/deposit_revert';
|
import { Revert } from '@vality/fistful-proto/internal/deposit_revert';
|
||||||
import { DialogSuperclass, NotifyLogService, toMinor, clean } from '@vality/ng-core';
|
import { DialogSuperclass, NotifyLogService, clean } from '@vality/ng-core';
|
||||||
import { BehaviorSubject } from 'rxjs';
|
import { BehaviorSubject, of } from 'rxjs';
|
||||||
|
import { Overwrite } from 'utility-types';
|
||||||
|
|
||||||
import { DepositManagementService } from '@cc/app/api/deposit';
|
import { DepositManagementService } from '@cc/app/api/deposit';
|
||||||
|
|
||||||
|
import { Cash } from '../../../../../components/cash-field';
|
||||||
|
import {
|
||||||
|
MetadataFormExtension,
|
||||||
|
isTypeWithAliases,
|
||||||
|
} from '../../../../shared/components/metadata-form';
|
||||||
import { UserInfoBasedIdGeneratorService } from '../../../../shared/services';
|
import { UserInfoBasedIdGeneratorService } from '../../../../shared/services';
|
||||||
|
|
||||||
import { CreateRevertDialogConfig } from './types/create-revert-dialog-config';
|
import { CreateRevertDialogConfig } from './types/create-revert-dialog-config';
|
||||||
@ -21,16 +34,24 @@ export class CreateRevertDialogComponent extends DialogSuperclass<
|
|||||||
CreateRevertDialogConfig,
|
CreateRevertDialogConfig,
|
||||||
Revert
|
Revert
|
||||||
> {
|
> {
|
||||||
form = this.fb.group({
|
control = new FormControl({
|
||||||
amount: [undefined as number, [Validators.pattern(/^\d+([,.]\d{1,2})?$/)]],
|
id: this.idGenerator.getUsernameBasedId(),
|
||||||
currency: this.dialogData.currency,
|
body: { currencyCode: this.dialogData.currency },
|
||||||
reason: undefined as string,
|
} as Overwrite<DepositParams, { body: Cash }>);
|
||||||
externalID: undefined as string,
|
|
||||||
});
|
|
||||||
progress$ = new BehaviorSubject(0);
|
progress$ = new BehaviorSubject(0);
|
||||||
|
cashTemplate = viewChild<TemplateRef<unknown>>('cashTemplate');
|
||||||
|
extensions: MetadataFormExtension[] = [
|
||||||
|
{
|
||||||
|
determinant: (data) => of(isTypeWithAliases(data, 'RevertID', 'deposit_revert')),
|
||||||
|
extension: () => of({ hidden: true }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
determinant: (data) => of(isTypeWithAliases(data, 'Cash', 'base')),
|
||||||
|
extension: () => of({ template: this.cashTemplate() }),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private fb: NonNullableFormBuilder,
|
|
||||||
private depositManagementService: DepositManagementService,
|
private depositManagementService: DepositManagementService,
|
||||||
private idGenerator: UserInfoBasedIdGeneratorService,
|
private idGenerator: UserInfoBasedIdGeneratorService,
|
||||||
private log: NotifyLogService,
|
private log: NotifyLogService,
|
||||||
@ -40,21 +61,17 @@ export class CreateRevertDialogComponent extends DialogSuperclass<
|
|||||||
}
|
}
|
||||||
|
|
||||||
createRevert() {
|
createRevert() {
|
||||||
const { reason, amount, currency, externalID } = this.form.value;
|
const { body, ...value } = this.control.value;
|
||||||
this.depositManagementService
|
this.depositManagementService
|
||||||
.CreateRevert(
|
.CreateRevert(
|
||||||
this.dialogData.depositID,
|
this.dialogData.depositID,
|
||||||
clean(
|
clean(
|
||||||
{
|
{
|
||||||
id: this.idGenerator.getUsernameBasedId(),
|
...value,
|
||||||
body: {
|
body: {
|
||||||
amount: toMinor(amount, currency),
|
currency: { symbolic_code: body.currencyCode },
|
||||||
currency: {
|
amount: body.amount,
|
||||||
symbolic_code: currency,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
reason,
|
|
||||||
external_id: externalID,
|
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
|
@ -10,6 +10,9 @@ import { DialogModule } from '@vality/ng-core';
|
|||||||
|
|
||||||
import { UserInfoBasedIdGeneratorModule } from '@cc/app/shared/services/user-info-based-id-generator/user-info-based-id-generator.module';
|
import { UserInfoBasedIdGeneratorModule } from '@cc/app/shared/services/user-info-based-id-generator/user-info-based-id-generator.module';
|
||||||
|
|
||||||
|
import { CashFieldComponent } from '../../../../../components/cash-field';
|
||||||
|
import { FistfulThriftFormComponent } from '../../../../shared/components/fistful-thrift-form';
|
||||||
|
|
||||||
import { CreateRevertDialogComponent } from './create-revert-dialog.component';
|
import { CreateRevertDialogComponent } from './create-revert-dialog.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@ -23,6 +26,8 @@ import { CreateRevertDialogComponent } from './create-revert-dialog.component';
|
|||||||
MatInputModule,
|
MatInputModule,
|
||||||
UserInfoBasedIdGeneratorModule,
|
UserInfoBasedIdGeneratorModule,
|
||||||
DialogModule,
|
DialogModule,
|
||||||
|
FistfulThriftFormComponent,
|
||||||
|
CashFieldComponent,
|
||||||
],
|
],
|
||||||
declarations: [CreateRevertDialogComponent],
|
declarations: [CreateRevertDialogComponent],
|
||||||
})
|
})
|
||||||
|
@ -4,7 +4,7 @@ import { DialogService, Column } from '@vality/ng-core';
|
|||||||
import startCase from 'lodash-es/startCase';
|
import startCase from 'lodash-es/startCase';
|
||||||
import { filter } from 'rxjs/operators';
|
import { filter } from 'rxjs/operators';
|
||||||
|
|
||||||
import { getDepositStatus, createCurrencyColumn } from '@cc/app/shared/utils';
|
import { createCurrencyColumn } from '@cc/app/shared/utils';
|
||||||
|
|
||||||
import { getUnionKey } from '../../../../utils';
|
import { getUnionKey } from '../../../../utils';
|
||||||
|
|
||||||
@ -70,7 +70,7 @@ export class RevertsComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isCreateRevertAvailable(status: DepositStatus): boolean {
|
isCreateRevertAvailable(status: DepositStatus): boolean {
|
||||||
return getDepositStatus(status) !== 'succeeded';
|
return getUnionKey(status) !== 'succeeded';
|
||||||
}
|
}
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
import { NotifyLogService } from '@vality/ng-core';
|
||||||
import { merge, ReplaySubject, Subject, EMPTY } from 'rxjs';
|
import { merge, ReplaySubject, Subject, EMPTY } from 'rxjs';
|
||||||
import { catchError, switchMap, shareReplay, map } from 'rxjs/operators';
|
import { catchError, switchMap, shareReplay, map } from 'rxjs/operators';
|
||||||
|
|
||||||
import { FistfulStatisticsService, createDsl } from '@cc/app/api/fistful-stat';
|
import { FistfulStatisticsService, createDsl } from '@cc/app/api/fistful-stat';
|
||||||
import { progress } from '@cc/app/shared/custom-operators';
|
import { progress } from '@cc/app/shared/custom-operators';
|
||||||
|
|
||||||
import { NotificationErrorService } from '../../../../shared/services/notification-error';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ReceiveDepositService {
|
export class ReceiveDepositService {
|
||||||
private receiveDeposit$ = new ReplaySubject<string>();
|
private receiveDeposit$ = new ReplaySubject<string>();
|
||||||
@ -20,7 +19,7 @@ export class ReceiveDepositService {
|
|||||||
.pipe(
|
.pipe(
|
||||||
catchError((err) => {
|
catchError((err) => {
|
||||||
this.error$.next(true);
|
this.error$.next(true);
|
||||||
this.notificationErrorService.error(err);
|
this.log.error(err);
|
||||||
return EMPTY;
|
return EMPTY;
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
@ -34,7 +33,7 @@ export class ReceiveDepositService {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private fistfulStatisticsService: FistfulStatisticsService,
|
private fistfulStatisticsService: FistfulStatisticsService,
|
||||||
private notificationErrorService: NotificationErrorService,
|
private log: NotifyLogService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
receiveDeposit(id: string) {
|
receiveDeposit(id: string) {
|
||||||
|
@ -19,6 +19,6 @@
|
|||||||
</v-dialog-actions>
|
</v-dialog-actions>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
|
|
||||||
<ng-template #sourceCashTemplate let-control="control">
|
<ng-template #sourceCashTemplate let-cashControl="control">
|
||||||
<cc-source-cash-field [formControl]="control"></cc-source-cash-field>
|
<cc-source-cash-field [formControl]="cashControl"></cc-source-cash-field>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
@ -58,7 +58,10 @@ export class DepositsComponent implements OnInit {
|
|||||||
columns: Column<StatDeposit>[] = [
|
columns: Column<StatDeposit>[] = [
|
||||||
{
|
{
|
||||||
field: 'id',
|
field: 'id',
|
||||||
|
formatter: (d) => d.description || `#${d.id}`,
|
||||||
link: (d) => `/deposits/${d.id}`,
|
link: (d) => `/deposits/${d.id}`,
|
||||||
|
description: 'id',
|
||||||
|
maxWidth: 'max(300px, 30vw)',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'status',
|
field: 'status',
|
||||||
|
@ -1,35 +1,34 @@
|
|||||||
<ng-container *ngIf="view?.items$ | async as items">
|
<ng-container *ngIf="!extension?.hidden">
|
||||||
<div
|
<div
|
||||||
*ngIf="!(view.isValue$ | async); else onlyValue"
|
*ngIf="!(view.isValue$ | async); else onlyValue"
|
||||||
style="display: grid; grid-template-columns: 1fr; gap: 16px"
|
style="display: grid; grid-template-columns: 1fr; gap: 16px"
|
||||||
>
|
>
|
||||||
<div
|
<div *ngIf="(view.leaves$ | async)?.length as count" class="grid-container">
|
||||||
*ngIf="(view.leaves$ | async)?.length as count"
|
<div [ngClass]="['grid', 'grid-columns-' + count]">
|
||||||
[ngStyle]="{
|
<ng-container *ngFor="let item of view.leaves$ | async; let i = index">
|
||||||
display: 'grid',
|
<div
|
||||||
gap: '16px',
|
*ngIf="!((item.current$ | async)?.extension$ | async)?.hidden"
|
||||||
'grid-template-columns':
|
style="display: grid; grid-template-rows: auto 1fr; gap: 8px"
|
||||||
count === 1 ? '1fr' : count === 2 ? '1fr 1fr' : '1fr 1fr 1fr'
|
>
|
||||||
}"
|
<cc-key
|
||||||
>
|
[keys]="item.path$ | async"
|
||||||
<div
|
class="mat-caption mat-secondary-text"
|
||||||
*ngFor="let item of view.leaves$ | async; let i = index"
|
></cc-key>
|
||||||
style="display: grid; grid-template-rows: auto 1fr; gap: 8px"
|
|
||||||
>
|
|
||||||
<cc-key [keys]="item.path$ | async" class="mat-caption mat-secondary-text"></cc-key>
|
|
||||||
|
|
||||||
<div *ngIf="item.current$ | async as current" class="mat-body-1">
|
<div *ngIf="item.current$ | async as current" class="mat-body-1">
|
||||||
<cc-json-viewer
|
<cc-json-viewer
|
||||||
*ngIf="!(current.isEmpty$ | async); else empty"
|
*ngIf="!(current.isEmpty$ | async); else empty"
|
||||||
[data]="current.data$ | async"
|
[data]="current.data$ | async"
|
||||||
[extension]="current.extension$ | async"
|
[extension]="current.extension$ | async"
|
||||||
[extensions]="extensions"
|
[extensions]="extensions"
|
||||||
[value]="current.value$ | async"
|
[value]="current.value$ | async"
|
||||||
></cc-json-viewer>
|
></cc-json-viewer>
|
||||||
<ng-template #empty>
|
<ng-template #empty>
|
||||||
<mat-icon class="mat-secondary-text" inline>hide_source</mat-icon>
|
<mat-icon class="mat-secondary-text" inline>hide_source</mat-icon>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -68,25 +67,36 @@
|
|||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-template #onlyValue>
|
|
||||||
<span
|
|
||||||
[ngClass]="{
|
|
||||||
link: !!extension?.click || !!extension?.link?.[0],
|
|
||||||
'tooltip-link': !!extension?.tooltip,
|
|
||||||
'mat-body-1': true
|
|
||||||
}"
|
|
||||||
[queryParams]="extensionQueryParams$ | async"
|
|
||||||
[routerLink]="extension?.link?.[0]"
|
|
||||||
(click)="extension?.click?.()"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
*ngIf="extension?.tooltip; else simpleValue"
|
|
||||||
[matTooltip]="getTooltip(extension.tooltip)"
|
|
||||||
matTooltipClass="tooltip"
|
|
||||||
>{{ view.renderValue$ | async }}</span
|
|
||||||
>
|
|
||||||
<ng-template #simpleValue>{{ view.renderValue$ | async }}</ng-template>
|
|
||||||
</span>
|
|
||||||
</ng-template>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-template #onlyValue>
|
||||||
|
<span
|
||||||
|
[queryParams]="extensionQueryParams$ | async"
|
||||||
|
[routerLink]="extension?.link?.[0]"
|
||||||
|
(click)="extension?.click?.()"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
*ngIf="extension?.tooltip; else tagValue"
|
||||||
|
[matTooltip]="getTooltip(extension.tooltip)"
|
||||||
|
matTooltipClass="tooltip"
|
||||||
|
>
|
||||||
|
<ng-container *ngTemplateOutlet="tagValue"></ng-container>
|
||||||
|
</span>
|
||||||
|
<ng-template #tagValue>
|
||||||
|
<v-tag *ngIf="extension?.tag; else simpleValue" [color]="extension?.color">
|
||||||
|
<ng-container *ngTemplateOutlet="simpleValue"></ng-container>
|
||||||
|
</v-tag>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #simpleValue>
|
||||||
|
<span
|
||||||
|
[ngClass]="{
|
||||||
|
link: !!extension?.click || !!extension?.link?.[0],
|
||||||
|
'tooltip-link': !!extension?.tooltip,
|
||||||
|
'mat-body-1': !extension?.tag
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ view.renderValue$ | async }}
|
||||||
|
</span>
|
||||||
|
</ng-template>
|
||||||
|
</span>
|
||||||
|
</ng-template>
|
||||||
|
@ -7,6 +7,7 @@ import { MatDividerModule } from '@angular/material/divider';
|
|||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { TagModule } from '@vality/ng-core';
|
||||||
|
|
||||||
import { ThriftPipesModule } from '@cc/app/shared';
|
import { ThriftPipesModule } from '@cc/app/shared';
|
||||||
import { DetailsItemModule } from '@cc/components/details-item';
|
import { DetailsItemModule } from '@cc/components/details-item';
|
||||||
@ -28,6 +29,7 @@ import { JsonViewerComponent } from './json-viewer.component';
|
|||||||
MatTooltipModule,
|
MatTooltipModule,
|
||||||
MatBadgeModule,
|
MatBadgeModule,
|
||||||
RouterModule,
|
RouterModule,
|
||||||
|
TagModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class JsonViewerModule {}
|
export class JsonViewerModule {}
|
||||||
|
@ -11,11 +11,61 @@
|
|||||||
.link,
|
.link,
|
||||||
.tooltip-link {
|
.tooltip-link {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-link {
|
||||||
text-decoration-style: dotted;
|
text-decoration-style: dotted;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link {
|
.link {
|
||||||
text-decoration-style: solid !important;
|
text-decoration-style: solid;
|
||||||
cursor: pointer !important;
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-container {
|
||||||
|
container-type: inline-size;
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
gap: 16px;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
|
||||||
|
&.grid-columns-1 {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.grid-columns-2 {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.grid-columns-3 {
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@container (width < 1200px) {
|
||||||
|
.grid-container > .grid {
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@container (width < 900px) {
|
||||||
|
.grid-container > .grid {
|
||||||
|
&,
|
||||||
|
&.grid-columns-3 {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@container (width < 600px) {
|
||||||
|
.grid-container > .grid {
|
||||||
|
&,
|
||||||
|
&.grid-columns-3,
|
||||||
|
&.grid-columns-2 {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
|
import { Color } from '@vality/ng-core';
|
||||||
import { Observable, combineLatest, switchMap, of } from 'rxjs';
|
import { Observable, combineLatest, switchMap, of } from 'rxjs';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
@ -6,26 +7,34 @@ import { MetadataFormData } from '../../metadata-form';
|
|||||||
|
|
||||||
export interface MetadataViewExtensionResult {
|
export interface MetadataViewExtensionResult {
|
||||||
key?: string;
|
key?: string;
|
||||||
value: string;
|
value?: string;
|
||||||
|
hidden?: boolean;
|
||||||
tooltip?: unknown;
|
tooltip?: unknown;
|
||||||
link?: Parameters<Router['navigate']>;
|
link?: Parameters<Router['navigate']>;
|
||||||
click?: () => void;
|
click?: () => void;
|
||||||
|
color?: Color;
|
||||||
|
tag?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MetadataViewExtension = {
|
export type MetadataViewExtension = {
|
||||||
determinant: (data: MetadataFormData, value: unknown) => Observable<boolean>;
|
determinant: (data: MetadataFormData, value: unknown) => Observable<boolean>;
|
||||||
extension: (data: MetadataFormData, value: unknown) => Observable<MetadataViewExtensionResult>;
|
extension: (
|
||||||
|
data: MetadataFormData,
|
||||||
|
value: unknown,
|
||||||
|
viewValue: unknown,
|
||||||
|
) => Observable<MetadataViewExtensionResult>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getFirstDeterminedExtensionsResult(
|
export function getFirstDeterminedExtensionsResult(
|
||||||
sourceExtensions: MetadataViewExtension[],
|
sourceExtensions: MetadataViewExtension[],
|
||||||
data: MetadataFormData,
|
data: MetadataFormData,
|
||||||
value: unknown,
|
value: unknown,
|
||||||
|
viewValue: unknown,
|
||||||
): Observable<MetadataViewExtensionResult> {
|
): Observable<MetadataViewExtensionResult> {
|
||||||
return sourceExtensions?.length
|
return sourceExtensions?.length
|
||||||
? combineLatest(sourceExtensions.map(({ determinant }) => determinant(data, value))).pipe(
|
? combineLatest(sourceExtensions.map(({ determinant }) => determinant(data, value))).pipe(
|
||||||
map((determined) => sourceExtensions.find((_, idx) => determined[idx])),
|
map((determined) => sourceExtensions.find((_, idx) => determined[idx])),
|
||||||
switchMap((extension) => extension?.extension(data, value) ?? of(null)),
|
switchMap((extension) => extension?.extension(data, value, viewValue) ?? of(null)),
|
||||||
)
|
)
|
||||||
: of(null);
|
: of(null);
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,8 @@ import { isEmpty } from '@vality/ng-core';
|
|||||||
import { SetType, ListType, MapType, ValueType } from '@vality/thrift-ts';
|
import { SetType, ListType, MapType, ValueType } from '@vality/thrift-ts';
|
||||||
import isNil from 'lodash-es/isNil';
|
import isNil from 'lodash-es/isNil';
|
||||||
import isObject from 'lodash-es/isObject';
|
import isObject from 'lodash-es/isObject';
|
||||||
import { Observable, of, switchMap, combineLatest } from 'rxjs';
|
import { Observable, of, switchMap, combineLatest, defer } from 'rxjs';
|
||||||
import { map, shareReplay } from 'rxjs/operators';
|
import { map, shareReplay, distinctUntilChanged, startWith } from 'rxjs/operators';
|
||||||
|
|
||||||
import { MetadataFormData } from '../../metadata-form';
|
import { MetadataFormData } from '../../metadata-form';
|
||||||
|
|
||||||
@ -12,42 +12,34 @@ import { getEntries } from './get-entries';
|
|||||||
import {
|
import {
|
||||||
MetadataViewExtension,
|
MetadataViewExtension,
|
||||||
getFirstDeterminedExtensionsResult,
|
getFirstDeterminedExtensionsResult,
|
||||||
|
MetadataViewExtensionResult,
|
||||||
} from './metadata-view-extension';
|
} from './metadata-view-extension';
|
||||||
|
|
||||||
export class MetadataViewItem {
|
export class MetadataViewItem {
|
||||||
extension$ = getFirstDeterminedExtensionsResult(this.extensions, this.data, this.value).pipe(
|
extension$: Observable<MetadataViewExtensionResult> = defer(() => this.renderValue$).pipe(
|
||||||
|
switchMap((viewValue) =>
|
||||||
|
getFirstDeterminedExtensionsResult(this.extensions, this.data, this.value, viewValue),
|
||||||
|
),
|
||||||
|
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||||
|
);
|
||||||
|
data$ = this.extension$.pipe(
|
||||||
|
startWith(null),
|
||||||
|
map((ext) => (ext ? null : this.data)),
|
||||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||||
);
|
);
|
||||||
data$ = this.extension$.pipe(map((ext) => (ext ? null : this.data)));
|
|
||||||
key$ = this.extension$.pipe(
|
key$ = this.extension$.pipe(
|
||||||
map((ext) => (isNil(ext?.key) ? this.key : new MetadataViewItem(ext.key))),
|
map((ext) => (isNil(ext?.key) ? this.key : new MetadataViewItem(ext.key))),
|
||||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||||
);
|
);
|
||||||
value$ = this.extension$.pipe(
|
value$ = this.extension$.pipe(
|
||||||
map((ext) => {
|
startWith(null),
|
||||||
const value = ext?.value ?? this.value;
|
map((ext) => this.getValue(ext)),
|
||||||
return isEmpty(value) ? null : value;
|
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
renderValue$ = combineLatest([this.value$, this.data$]).pipe(
|
renderValue$ = combineLatest([this.value$, this.data$]).pipe(
|
||||||
map(([value, data]) => {
|
map(([value, data]) => this.getRenderValue(value, data)),
|
||||||
if (data?.trueTypeNode?.data?.objectType === 'enum') {
|
distinctUntilChanged(),
|
||||||
return (
|
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||||
(data.trueTypeNode.data as MetadataFormData<ValueType, 'enum'>).ast.items.find(
|
|
||||||
(i, idx) => {
|
|
||||||
if ('value' in i) {
|
|
||||||
return i.value === value;
|
|
||||||
}
|
|
||||||
return idx === value;
|
|
||||||
},
|
|
||||||
).name ?? value
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (data?.objectType === 'union' && isEmpty(getEntries(value)?.[0]?.[1])) {
|
|
||||||
return getEntries(value)?.[0]?.[0];
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
isEmpty$ = this.renderValue$.pipe(map((value) => isEmpty(value)));
|
isEmpty$ = this.renderValue$.pipe(map((value) => isEmpty(value)));
|
||||||
|
|
||||||
@ -199,4 +191,28 @@ export class MetadataViewItem {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getValue(ext: MetadataViewExtensionResult) {
|
||||||
|
const value = ext?.value ?? this.value;
|
||||||
|
return isEmpty(value) || ext?.hidden ? null : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getRenderValue(value: unknown, data: MetadataFormData) {
|
||||||
|
if (data?.trueTypeNode?.data?.objectType === 'enum') {
|
||||||
|
return (
|
||||||
|
(data.trueTypeNode.data as MetadataFormData<ValueType, 'enum'>).ast.items.find(
|
||||||
|
(i, idx) => {
|
||||||
|
if ('value' in i) {
|
||||||
|
return i.value === value;
|
||||||
|
}
|
||||||
|
return idx === value;
|
||||||
|
},
|
||||||
|
).name ?? value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (data?.objectType === 'union' && isEmpty(getEntries(value)?.[0]?.[1])) {
|
||||||
|
return getEntries(value)?.[0]?.[0];
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ import {
|
|||||||
import { JsonViewerModule } from '@cc/app/shared/components/json-viewer';
|
import { JsonViewerModule } from '@cc/app/shared/components/json-viewer';
|
||||||
import { ThriftPipesModule } from '@cc/app/shared/pipes/thrift';
|
import { ThriftPipesModule } from '@cc/app/shared/pipes/thrift';
|
||||||
import { ValueTypeTitleModule } from '@cc/app/shared/pipes/value-type-title';
|
import { ValueTypeTitleModule } from '@cc/app/shared/pipes/value-type-title';
|
||||||
import { CashModule } from '@cc/components/cash-field';
|
import { CashFieldComponent } from '@cc/components/cash-field';
|
||||||
|
|
||||||
import { ComplexFormComponent } from './components/complex-form/complex-form.component';
|
import { ComplexFormComponent } from './components/complex-form/complex-form.component';
|
||||||
import { EnumFieldComponent } from './components/enum-field/enum-field.component';
|
import { EnumFieldComponent } from './components/enum-field/enum-field.component';
|
||||||
@ -58,7 +58,7 @@ import { FieldLabelPipe } from './pipes/field-label.pipe';
|
|||||||
MatDatepickerModule,
|
MatDatepickerModule,
|
||||||
DatetimeFieldModule,
|
DatetimeFieldModule,
|
||||||
PipesModule,
|
PipesModule,
|
||||||
CashModule,
|
CashFieldComponent,
|
||||||
AutocompleteFieldModule,
|
AutocompleteFieldModule,
|
||||||
TagModule,
|
TagModule,
|
||||||
],
|
],
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
import { DepositStatus } from '@vality/fistful-proto/fistful_stat';
|
|
||||||
|
|
||||||
import { clearNullFields } from '@cc/utils/thrift-utils';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated use get union key
|
|
||||||
*/
|
|
||||||
export const getDepositStatus = (status: DepositStatus): string =>
|
|
||||||
Object.keys(clearNullFields(status))[0];
|
|
@ -1,4 +1,2 @@
|
|||||||
export * from './polling-conditions';
|
|
||||||
export * from './deposit-status';
|
|
||||||
export * from './clean-thrift';
|
export * from './clean-thrift';
|
||||||
export * from './table';
|
export * from './table';
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
import { StatDeposit } from '@vality/fistful-proto/fistful_stat';
|
|
||||||
|
|
||||||
import { getDepositStatus } from './deposit-status';
|
|
||||||
|
|
||||||
export const createDepositStopPollingCondition = (deposit: StatDeposit): boolean =>
|
|
||||||
!!deposit && getDepositStatus(deposit.status) !== 'pending';
|
|
@ -9,25 +9,10 @@
|
|||||||
matInput
|
matInput
|
||||||
/>
|
/>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field style="min-width: 28px; width: 50%">
|
<v-select-field
|
||||||
<mat-label>Currency</mat-label>
|
[formControl]="currencyControl"
|
||||||
<input
|
[options]="options$ | async"
|
||||||
[formControl]="currencyCodeControl"
|
[style.width.px]="200"
|
||||||
[inputMask]="currencyMask"
|
label="Currency"
|
||||||
[matAutocomplete]="auto"
|
></v-select-field>
|
||||||
[required]="required"
|
|
||||||
matInput
|
|
||||||
/>
|
|
||||||
<mat-autocomplete
|
|
||||||
#auto="matAutocomplete"
|
|
||||||
(optionSelected)="this.currencyCodeControl.setValue($event.option.value)"
|
|
||||||
>
|
|
||||||
<mat-option
|
|
||||||
*ngFor="let currency of currencies$ | async"
|
|
||||||
[value]="currency.data.symbolic_code"
|
|
||||||
>
|
|
||||||
{{ currency.data.symbolic_code }} ({{ currency.data.name }})
|
|
||||||
</mat-option>
|
|
||||||
</mat-autocomplete>
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { getCurrencySymbol } from '@angular/common';
|
import { getCurrencySymbol, CommonModule } from '@angular/common';
|
||||||
import {
|
import {
|
||||||
Component,
|
Component,
|
||||||
Input,
|
Input,
|
||||||
Injector,
|
|
||||||
Inject,
|
Inject,
|
||||||
LOCALE_ID,
|
LOCALE_ID,
|
||||||
OnInit,
|
OnInit,
|
||||||
@ -10,14 +9,26 @@ import {
|
|||||||
DestroyRef,
|
DestroyRef,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
import { Validator, ValidationErrors, FormControl } from '@angular/forms';
|
import { Validator, ValidationErrors, FormControl, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { createMask } from '@ngneat/input-mask';
|
import { MatFormField } from '@angular/material/form-field';
|
||||||
import { FormComponentSuperclass, createControlProviders, getValueChanges } from '@vality/ng-core';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
import sortBy from 'lodash-es/sortBy';
|
import { createMask, InputMaskModule } from '@ngneat/input-mask';
|
||||||
|
import { CurrencyObject } from '@vality/domain-proto/domain';
|
||||||
|
import {
|
||||||
|
FormComponentSuperclass,
|
||||||
|
createControlProviders,
|
||||||
|
getValueChanges,
|
||||||
|
Option,
|
||||||
|
SelectFieldModule,
|
||||||
|
toMinorByExponent,
|
||||||
|
toMajorByExponent,
|
||||||
|
compareDifferentTypes,
|
||||||
|
} from '@vality/ng-core';
|
||||||
|
import isNil from 'lodash-es/isNil';
|
||||||
import { combineLatest } from 'rxjs';
|
import { combineLatest } from 'rxjs';
|
||||||
import { map, switchMap, first, distinctUntilChanged } from 'rxjs/operators';
|
import { map, distinctUntilChanged, shareReplay, startWith, take } from 'rxjs/operators';
|
||||||
|
|
||||||
import { DomainStoreService } from '@cc/app/api/domain-config';
|
import { DomainStoreService } from '../../app/api/domain-config';
|
||||||
|
|
||||||
export interface Cash {
|
export interface Cash {
|
||||||
amount: number;
|
amount: number;
|
||||||
@ -25,104 +36,147 @@ export interface Cash {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const GROUP_SEPARATOR = ' ';
|
const GROUP_SEPARATOR = ' ';
|
||||||
|
const DEFAULT_EXPONENT = 2;
|
||||||
|
const RADIX_POINT = '.';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
standalone: true,
|
||||||
selector: 'cc-cash-field',
|
selector: 'cc-cash-field',
|
||||||
templateUrl: './cash-field.component.html',
|
templateUrl: './cash-field.component.html',
|
||||||
providers: createControlProviders(() => CashFieldComponent),
|
providers: createControlProviders(() => CashFieldComponent),
|
||||||
|
imports: [
|
||||||
|
MatFormField,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
InputMaskModule,
|
||||||
|
SelectFieldModule,
|
||||||
|
CommonModule,
|
||||||
|
MatInputModule,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class CashFieldComponent extends FormComponentSuperclass<Cash> implements Validator, OnInit {
|
export class CashFieldComponent extends FormComponentSuperclass<Cash> implements Validator, OnInit {
|
||||||
@Input() label?: string;
|
@Input() label?: string;
|
||||||
@Input({ transform: booleanAttribute }) required: boolean = false;
|
@Input({ transform: booleanAttribute }) required: boolean = false;
|
||||||
@Input({ transform: booleanAttribute }) minor: boolean = false;
|
|
||||||
|
|
||||||
amountControl = new FormControl<string>(null);
|
amountControl = new FormControl<string>(null);
|
||||||
currencyCodeControl = new FormControl<string>(null);
|
currencyControl = new FormControl<CurrencyObject>(null);
|
||||||
|
|
||||||
currencies$ = combineLatest([
|
options$ = this.domainStoreService.getObjects('currency').pipe(
|
||||||
getValueChanges(this.currencyCodeControl),
|
startWith([] as CurrencyObject[]),
|
||||||
this.domainStoreService.getObjects('currency'),
|
map((objs): Option<CurrencyObject>[] =>
|
||||||
]).pipe(
|
objs
|
||||||
map(([code, currencies]) =>
|
.sort((a, b) => compareDifferentTypes(a.data.symbolic_code, b.data.symbolic_code))
|
||||||
sortBy(currencies, 'data', 'symbolic_code').filter(
|
.map((s) => ({
|
||||||
(c) =>
|
label: s.data.symbolic_code,
|
||||||
c.data.symbolic_code.toUpperCase().includes(code) || c.data.name.includes(code),
|
description: s.data.name,
|
||||||
),
|
value: s,
|
||||||
|
})),
|
||||||
),
|
),
|
||||||
|
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||||
);
|
);
|
||||||
|
currencyExponent$ = getValueChanges(this.currencyControl).pipe(
|
||||||
amountMask$ = getValueChanges(this.currencyCodeControl).pipe(
|
map((obj) => obj?.data?.exponent ?? DEFAULT_EXPONENT),
|
||||||
switchMap((code) => this.getCurrencyByCode(code)),
|
|
||||||
map((c) => (this.minor ? 0 : c?.data?.exponent || 2)),
|
|
||||||
distinctUntilChanged(),
|
distinctUntilChanged(),
|
||||||
map((digits) =>
|
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||||
|
);
|
||||||
|
amountMask$ = this.currencyExponent$.pipe(
|
||||||
|
distinctUntilChanged(),
|
||||||
|
map((exponent) =>
|
||||||
createMask({
|
createMask({
|
||||||
alias: 'numeric',
|
alias: 'numeric',
|
||||||
groupSeparator: GROUP_SEPARATOR,
|
groupSeparator: GROUP_SEPARATOR,
|
||||||
digits,
|
digits: exponent,
|
||||||
digitsOptional: true,
|
digitsOptional: true,
|
||||||
placeholder: '',
|
placeholder: '',
|
||||||
|
onBeforePaste: (pastedValue: string) =>
|
||||||
|
this.convertPastedToStringNumber(pastedValue),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||||
);
|
);
|
||||||
currencyMask = createMask({ mask: 'AAA', placeholder: '' });
|
|
||||||
|
get currencyCode() {
|
||||||
|
return this.currencyControl.value?.data?.symbolic_code;
|
||||||
|
}
|
||||||
|
|
||||||
get prefix() {
|
get prefix() {
|
||||||
return getCurrencySymbol(this.currencyCodeControl.value, 'narrow', this._locale);
|
return getCurrencySymbol(this.currencyCode, 'narrow', this._locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
injector: Injector,
|
|
||||||
@Inject(LOCALE_ID) private _locale: string,
|
@Inject(LOCALE_ID) private _locale: string,
|
||||||
private domainStoreService: DomainStoreService,
|
|
||||||
private destroyRef: DestroyRef,
|
private destroyRef: DestroyRef,
|
||||||
|
private domainStoreService: DomainStoreService,
|
||||||
) {
|
) {
|
||||||
super(injector);
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
super.ngOnInit();
|
||||||
combineLatest([
|
combineLatest([
|
||||||
getValueChanges(this.currencyCodeControl),
|
combineLatest([getValueChanges(this.amountControl), this.currencyExponent$]).pipe(
|
||||||
getValueChanges(this.amountControl),
|
map(([amountStr, exponent]) => {
|
||||||
|
const amount = amountStr
|
||||||
|
? Number(amountStr.replaceAll(GROUP_SEPARATOR, ''))
|
||||||
|
: null;
|
||||||
|
return isNil(amount) ? null : toMinorByExponent(amount, exponent);
|
||||||
|
}),
|
||||||
|
distinctUntilChanged(),
|
||||||
|
),
|
||||||
|
getValueChanges(this.currencyControl).pipe(
|
||||||
|
map((obj) => obj?.data?.symbolic_code),
|
||||||
|
distinctUntilChanged(),
|
||||||
|
),
|
||||||
])
|
])
|
||||||
.pipe(
|
.pipe(
|
||||||
switchMap(([currencyCode]) => this.getCurrencyByCode(currencyCode)),
|
map(([amount, currencyCode]) =>
|
||||||
|
!isNil(amount) && currencyCode ? { amount, currencyCode } : null,
|
||||||
|
),
|
||||||
|
distinctUntilChanged(),
|
||||||
takeUntilDestroyed(this.destroyRef),
|
takeUntilDestroyed(this.destroyRef),
|
||||||
)
|
)
|
||||||
.subscribe((currency) => {
|
.subscribe((value) => {
|
||||||
const amountStr = this.amountControl.value;
|
this.emitOutgoingValue(value);
|
||||||
if (amountStr && currency && !this.validate()) {
|
|
||||||
const [whole, fractional] = amountStr.split('.');
|
|
||||||
if (fractional?.length > currency.data.exponent) {
|
|
||||||
this.amountControl.setValue(
|
|
||||||
`${whole}.${fractional.slice(0, currency.data.exponent)}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const amount = Number(this.amountControl.value.replaceAll(GROUP_SEPARATOR, ''));
|
|
||||||
this.emitOutgoingValue({ amount, currencyCode: currency.data.symbolic_code });
|
|
||||||
} else {
|
|
||||||
this.emitOutgoingValue(null);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
validate(): ValidationErrors | null {
|
validate(): ValidationErrors | null {
|
||||||
return !this.amountControl.value || this.currencyCodeControl.value?.length !== 3
|
return !this.amountControl.value || !this.currencyControl.value
|
||||||
? { invalidCash: true }
|
? { invalidCash: true }
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleIncomingValue(value: Cash) {
|
handleIncomingValue(value: Cash) {
|
||||||
this.amountControl.setValue(
|
const { currencyCode, amount } = value || {};
|
||||||
typeof value?.amount === 'number' ? String(value.amount) : null,
|
if (!currencyCode) {
|
||||||
);
|
this.setValues(amount, null);
|
||||||
this.currencyCodeControl.setValue(value?.currencyCode);
|
}
|
||||||
|
this.options$
|
||||||
|
.pipe(
|
||||||
|
map(
|
||||||
|
(options) =>
|
||||||
|
options.find((o) => o.value?.data?.symbolic_code === value.currencyCode)
|
||||||
|
?.value ?? null,
|
||||||
|
),
|
||||||
|
take(1),
|
||||||
|
takeUntilDestroyed(this.destroyRef),
|
||||||
|
)
|
||||||
|
.subscribe((obj) => {
|
||||||
|
this.setValues(amount, obj);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private getCurrencyByCode(currencyCode: string) {
|
private setValues(amount: number, currencyObject: CurrencyObject) {
|
||||||
return this.domainStoreService.getObjects('currency').pipe(
|
this.currencyControl.setValue(currencyObject);
|
||||||
map((c) => c.find((v) => v.data.symbolic_code === currencyCode)),
|
this.amountControl.setValue(
|
||||||
first(),
|
typeof amount === 'number'
|
||||||
|
? String(
|
||||||
|
toMajorByExponent(amount, currencyObject?.data?.exponent ?? DEFAULT_EXPONENT),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private convertPastedToStringNumber(pastedValue: string) {
|
||||||
|
return pastedValue.replaceAll(',', RADIX_POINT);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
import { CommonModule } from '@angular/common';
|
|
||||||
import { NgModule } from '@angular/core';
|
|
||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
|
||||||
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
|
||||||
import { MatInputModule } from '@angular/material/input';
|
|
||||||
import { InputMaskModule } from '@ngneat/input-mask';
|
|
||||||
|
|
||||||
import { CashFieldComponent } from './cash-field.component';
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
declarations: [CashFieldComponent],
|
|
||||||
imports: [
|
|
||||||
CommonModule,
|
|
||||||
FormsModule,
|
|
||||||
MatInputModule,
|
|
||||||
MatAutocompleteModule,
|
|
||||||
InputMaskModule,
|
|
||||||
ReactiveFormsModule,
|
|
||||||
],
|
|
||||||
exports: [CashFieldComponent],
|
|
||||||
})
|
|
||||||
export class CashModule {}
|
|
@ -1,2 +1 @@
|
|||||||
export * from './cash-field.module';
|
|
||||||
export * from './cash-field.component';
|
export * from './cash-field.component';
|
||||||
|
@ -25,7 +25,7 @@ import {
|
|||||||
} from '@vality/ng-core';
|
} from '@vality/ng-core';
|
||||||
import isNil from 'lodash-es/isNil';
|
import isNil from 'lodash-es/isNil';
|
||||||
import { combineLatest, switchMap, of } from 'rxjs';
|
import { combineLatest, switchMap, of } from 'rxjs';
|
||||||
import { map, first, distinctUntilChanged, shareReplay, startWith } from 'rxjs/operators';
|
import { map, distinctUntilChanged, shareReplay, startWith, take } from 'rxjs/operators';
|
||||||
|
|
||||||
import { DomainStoreService } from '../../app/api/domain-config';
|
import { DomainStoreService } from '../../app/api/domain-config';
|
||||||
import { FetchSourcesService } from '../../app/sections/sources';
|
import { FetchSourcesService } from '../../app/sections/sources';
|
||||||
@ -36,6 +36,7 @@ export interface SourceCash {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const GROUP_SEPARATOR = ' ';
|
const GROUP_SEPARATOR = ' ';
|
||||||
|
const DEFAULT_EXPONENT = 2;
|
||||||
const RADIX_POINT = '.';
|
const RADIX_POINT = '.';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -159,7 +160,7 @@ export class SourceCashFieldComponent
|
|||||||
switchMap((s) =>
|
switchMap((s) =>
|
||||||
combineLatest([of(s), this.getCurrencyExponent(s?.currency_symbolic_code)]),
|
combineLatest([of(s), this.getCurrencyExponent(s?.currency_symbolic_code)]),
|
||||||
),
|
),
|
||||||
first(),
|
take(1),
|
||||||
takeUntilDestroyed(this.destroyRef),
|
takeUntilDestroyed(this.destroyRef),
|
||||||
)
|
)
|
||||||
.subscribe(([source, exponent]) => {
|
.subscribe(([source, exponent]) => {
|
||||||
@ -174,12 +175,12 @@ export class SourceCashFieldComponent
|
|||||||
map(
|
map(
|
||||||
(currencies) =>
|
(currencies) =>
|
||||||
currencies.find((c) => c.data.symbolic_code === symbolicCode)?.data
|
currencies.find((c) => c.data.symbolic_code === symbolicCode)?.data
|
||||||
?.exponent ?? 2,
|
?.exponent ?? DEFAULT_EXPONENT,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private setValues(amount: number, source: StatSource, exponent: number = 2) {
|
private setValues(amount: number, source: StatSource, exponent: number = DEFAULT_EXPONENT) {
|
||||||
this.sourceControl.setValue(source);
|
this.sourceControl.setValue(source);
|
||||||
this.amountControl.setValue(
|
this.amountControl.setValue(
|
||||||
typeof amount === 'number' ? String(toMajorByExponent(amount, exponent)) : null,
|
typeof amount === 'number' ? String(toMajorByExponent(amount, exponent)) : null,
|
||||||
|
Loading…
Reference in New Issue
Block a user