Moving to table2 with lazy column (#405)

This commit is contained in:
Rinat Arsaev 2024-10-18 22:07:21 +09:00 committed by GitHub
parent 753ad0d727
commit 9e3b6d5ae8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
58 changed files with 745 additions and 838 deletions

8
package-lock.json generated
View File

@ -26,7 +26,7 @@
"@vality/fistful-proto": "2.0.1-88e69a5.0", "@vality/fistful-proto": "2.0.1-88e69a5.0",
"@vality/machinegun-proto": "1.0.1-3decc8f.0", "@vality/machinegun-proto": "1.0.1-3decc8f.0",
"@vality/magista-proto": "2.0.2-ec1bdb9.0", "@vality/magista-proto": "2.0.2-ec1bdb9.0",
"@vality/ng-core": "18.4.1-pr-74-55d4acd.0", "@vality/ng-core": "18.4.1-pr-78-9131f9a.0",
"@vality/ng-thrift": "18.0.1-pr-13-bdb6d51.0", "@vality/ng-thrift": "18.0.1-pr-13-bdb6d51.0",
"@vality/repairer-proto": "2.0.2-07b73e9.0", "@vality/repairer-proto": "2.0.2-07b73e9.0",
"@vality/scrooge-proto": "0.1.1-9ce7fc6.0", "@vality/scrooge-proto": "0.1.1-9ce7fc6.0",
@ -5969,9 +5969,9 @@
"integrity": "sha512-XWF7qM/CARRAey0scGVhfGU6jNq+UdlGE2mg3jn4eIFDuIWQJqsT+Bah300RBUrl+XgFsmj95C6HWRfeA5Q8kw==" "integrity": "sha512-XWF7qM/CARRAey0scGVhfGU6jNq+UdlGE2mg3jn4eIFDuIWQJqsT+Bah300RBUrl+XgFsmj95C6HWRfeA5Q8kw=="
}, },
"node_modules/@vality/ng-core": { "node_modules/@vality/ng-core": {
"version": "18.4.1-pr-74-55d4acd.0", "version": "18.4.1-pr-78-9131f9a.0",
"resolved": "https://registry.npmjs.org/@vality/ng-core/-/ng-core-18.4.1-pr-74-55d4acd.0.tgz", "resolved": "https://registry.npmjs.org/@vality/ng-core/-/ng-core-18.4.1-pr-78-9131f9a.0.tgz",
"integrity": "sha512-EkGTPth5+HA0MLc+BYLIV5ie7xnwBrXftTxoVJw+5zlcWoLm5V1+10AGz6wmhX4VIunFFZR/p9XUNAhNqvluVw==", "integrity": "sha512-qmTGnGe+pBWlIQ+cX64pt2aLWdoLM6M2Apc180dQQrLca4VBHB4Oh8yo7vvv/sxws1DLXEzWutgNzi9/bRdtZQ==",
"dependencies": { "dependencies": {
"@angular/material-date-fns-adapter": "^18.2.2", "@angular/material-date-fns-adapter": "^18.2.2",
"@ng-matero/extensions": "^18.2.0", "@ng-matero/extensions": "^18.2.0",

View File

@ -35,7 +35,7 @@
"@vality/fistful-proto": "2.0.1-88e69a5.0", "@vality/fistful-proto": "2.0.1-88e69a5.0",
"@vality/machinegun-proto": "1.0.1-3decc8f.0", "@vality/machinegun-proto": "1.0.1-3decc8f.0",
"@vality/magista-proto": "2.0.2-ec1bdb9.0", "@vality/magista-proto": "2.0.2-ec1bdb9.0",
"@vality/ng-core": "18.4.1-pr-74-55d4acd.0", "@vality/ng-core": "18.4.1-pr-78-9131f9a.0",
"@vality/ng-thrift": "18.0.1-pr-13-bdb6d51.0", "@vality/ng-thrift": "18.0.1-pr-13-bdb6d51.0",
"@vality/repairer-proto": "2.0.2-07b73e9.0", "@vality/repairer-proto": "2.0.2-07b73e9.0",
"@vality/scrooge-proto": "0.1.1-9ce7fc6.0", "@vality/scrooge-proto": "0.1.1-9ce7fc6.0",

View File

@ -12,8 +12,8 @@ import { environment } from '../environments/environment';
import { ROUTING_CONFIG as CLAIMS_ROUTING_CONFIG } from './sections/claims/routing-config'; import { ROUTING_CONFIG as CLAIMS_ROUTING_CONFIG } from './sections/claims/routing-config';
import { ROUTING_CONFIG as DEPOSITS_ROUTING_CONFIG } from './sections/deposits/routing-config'; import { ROUTING_CONFIG as DEPOSITS_ROUTING_CONFIG } from './sections/deposits/routing-config';
import { ROUTING_CONFIG as DOMAIN_ROUTING_CONFIG } from './sections/domain/routing-config'; import { ROUTING_CONFIG as DOMAIN_ROUTING_CONFIG } from './sections/domain/routing-config';
import { ROUTING_CONFIG as MACHINES_ROUTING_CONFIG } from './sections/machines/routing-config';
import { ROUTING_CONFIG as PAYMENTS_ROUTING_CONFIG } from './sections/payments/routing-config'; import { ROUTING_CONFIG as PAYMENTS_ROUTING_CONFIG } from './sections/payments/routing-config';
import { ROUTING_CONFIG as REPAIRING_ROUTING_CONFIG } from './sections/repairing/routing-config';
import { ROUTING_CONFIG as PARTIES_ROUTING_CONFIG } from './sections/search-parties/routing-config'; import { ROUTING_CONFIG as PARTIES_ROUTING_CONFIG } from './sections/search-parties/routing-config';
import { SHOPS_ROUTING_CONFIG } from './sections/shops'; import { SHOPS_ROUTING_CONFIG } from './sections/shops';
import { ROUTING_CONFIG as SOURCES_ROUTING_CONFIG } from './sections/sources/routing-config'; import { ROUTING_CONFIG as SOURCES_ROUTING_CONFIG } from './sections/sources/routing-config';
@ -73,7 +73,7 @@ export class AppComponent {
{ {
label: 'Machines', label: 'Machines',
url: '/machines', url: '/machines',
services: REPAIRING_ROUTING_CONFIG.services, services: MACHINES_ROUTING_CONFIG.services,
}, },
{ {
label: 'Sources', label: 'Sources',

View File

@ -1,4 +1,4 @@
<cc-page-layout title="Chargebacks"> <cc-page-layout fullHeight title="Chargebacks">
<cc-page-layout-actions> <cc-page-layout-actions>
<v-more-filters-button [filters]="filters"></v-more-filters-button> <v-more-filters-button [filters]="filters"></v-more-filters-button>
</cc-page-layout-actions> </cc-page-layout-actions>
@ -47,14 +47,6 @@
(more)="more()" (more)="more()"
(update)="reload($event)" (update)="reload($event)"
> >
<button
[disabled]="!selected.length"
color="primary"
mat-raised-button
(click)="changeStatuses()"
>
Change statuses
</button>
<button color="primary" mat-raised-button (click)="create()">Create by file</button> <button color="primary" mat-raised-button (click)="create()">Create by file</button>
</cc-chargebacks-table> </cc-chargebacks-table>
</cc-page-layout> </cc-page-layout>

View File

@ -29,7 +29,6 @@ import {
CHARGEBACK_CATEGORIES, CHARGEBACK_CATEGORIES,
} from '@cc/app/api/fistful-stat'; } from '@cc/app/api/fistful-stat';
import { ChangeChargebacksStatusDialogComponent } from '../../shared/components/change-chargebacks-status-dialog';
import { DATE_RANGE_DAYS, DEBOUNCE_TIME_MS } from '../../tokens'; import { DATE_RANGE_DAYS, DEBOUNCE_TIME_MS } from '../../tokens';
import { CreateChargebacksByFileDialogComponent } from './components/create-chargebacks-by-file-dialog/create-chargebacks-by-file-dialog.component'; import { CreateChargebacksByFileDialogComponent } from './components/create-chargebacks-by-file-dialog/create-chargebacks-by-file-dialog.component';
@ -46,7 +45,7 @@ type FormValue = {
@Component({ @Component({
selector: 'cc-chargebacks', selector: 'cc-chargebacks',
templateUrl: './chargebacks.component.html', templateUrl: './chargebacks.component.html',
styles: [], providers: [FetchChargebacksService],
}) })
export class ChargebacksComponent implements OnInit { export class ChargebacksComponent implements OnInit {
filtersForm = new FormGroup( filtersForm = new FormGroup(
@ -141,17 +140,4 @@ export class ChargebacksComponent implements OnInit {
}); });
}); });
} }
changeStatuses() {
this.dialog
.open(ChangeChargebacksStatusDialogComponent, { chargebacks: this.selected })
.afterClosed()
.pipe(
filter((res) => res.status === DialogResponseStatus.Success),
takeUntilDestroyed(this.dr),
)
.subscribe(() => {
this.reload();
});
}
} }

View File

@ -20,6 +20,8 @@ import {
} from '@vality/ng-core'; } from '@vality/ng-core';
import { ThriftPipesModule } from '@vality/ng-thrift'; import { ThriftPipesModule } from '@vality/ng-thrift';
import { ChargebacksTableComponent } from '@cc/app/shared/components/chargebacks-table';
import { UploadCsvComponent } from '../../../components/upload-csv'; import { UploadCsvComponent } from '../../../components/upload-csv';
import { PageLayoutModule, ShopFieldModule } from '../../shared'; import { PageLayoutModule, ShopFieldModule } from '../../shared';
import { MerchantFieldModule } from '../../shared/components/merchant-field'; import { MerchantFieldModule } from '../../shared/components/merchant-field';
@ -31,15 +33,10 @@ import {
import { ChargebacksRoutingModule } from './chargebacks-routing.module'; import { ChargebacksRoutingModule } from './chargebacks-routing.module';
import { ChargebacksComponent } from './chargebacks.component'; import { ChargebacksComponent } from './chargebacks.component';
import { ChargebacksTableComponent } from './components/chargebacks-table/chargebacks-table.component';
import { CreateChargebacksByFileDialogComponent } from './components/create-chargebacks-by-file-dialog/create-chargebacks-by-file-dialog.component'; import { CreateChargebacksByFileDialogComponent } from './components/create-chargebacks-by-file-dialog/create-chargebacks-by-file-dialog.component';
@NgModule({ @NgModule({
declarations: [ declarations: [ChargebacksComponent, CreateChargebacksByFileDialogComponent],
ChargebacksComponent,
ChargebacksTableComponent,
CreateChargebacksByFileDialogComponent,
],
imports: [ imports: [
CommonModule, CommonModule,
ChargebacksRoutingModule, ChargebacksRoutingModule,
@ -67,6 +64,7 @@ import { CreateChargebacksByFileDialogComponent } from './components/create-char
MatInputModule, MatInputModule,
MatCheckboxModule, MatCheckboxModule,
UploadCsvComponent, UploadCsvComponent,
ChargebacksTableComponent,
], ],
}) })
export class ChargebacksModule {} export class ChargebacksModule {}

View File

@ -1,144 +0,0 @@
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { InvoicePaymentChargebackStatus } from '@vality/magista-proto/internal/proto/domain';
import { StatChargeback } from '@vality/magista-proto/magista';
import { LoadOptions, Column, TagColumn } from '@vality/ng-core';
import { getUnionKey } from '@vality/ng-thrift';
import startCase from 'lodash-es/startCase';
import { AmountCurrencyService } from '../../../../shared/services';
@Component({
selector: 'cc-chargebacks-table',
templateUrl: './chargebacks-table.component.html',
styles: [],
})
export class ChargebacksTableComponent {
@Input() data!: StatChargeback[];
@Input() isLoading?: boolean | null;
@Input() hasMore?: boolean | null;
@Input() selected?: StatChargeback[];
@Output() selectedChange = new EventEmitter<StatChargeback[]>();
@Output() update = new EventEmitter<LoadOptions>();
@Output() more = new EventEmitter<void>();
columns: Column<StatChargeback>[] = [
{ field: 'chargeback_id', header: 'Id' },
{
field: 'chargeback_reason',
header: 'Reason',
formatter: (data) => startCase(getUnionKey(data.chargeback_reason.category)),
description: (data) => data.chargeback_reason.code,
},
{
field: 'chargeback_status',
type: 'tag',
header: 'Status',
formatter: (data) => getUnionKey(data.chargeback_status),
typeParameters: {
label: (data) => startCase(getUnionKey(data.chargeback_status)),
tags: {
pending: { color: 'pending' },
accepted: { color: 'success' },
rejected: { color: 'warn' },
cancelled: { color: 'neutral' },
},
},
} as TagColumn<StatChargeback, keyof InvoicePaymentChargebackStatus>,
{
field: 'amount',
type: 'currency',
formatter: (data) =>
this.amountCurrencyService.toMajor(data.amount, data.currency_code.symbolic_code),
typeParameters: {
currencyCode: (data) => data.currency_code.symbolic_code,
},
},
{
field: 'levy_amount',
type: 'currency',
formatter: (data) =>
this.amountCurrencyService.toMajor(
data.levy_amount,
data.levy_currency_code.symbolic_code,
),
typeParameters: {
currencyCode: (data) => data.levy_currency_code.symbolic_code,
},
},
{
field: 'fee',
type: 'currency',
formatter: (data) =>
this.amountCurrencyService.toMajor(data.fee, data.currency_code.symbolic_code),
typeParameters: {
currencyCode: (data) => data.currency_code.symbolic_code,
},
},
{
field: 'provider_fee',
type: 'currency',
formatter: (data) =>
this.amountCurrencyService.toMajor(
data.provider_fee,
data.currency_code.symbolic_code,
),
typeParameters: {
currencyCode: (data) => data.currency_code.symbolic_code,
},
},
{
field: 'external_fee',
type: 'currency',
formatter: (data) =>
this.amountCurrencyService.toMajor(
data.external_fee,
data.currency_code.symbolic_code,
),
typeParameters: {
currencyCode: (data) => data.currency_code.symbolic_code,
},
},
{
field: 'stage',
formatter: (data) => getUnionKey(data.stage),
},
{
field: 'invoice_id',
header: 'Invoice Id (Payment Id)',
description: 'payment_id',
link: (data) =>
`/party/${data.party_id}/invoice/${data.invoice_id}/payment/${data.payment_id}`,
},
{
field: 'created_at',
type: 'datetime',
},
{
field: 'party_id',
header: 'Party',
},
{
field: 'shop_id',
header: 'Shop',
},
{
field: 'external_id',
hide: true,
},
{
field: 'content.type',
description: 'content.data',
header: 'Content',
hide: true,
},
// createOperationColumn<StatChargeback>([
// {
// label: 'Change status',
// click: (data) => undefined,
// },
// ]),
];
constructor(private amountCurrencyService: AmountCurrencyService) {}
}

View File

@ -6,9 +6,7 @@ import { map, catchError } from 'rxjs/operators';
import { MerchantStatisticsService } from '../../api/magista'; import { MerchantStatisticsService } from '../../api/magista';
@Injectable({ @Injectable()
providedIn: 'root',
})
export class FetchChargebacksService extends FetchSuperclass< export class FetchChargebacksService extends FetchSuperclass<
StatChargeback, StatChargeback,
ChargebackSearchQuery ChargebackSearchQuery

View File

@ -1,5 +1,5 @@
<v-table <v-table2
[columns]="columns()" [columns]="columns"
[data]="data" [data]="data"
[hasMore]="hasMore" [hasMore]="hasMore"
[progress]="isLoading" [progress]="isLoading"
@ -7,4 +7,4 @@
(update)="update.emit($event)" (update)="update.emit($event)"
> >
<v-table-actions><ng-content></ng-content></v-table-actions> <v-table-actions><ng-content></ng-content></v-table-actions>
</v-table> </v-table2>

View File

@ -1,20 +1,11 @@
import { import { Component, Input, Output, EventEmitter, booleanAttribute, input } from '@angular/core';
Component, import { toObservable } from '@angular/core/rxjs-interop';
Input, import { Claim } from '@vality/domain-proto/claim_management';
Output, import { Column2, LoadOptions, createMenuColumn } from '@vality/ng-core';
EventEmitter,
booleanAttribute,
input,
computed,
} from '@angular/core';
import { Router } from '@angular/router';
import { Claim, ClaimStatus } from '@vality/domain-proto/claim_management';
import { Column, LoadOptions, TagColumn, createOperationColumn } from '@vality/ng-core';
import { getUnionKey } from '@vality/ng-thrift'; import { getUnionKey } from '@vality/ng-thrift';
import isObject from 'lodash-es/isObject';
import startCase from 'lodash-es/startCase'; import startCase from 'lodash-es/startCase';
import { createPartyColumn } from '../../../shared'; import { createPartyColumn } from '@cc/app/shared/utils/table2';
@Component({ @Component({
selector: 'cc-claims-table', selector: 'cc-claims-table',
@ -30,48 +21,33 @@ export class ClaimsTableComponent {
@Output() update = new EventEmitter<LoadOptions>(); @Output() update = new EventEmitter<LoadOptions>();
@Output() more = new EventEmitter<void>(); @Output() more = new EventEmitter<void>();
columns = computed<Column<Claim>[]>(() => columns: Column2<Claim>[] = [
this.sourceColumns.filter( { field: 'id', cell: (d) => ({ link: () => `/party/${d.party_id}/claim/${d.id}` }) },
(c) => (isObject(c) && c?.field !== 'party_id') || !this.noParty(), createPartyColumn((d) => ({ id: d.party_id }), { hidden: toObservable(this.noParty) }),
),
);
private sourceColumns: Column<Claim>[] = [
{ field: 'id', link: (d) => this.getClaimLink(d.party_id, d.id) },
createPartyColumn('party_id'),
{ {
field: 'status', field: 'status',
type: 'tag', cell: (d) => ({
formatter: (claim) => getUnionKey(claim.status), value: startCase(getUnionKey(d.status)),
typeParameters: {
label: (claim) => startCase(getUnionKey(claim.status)),
tags: { tags: {
pending: { color: 'pending' }, pending: 'pending',
review: { color: 'pending' }, review: 'pending',
pending_acceptance: { color: 'pending' }, pending_acceptance: 'pending',
accepted: { color: 'success' }, accepted: 'success',
denied: { color: 'warn' }, denied: 'warn',
revoked: { color: 'neutral' }, revoked: 'neutral',
}, },
}, }),
} as TagColumn<Claim, keyof ClaimStatus>, },
'revision', { field: 'revision' },
{ field: 'created_at', type: 'datetime' }, { field: 'created_at', cell: { type: 'datetime' } },
{ field: 'updated_at', type: 'datetime' }, { field: 'updated_at', cell: { type: 'datetime' } },
createOperationColumn<Claim>([ createMenuColumn((d) => ({
{ items: [
label: 'Details', {
click: (claim) => this.navigateToClaim(claim.party_id, claim.id), label: 'Details',
}, link: () => `/party/${d.party_id}/claim/${d.id}`,
]), },
],
})),
]; ];
constructor(private router: Router) {}
navigateToClaim(partyId: string, claimID: number) {
void this.router.navigate([this.getClaimLink(partyId, claimID)]);
}
private getClaimLink(partyId: string, claimID: number): string {
return `/party/${partyId}/claim/${claimID}`;
}
} }

View File

@ -1,4 +1,4 @@
<cc-page-layout title="Claims"> <cc-page-layout fullHeight title="Claims">
<cc-page-layout-actions <cc-page-layout-actions
><v-more-filters-button [filters]="filters"></v-more-filters-button ><v-more-filters-button [filters]="filters"></v-more-filters-button
></cc-page-layout-actions> ></cc-page-layout-actions>

View File

@ -1,6 +1,6 @@
<div style="display: flex; flex-direction: column; gap: 24px"> <div style="display: flex; flex-direction: column; gap: 24px">
<h1 class="mat-headline-5">Reverts</h1> <h1 class="mat-headline-5">Reverts</h1>
<v-table <v-table2
[columns]="columns" [columns]="columns"
[data]="reverts$ | async" [data]="reverts$ | async"
[hasMore]="hasMore$ | async" [hasMore]="hasMore$ | async"
@ -18,5 +18,5 @@
Create revert Create revert
</button> </button>
</v-table-actions> </v-table-actions>
</v-table> </v-table2>
</div> </div>

View File

@ -1,11 +1,11 @@
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
import { DepositStatus, StatDeposit, StatDepositRevert } from '@vality/fistful-proto/fistful_stat'; import { DepositStatus, StatDeposit, StatDepositRevert } from '@vality/fistful-proto/fistful_stat';
import { DialogService, Column, UpdateOptions } from '@vality/ng-core'; import { DialogService, UpdateOptions, Column2 } from '@vality/ng-core';
import { getUnionKey } from '@vality/ng-thrift'; import { getUnionKey } from '@vality/ng-thrift';
import startCase from 'lodash-es/startCase'; import startCase from 'lodash-es/startCase';
import { filter } from 'rxjs/operators'; import { filter } from 'rxjs/operators';
import { createCurrencyColumn } from '@cc/app/shared/utils'; import { createCurrencyColumn } from '@cc/app/shared/utils/table2';
import { CreateRevertDialogComponent } from './create-revert-dialog/create-revert-dialog.component'; import { CreateRevertDialogComponent } from './create-revert-dialog/create-revert-dialog.component';
import { FetchRevertsService } from './services/fetch-reverts/fetch-reverts.service'; import { FetchRevertsService } from './services/fetch-reverts/fetch-reverts.service';
@ -23,27 +23,29 @@ export class RevertsComponent implements OnInit {
reverts$ = this.fetchRevertsService.result$; reverts$ = this.fetchRevertsService.result$;
hasMore$ = this.fetchRevertsService.hasMore$; hasMore$ = this.fetchRevertsService.hasMore$;
isLoading$ = this.fetchRevertsService.isLoading$; isLoading$ = this.fetchRevertsService.isLoading$;
columns: Column<StatDepositRevert>[] = [ columns: Column2<StatDepositRevert>[] = [
{ field: 'id' }, { field: 'id' },
{ {
field: 'status', field: 'status',
type: 'tag', cell: (d) => ({
formatter: (d) => getUnionKey(d.status), value: startCase(getUnionKey(d.status)),
typeParameters: { color: (
label: (d) => startCase(getUnionKey(d.status)), {
tags: { pending: 'pending',
pending: { color: 'pending' }, succeeded: 'success',
succeeded: { color: 'success' }, failed: 'warn',
failed: { color: 'warn' }, } as const
}, )[getUnionKey(d.status)],
}, }),
}, },
createCurrencyColumn( createCurrencyColumn(
'amount', (d) => ({
(d) => d.body.amount, amount: d.body.amount,
(d) => d.body.currency.symbolic_code, code: d.body.currency.symbolic_code,
}),
{ header: 'Amount' },
), ),
{ field: 'created_at', type: 'datetime' }, { field: 'created_at', cell: { type: 'datetime' } },
]; ];
constructor( constructor(

View File

@ -1,4 +1,4 @@
<cc-page-layout title="Deposits"> <cc-page-layout fullHeight title="Deposits">
<cc-page-layout-actions> <cc-page-layout-actions>
<v-more-filters-button [filters]="filters"></v-more-filters-button> <v-more-filters-button [filters]="filters"></v-more-filters-button>
</cc-page-layout-actions> </cc-page-layout-actions>
@ -22,7 +22,7 @@
<cc-merchant-field formControlName="party_id"></cc-merchant-field> <cc-merchant-field formControlName="party_id"></cc-merchant-field>
</ng-template> </ng-template>
</v-filters> </v-filters>
<v-table <v-table2
[columns]="columns" [columns]="columns"
[data]="deposits$ | async" [data]="deposits$ | async"
[hasMore]="hasMore$ | async" [hasMore]="hasMore$ | async"
@ -31,10 +31,8 @@
(update)="reload($event)" (update)="reload($event)"
> >
<v-table-actions> <v-table-actions>
<button color="primary" mat-raised-button (click)="createByFile()"> <button mat-raised-button (click)="createByFile()">Create by file</button>
Create by file
</button>
<button color="primary" mat-raised-button (click)="createDeposit()">Create</button> <button color="primary" mat-raised-button (click)="createDeposit()">Create</button>
</v-table-actions> </v-table-actions>
</v-table> </v-table2>
</cc-page-layout> </cc-page-layout>

View File

@ -2,10 +2,9 @@ import { ChangeDetectionStrategy, Component, OnInit, Inject, DestroyRef } from '
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { NonNullableFormBuilder } from '@angular/forms'; import { NonNullableFormBuilder } from '@angular/forms';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { StatDeposit, RevertStatus } from '@vality/fistful-proto/fistful_stat'; import { fistful_stat } from '@vality/fistful-proto';
import { StatDeposit } from '@vality/fistful-proto/fistful_stat';
import { import {
Column,
createOperationColumn,
UpdateOptions, UpdateOptions,
createDateRangeToToday, createDateRangeToToday,
QueryParamsService, QueryParamsService,
@ -17,26 +16,24 @@ import {
debounceTimeWithFirst, debounceTimeWithFirst,
getValueChanges, getValueChanges,
countChanged, countChanged,
Column2,
getEnumKey,
createMenuColumn,
} from '@vality/ng-core'; } from '@vality/ng-core';
import { getUnionKey } from '@vality/ng-thrift'; import { getUnionKey } from '@vality/ng-thrift';
import { endOfDay } from 'date-fns'; import { endOfDay } from 'date-fns';
import startCase from 'lodash-es/startCase'; import startCase from 'lodash-es/startCase';
import { filter, map, shareReplay } from 'rxjs/operators'; import { filter, map, shareReplay } from 'rxjs/operators';
import { createCurrencyColumn } from '@cc/app/shared/utils/table2';
import { QueryDsl } from '../../api/fistful-stat'; import { QueryDsl } from '../../api/fistful-stat';
import { createCurrencyColumn } from '../../shared';
import { DATE_RANGE_DAYS, DEBOUNCE_TIME_MS } from '../../tokens'; import { DATE_RANGE_DAYS, DEBOUNCE_TIME_MS } from '../../tokens';
import { CreateDepositDialogComponent } from './components/create-deposit-dialog/create-deposit-dialog.component'; import { CreateDepositDialogComponent } from './components/create-deposit-dialog/create-deposit-dialog.component';
import { CreateDepositsByFileDialogComponent } from './components/create-deposits-by-file-dialog/create-deposits-by-file-dialog.component'; import { CreateDepositsByFileDialogComponent } from './components/create-deposits-by-file-dialog/create-deposits-by-file-dialog.component';
import { FetchDepositsService } from './services/fetch-deposits/fetch-deposits.service'; import { FetchDepositsService } from './services/fetch-deposits/fetch-deposits.service';
const REVERT_STATUS: { [N in RevertStatus]: string } = {
0: 'none',
1: 'partial',
2: 'full',
};
@Component({ @Component({
templateUrl: 'deposits.component.html', templateUrl: 'deposits.component.html',
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
@ -56,30 +53,31 @@ export class DepositsComponent implements OnInit {
deposits$ = this.fetchDepositsService.result$; deposits$ = this.fetchDepositsService.result$;
hasMore$ = this.fetchDepositsService.hasMore$; hasMore$ = this.fetchDepositsService.hasMore$;
isLoading$ = this.fetchDepositsService.isLoading$; isLoading$ = this.fetchDepositsService.isLoading$;
columns: Column<StatDeposit>[] = [ columns: Column2<StatDeposit>[] = [
{ {
field: 'id', field: 'id',
formatter: (d) => d.description || `#${d.id}`, cell: (d) => ({
link: (d) => `/deposits/${d.id}`, value: d.description || `#${d.id}`,
description: 'id', link: () => `/deposits/${d.id}`,
maxWidth: 'max(300px, 30vw)', description: d.id,
}),
}, },
{ {
field: 'status', field: 'status',
type: 'tag', cell: (d) => ({
formatter: (d) => getUnionKey(d.status), value: startCase(getUnionKey(d.status)),
typeParameters: { color: (
label: (d) => startCase(getUnionKey(d.status)), {
tags: { pending: 'pending',
pending: { color: 'pending' }, succeeded: 'success',
succeeded: { color: 'success' }, failed: 'warn',
failed: { color: 'warn' }, } as const
}, )[getUnionKey(d.status)],
}, }),
}, },
{ {
field: 'created_at', field: 'created_at',
type: 'datetime', cell: { type: 'datetime' },
}, },
{ {
field: 'destination_id', field: 'destination_id',
@ -87,36 +85,33 @@ export class DepositsComponent implements OnInit {
{ {
field: 'identity_id', field: 'identity_id',
}, },
createCurrencyColumn( createCurrencyColumn((d) => ({ amount: d.amount, code: d.currency_symbolic_code }), {
'amount', header: 'Amount',
(d) => d.amount, }),
(d) => d.currency_symbolic_code, createCurrencyColumn((d) => ({ amount: d.fee, code: d.currency_symbolic_code }), {
), header: 'Fee',
createCurrencyColumn( }),
'fee',
(d) => d.fee,
(d) => d.currency_symbolic_code,
{ hide: true },
),
{
field: 'source_id',
hide: true,
},
{ {
field: 'revert_status', field: 'revert_status',
type: 'tag', cell: (d) => ({
formatter: (d) => REVERT_STATUS[d.revert_status], value: startCase(getEnumKey(fistful_stat.RevertStatus, d.revert_status)),
typeParameters: { color: (
label: (d) => startCase(REVERT_STATUS[d.revert_status]), {
tags: {}, none: 'neutral',
}, partial: 'pending',
full: 'success',
} as const
)[getEnumKey(fistful_stat.RevertStatus, d.revert_status)],
}),
}, },
createOperationColumn([ createMenuColumn((d) => ({
{ items: [
label: 'Details', {
click: (d) => this.router.navigate([`/deposits/${d.id}`]), label: 'Details',
}, click: () => this.router.navigate([`/deposits/${d.id}`]),
]), },
],
})),
]; ];
depositStatuses: QueryDsl['query']['deposits']['status'][] = ['Pending', 'Succeeded', 'Failed']; depositStatuses: QueryDsl['query']['deposits']['status'][] = ['Pending', 'Succeeded', 'Failed'];
active$ = getValueChanges(this.filtersForm).pipe( active$ = getValueChanges(this.filtersForm).pipe(

View File

@ -13,7 +13,6 @@ import {
getValueChanges, getValueChanges,
Column2, Column2,
createMenuColumn, createMenuColumn,
TABLE_WRAPPER_STYLE,
} from '@vality/ng-core'; } from '@vality/ng-core';
import sortBy from 'lodash-es/sortBy'; import sortBy from 'lodash-es/sortBy';
import startCase from 'lodash-es/startCase'; import startCase from 'lodash-es/startCase';
@ -51,7 +50,6 @@ interface DomainObjectData {
ActionsModule, ActionsModule,
MatButtonModule, MatButtonModule,
], ],
host: { style: TABLE_WRAPPER_STYLE },
}) })
export class DomainObjectsTableComponent implements OnInit { export class DomainObjectsTableComponent implements OnInit {
@Output() selectedChange = new EventEmitter<string[]>(); @Output() selectedChange = new EventEmitter<string[]>();

View File

@ -0,0 +1,36 @@
import { Component, input } from '@angular/core';
import { Column2, getEnumKey, TableModule } from '@vality/ng-core';
import { repairer } from '@vality/repairer-proto';
import { StatusHistory } from '@vality/repairer-proto/repairer';
import { startCase } from 'lodash-es';
import { SidenavInfoModule } from '@cc/app/shared/components/sidenav-info';
@Component({
standalone: true,
template: `<cc-card title="Machine #{{ id() }} Status History"
><v-table2 [columns]="columns" [data]="history()"></v-table2
></cc-card>`,
imports: [TableModule, SidenavInfoModule],
})
export class MachineStatusHistoryCardComponent {
history = input<StatusHistory[]>([]);
id = input<string>('');
columns: Column2<StatusHistory>[] = [
{ field: 'changed_at', cell: { type: 'datetime' } },
{
field: 'status',
cell: (d) => ({
value: startCase(getEnumKey(repairer.RepairStatus, d.status)),
color: (
{
failed: 'warn',
in_progress: 'pending',
repaired: 'success',
} as const
)[getEnumKey(repairer.RepairStatus, d.status)],
}),
},
];
}

View File

@ -3,7 +3,7 @@ import { RouterModule } from '@angular/router';
import { AppAuthGuardService } from '../../shared/services'; import { AppAuthGuardService } from '../../shared/services';
import { RepairingComponent } from './repairing.component'; import { MachinesComponent } from './machines.component';
import { ROUTING_CONFIG } from './routing-config'; import { ROUTING_CONFIG } from './routing-config';
@NgModule({ @NgModule({
@ -11,7 +11,7 @@ import { ROUTING_CONFIG } from './routing-config';
RouterModule.forChild([ RouterModule.forChild([
{ {
path: '', path: '',
component: RepairingComponent, component: MachinesComponent,
canActivate: [AppAuthGuardService], canActivate: [AppAuthGuardService],
data: ROUTING_CONFIG, data: ROUTING_CONFIG,
}, },
@ -19,4 +19,4 @@ import { ROUTING_CONFIG } from './routing-config';
], ],
exports: [RouterModule], exports: [RouterModule],
}) })
export class RepairingRoutingModule {} export class MachinesRoutingModule {}

View File

@ -1,4 +1,4 @@
<cc-page-layout title="Machines"> <cc-page-layout fullHeight title="Machines">
<cc-page-layout-actions> <cc-page-layout-actions>
<v-more-filters-button [filters]="filters"></v-more-filters-button> <v-more-filters-button [filters]="filters"></v-more-filters-button>
</cc-page-layout-actions> </cc-page-layout-actions>
@ -33,7 +33,7 @@
</ng-template> </ng-template>
</v-filters> </v-filters>
<v-table <v-table2
[columns]="columns" [columns]="columns"
[data]="machines$ | async" [data]="machines$ | async"
[hasMore]="hasMore$ | async" [hasMore]="hasMore$ | async"
@ -61,5 +61,5 @@
Simple repair Simple repair
</button> </button>
</v-table-actions> </v-table-actions>
</v-table> </v-table2>
</cc-page-layout> </cc-page-layout>

View File

@ -5,7 +5,6 @@ import {
DialogResponseStatus, DialogResponseStatus,
DialogService, DialogService,
clean, clean,
Column,
ConfirmDialogComponent, ConfirmDialogComponent,
QueryParamsService, QueryParamsService,
NotifyLogService, NotifyLogService,
@ -18,6 +17,7 @@ import {
debounceTimeWithFirst, debounceTimeWithFirst,
FetchOptions, FetchOptions,
getEnumKey, getEnumKey,
Column2,
} from '@vality/ng-core'; } from '@vality/ng-core';
import { repairer } from '@vality/repairer-proto'; import { repairer } from '@vality/repairer-proto';
import { Namespace, ProviderID, RepairStatus, Machine } from '@vality/repairer-proto/repairer'; import { Namespace, ProviderID, RepairStatus, Machine } from '@vality/repairer-proto/repairer';
@ -27,10 +27,13 @@ import startCase from 'lodash-es/startCase';
import { BehaviorSubject } from 'rxjs'; import { BehaviorSubject } from 'rxjs';
import { filter, switchMap, map, shareReplay } from 'rxjs/operators'; import { filter, switchMap, map, shareReplay } from 'rxjs/operators';
import { SidenavInfoService } from '@cc/app/shared/components/sidenav-info';
import { createDomainObjectColumn } from '@cc/app/shared/utils/table2';
import { RepairManagementService } from '../../api/repairer'; import { RepairManagementService } from '../../api/repairer';
import { createProviderColumn } from '../../shared/utils/table/create-provider-column';
import { DATE_RANGE_DAYS, DEBOUNCE_TIME_MS } from '../../tokens'; import { DATE_RANGE_DAYS, DEBOUNCE_TIME_MS } from '../../tokens';
import { MachineStatusHistoryCardComponent } from './components/machine-status-history-card.component';
import { RepairByScenarioDialogComponent } from './components/repair-by-scenario-dialog/repair-by-scenario-dialog.component'; import { RepairByScenarioDialogComponent } from './components/repair-by-scenario-dialog/repair-by-scenario-dialog.component';
import { MachinesService } from './services/machines.service'; import { MachinesService } from './services/machines.service';
@ -44,11 +47,11 @@ interface Filters {
} }
@Component({ @Component({
selector: 'cc-repairing', selector: 'cc-machines',
templateUrl: './repairing.component.html', templateUrl: './machines.component.html',
providers: [MachinesService], providers: [MachinesService],
}) })
export class RepairingComponent implements OnInit { export class MachinesComponent implements OnInit {
machines$ = this.machinesService.result$; machines$ = this.machinesService.result$;
inProgress$ = this.machinesService.isLoading$; inProgress$ = this.machinesService.isLoading$;
hasMore$ = this.machinesService.hasMore$; hasMore$ = this.machinesService.hasMore$;
@ -62,28 +65,36 @@ export class RepairingComponent implements OnInit {
}); });
selected$ = new BehaviorSubject<Machine[]>([]); selected$ = new BehaviorSubject<Machine[]>([]);
status = repairer.RepairStatus; status = repairer.RepairStatus;
columns: Column<Machine>[] = [ columns: Column2<Machine>[] = [
{ field: 'id' }, { field: 'id', sticky: 'start' },
{ header: 'Namespace', field: 'ns' }, { header: 'Namespace', field: 'ns' },
{ field: 'created_at', type: 'datetime' }, { field: 'created_at', cell: { type: 'datetime' } },
createProviderColumn((d) => Number(d.provider_id)), createDomainObjectColumn((d) => ({ ref: { terminal: { id: Number(d.provider_id) } } }), {
header: 'Terminal',
}),
{ {
field: 'status', field: 'status',
formatter: (d) => getEnumKey(repairer.RepairStatus, d.status), cell: (d) => ({
type: 'tag', value: startCase(getEnumKey(repairer.RepairStatus, d.status)),
typeParameters: { color: (
label: (d) => startCase(getEnumKey(repairer.RepairStatus, d.status)), {
tags: { failed: 'warn',
failed: { color: 'warn' }, in_progress: 'pending',
in_progress: { color: 'pending' }, repaired: 'success',
repaired: { color: 'success' }, } as const
}, )[getEnumKey(repairer.RepairStatus, d.status)],
}, }),
}, },
{ {
field: 'history', field: 'history',
formatter: (data) => (data.history?.length ? String(data.history.length) : ''), cell: (d) => ({
tooltip: 'history', value: d.history?.length ? String(d.history.length) : '',
click: () => {
this.sidenavInfoService.toggle(MachineStatusHistoryCardComponent, {
history: d.history,
});
},
}),
}, },
{ {
field: 'error_message', field: 'error_message',
@ -106,6 +117,7 @@ export class RepairingComponent implements OnInit {
private destroyRef: DestroyRef, private destroyRef: DestroyRef,
@Inject(DATE_RANGE_DAYS) private dateRangeDays: number, @Inject(DATE_RANGE_DAYS) private dateRangeDays: number,
@Inject(DEBOUNCE_TIME_MS) private debounceTimeMs: number, @Inject(DEBOUNCE_TIME_MS) private debounceTimeMs: number,
private sidenavInfoService: SidenavInfoService,
) {} ) {}
ngOnInit() { ngOnInit() {

View File

@ -28,13 +28,13 @@ import { ThriftFormModule } from '@cc/app/shared/components/metadata-form';
import { DomainObjectFieldComponent } from '@cc/app/shared/components/thrift-api-crud'; import { DomainObjectFieldComponent } from '@cc/app/shared/components/thrift-api-crud';
import { RepairByScenarioDialogComponent } from './components/repair-by-scenario-dialog/repair-by-scenario-dialog.component'; import { RepairByScenarioDialogComponent } from './components/repair-by-scenario-dialog/repair-by-scenario-dialog.component';
import { RepairingRoutingModule } from './repairing-routing.module'; import { MachinesRoutingModule } from './machines-routing.module';
import { RepairingComponent } from './repairing.component'; import { MachinesComponent } from './machines.component';
@NgModule({ @NgModule({
imports: [ imports: [
CommonModule, CommonModule,
RepairingRoutingModule, MachinesRoutingModule,
TableModule, TableModule,
MatCardModule, MatCardModule,
ReactiveFormsModule, ReactiveFormsModule,
@ -59,6 +59,6 @@ import { RepairingComponent } from './repairing.component';
ThriftPipesModule, ThriftPipesModule,
FiltersModule, FiltersModule,
], ],
declarations: [RepairingComponent, RepairByScenarioDialogComponent], declarations: [MachinesComponent, RepairByScenarioDialogComponent],
}) })
export class RepairingModule {} export class MachinesModule {}

View File

@ -1,4 +1,4 @@
<cc-page-layout title="Shops"> <cc-page-layout fullHeight title="Shops">
<cc-shops-table <cc-shops-table
[progress]="progress$ | async" [progress]="progress$ | async"
[shops]="shopsParty$ | async" [shops]="shopsParty$ | async"

View File

@ -1,3 +1,3 @@
<cc-sub-page-layout [links]="links" [tags]="tags$ | async" <cc-sub-page-layout [links]="links">
><router-outlet></router-outlet <router-outlet></router-outlet>
></cc-sub-page-layout> </cc-sub-page-layout>

View File

@ -1,10 +1,12 @@
<cc-page-layout [progress]="isLoading$ | async" title="Chargebacks"> <cc-page-layout [progress]="isLoading$ | async" fullHeight title="Chargebacks">
<cc-page-layout-actions> <cc-chargebacks-table
[data]="chargebacks$ | async"
[hasMore]="fetchChargebacksService.hasMore$ | async"
[isLoading]="isLoading$ | async"
onePayment
(more)="fetchChargebacksService.more()"
(update)="update()"
>
<button color="primary" mat-raised-button (click)="createChargeback()">Create</button> <button color="primary" mat-raised-button (click)="createChargeback()">Create</button>
</cc-page-layout-actions> </cc-chargebacks-table>
<cc-chargebacks
[chargebacks]="chargebacks$ | async"
[invoiceId]="(payment$ | async).invoice_id"
[paymentId]="(payment$ | async).id"
></cc-chargebacks>
</cc-page-layout> </cc-page-layout>

View File

@ -1,49 +1,42 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { Component, DestroyRef } from '@angular/core'; import { Component, DestroyRef, OnInit } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { MatButton } from '@angular/material/button'; import { MatButton } from '@angular/material/button';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { DialogResponseStatus, DialogService } from '@vality/ng-core'; import { DialogResponseStatus, DialogService } from '@vality/ng-core';
import { merge, defer, Subject } from 'rxjs'; import { first } from 'rxjs';
import { map, switchMap, shareReplay } from 'rxjs/operators';
import { FetchChargebacksService } from '@cc/app/sections/chargebacks/fetch-chargebacks.service';
import { ChargebacksTableComponent } from '@cc/app/shared/components/chargebacks-table';
import { InvoicingService } from '../../../../api/payment-processing';
import { PageLayoutModule } from '../../../../shared'; import { PageLayoutModule } from '../../../../shared';
import { ChargebacksComponent } from '../../../../shared/components/chargebacks/chargebacks.component';
import { CreateChargebackDialogComponent } from '../../create-chargeback-dialog/create-chargeback-dialog.component'; import { CreateChargebackDialogComponent } from '../../create-chargeback-dialog/create-chargeback-dialog.component';
import { PaymentDetailsService } from '../../payment-details.service'; import { PaymentDetailsService } from '../../payment-details.service';
@Component({ @Component({
selector: 'cc-payment-chargebacks', selector: 'cc-payment-chargebacks',
standalone: true, standalone: true,
imports: [CommonModule, PageLayoutModule, MatButton, ChargebacksComponent], imports: [CommonModule, PageLayoutModule, MatButton, ChargebacksTableComponent],
templateUrl: './payment-chargebacks.component.html', templateUrl: './payment-chargebacks.component.html',
styles: ``, providers: [FetchChargebacksService],
}) })
export class PaymentChargebacksComponent { export class PaymentChargebacksComponent implements OnInit {
payment$ = this.paymentDetailsService.payment$; payment$ = this.paymentDetailsService.payment$;
isLoading$ = this.paymentDetailsService.isLoading$; isLoading$ = this.paymentDetailsService.isLoading$;
chargebacks$ = merge( chargebacks$ = this.fetchChargebacksService.result$;
this.route.params,
defer(() => this.updateChargebacks$),
).pipe(
map(() => this.route.snapshot.params as Record<'invoiceID' | 'paymentID', string>),
switchMap(({ invoiceID, paymentID }) =>
this.invoicingService.GetPayment(invoiceID, paymentID),
),
map(({ chargebacks }) => chargebacks),
shareReplay({ refCount: true, bufferSize: 1 }),
);
private updateChargebacks$ = new Subject<void>();
constructor( constructor(
private route: ActivatedRoute, private route: ActivatedRoute,
private invoicingService: InvoicingService,
private dialogService: DialogService, private dialogService: DialogService,
private dr: DestroyRef, private dr: DestroyRef,
private paymentDetailsService: PaymentDetailsService, private paymentDetailsService: PaymentDetailsService,
protected fetchChargebacksService: FetchChargebacksService,
) {} ) {}
ngOnInit() {
this.update();
}
createChargeback() { createChargeback() {
this.dialogService this.dialogService
.open( .open(
@ -54,8 +47,21 @@ export class PaymentChargebacksComponent {
.pipe(takeUntilDestroyed(this.dr)) .pipe(takeUntilDestroyed(this.dr))
.subscribe(({ status }) => { .subscribe(({ status }) => {
if (status === DialogResponseStatus.Success) { if (status === DialogResponseStatus.Success) {
this.updateChargebacks$.next(); this.update();
} }
}); });
} }
update() {
this.payment$.pipe(first(), takeUntilDestroyed(this.dr)).subscribe((p) => {
this.fetchChargebacksService.load({
common_search_query_params: {
from_time: new Date(0).toISOString(),
to_time: new Date().toISOString(),
},
payment_id: p.id,
invoice_ids: [p.invoice_id],
});
});
}
} }

View File

@ -3,8 +3,8 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormControl } from '@angular/forms'; import { FormControl } from '@angular/forms';
import { InvoicePaymentChargeback } from '@vality/domain-proto/domain'; import { InvoicePaymentChargeback } from '@vality/domain-proto/domain';
import { InvoicePaymentChargebackParams } from '@vality/domain-proto/payment_processing'; import { InvoicePaymentChargebackParams } from '@vality/domain-proto/payment_processing';
import { DialogSuperclass, NotifyLogService } from '@vality/ng-core'; import { DialogSuperclass, getImportValue, NotifyLogService } from '@vality/ng-core';
import { from } from 'rxjs'; import { ThriftAstMetadata } from '@vality/ng-thrift';
import short from 'short-uuid'; import short from 'short-uuid';
import { InvoicingService } from '@cc/app/api/payment-processing'; import { InvoicingService } from '@cc/app/api/payment-processing';
@ -20,7 +20,7 @@ export class CreateChargebackDialogComponent extends DialogSuperclass<
InvoicePaymentChargeback InvoicePaymentChargeback
> { > {
form = new FormControl<Partial<InvoicePaymentChargebackParams>>({ id: short().generate() }); form = new FormControl<Partial<InvoicePaymentChargebackParams>>({ id: short().generate() });
metadata$ = from(import('@vality/domain-proto/metadata.json').then((m) => m.default)); metadata$ = getImportValue<ThriftAstMetadata[]>(import('@vality/domain-proto/metadata.json'));
extensions$ = this.domainMetadataFormExtensionsService.extensions$; extensions$ = this.domainMetadataFormExtensionsService.extensions$;
constructor( constructor(

View File

@ -5,7 +5,6 @@
{ label: 'Refunds', url: 'refunds' }, { label: 'Refunds', url: 'refunds' },
{ label: 'Chargebacks', url: 'chargebacks' } { label: 'Chargebacks', url: 'chargebacks' }
]" ]"
[tags]="tags$ | async"
id="{{ id="{{
(payment$ | async) ? (payment$ | async)?.invoice_id + '.' + (payment$ | async)?.id : '' (payment$ | async) ? (payment$ | async)?.invoice_id + '.' + (payment$ | async)?.id : ''
}}" }}"

View File

@ -12,7 +12,6 @@ import { StatusModule, PageLayoutModule, SubPageLayoutComponent } from '@cc/app/
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 { ChargebacksComponent } from '../../shared/components/chargebacks/chargebacks.component';
import { JsonViewerModule } from '../../shared/components/json-viewer'; import { JsonViewerModule } from '../../shared/components/json-viewer';
import { ThriftFormModule } from '../../shared/components/metadata-form'; import { ThriftFormModule } from '../../shared/components/metadata-form';
import { MagistaThriftViewerComponent } from '../../shared/components/thrift-api-crud'; import { MagistaThriftViewerComponent } from '../../shared/components/thrift-api-crud';
@ -34,7 +33,6 @@ import { RefundsTableModule } from './refunds-table';
MatProgressSpinnerModule, MatProgressSpinnerModule,
MatButtonModule, MatButtonModule,
MatDialogModule, MatDialogModule,
ChargebacksComponent,
ActionsModule, ActionsModule,
DialogModule, DialogModule,
ThriftFormModule, ThriftFormModule,

View File

@ -1,7 +1,7 @@
import { Component, Input, Output, EventEmitter } from '@angular/core'; import { Component, Input, Output, EventEmitter } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { StatPayment } from '@vality/magista-proto/magista'; import { StatPayment } from '@vality/magista-proto/magista';
import { LoadOptions, Column2, createMenuColumn, TABLE_WRAPPER_STYLE } from '@vality/ng-core'; import { LoadOptions, Column2, createMenuColumn } from '@vality/ng-core';
import { getUnionKey } from '@vality/ng-thrift'; import { getUnionKey } from '@vality/ng-thrift';
import startCase from 'lodash-es/startCase'; import startCase from 'lodash-es/startCase';
@ -18,7 +18,6 @@ import {
@Component({ @Component({
selector: 'cc-payments-table', selector: 'cc-payments-table',
templateUrl: './payments-table.component.html', templateUrl: './payments-table.component.html',
host: { style: TABLE_WRAPPER_STYLE },
}) })
export class PaymentsTableComponent { export class PaymentsTableComponent {
@Input() data!: StatPayment[]; @Input() data!: StatPayment[];

View File

@ -34,7 +34,7 @@ const ROUTES: Routes = [
}, },
{ {
path: 'machines', path: 'machines',
loadChildren: () => import('./repairing/repairing.module').then((m) => m.RepairingModule), loadChildren: () => import('./machines/machines.module').then((m) => m.MachinesModule),
}, },
{ {
path: 'payments', path: 'payments',

View File

@ -1,9 +1,7 @@
<cc-page-layout title="Shops"> <cc-page-layout fullHeight title="Shops">
<cc-page-layout-actions></cc-page-layout-actions>
<cc-shops-table <cc-shops-table
[progress]="progress$ | async" [progress]="progress$ | async"
[shops]="shopsParty$ | async" [shops]="shopsParty$ | async"
noSort
(filterChange)="filterChange$.next($event)" (filterChange)="filterChange$.next($event)"
(update)="update()" (update)="update()"
></cc-shops-table> ></cc-shops-table>

View File

@ -2,7 +2,14 @@ import { Component } from '@angular/core';
import { SearchShopHit } from '@vality/deanonimus-proto/deanonimus'; import { SearchShopHit } from '@vality/deanonimus-proto/deanonimus';
import { Column, progressTo, NotifyLogService } from '@vality/ng-core'; import { Column, progressTo, NotifyLogService } from '@vality/ng-core';
import { BehaviorSubject, defer, of, combineLatest, Subject, Observable } from 'rxjs'; import { BehaviorSubject, defer, of, combineLatest, Subject, Observable } from 'rxjs';
import { switchMap, shareReplay, catchError, map } from 'rxjs/operators'; import {
switchMap,
shareReplay,
catchError,
map,
debounceTime,
distinctUntilChanged,
} from 'rxjs/operators';
import { DeanonimusService } from '../../api/deanonimus'; import { DeanonimusService } from '../../api/deanonimus';
import { ShopParty } from '../../shared/components/shops-table'; import { ShopParty } from '../../shared/components/shops-table';
@ -14,7 +21,7 @@ import { ShopParty } from '../../shared/components/shops-table';
export class ShopsComponent { export class ShopsComponent {
filterChange$ = new Subject<string>(); filterChange$ = new Subject<string>();
shopsParty$: Observable<ShopParty[]> = combineLatest([ shopsParty$: Observable<ShopParty[]> = combineLatest([
this.filterChange$, this.filterChange$.pipe(distinctUntilChanged(), debounceTime(500)),
defer(() => this.updateShops$), defer(() => this.updateShops$),
]).pipe( ]).pipe(
switchMap(([search]) => switchMap(([search]) =>

View File

@ -1,13 +1,12 @@
<cc-page-layout title="Sources"> <cc-page-layout fullHeight title="Sources">
<cc-page-layout-actions> <cc-page-layout-actions>
<button color="primary" mat-raised-button (click)="create()">Create</button> <button color="primary" mat-raised-button (click)="create()">Create</button>
</cc-page-layout-actions> </cc-page-layout-actions>
<v-table <v-table2
[(sort)]="sort"
[columns]="columns" [columns]="columns"
[data]="sources$ | async" [data]="sources$ | async"
[progress]="!!(progress$ | async)" [progress]="!!(progress$ | async)"
[sort]="{ direction: 'asc', active: 'name' }" standaloneFilter
noActions ></v-table2>
sortOnFront
></v-table>
</cc-page-layout> </cc-page-layout>

View File

@ -1,6 +1,7 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { Sort } from '@angular/material/sort';
import { Source } from '@vality/fistful-proto/internal/source'; import { Source } from '@vality/fistful-proto/internal/source';
import { DialogService, Column } from '@vality/ng-core'; import { DialogService, Column2 } from '@vality/ng-core';
import { CreateSourceComponent } from './create-source/create-source.component'; import { CreateSourceComponent } from './create-source/create-source.component';
import { FetchSourcesService } from './fetch-sources.service'; import { FetchSourcesService } from './fetch-sources.service';
@ -11,13 +12,14 @@ import { FetchSourcesService } from './fetch-sources.service';
export class SourcesComponent { export class SourcesComponent {
sources$ = this.fetchSourcesService.sources$; sources$ = this.fetchSourcesService.sources$;
progress$ = this.fetchSourcesService.progress$; progress$ = this.fetchSourcesService.progress$;
columns: Column<Source>[] = [ columns: Column2<Source>[] = [
{ field: 'id' }, { field: 'id' },
{ field: 'name', sortable: true }, { field: 'name' },
'identity', { field: 'identity' },
{ field: 'currency_symbolic_code', sortable: true }, { field: 'currency_symbolic_code' },
{ field: 'created_at', type: 'datetime' }, { field: 'created_at', cell: { type: 'datetime' } },
]; ];
sort: Sort = { direction: 'asc', active: 'name' };
constructor( constructor(
private fetchSourcesService: FetchSourcesService, private fetchSourcesService: FetchSourcesService,

View File

@ -1,53 +1,53 @@
<cc-page-layout title="Wallets"> <cc-page-layout fullHeight title="Wallets">
<cc-page-layout-actions> <cc-page-layout-actions>
<v-more-filters-button *ngIf="filters" [filters]="filters"></v-more-filters-button> <v-more-filters-button *ngIf="filters" [filters]="filters"></v-more-filters-button>
<v-switch-button @if (!(party$ | async)) {
[formControl]="isFilterControl" <v-switch-button
[states]="[ [formControl]="isFilterControl"
{ label: 'full-text search', icon: 'travel_explore' }, [states]="[
{ label: 'filters', icon: 'manage_search' } { label: 'full-text search', icon: 'travel_explore' },
]" { label: 'filters', icon: 'manage_search' }
></v-switch-button> ]"
></v-switch-button>
}
</cc-page-layout-actions> </cc-page-layout-actions>
<v-filters @if (isFilterTable$ | async) {
*ngIf="isFilterControl.value" <v-filters [active]="active$ | async" merge (clear)="filtersForm.reset()">
[active]="active$ | async" <ng-template [formGroup]="filtersForm">
merge <cc-merchant-field
(clear)="filtersForm.reset()" *ngIf="!(party$ | async)"
> formControlName="party_id"
<ng-template [formGroup]="filtersForm"> ></cc-merchant-field>
<cc-merchant-field <v-list-field formControlName="wallet_id" label="Wallet IDs"></v-list-field>
*ngIf="!(party$ | async)" <mat-form-field>
formControlName="party_id" <mat-label>Identity ID</mat-label>
></cc-merchant-field> <input formControlName="identity_id" matInput />
<v-list-field formControlName="wallet_id" label="Wallet IDs"></v-list-field> </mat-form-field>
<mat-form-field> <cc-currency-field formControlName="currency_code"></cc-currency-field>
<mat-label>Identity ID</mat-label> </ng-template>
<input formControlName="identity_id" matInput /> </v-filters>
</mat-form-field> }
<cc-currency-field formControlName="currency_code"></cc-currency-field>
</ng-template>
</v-filters>
<v-table @if (isFilterTable$ | async) {
*ngIf="isFilterControl.value" <v-table2
[columns]="filterColumns$ | async" [columns]="filterColumns"
[data]="filterWallets$ | async" [data]="filterWallets$ | async"
[hasMore]="filterHasMore$ | async" [hasMore]="filterHasMore$ | async"
[progress]="filtersLoading$ | async" [progress]="filtersLoading$ | async"
name="filterWallets" name="filterWallets"
(more)="filterMore()" (more)="filterMore()"
(update)="filterSearch($event)" (update)="filterSearch($event)"
></v-table> ></v-table2>
<v-table } @else {
*ngIf="!isFilterControl.value" <v-table2
[columns]="fullTextSearchColumns" [columns]="fullTextSearchColumns"
[data]="fullTextSearchWallets$ | async" [data]="fullTextSearchWallets$ | async"
[progress]="fullTextSearchLoading$ | async" [progress]="fullTextSearchLoading$ | async"
externalFilter externalFilter
name="ftsWallets" name="ftsWallets"
noActions standaloneFilter
(filterChange)="fullTextSearch($event)" (filterChange)="fullTextSearch($event)"
(update)="fullTextSearchReload()" (update)="fullTextSearchReload()"
></v-table> ></v-table2>
}
</cc-page-layout> </cc-page-layout>

View File

@ -1,12 +1,4 @@
import { import { Component, OnInit, Inject, ViewChild, DestroyRef } from '@angular/core';
Component,
OnInit,
Inject,
ViewChild,
DestroyRef,
Injector,
runInInjectionContext,
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormControl, NonNullableFormBuilder } from '@angular/forms'; import { FormControl, NonNullableFormBuilder } from '@angular/forms';
import { SearchWalletHit } from '@vality/deanonimus-proto/internal/deanonimus'; import { SearchWalletHit } from '@vality/deanonimus-proto/internal/deanonimus';
@ -15,17 +7,17 @@ import { AccountBalance } from '@vality/fistful-proto/internal/account';
import { StatWallet } from '@vality/fistful-proto/internal/fistful_stat'; import { StatWallet } from '@vality/fistful-proto/internal/fistful_stat';
import { import {
clean, clean,
Column,
QueryParamsService, QueryParamsService,
NotifyLogService,
FiltersComponent, FiltersComponent,
UpdateOptions, UpdateOptions,
getValueChanges, getValueChanges,
countChanged, countChanged,
debounceTimeWithFirst, debounceTimeWithFirst,
Column2,
DebounceTime,
} from '@vality/ng-core'; } from '@vality/ng-core';
import isNil from 'lodash-es/isNil'; import isNil from 'lodash-es/isNil';
import { of } from 'rxjs'; import { combineLatest, of } from 'rxjs';
import { map, shareReplay, catchError, take } from 'rxjs/operators'; import { map, shareReplay, catchError, take } from 'rxjs/operators';
import { MemoizeExpiring } from 'typescript-memoize'; import { MemoizeExpiring } from 'typescript-memoize';
@ -33,7 +25,7 @@ import { WalletParams } from '@cc/app/api/fistful-stat/query-dsl/types/wallet';
import { ManagementService } from '@cc/app/api/wallet'; import { ManagementService } from '@cc/app/api/wallet';
import { IdentityManagementService } from '../../api/identity'; import { IdentityManagementService } from '../../api/identity';
import { createCurrencyColumn, createPartyColumn } from '../../shared'; import { createCurrencyColumn, createPartyColumn } from '../../shared/utils/table2';
import { DEBOUNCE_TIME_MS } from '../../tokens'; import { DEBOUNCE_TIME_MS } from '../../tokens';
import { PartyStoreService } from '../party'; import { PartyStoreService } from '../party';
@ -55,81 +47,81 @@ export class WalletsComponent implements OnInit {
fullTextSearchWallets$ = this.fetchWalletsTextService.result$; fullTextSearchWallets$ = this.fetchWalletsTextService.result$;
fullTextSearchLoading$ = this.fetchWalletsTextService.isLoading$; fullTextSearchLoading$ = this.fetchWalletsTextService.isLoading$;
filterColumns$ = this.partyStoreService.party$.pipe( filterColumns: Column2<StatWallet>[] = [
map((party) => { field: 'id' },
runInInjectionContext(this.injector, () => [ { field: 'name' },
{ field: 'id' }, { field: 'currency_symbolic_code' },
{ field: 'name' }, { field: 'identity_id' },
'currency_symbolic_code', { field: 'created_at', cell: { type: 'datetime' } },
'identity_id', createCurrencyColumn(
{ field: 'created_at', type: 'datetime' }, (d) =>
createCurrencyColumn<StatWallet>( this.getBalance(d.id).pipe(
'balance', map((b) => ({ amount: b.current, code: b.currency.symbolic_code })),
(d) => this.getBalance(d.id).pipe(map((b) => b.current)),
(d) => this.getBalance(d.id).pipe(map((b) => b.currency.symbolic_code)),
{ lazy: true },
), ),
createCurrencyColumn<StatWallet>( { header: 'Balance', isLazyCell: true },
'hold',
(d) => this.getBalance(d.id).pipe(map((b) => b.current - b.expected_min)),
(d) => this.getBalance(d.id).pipe(map((b) => b.currency.symbolic_code)),
{ lazy: true },
),
createCurrencyColumn<StatWallet>(
'expected_min',
(d) => this.getBalance(d.id).pipe(map((b) => b.expected_min)),
(d) => this.getBalance(d.id).pipe(map((b) => b.currency.symbolic_code)),
{ lazy: true },
),
{
field: 'contract_id',
formatter: (d) =>
this.getIdentity(d.identity_id).pipe(
map((identity) => identity.contract_id),
),
lazy: true,
},
...(party
? []
: [
createPartyColumn<StatWallet>(
'party',
(d) =>
this.getIdentity(d.identity_id).pipe(
map((identity) => identity.party_id),
),
undefined,
{ lazy: true },
),
]),
]),
), ),
); createCurrencyColumn(
fullTextSearchColumns: Column<SearchWalletHit>[] = [ (d) =>
this.getBalance(d.id).pipe(
map((b) => ({
amount: b.current - b.expected_min,
code: b.currency.symbolic_code,
})),
),
{ header: 'Hold', isLazyCell: true },
),
createCurrencyColumn(
(d) =>
this.getBalance(d.id).pipe(
map((b) => ({ amount: b.expected_min, code: b.currency.symbolic_code })),
),
{ header: 'Expected Min', isLazyCell: true },
),
{
field: 'contract_id',
lazyCell: (d) =>
this.getIdentity(d.identity_id).pipe(
map((identity) => ({ value: identity.contract_id })),
),
},
createPartyColumn(
(d) =>
this.getIdentity(d.identity_id).pipe(
map((identity) => ({ id: identity.party_id })),
),
{ hidden: this.partyStoreService.party$.pipe(map((p) => !p)) },
),
];
fullTextSearchColumns: Column2<SearchWalletHit>[] = [
{ field: 'wallet.id' }, { field: 'wallet.id' },
{ field: 'wallet.name' }, { field: 'wallet.name' },
createPartyColumn<SearchWalletHit>( createPartyColumn((d) => ({
'party', id: d.party.id,
(d) => d.party.id, partyName: d.party.email,
(d) => d.party.email, })),
createCurrencyColumn(
(d) =>
this.getBalance(d.wallet.id).pipe(
map((b) => ({ amount: b.current, code: b.currency.symbolic_code })),
),
{ header: 'Balance', isLazyCell: true },
), ),
createCurrencyColumn<SearchWalletHit>( createCurrencyColumn(
'balance', (d) =>
(d) => this.getBalance(d.wallet.id).pipe(map((b) => b.current)), this.getBalance(d.wallet.id).pipe(
(d) => this.getBalance(d.wallet.id).pipe(map((b) => b.currency.symbolic_code)), map((b) => ({
{ lazy: true }, amount: b.current - b.expected_min,
code: b.currency.symbolic_code,
})),
),
{ header: 'Hold', isLazyCell: true },
), ),
createCurrencyColumn<SearchWalletHit>( createCurrencyColumn(
'hold', (d) =>
(d) => this.getBalance(d.wallet.id).pipe(map((b) => b.current - b.expected_min)), this.getBalance(d.wallet.id).pipe(
(d) => this.getBalance(d.wallet.id).pipe(map((b) => b.currency.symbolic_code)), map((b) => ({ amount: b.expected_min, code: b.currency.symbolic_code })),
{ lazy: true }, ),
), { header: 'Expected Min', isLazyCell: true },
createCurrencyColumn<SearchWalletHit>(
'expected_min',
(d) => this.getBalance(d.wallet.id).pipe(map((b) => b.expected_min)),
(d) => this.getBalance(d.wallet.id).pipe(map((b) => b.currency.symbolic_code)),
{ lazy: true },
), ),
]; ];
filtersForm = this.fb.group({ filtersForm = this.fb.group({
@ -145,6 +137,10 @@ export class WalletsComponent implements OnInit {
@ViewChild(FiltersComponent) filters!: FiltersComponent; @ViewChild(FiltersComponent) filters!: FiltersComponent;
typeQp = this.qp.createNamespace<{ isFilter: boolean }>('type'); typeQp = this.qp.createNamespace<{ isFilter: boolean }>('type');
party$ = this.partyStoreService.party$; party$ = this.partyStoreService.party$;
isFilterTable$ = combineLatest([getValueChanges(this.isFilterControl), this.party$]).pipe(
map(([isFilterControl, party]) => isFilterControl || !!party),
shareReplay({ refCount: true, bufferSize: 1 }),
);
private initFilters = this.filtersForm.value; private initFilters = this.filtersForm.value;
@ -154,12 +150,10 @@ export class WalletsComponent implements OnInit {
private qp: QueryParamsService<WalletParams>, private qp: QueryParamsService<WalletParams>,
private fb: NonNullableFormBuilder, private fb: NonNullableFormBuilder,
private walletManagementService: ManagementService, private walletManagementService: ManagementService,
private log: NotifyLogService,
@Inject(DEBOUNCE_TIME_MS) private debounceTimeMs: number, @Inject(DEBOUNCE_TIME_MS) private debounceTimeMs: number,
private destroyRef: DestroyRef, private destroyRef: DestroyRef,
private identityManagementService: IdentityManagementService, private identityManagementService: IdentityManagementService,
private partyStoreService: PartyStoreService, private partyStoreService: PartyStoreService,
private injector: Injector,
) {} ) {}
ngOnInit() { ngOnInit() {
@ -198,6 +192,7 @@ export class WalletsComponent implements OnInit {
this.fetchWalletsService.more(); this.fetchWalletsService.more();
} }
@DebounceTime()
fullTextSearch(text: string) { fullTextSearch(text: string) {
this.fetchWalletsTextService.load(text); this.fetchWalletsTextService.load(text);
} }

View File

@ -13,8 +13,10 @@ import {
NotifyLogService, NotifyLogService,
EnumKeysPipe, EnumKeysPipe,
EnumKeyPipe, EnumKeyPipe,
getImportValue,
} from '@vality/ng-core'; } from '@vality/ng-core';
import { from, BehaviorSubject } from 'rxjs'; import { ThriftAstMetadata } from '@vality/ng-thrift';
import { BehaviorSubject } from 'rxjs';
import { InvoicingService } from '@cc/app/api/payment-processing'; import { InvoicingService } from '@cc/app/api/payment-processing';
import { DomainMetadataFormExtensionsService } from '@cc/app/shared/services'; import { DomainMetadataFormExtensionsService } from '@cc/app/shared/services';
@ -57,7 +59,7 @@ export class ChangeChargebacksStatusDialogComponent
> >
implements OnInit implements OnInit
{ {
metadata$ = from(import('@vality/domain-proto/metadata.json').then((m) => m.default)); metadata$ = getImportValue<ThriftAstMetadata[]>(import('@vality/domain-proto/metadata.json'));
extensions$ = this.domainMetadataFormExtensionsService.extensions$; extensions$ = this.domainMetadataFormExtensionsService.extensions$;
control = new FormControl(); control = new FormControl();
actionControl = new FormControl<Action>(null, Validators.required); actionControl = new FormControl<Action>(null, Validators.required);

View File

@ -1,15 +1,17 @@
<v-table <v-table2
[(rowSelected)]="selected"
[columns]="columns" [columns]="columns"
[data]="data" [data]="data"
[hasMore]="hasMore" [hasMore]="hasMore"
[progress]="isLoading" [progress]="isLoading"
[rowSelected]="selected"
rowSelectable rowSelectable
(more)="more.emit()" (more)="more.emit()"
(rowSelectedChange)="selectedChange.emit($event)"
(update)="update.emit($event)" (update)="update.emit($event)"
> >
<v-table-actions> <v-table-actions>
<button [disabled]="!selected().length" mat-raised-button (click)="changeStatuses()">
Change statuses
</button>
<ng-content></ng-content> <ng-content></ng-content>
</v-table-actions> </v-table-actions>
</v-table> </v-table2>

View File

@ -0,0 +1,155 @@
import { CommonModule } from '@angular/common';
import {
Component,
Input,
Output,
EventEmitter,
booleanAttribute,
input,
DestroyRef,
model,
} from '@angular/core';
import { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop';
import { MatButtonModule } from '@angular/material/button';
import { StatChargeback } from '@vality/magista-proto/magista';
import {
LoadOptions,
Column2,
TableModule,
DialogService,
createMenuColumn,
DialogResponseStatus,
} from '@vality/ng-core';
import { getUnionKey } from '@vality/ng-thrift';
import startCase from 'lodash-es/startCase';
import { filter } from 'rxjs';
import { createCurrencyColumn, createPartyColumn, createShopColumn } from '../../utils/table2';
import { ChangeChargebacksStatusDialogComponent } from '../change-chargebacks-status-dialog';
@Component({
standalone: true,
selector: 'cc-chargebacks-table',
templateUrl: './chargebacks-table.component.html',
imports: [CommonModule, TableModule, MatButtonModule],
})
export class ChargebacksTableComponent {
@Input() data!: StatChargeback[];
@Input() isLoading?: boolean | null;
@Input() hasMore?: boolean | null;
selected = model<StatChargeback[]>([]);
onePayment = input(false, { transform: booleanAttribute });
@Output() selectedChange = new EventEmitter<StatChargeback[]>();
@Output() update = new EventEmitter<LoadOptions>();
@Output() more = new EventEmitter<void>();
columns: Column2<StatChargeback>[] = [
{ field: 'chargeback_id', header: 'Id' },
{
field: 'chargeback_reason',
header: 'Reason',
cell: (d) => ({
value: startCase(getUnionKey(d.chargeback_reason.category)),
description: d.chargeback_reason.code,
}),
},
{
field: 'chargeback_status',
header: 'Status',
cell: (d) => ({
value: startCase(getUnionKey(d.chargeback_status)),
color: (
{
pending: 'pending',
accepted: 'success',
rejected: 'warn',
cancelled: 'neutral',
} as const
)[getUnionKey(d.chargeback_status)],
}),
},
createCurrencyColumn((d) => ({ amount: d.amount, code: d.currency_code.symbolic_code }), {
header: 'Amount',
}),
createCurrencyColumn(
(d) => ({ amount: d.levy_amount, code: d.levy_currency_code.symbolic_code }),
{ header: 'Levy Amount' },
),
createCurrencyColumn((d) => ({ amount: d.fee, code: d.currency_code.symbolic_code }), {
header: 'Fee',
}),
createCurrencyColumn(
(d) => ({ amount: d.provider_fee, code: d.currency_code.symbolic_code }),
{ header: 'Provider Fee' },
),
createCurrencyColumn(
(d) => ({ amount: d.external_fee, code: d.currency_code.symbolic_code }),
{ header: 'External Fee' },
),
{
field: 'stage',
cell: (d) => ({ value: startCase(getUnionKey(d.stage)) }),
},
{
field: 'invoice_id',
header: {
value: 'Invoice Id',
description: 'Payment Id',
},
cell: (d) => ({
description: d.payment_id,
link: () => `/party/${d.party_id}/invoice/${d.invoice_id}/payment/${d.payment_id}`,
}),
hidden: toObservable(this.onePayment),
},
{
field: 'created_at',
cell: { type: 'datetime' },
},
createPartyColumn((d) => ({ id: d.party_id }), { hidden: toObservable(this.onePayment) }),
createShopColumn((d) => ({ shopId: d.shop_id, partyId: d.party_id }), {
hidden: toObservable(this.onePayment),
}),
createMenuColumn((d) => ({
items: [
{
label: 'Change status',
click: () => {
this.changeStatus(d);
},
},
],
})),
];
constructor(
private dialogService: DialogService,
private dr: DestroyRef,
) {}
changeStatus(chargeback: StatChargeback) {
this.dialogService.open(ChangeChargebacksStatusDialogComponent, {
chargebacks: [
{
payment_id: chargeback.payment_id,
invoice_id: chargeback.invoice_id,
chargeback_id: chargeback.chargeback_id,
},
],
});
}
changeStatuses() {
this.dialogService
.open(ChangeChargebacksStatusDialogComponent, { chargebacks: this.selected() })
.afterClosed()
.pipe(
filter((res) => res.status === DialogResponseStatus.Success),
takeUntilDestroyed(this.dr),
)
.subscribe(() => {
this.update.emit();
});
}
}

View File

@ -0,0 +1 @@
export * from './chargebacks-table.component';

View File

@ -1 +0,0 @@
<v-table [columns]="columns" [data]="chargebacks"></v-table>

View File

@ -1,84 +0,0 @@
import { CommonModule } from '@angular/common';
import { Component, Input } from '@angular/core';
import { InvoicePaymentChargeback } from '@vality/domain-proto/payment_processing';
import { DialogService, Column, TableModule, createOperationColumn } from '@vality/ng-core';
import { getUnionKey } from '@vality/ng-thrift';
import startCase from 'lodash-es/startCase';
import { createCurrencyColumn } from '@cc/app/shared';
import { DetailsDialogComponent } from '@cc/app/shared/components/details-dialog/details-dialog.component';
import { ChangeChargebacksStatusDialogComponent } from '../change-chargebacks-status-dialog';
@Component({
standalone: true,
selector: 'cc-chargebacks',
templateUrl: './chargebacks.component.html',
imports: [CommonModule, TableModule],
})
export class ChargebacksComponent {
@Input() chargebacks: InvoicePaymentChargeback[];
@Input() paymentId: string;
@Input() invoiceId: string;
columns: Column<InvoicePaymentChargeback>[] = [
'chargeback.id',
{
field: 'status',
type: 'tag',
formatter: (d) => getUnionKey(d.chargeback.status),
typeParameters: {
label: (d) => startCase(getUnionKey(d.chargeback.status)),
tags: {
pending: { color: 'pending' },
accepted: { color: 'success' },
rejected: { color: 'warn' },
cancelled: { color: 'neutral' },
},
},
},
{ field: 'chargeback.created_at', type: 'datetime' },
createCurrencyColumn(
'body',
(d) => d.chargeback.body.amount,
(d) => d.chargeback.body.currency.symbolic_code,
),
createCurrencyColumn(
'levy',
(d) => d.chargeback.levy.amount,
(d) => d.chargeback.levy.currency.symbolic_code,
),
{ field: 'stage', formatter: (d) => getUnionKey(d.chargeback.stage) },
createOperationColumn([
{
label: 'Details',
click: (d) => this.showDetails(d),
},
{
label: 'Change status',
click: (d) => this.changeStatus(d.chargeback.id),
},
]),
];
constructor(private dialogService: DialogService) {}
changeStatus(id: string) {
this.dialogService.open(ChangeChargebacksStatusDialogComponent, {
chargebacks: [
{
payment_id: this.paymentId,
invoice_id: this.invoiceId,
chargeback_id: id,
},
],
});
}
showDetails(chargeback: InvoicePaymentChargeback) {
this.dialogService.open(DetailsDialogComponent, {
title: 'Chargeback details',
json: chargeback,
});
}
}

View File

@ -4,44 +4,44 @@
(extensionResult$ | async)?.type || (extensionResult$ | async)?.template; (extensionResult$ | async)?.type || (extensionResult$ | async)?.template;
else defaultFields else defaultFields
" "
[data]="data" [data]="$any(data)"
[extensions]="extensions" [extensions]="extensions"
[formControl]="control" [formControl]="control"
></cc-extension-field> ></cc-extension-field>
<ng-template #defaultFields> <ng-template #defaultFields>
<cc-primitive-field <cc-primitive-field
*ngSwitchCase="'primitive'" *ngSwitchCase="'primitive'"
[data]="data" [data]="$any(data)"
[extensions]="extensions" [extensions]="extensions"
[formControl]="control" [formControl]="control"
></cc-primitive-field> ></cc-primitive-field>
<cc-complex-form <cc-complex-form
*ngSwitchCase="'complex'" *ngSwitchCase="'complex'"
[data]="data" [data]="$any(data)"
[extensions]="extensions" [extensions]="extensions"
[formControl]="control" [formControl]="control"
></cc-complex-form> ></cc-complex-form>
<ng-container *ngSwitchCase="'object'" [ngSwitch]="data.objectType"> <ng-container *ngSwitchCase="'object'" [ngSwitch]="data.objectType">
<cc-struct-form <cc-struct-form
*ngSwitchCase="'struct'" *ngSwitchCase="'struct'"
[data]="data" [data]="$any(data)"
[extensions]="extensions" [extensions]="extensions"
[formControl]="control" [formControl]="control"
></cc-struct-form> ></cc-struct-form>
<cc-union-field <cc-union-field
*ngSwitchCase="'union'" *ngSwitchCase="'union'"
[data]="data" [data]="$any(data)"
[extensions]="extensions" [extensions]="extensions"
[formControl]="control" [formControl]="control"
></cc-union-field> ></cc-union-field>
<cc-enum-field <cc-enum-field
*ngSwitchCase="'enum'" *ngSwitchCase="'enum'"
[data]="data" [data]="$any(data)"
[formControl]="control" [formControl]="control"
></cc-enum-field> ></cc-enum-field>
<cc-typedef-form <cc-typedef-form
*ngSwitchCase="'typedef'" *ngSwitchCase="'typedef'"
[data]="data" [data]="$any(data)"
[extensions]="extensions" [extensions]="extensions"
[formControl]="control" [formControl]="control"
></cc-typedef-form> ></cc-typedef-form>

View File

@ -16,4 +16,9 @@ $offset: 24px;
min-height: 48px; min-height: 48px;
gap: 8px; gap: 8px;
} }
::ng-deep & > *:last-child {
min-height: 0;
height: 100%;
}
} }

View File

@ -9,7 +9,7 @@ import { CardComponent } from '../sidenav-info/components/card/card.component';
import { DomainThriftViewerComponent } from '../thrift-api-crud'; import { DomainThriftViewerComponent } from '../thrift-api-crud';
@Component({ @Component({
selector: 'cc-contract-card', selector: 'cc-shop-contract-card',
standalone: true, standalone: true,
imports: [CommonModule, CardComponent, DomainThriftViewerComponent], imports: [CommonModule, CardComponent, DomainThriftViewerComponent],
templateUrl: './shop-contract-card.component.html', templateUrl: './shop-contract-card.component.html',

View File

@ -18,6 +18,7 @@ import {
Option, Option,
NotifyLogService, NotifyLogService,
progressTo, progressTo,
SelectFieldComponent,
} from '@vality/ng-core'; } from '@vality/ng-core';
import { import {
BehaviorSubject, BehaviorSubject,
@ -47,8 +48,8 @@ export class ShopFieldComponent
{ {
@Input() label: string; @Input() label: string;
@Input({ transform: booleanAttribute }) required: boolean; @Input({ transform: booleanAttribute }) required: boolean;
@Input() size?: string; @Input() size?: SelectFieldComponent['size'];
@Input() appearance?: string; @Input() appearance?: SelectFieldComponent['appearance'];
@Input() hint?: string; @Input() hint?: string;
@Input({ transform: booleanAttribute }) multiple = false; @Input({ transform: booleanAttribute }) multiple = false;
partyId = input<PartyID>(); partyId = input<PartyID>();

View File

@ -1,14 +1,11 @@
<v-table <v-table2
[(sort)]="sort" [(sort)]="sort"
[columns]="columns$ | async" [columns]="columns"
[data]="shops()" [data]="shops()"
[externalFilter]="filterChange.observed" [externalFilter]="filterChange.observed"
[progress]="progress" [progress]="progress"
[size]="100"
name="shops" name="shops"
noActions
sortOnFront
standaloneFilter standaloneFilter
(filterChange)="filterChange.emit($event)" (filterChange)="filterChange.emit($event)"
(update)="update.emit()" (update)="update.emit()"
></v-table> ></v-table2>

View File

@ -5,8 +5,8 @@ import {
EventEmitter, EventEmitter,
Input, Input,
booleanAttribute, booleanAttribute,
OnChanges,
input, input,
Injector,
} from '@angular/core'; } from '@angular/core';
import { toObservable } from '@angular/core/rxjs-interop'; import { toObservable } from '@angular/core/rxjs-interop';
import { MatCardModule } from '@angular/material/card'; import { MatCardModule } from '@angular/material/card';
@ -16,18 +16,17 @@ import { Shop, Party, PartyID, RoutingRulesetRef } from '@vality/domain-proto/do
import { import {
InputFieldModule, InputFieldModule,
TableModule, TableModule,
Column,
createOperationColumn,
DialogService, DialogService,
NotifyLogService, NotifyLogService,
ConfirmDialogComponent, ConfirmDialogComponent,
DialogResponseStatus, DialogResponseStatus,
ComponentChanges, Column2,
createMenuColumn,
} from '@vality/ng-core'; } from '@vality/ng-core';
import { getUnionKey } from '@vality/ng-thrift'; import { getUnionKey } from '@vality/ng-thrift';
import isNil from 'lodash-es/isNil'; import { isNil } from 'lodash-es';
import startCase from 'lodash-es/startCase'; import startCase from 'lodash-es/startCase';
import { map, switchMap, Subject, defer, combineLatest, of } from 'rxjs'; import { map, switchMap, combineLatest, of } from 'rxjs';
import { filter, shareReplay, startWith, take, first } from 'rxjs/operators'; import { filter, shareReplay, startWith, take, first } from 'rxjs/operators';
import { MemoizeExpiring } from 'typescript-memoize'; import { MemoizeExpiring } from 'typescript-memoize';
@ -38,6 +37,7 @@ import {
DelegateWithPaymentInstitution, DelegateWithPaymentInstitution,
} from '../../../sections/routing-rules/party-delegate-rulesets'; } from '../../../sections/routing-rules/party-delegate-rulesets';
import { RoutingRulesType } from '../../../sections/routing-rules/types/routing-rules-type'; import { RoutingRulesType } from '../../../sections/routing-rules/types/routing-rules-type';
import { createPartyColumn } from '../../utils/table2';
import { ShopCardComponent } from '../shop-card/shop-card.component'; import { ShopCardComponent } from '../shop-card/shop-card.component';
import { ShopContractCardComponent } from '../shop-contract-card/shop-contract-card.component'; import { ShopContractCardComponent } from '../shop-contract-card/shop-contract-card.component';
import { SidenavInfoService } from '../sidenav-info'; import { SidenavInfoService } from '../sidenav-info';
@ -68,172 +68,140 @@ export interface ShopParty {
templateUrl: './shops-table.component.html', templateUrl: './shops-table.component.html',
providers: [PartyDelegateRulesetsService], providers: [PartyDelegateRulesetsService],
}) })
export class ShopsTableComponent implements OnChanges { export class ShopsTableComponent {
shops = input<ShopParty[]>([]); shops = input<ShopParty[]>([]);
@Input({ transform: booleanAttribute }) changed!: boolean;
@Input() progress: number | boolean = false; @Input() progress: number | boolean = false;
@Output() update = new EventEmitter<void>(); @Output() update = new EventEmitter<void>();
@Output() filterChange = new EventEmitter<string>(); @Output() filterChange = new EventEmitter<string>();
@Input({ transform: booleanAttribute }) noSort: boolean = false; noPartyColumn = input(false, { transform: booleanAttribute });
@Input({ transform: booleanAttribute }) noPartyColumn: boolean = false;
columns$ = combineLatest([ columns: Column2<ShopParty>[] = [
toObservable(this.shops).pipe( {
startWith(null), field: 'shop.id',
map((shops) => },
shops?.length ? Array.from(new Set(shops.map((s) => s.party.id))) : [], {
), field: 'shop.details.name',
switchMap((parties) => cell: (d) => ({
parties?.length description: d.shop.details.description,
? combineLatest( click: () => {
parties.map((id) =>
this.partyDelegateRulesetsService.getDelegatesWithPaymentInstitution(
RoutingRulesType.Payment,
id,
),
),
).pipe(map((rules) => new Map(rules.map((r, idx) => [parties[idx], r]))))
: of(new Map<string, DelegateWithPaymentInstitution[]>()),
),
map((delegatesWithPaymentInstitutionByParty) => ({
delegatesWithPaymentInstitutionByParty,
rulesetIds: Array.from(
Array.from(delegatesWithPaymentInstitutionByParty.values()).reduce((acc, d) => {
d?.map((v) => v?.partyDelegate?.ruleset?.id).forEach((v) => acc.add(v));
return acc;
}, new Set<number>([])),
),
})),
),
defer(() => this.updateColumns$).pipe(startWith(null)),
]).pipe(
map(([delegatesByParty]): Column<ShopParty>[] => [
{
field: 'shop.id',
sortable: !this.noSort,
},
{
field: 'shop.details.name',
description: 'shop.details.description',
click: (d) => {
this.sidenavInfoService.toggle(ShopCardComponent, { this.sidenavInfoService.toggle(ShopCardComponent, {
partyId: d.party.id, partyId: d.party.id,
id: d.shop.id, id: d.shop.id,
}); });
}, },
sortable: !this.noSort, }),
}, },
...(this.noPartyColumn createPartyColumn(
? [] (d) => ({
: [ id: d.party.id,
{ partyName: d.party.email,
field: 'party.email', }),
header: 'Party',
description: 'party.id',
link: (d) => `/party/${d.party.id}`,
},
]),
{ {
field: 'shop.contract_id', hidden: toObservable(this.noPartyColumn),
header: 'Contract', },
click: (d) => { ),
{
field: 'shop.contract_id',
header: 'Contract',
cell: (d) => ({
click: () => {
this.sidenavInfoService.toggle(ShopContractCardComponent, { this.sidenavInfoService.toggle(ShopContractCardComponent, {
partyId: d.party.id, partyId: d.party.id,
id: d.shop.id, id: d.shop.id,
}); });
}, },
}, }),
{ },
field: 'terms', {
formatter: (d) => field: 'terms',
this.getTerms(d.party.id, d.shop.id).pipe( lazyCell: (d) =>
map((terms) => getDomainObjectDetails(terms).label), this.getTerms(d.party.id, d.shop.id).pipe(
), map((terms) => getDomainObjectDetails(terms)),
description: (d) => map((details) => ({
this.getTerms(d.party.id, d.shop.id).pipe( value: details.label,
map((terms) => getDomainObjectDetails(terms).id), description: details.description,
), click: () => {
lazy: true, this.getTerms(d.party.id, d.shop.id).subscribe((terms) => {
click: (d) => { this.sidenavInfoService.toggle(DomainObjectCardComponent, {
this.getTerms(d.party.id, d.shop.id).subscribe((terms) => { ref: {
this.sidenavInfoService.toggle(DomainObjectCardComponent, { term_set_hierarchy: terms.term_set_hierarchy.ref,
ref: { term_set_hierarchy: terms.term_set_hierarchy.ref }, },
}); });
}); });
}, },
}, })),
{ ),
field: 'shop.location.url', },
}, {
{ field: 'shop.location.url',
field: 'shop.account.currency.symbolic_code', },
header: 'Currency', {
}, field: 'shop.account.currency.symbolic_code',
{ header: 'Currency',
field: 'shop.blocking', },
type: 'tag', {
formatter: ({ shop }) => getUnionKey(shop.blocking), field: 'shop.blocking',
typeParameters: { cell: (d) => ({
label: ({ shop }) => startCase(getUnionKey(shop.blocking)), value: startCase(getUnionKey(d.shop.blocking)),
tags: { color: (
blocked: { color: 'warn' }, {
unblocked: { color: 'success' }, blocked: 'warn',
}, unblocked: 'success',
}, } as const
}, )[getUnionKey(d.shop.blocking)],
{ }),
field: 'shop.suspension', },
type: 'tag', {
formatter: ({ shop }) => getUnionKey(shop.suspension), field: 'shop.suspension',
typeParameters: { cell: (d) => ({
label: ({ shop }) => startCase(getUnionKey(shop.suspension)), value: startCase(getUnionKey(d.shop.suspension)),
tags: { color: (
suspended: { color: 'warn' }, {
active: { color: 'success' }, suspended: 'warn',
}, active: 'success',
}, } as const
}, )[getUnionKey(d.shop.suspension)],
createOperationColumn<ShopParty>([ }),
...delegatesByParty.rulesetIds.map((id) => ({ },
label: `Routing rules #${id}`, createMenuColumn((d) =>
click: ({ shop, party }) => this.getDelegatesByParty().pipe(
this.openRoutingRules( map((delegatesByParty) => ({
delegatesByParty.delegatesWithPaymentInstitutionByParty items: [
.get(party.id) ...delegatesByParty.rulesetIds.map((id) => {
.find((d) => d?.partyDelegate?.ruleset?.id === id)?.partyDelegate const rulesetId =
?.ruleset?.id, delegatesByParty.delegatesWithPaymentInstitutionByParty
shop.id, ?.get?.(d.party.id)
party.id, ?.find?.((v) => v?.partyDelegate?.ruleset?.id === id)
), ?.partyDelegate?.ruleset?.id;
disabled: ({ party }) => return {
isNil( label: `Routing rules #${id}`,
delegatesByParty.delegatesWithPaymentInstitutionByParty click: () =>
.get(party.id) this.openRoutingRules(rulesetId, d.shop.id, d.party.id),
.find((d) => d?.partyDelegate?.ruleset?.id === id)?.partyDelegate disabled: isNil(rulesetId),
?.ruleset?.id, };
), }),
{
label:
getUnionKey(d.shop.suspension) === 'suspended'
? 'Activate'
: 'Suspend',
click: () => {
this.toggleSuspension(d);
},
},
{
label: getUnionKey(d.shop.blocking) === 'blocked' ? 'Unblock' : 'Block',
click: () => {
this.toggleBlocking(d);
},
},
],
})), })),
{ ),
label: ({ shop }) => ),
getUnionKey(shop.suspension) === 'suspended' ? 'Activate' : 'Suspend', ];
click: (d) => {
this.toggleSuspension(d);
},
},
{
label: ({ shop }) =>
getUnionKey(shop.blocking) === 'blocked' ? 'Unblock' : 'Block',
click: (d) => {
this.toggleBlocking(d);
},
},
]),
]),
shareReplay({ refCount: true, bufferSize: 1 }),
);
sort: Sort = { active: 'shop.details.name', direction: 'asc' }; sort: Sort = { active: 'shop.details.name', direction: 'asc' };
private updateColumns$ = new Subject<void>();
constructor( constructor(
private sidenavInfoService: SidenavInfoService, private sidenavInfoService: SidenavInfoService,
@ -243,17 +211,9 @@ export class ShopsTableComponent implements OnChanges {
private router: Router, private router: Router,
private partyDelegateRulesetsService: PartyDelegateRulesetsService, private partyDelegateRulesetsService: PartyDelegateRulesetsService,
private domainStoreService: DomainStoreService, private domainStoreService: DomainStoreService,
private injector: Injector,
) {} ) {}
ngOnChanges(changes: ComponentChanges<ShopsTableComponent>) {
if (changes.noSort || changes.noPartyColumn) {
if (this.noSort) {
this.sort = { active: '', direction: '' };
}
this.updateColumns$.next();
}
}
toggleBlocking({ party, shop }: ShopParty) { toggleBlocking({ party, shop }: ShopParty) {
this.dialogService this.dialogService
.open(ConfirmDialogComponent, { .open(ConfirmDialogComponent, {
@ -369,4 +329,34 @@ export class ShopsTableComponent implements OnChanges {
}); });
}); });
} }
private getDelegatesByParty() {
return toObservable(this.shops, { injector: this.injector }).pipe(
startWith(null),
map((shops) =>
shops?.length ? Array.from(new Set(shops.map((s) => s.party.id))) : [],
),
switchMap((parties) =>
parties?.length
? combineLatest(
parties.map((id) =>
this.partyDelegateRulesetsService.getDelegatesWithPaymentInstitution(
RoutingRulesType.Payment,
id,
),
),
).pipe(map((rules) => new Map(rules.map((r, idx) => [parties[idx], r]))))
: of(new Map<string, DelegateWithPaymentInstitution[]>()),
),
map((delegatesWithPaymentInstitutionByParty) => ({
delegatesWithPaymentInstitutionByParty,
rulesetIds: Array.from(
Array.from(delegatesWithPaymentInstitutionByParty.values()).reduce((acc, d) => {
d?.map((v) => v?.partyDelegate?.ruleset?.id).forEach((v) => acc.add(v));
return acc;
}, new Set<number>([])),
),
})),
);
}
} }

View File

@ -1,7 +1,7 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { Component, Input, booleanAttribute } from '@angular/core'; import { Component, Input, booleanAttribute } from '@angular/core';
import { ThriftAstMetadata } from '@vality/domain-proto'; import { ThriftAstMetadata } from '@vality/domain-proto';
import { getImportValue } from '@vality/ng-core'; import { getImportValue, UnionEnum } from '@vality/ng-core';
import { ValueType } from '@vality/thrift-ts'; import { ValueType } from '@vality/thrift-ts';
import { ThriftViewerModule, ViewerKind } from '../../../thrift-viewer'; import { ThriftViewerModule, ViewerKind } from '../../../thrift-viewer';
@ -15,7 +15,7 @@ import { DomainMetadataViewExtensionsService } from './services/domain-metadata-
imports: [CommonModule, ThriftViewerModule], imports: [CommonModule, ThriftViewerModule],
}) })
export class DomainThriftViewerComponent<T> { export class DomainThriftViewerComponent<T> {
@Input() kind: ViewerKind = ViewerKind.Component; @Input() kind: UnionEnum<ViewerKind> = ViewerKind.Component;
@Input() value: T; @Input() value: T;
@Input() compared?: T; @Input() compared?: T;
@Input() type: ValueType; @Input() type: ValueType;

View File

@ -9,6 +9,7 @@ import {
FormControlSuperclass, FormControlSuperclass,
} from '@vality/ng-core'; } from '@vality/ng-core';
import { toJson } from '@vality/ng-thrift'; import { toJson } from '@vality/ng-thrift';
import { ValueType } from '@vality/thrift-ts';
import { merge, defer, of, Subject } from 'rxjs'; import { merge, defer, of, Subject } from 'rxjs';
import { map, filter, shareReplay } from 'rxjs/operators'; import { map, filter, shareReplay } from 'rxjs/operators';
@ -32,7 +33,7 @@ export class ThriftEditorComponent<T> extends FormControlSuperclass<T> {
@Input() metadata: ThriftAstMetadata[]; @Input() metadata: ThriftAstMetadata[];
@Input() namespace: string; @Input() namespace: string;
@Input() type: string; @Input() type: ValueType;
@Input() extensions: MetadataFormExtension[]; @Input() extensions: MetadataFormExtension[];
@Input({ transform: booleanAttribute }) noChangeKind = false; @Input({ transform: booleanAttribute }) noChangeKind = false;
@Input({ transform: booleanAttribute }) noToolbar = false; @Input({ transform: booleanAttribute }) noToolbar = false;

View File

@ -1,6 +1,6 @@
import { Component, Input, OnChanges, Output, EventEmitter, booleanAttribute } from '@angular/core'; import { Component, Input, OnChanges, Output, EventEmitter, booleanAttribute } from '@angular/core';
import { ThriftAstMetadata } from '@vality/domain-proto'; import { ThriftAstMetadata } from '@vality/domain-proto';
import { ComponentChanges } from '@vality/ng-core'; import { ComponentChanges, UnionEnum } from '@vality/ng-core';
import { toJson } from '@vality/ng-thrift'; import { toJson } from '@vality/ng-thrift';
import { ValueType } from '@vality/thrift-ts'; import { ValueType } from '@vality/thrift-ts';
import { DiffEditorModel } from 'ngx-monaco-editor-v2'; import { DiffEditorModel } from 'ngx-monaco-editor-v2';
@ -19,7 +19,7 @@ export enum ViewerKind {
styleUrls: ['./thrift-viewer.component.scss'], styleUrls: ['./thrift-viewer.component.scss'],
}) })
export class ThriftViewerComponent<T> implements OnChanges { export class ThriftViewerComponent<T> implements OnChanges {
@Input() kind: ViewerKind = ViewerKind.Component; @Input() kind: UnionEnum<ViewerKind> = ViewerKind.Component;
@Input() value: T; @Input() value: T;
@Input() compared?: T; @Input() compared?: T;
@Input({ transform: booleanAttribute }) progress: boolean = false; @Input({ transform: booleanAttribute }) progress: boolean = false;
@ -62,6 +62,6 @@ export class ThriftViewerComponent<T> implements OnChanges {
this.kind = ViewerKind.Editor; this.kind = ViewerKind.Editor;
break; break;
} }
this.changeKind.emit(this.kind); this.changeKind.emit(this.kind as ViewerKind);
} }
} }

View File

@ -15,6 +15,7 @@ import {
createControlProviders, createControlProviders,
debounceTimeWithFirst, debounceTimeWithFirst,
progressTo, progressTo,
SelectFieldComponent,
} from '@vality/ng-core'; } from '@vality/ng-core';
import { BehaviorSubject, Observable, of, ReplaySubject, Subject, concat, forkJoin } from 'rxjs'; import { BehaviorSubject, Observable, of, ReplaySubject, Subject, concat, forkJoin } from 'rxjs';
import { catchError, map, switchMap, tap, distinctUntilChanged } from 'rxjs/operators'; import { catchError, map, switchMap, tap, distinctUntilChanged } from 'rxjs/operators';
@ -34,8 +35,8 @@ export class WalletFieldComponent
{ {
@Input() label: string; @Input() label: string;
@Input({ transform: booleanAttribute }) required: boolean; @Input({ transform: booleanAttribute }) required: boolean;
@Input() size?: string; @Input() size?: SelectFieldComponent['size'];
@Input() appearance?: string; @Input() appearance?: SelectFieldComponent['appearance'];
@Input() hint?: string; @Input() hint?: string;
@Input({ transform: booleanAttribute }) multiple = false; @Input({ transform: booleanAttribute }) multiple = false;

View File

@ -1,12 +0,0 @@
import { Column, PossiblyAsync, getPossiblyAsyncObservable } from '@vality/ng-core';
import { map } from 'rxjs/operators';
import { createDomainObjectColumn } from './create-domain-object-column';
export function createProviderColumn<T extends object>(
selectTerminalId: (d: T) => PossiblyAsync<number>,
): Column<T> {
return createDomainObjectColumn('provider', (d) =>
getPossiblyAsyncObservable(selectTerminalId(d)).pipe(map((id) => ({ id }))),
);
}

View File

@ -1,5 +1,4 @@
import { inject } from '@angular/core'; import { inject } from '@angular/core';
import { Router } from '@angular/router';
import { createColumn } from '@vality/ng-core'; import { createColumn } from '@vality/ng-core';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { map, startWith } from 'rxjs/operators'; import { map, startWith } from 'rxjs/operators';
@ -16,9 +15,7 @@ export const createPartyColumn = createColumn(
.pipe(map((party) => party.contact_info.registration_email)); .pipe(map((party) => party.contact_info.registration_email));
const partyCell = { const partyCell = {
description: id, description: id,
link: () => { link: () => `/party/${id}`,
void inject(Router).navigate([`/party/${id}`]);
},
}; };
return partyName$.pipe( return partyName$.pipe(
map((partyName) => ({ map((partyName) => ({