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/machinegun-proto": "1.0.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/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-0134d85.0",
|
||||
"resolved": "https://registry.npmjs.org/@vality/ng-core/-/ng-core-17.2.1-pr-57-0134d85.0.tgz",
|
||||
"integrity": "sha512-0wlLzf2+smFYVYqu/iEptyeKrasRsxWRd2hNwqG+cd0eiIwxNWyBMaI0+FuWS141CtodSgF8Ab8CAG2ceD+vkw==",
|
||||
"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==",
|
||||
"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-8ecf2b7.0",
|
||||
"@vality/machinegun-proto": "1.0.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/repairer-proto": "2.0.2-07b73e9.0",
|
||||
"@vality/thrift-ts": "2.4.1-8ad5123.0",
|
||||
|
@ -1,7 +1,14 @@
|
||||
<cc-page-layout title="Deposit details">
|
||||
<cc-deposit-main-info
|
||||
*ngIf="deposit$ | async as deposit"
|
||||
[deposit]="deposit"
|
||||
></cc-deposit-main-info>
|
||||
<cc-page-layout [progress]="isLoading$ | async" title="Deposit details">
|
||||
<mat-card *ngIf="deposit$ | async">
|
||||
<mat-card-content>
|
||||
<cc-thrift-viewer
|
||||
[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-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 { 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';
|
||||
|
||||
@ -11,15 +27,114 @@ import { ReceiveDepositService } from './services/receive-deposit/receive-deposi
|
||||
})
|
||||
export class DepositDetailsComponent implements OnInit {
|
||||
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(
|
||||
private fetchDepositService: ReceiveDepositService,
|
||||
private route: ActivatedRoute,
|
||||
@Inject(LOCALE_ID) private _locale: string,
|
||||
private amountCurrencyService: AmountCurrencyService,
|
||||
private walletManagementService: ManagementService,
|
||||
private fetchSourcesService: FetchSourcesService,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.route.params
|
||||
.pipe(take(1), pluck('depositID'))
|
||||
.pipe(
|
||||
take(1),
|
||||
map((p) => p.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 { HeadlineModule } from '@cc/components/headline';
|
||||
|
||||
import { ThriftViewerModule } from '../../shared/components/thrift-viewer';
|
||||
|
||||
import { DepositDetailsRoutingModule } from './deposit-details-routing.module';
|
||||
import { DepositDetailsComponent } from './deposit-details.component';
|
||||
import { DepositMainInfoModule } from './deposit-main-info/deposit-main-info.module';
|
||||
import { RevertsModule } from './reverts/reverts.module';
|
||||
|
||||
@NgModule({
|
||||
@ -25,9 +26,9 @@ import { RevertsModule } from './reverts/reverts.module';
|
||||
MatProgressSpinnerModule,
|
||||
MatButtonModule,
|
||||
MatDialogModule,
|
||||
DepositMainInfoModule,
|
||||
RevertsModule,
|
||||
PageLayoutModule,
|
||||
ThriftViewerModule,
|
||||
],
|
||||
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">
|
||||
<div *ngIf="form" [formGroup]="form" style="display: flex; flex-direction: column; gap: 16px">
|
||||
<div style="display: flex; gap: 24px">
|
||||
<mat-form-field style="flex: 1">
|
||||
<input
|
||||
formControlName="amount"
|
||||
matInput
|
||||
placeholder="Amount"
|
||||
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>
|
||||
<cc-fistful-thrift-form
|
||||
[extensions]="extensions"
|
||||
[formControl]="control"
|
||||
namespace="deposit_revert"
|
||||
noChangeKind
|
||||
type="RevertParams"
|
||||
></cc-fistful-thrift-form>
|
||||
|
||||
<v-dialog-actions>
|
||||
<button
|
||||
[disabled]="!!(progress$ | async) || form.invalid"
|
||||
[disabled]="!!(progress$ | async) || control.invalid"
|
||||
color="primary"
|
||||
mat-button
|
||||
(click)="createRevert()"
|
||||
@ -47,3 +18,7 @@
|
||||
</button>
|
||||
</v-dialog-actions>
|
||||
</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 { 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 { DialogSuperclass, NotifyLogService, toMinor, clean } from '@vality/ng-core';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { DialogSuperclass, NotifyLogService, clean } from '@vality/ng-core';
|
||||
import { BehaviorSubject, of } from 'rxjs';
|
||||
import { Overwrite } from 'utility-types';
|
||||
|
||||
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 { CreateRevertDialogConfig } from './types/create-revert-dialog-config';
|
||||
@ -21,16 +34,24 @@ export class CreateRevertDialogComponent extends DialogSuperclass<
|
||||
CreateRevertDialogConfig,
|
||||
Revert
|
||||
> {
|
||||
form = this.fb.group({
|
||||
amount: [undefined as number, [Validators.pattern(/^\d+([,.]\d{1,2})?$/)]],
|
||||
currency: this.dialogData.currency,
|
||||
reason: undefined as string,
|
||||
externalID: undefined as string,
|
||||
});
|
||||
control = new FormControl({
|
||||
id: this.idGenerator.getUsernameBasedId(),
|
||||
body: { currencyCode: this.dialogData.currency },
|
||||
} as Overwrite<DepositParams, { body: Cash }>);
|
||||
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(
|
||||
private fb: NonNullableFormBuilder,
|
||||
private depositManagementService: DepositManagementService,
|
||||
private idGenerator: UserInfoBasedIdGeneratorService,
|
||||
private log: NotifyLogService,
|
||||
@ -40,21 +61,17 @@ export class CreateRevertDialogComponent extends DialogSuperclass<
|
||||
}
|
||||
|
||||
createRevert() {
|
||||
const { reason, amount, currency, externalID } = this.form.value;
|
||||
const { body, ...value } = this.control.value;
|
||||
this.depositManagementService
|
||||
.CreateRevert(
|
||||
this.dialogData.depositID,
|
||||
clean(
|
||||
{
|
||||
id: this.idGenerator.getUsernameBasedId(),
|
||||
...value,
|
||||
body: {
|
||||
amount: toMinor(amount, currency),
|
||||
currency: {
|
||||
symbolic_code: currency,
|
||||
},
|
||||
currency: { symbolic_code: body.currencyCode },
|
||||
amount: body.amount,
|
||||
},
|
||||
reason,
|
||||
external_id: externalID,
|
||||
},
|
||||
false,
|
||||
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 { CashFieldComponent } from '../../../../../components/cash-field';
|
||||
import { FistfulThriftFormComponent } from '../../../../shared/components/fistful-thrift-form';
|
||||
|
||||
import { CreateRevertDialogComponent } from './create-revert-dialog.component';
|
||||
|
||||
@NgModule({
|
||||
@ -23,6 +26,8 @@ import { CreateRevertDialogComponent } from './create-revert-dialog.component';
|
||||
MatInputModule,
|
||||
UserInfoBasedIdGeneratorModule,
|
||||
DialogModule,
|
||||
FistfulThriftFormComponent,
|
||||
CashFieldComponent,
|
||||
],
|
||||
declarations: [CreateRevertDialogComponent],
|
||||
})
|
||||
|
@ -4,7 +4,7 @@ import { DialogService, Column } from '@vality/ng-core';
|
||||
import startCase from 'lodash-es/startCase';
|
||||
import { filter } from 'rxjs/operators';
|
||||
|
||||
import { getDepositStatus, createCurrencyColumn } from '@cc/app/shared/utils';
|
||||
import { createCurrencyColumn } from '@cc/app/shared/utils';
|
||||
|
||||
import { getUnionKey } from '../../../../utils';
|
||||
|
||||
@ -70,7 +70,7 @@ export class RevertsComponent implements OnInit {
|
||||
}
|
||||
|
||||
isCreateRevertAvailable(status: DepositStatus): boolean {
|
||||
return getDepositStatus(status) !== 'succeeded';
|
||||
return getUnionKey(status) !== 'succeeded';
|
||||
}
|
||||
|
||||
update() {
|
||||
|
@ -1,12 +1,11 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { NotifyLogService } from '@vality/ng-core';
|
||||
import { merge, ReplaySubject, Subject, EMPTY } from 'rxjs';
|
||||
import { catchError, switchMap, shareReplay, map } from 'rxjs/operators';
|
||||
|
||||
import { FistfulStatisticsService, createDsl } from '@cc/app/api/fistful-stat';
|
||||
import { progress } from '@cc/app/shared/custom-operators';
|
||||
|
||||
import { NotificationErrorService } from '../../../../shared/services/notification-error';
|
||||
|
||||
@Injectable()
|
||||
export class ReceiveDepositService {
|
||||
private receiveDeposit$ = new ReplaySubject<string>();
|
||||
@ -20,7 +19,7 @@ export class ReceiveDepositService {
|
||||
.pipe(
|
||||
catchError((err) => {
|
||||
this.error$.next(true);
|
||||
this.notificationErrorService.error(err);
|
||||
this.log.error(err);
|
||||
return EMPTY;
|
||||
}),
|
||||
),
|
||||
@ -34,7 +33,7 @@ export class ReceiveDepositService {
|
||||
|
||||
constructor(
|
||||
private fistfulStatisticsService: FistfulStatisticsService,
|
||||
private notificationErrorService: NotificationErrorService,
|
||||
private log: NotifyLogService,
|
||||
) {}
|
||||
|
||||
receiveDeposit(id: string) {
|
||||
|
@ -19,6 +19,6 @@
|
||||
</v-dialog-actions>
|
||||
</v-dialog>
|
||||
|
||||
<ng-template #sourceCashTemplate let-control="control">
|
||||
<cc-source-cash-field [formControl]="control"></cc-source-cash-field>
|
||||
<ng-template #sourceCashTemplate let-cashControl="control">
|
||||
<cc-source-cash-field [formControl]="cashControl"></cc-source-cash-field>
|
||||
</ng-template>
|
||||
|
@ -58,7 +58,10 @@ export class DepositsComponent implements OnInit {
|
||||
columns: Column<StatDeposit>[] = [
|
||||
{
|
||||
field: 'id',
|
||||
formatter: (d) => d.description || `#${d.id}`,
|
||||
link: (d) => `/deposits/${d.id}`,
|
||||
description: 'id',
|
||||
maxWidth: 'max(300px, 30vw)',
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
|
@ -1,35 +1,34 @@
|
||||
<ng-container *ngIf="view?.items$ | async as items">
|
||||
<ng-container *ngIf="!extension?.hidden">
|
||||
<div
|
||||
*ngIf="!(view.isValue$ | async); else onlyValue"
|
||||
style="display: grid; grid-template-columns: 1fr; gap: 16px"
|
||||
>
|
||||
<div
|
||||
*ngIf="(view.leaves$ | async)?.length as count"
|
||||
[ngStyle]="{
|
||||
display: 'grid',
|
||||
gap: '16px',
|
||||
'grid-template-columns':
|
||||
count === 1 ? '1fr' : count === 2 ? '1fr 1fr' : '1fr 1fr 1fr'
|
||||
}"
|
||||
>
|
||||
<div
|
||||
*ngFor="let item of view.leaves$ | async; let i = index"
|
||||
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="(view.leaves$ | async)?.length as count" class="grid-container">
|
||||
<div [ngClass]="['grid', 'grid-columns-' + count]">
|
||||
<ng-container *ngFor="let item of view.leaves$ | async; let i = index">
|
||||
<div
|
||||
*ngIf="!((item.current$ | async)?.extension$ | async)?.hidden"
|
||||
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">
|
||||
<cc-json-viewer
|
||||
*ngIf="!(current.isEmpty$ | async); else empty"
|
||||
[data]="current.data$ | async"
|
||||
[extension]="current.extension$ | async"
|
||||
[extensions]="extensions"
|
||||
[value]="current.value$ | async"
|
||||
></cc-json-viewer>
|
||||
<ng-template #empty>
|
||||
<mat-icon class="mat-secondary-text" inline>hide_source</mat-icon>
|
||||
</ng-template>
|
||||
</div>
|
||||
<div *ngIf="item.current$ | async as current" class="mat-body-1">
|
||||
<cc-json-viewer
|
||||
*ngIf="!(current.isEmpty$ | async); else empty"
|
||||
[data]="current.data$ | async"
|
||||
[extension]="current.extension$ | async"
|
||||
[extensions]="extensions"
|
||||
[value]="current.value$ | async"
|
||||
></cc-json-viewer>
|
||||
<ng-template #empty>
|
||||
<mat-icon class="mat-secondary-text" inline>hide_source</mat-icon>
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -68,25 +67,36 @@
|
||||
</div>
|
||||
</ng-container>
|
||||
</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-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 { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { TagModule } from '@vality/ng-core';
|
||||
|
||||
import { ThriftPipesModule } from '@cc/app/shared';
|
||||
import { DetailsItemModule } from '@cc/components/details-item';
|
||||
@ -28,6 +29,7 @@ import { JsonViewerComponent } from './json-viewer.component';
|
||||
MatTooltipModule,
|
||||
MatBadgeModule,
|
||||
RouterModule,
|
||||
TagModule,
|
||||
],
|
||||
})
|
||||
export class JsonViewerModule {}
|
||||
|
@ -11,11 +11,61 @@
|
||||
.link,
|
||||
.tooltip-link {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.tooltip-link {
|
||||
text-decoration-style: dotted;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.link {
|
||||
text-decoration-style: solid !important;
|
||||
cursor: pointer !important;
|
||||
text-decoration-style: solid;
|
||||
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 { Color } from '@vality/ng-core';
|
||||
import { Observable, combineLatest, switchMap, of } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
@ -6,26 +7,34 @@ import { MetadataFormData } from '../../metadata-form';
|
||||
|
||||
export interface MetadataViewExtensionResult {
|
||||
key?: string;
|
||||
value: string;
|
||||
value?: string;
|
||||
hidden?: boolean;
|
||||
tooltip?: unknown;
|
||||
link?: Parameters<Router['navigate']>;
|
||||
click?: () => void;
|
||||
color?: Color;
|
||||
tag?: boolean;
|
||||
}
|
||||
|
||||
export type MetadataViewExtension = {
|
||||
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(
|
||||
sourceExtensions: MetadataViewExtension[],
|
||||
data: MetadataFormData,
|
||||
value: unknown,
|
||||
viewValue: unknown,
|
||||
): Observable<MetadataViewExtensionResult> {
|
||||
return sourceExtensions?.length
|
||||
? combineLatest(sourceExtensions.map(({ determinant }) => determinant(data, value))).pipe(
|
||||
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);
|
||||
}
|
||||
|
@ -2,8 +2,8 @@ import { isEmpty } from '@vality/ng-core';
|
||||
import { SetType, ListType, MapType, ValueType } from '@vality/thrift-ts';
|
||||
import isNil from 'lodash-es/isNil';
|
||||
import isObject from 'lodash-es/isObject';
|
||||
import { Observable, of, switchMap, combineLatest } from 'rxjs';
|
||||
import { map, shareReplay } from 'rxjs/operators';
|
||||
import { Observable, of, switchMap, combineLatest, defer } from 'rxjs';
|
||||
import { map, shareReplay, distinctUntilChanged, startWith } from 'rxjs/operators';
|
||||
|
||||
import { MetadataFormData } from '../../metadata-form';
|
||||
|
||||
@ -12,42 +12,34 @@ import { getEntries } from './get-entries';
|
||||
import {
|
||||
MetadataViewExtension,
|
||||
getFirstDeterminedExtensionsResult,
|
||||
MetadataViewExtensionResult,
|
||||
} from './metadata-view-extension';
|
||||
|
||||
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 }),
|
||||
);
|
||||
data$ = this.extension$.pipe(map((ext) => (ext ? null : this.data)));
|
||||
key$ = this.extension$.pipe(
|
||||
map((ext) => (isNil(ext?.key) ? this.key : new MetadataViewItem(ext.key))),
|
||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||
);
|
||||
value$ = this.extension$.pipe(
|
||||
map((ext) => {
|
||||
const value = ext?.value ?? this.value;
|
||||
return isEmpty(value) ? null : value;
|
||||
}),
|
||||
startWith(null),
|
||||
map((ext) => this.getValue(ext)),
|
||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||
);
|
||||
renderValue$ = combineLatest([this.value$, this.data$]).pipe(
|
||||
map(([value, data]) => {
|
||||
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;
|
||||
}),
|
||||
map(([value, data]) => this.getRenderValue(value, data)),
|
||||
distinctUntilChanged(),
|
||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||
);
|
||||
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 { ThriftPipesModule } from '@cc/app/shared/pipes/thrift';
|
||||
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 { EnumFieldComponent } from './components/enum-field/enum-field.component';
|
||||
@ -58,7 +58,7 @@ import { FieldLabelPipe } from './pipes/field-label.pipe';
|
||||
MatDatepickerModule,
|
||||
DatetimeFieldModule,
|
||||
PipesModule,
|
||||
CashModule,
|
||||
CashFieldComponent,
|
||||
AutocompleteFieldModule,
|
||||
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 './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
|
||||
/>
|
||||
</mat-form-field>
|
||||
<mat-form-field style="min-width: 28px; width: 50%">
|
||||
<mat-label>Currency</mat-label>
|
||||
<input
|
||||
[formControl]="currencyCodeControl"
|
||||
[inputMask]="currencyMask"
|
||||
[matAutocomplete]="auto"
|
||||
[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>
|
||||
<v-select-field
|
||||
[formControl]="currencyControl"
|
||||
[options]="options$ | async"
|
||||
[style.width.px]="200"
|
||||
label="Currency"
|
||||
></v-select-field>
|
||||
</div>
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { getCurrencySymbol } from '@angular/common';
|
||||
import { getCurrencySymbol, CommonModule } from '@angular/common';
|
||||
import {
|
||||
Component,
|
||||
Input,
|
||||
Injector,
|
||||
Inject,
|
||||
LOCALE_ID,
|
||||
OnInit,
|
||||
@ -10,14 +9,26 @@ import {
|
||||
DestroyRef,
|
||||
} from '@angular/core';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { Validator, ValidationErrors, FormControl } from '@angular/forms';
|
||||
import { createMask } from '@ngneat/input-mask';
|
||||
import { FormComponentSuperclass, createControlProviders, getValueChanges } from '@vality/ng-core';
|
||||
import sortBy from 'lodash-es/sortBy';
|
||||
import { Validator, ValidationErrors, FormControl, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatFormField } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
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 { 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 {
|
||||
amount: number;
|
||||
@ -25,104 +36,147 @@ export interface Cash {
|
||||
}
|
||||
|
||||
const GROUP_SEPARATOR = ' ';
|
||||
const DEFAULT_EXPONENT = 2;
|
||||
const RADIX_POINT = '.';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'cc-cash-field',
|
||||
templateUrl: './cash-field.component.html',
|
||||
providers: createControlProviders(() => CashFieldComponent),
|
||||
imports: [
|
||||
MatFormField,
|
||||
ReactiveFormsModule,
|
||||
InputMaskModule,
|
||||
SelectFieldModule,
|
||||
CommonModule,
|
||||
MatInputModule,
|
||||
],
|
||||
})
|
||||
export class CashFieldComponent extends FormComponentSuperclass<Cash> implements Validator, OnInit {
|
||||
@Input() label?: string;
|
||||
@Input({ transform: booleanAttribute }) required: boolean = false;
|
||||
@Input({ transform: booleanAttribute }) minor: boolean = false;
|
||||
|
||||
amountControl = new FormControl<string>(null);
|
||||
currencyCodeControl = new FormControl<string>(null);
|
||||
currencyControl = new FormControl<CurrencyObject>(null);
|
||||
|
||||
currencies$ = combineLatest([
|
||||
getValueChanges(this.currencyCodeControl),
|
||||
this.domainStoreService.getObjects('currency'),
|
||||
]).pipe(
|
||||
map(([code, currencies]) =>
|
||||
sortBy(currencies, 'data', 'symbolic_code').filter(
|
||||
(c) =>
|
||||
c.data.symbolic_code.toUpperCase().includes(code) || c.data.name.includes(code),
|
||||
),
|
||||
options$ = this.domainStoreService.getObjects('currency').pipe(
|
||||
startWith([] as CurrencyObject[]),
|
||||
map((objs): Option<CurrencyObject>[] =>
|
||||
objs
|
||||
.sort((a, b) => compareDifferentTypes(a.data.symbolic_code, b.data.symbolic_code))
|
||||
.map((s) => ({
|
||||
label: s.data.symbolic_code,
|
||||
description: s.data.name,
|
||||
value: s,
|
||||
})),
|
||||
),
|
||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||
);
|
||||
|
||||
amountMask$ = getValueChanges(this.currencyCodeControl).pipe(
|
||||
switchMap((code) => this.getCurrencyByCode(code)),
|
||||
map((c) => (this.minor ? 0 : c?.data?.exponent || 2)),
|
||||
currencyExponent$ = getValueChanges(this.currencyControl).pipe(
|
||||
map((obj) => obj?.data?.exponent ?? DEFAULT_EXPONENT),
|
||||
distinctUntilChanged(),
|
||||
map((digits) =>
|
||||
shareReplay({ refCount: true, bufferSize: 1 }),
|
||||
);
|
||||
amountMask$ = this.currencyExponent$.pipe(
|
||||
distinctUntilChanged(),
|
||||
map((exponent) =>
|
||||
createMask({
|
||||
alias: 'numeric',
|
||||
groupSeparator: GROUP_SEPARATOR,
|
||||
digits,
|
||||
digits: exponent,
|
||||
digitsOptional: true,
|
||||
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() {
|
||||
return getCurrencySymbol(this.currencyCodeControl.value, 'narrow', this._locale);
|
||||
return getCurrencySymbol(this.currencyCode, 'narrow', this._locale);
|
||||
}
|
||||
|
||||
constructor(
|
||||
injector: Injector,
|
||||
@Inject(LOCALE_ID) private _locale: string,
|
||||
private domainStoreService: DomainStoreService,
|
||||
private destroyRef: DestroyRef,
|
||||
private domainStoreService: DomainStoreService,
|
||||
) {
|
||||
super(injector);
|
||||
super();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
super.ngOnInit();
|
||||
combineLatest([
|
||||
getValueChanges(this.currencyCodeControl),
|
||||
getValueChanges(this.amountControl),
|
||||
combineLatest([getValueChanges(this.amountControl), this.currencyExponent$]).pipe(
|
||||
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(
|
||||
switchMap(([currencyCode]) => this.getCurrencyByCode(currencyCode)),
|
||||
map(([amount, currencyCode]) =>
|
||||
!isNil(amount) && currencyCode ? { amount, currencyCode } : null,
|
||||
),
|
||||
distinctUntilChanged(),
|
||||
takeUntilDestroyed(this.destroyRef),
|
||||
)
|
||||
.subscribe((currency) => {
|
||||
const amountStr = this.amountControl.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);
|
||||
}
|
||||
.subscribe((value) => {
|
||||
this.emitOutgoingValue(value);
|
||||
});
|
||||
}
|
||||
|
||||
validate(): ValidationErrors | null {
|
||||
return !this.amountControl.value || this.currencyCodeControl.value?.length !== 3
|
||||
return !this.amountControl.value || !this.currencyControl.value
|
||||
? { invalidCash: true }
|
||||
: null;
|
||||
}
|
||||
|
||||
handleIncomingValue(value: Cash) {
|
||||
this.amountControl.setValue(
|
||||
typeof value?.amount === 'number' ? String(value.amount) : null,
|
||||
);
|
||||
this.currencyCodeControl.setValue(value?.currencyCode);
|
||||
const { currencyCode, amount } = value || {};
|
||||
if (!currencyCode) {
|
||||
this.setValues(amount, null);
|
||||
}
|
||||
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) {
|
||||
return this.domainStoreService.getObjects('currency').pipe(
|
||||
map((c) => c.find((v) => v.data.symbolic_code === currencyCode)),
|
||||
first(),
|
||||
private setValues(amount: number, currencyObject: CurrencyObject) {
|
||||
this.currencyControl.setValue(currencyObject);
|
||||
this.amountControl.setValue(
|
||||
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';
|
||||
|
@ -25,7 +25,7 @@ import {
|
||||
} from '@vality/ng-core';
|
||||
import isNil from 'lodash-es/isNil';
|
||||
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 { FetchSourcesService } from '../../app/sections/sources';
|
||||
@ -36,6 +36,7 @@ export interface SourceCash {
|
||||
}
|
||||
|
||||
const GROUP_SEPARATOR = ' ';
|
||||
const DEFAULT_EXPONENT = 2;
|
||||
const RADIX_POINT = '.';
|
||||
|
||||
@Component({
|
||||
@ -159,7 +160,7 @@ export class SourceCashFieldComponent
|
||||
switchMap((s) =>
|
||||
combineLatest([of(s), this.getCurrencyExponent(s?.currency_symbolic_code)]),
|
||||
),
|
||||
first(),
|
||||
take(1),
|
||||
takeUntilDestroyed(this.destroyRef),
|
||||
)
|
||||
.subscribe(([source, exponent]) => {
|
||||
@ -174,12 +175,12 @@ export class SourceCashFieldComponent
|
||||
map(
|
||||
(currencies) =>
|
||||
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.amountControl.setValue(
|
||||
typeof amount === 'number' ? String(toMajorByExponent(amount, exponent)) : null,
|
||||
|
Loading…
Reference in New Issue
Block a user