mirror of
https://github.com/valitydev/control-center.git
synced 2024-11-06 02:25:17 +00:00
TD-364: Add chargebacks list, details, actions. Create claim. Fix searching payments (#122)
This commit is contained in:
parent
058b7147fd
commit
ba22e86d20
@ -4,3 +4,6 @@ node_modules
|
|||||||
dist
|
dist
|
||||||
src/assets/icons/
|
src/assets/icons/
|
||||||
.angular
|
.angular
|
||||||
|
|
||||||
|
.github/settings.*
|
||||||
|
.github/workflows/basic-*
|
12
.run/App Dev Server.run.xml
Normal file
12
.run/App Dev Server.run.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="App Dev Server" type="js.build_tools.npm" activateToolWindowBeforeRun="false">
|
||||||
|
<package-json value="$PROJECT_DIR$/package.json" />
|
||||||
|
<command value="run" />
|
||||||
|
<scripts>
|
||||||
|
<script value="dev" />
|
||||||
|
</scripts>
|
||||||
|
<node-interpreter value="project" />
|
||||||
|
<envs />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
5
.run/Debug.run.xml
Normal file
5
.run/Debug.run.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Debug" type="JavascriptDebugType" uri="http://localhost:4200">
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
12
.run/Libs Dev Server.run.xml
Normal file
12
.run/Libs Dev Server.run.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Libs Dev Server" type="js.build_tools.npm">
|
||||||
|
<package-json value="$PROJECT_DIR$/package.json" />
|
||||||
|
<command value="run" />
|
||||||
|
<scripts>
|
||||||
|
<script value="dev-libs" />
|
||||||
|
</scripts>
|
||||||
|
<node-interpreter value="project" />
|
||||||
|
<envs />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
7
.run/Start.run.xml
Normal file
7
.run/Start.run.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Start" type="CompoundRunConfigurationType">
|
||||||
|
<toRun name="App Dev Server" type="js.build_tools.npm" />
|
||||||
|
<toRun name="Libs Dev Server" type="js.build_tools.npm" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
@ -14,7 +14,7 @@ export class BaseDialogService {
|
|||||||
private dialog: MatDialog,
|
private dialog: MatDialog,
|
||||||
@Optional()
|
@Optional()
|
||||||
@Inject(DIALOG_CONFIG)
|
@Inject(DIALOG_CONFIG)
|
||||||
private dialogConfig: DialogConfig
|
private readonly dialogConfig: DialogConfig
|
||||||
) {
|
) {
|
||||||
if (!dialogConfig) this.dialogConfig = DEFAULT_DIALOG_CONFIG;
|
if (!dialogConfig) this.dialogConfig = DEFAULT_DIALOG_CONFIG;
|
||||||
}
|
}
|
||||||
@ -31,7 +31,7 @@ export class BaseDialogService {
|
|||||||
: [data: D, configOrConfigName?: Omit<MatDialogConfig<D>, 'data'> | keyof DialogConfig]
|
: [data: D, configOrConfigName?: Omit<MatDialogConfig<D>, 'data'> | keyof DialogConfig]
|
||||||
): MatDialogRef<C, BaseDialogResponse<R, S>> {
|
): MatDialogRef<C, BaseDialogResponse<R, S>> {
|
||||||
let config: Partial<MatDialogConfig<D>>;
|
let config: Partial<MatDialogConfig<D>>;
|
||||||
if (!configOrConfigName) config = this.dialogConfig.medium;
|
if (!configOrConfigName) config = {};
|
||||||
else if (typeof configOrConfigName === 'string')
|
else if (typeof configOrConfigName === 'string')
|
||||||
config = this.dialogConfig[configOrConfigName];
|
config = this.dialogConfig[configOrConfigName];
|
||||||
else config = configOrConfigName;
|
else config = configOrConfigName;
|
||||||
|
@ -16,5 +16,5 @@ export const BASE_CONFIG: ValuesType<DialogConfig> = {
|
|||||||
export const DEFAULT_DIALOG_CONFIG: DialogConfig = {
|
export const DEFAULT_DIALOG_CONFIG: DialogConfig = {
|
||||||
small: { ...BASE_CONFIG, width: '360px' },
|
small: { ...BASE_CONFIG, width: '360px' },
|
||||||
medium: BASE_CONFIG,
|
medium: BASE_CONFIG,
|
||||||
large: { ...BASE_CONFIG, width: '648px' },
|
large: { ...BASE_CONFIG, width: '800px' },
|
||||||
};
|
};
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
export * from './components';
|
export * from './components';
|
||||||
export * from './utils/objects';
|
export * from './utils';
|
||||||
|
45
projects/ng-core/src/lib/utils/clean.ts
Normal file
45
projects/ng-core/src/lib/utils/clean.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import isEmpty from 'lodash-es/isEmpty';
|
||||||
|
import isNil from 'lodash-es/isNil';
|
||||||
|
import isObject from 'lodash-es/isObject';
|
||||||
|
import { ValuesType } from 'utility-types';
|
||||||
|
|
||||||
|
function isEmptyPrimitive(value: unknown): boolean {
|
||||||
|
return isNil(value) || value === '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEmptyObjectOrPrimitive(value: unknown): boolean {
|
||||||
|
return isObject(value) ? isEmpty(value) : isEmptyPrimitive(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clean<T>(
|
||||||
|
value: T,
|
||||||
|
allowRootRemoval = false,
|
||||||
|
isNotDeep = false,
|
||||||
|
filterPredicate: (v: unknown, k?: PropertyKey) => boolean = (v) => !isEmptyObjectOrPrimitive(v)
|
||||||
|
): T | null {
|
||||||
|
if (!isObject(value)) return value;
|
||||||
|
if (allowRootRemoval && !filterPredicate(value as never)) return null;
|
||||||
|
let result: unknown;
|
||||||
|
const cleanChild = (v: unknown) =>
|
||||||
|
isNotDeep ? v : clean(v as never, allowRootRemoval, isNotDeep, filterPredicate);
|
||||||
|
if (Array.isArray(value))
|
||||||
|
result = (value as ValuesType<T>[])
|
||||||
|
.slice()
|
||||||
|
.map((v) => cleanChild(v))
|
||||||
|
.filter((v, idx) => filterPredicate(v, idx));
|
||||||
|
else
|
||||||
|
result = Object.fromEntries(
|
||||||
|
(Object.entries(value) as [keyof T, ValuesType<T>][])
|
||||||
|
.map(([k, v]) => [k, cleanChild(v)] as const)
|
||||||
|
.filter(([k, v]) => filterPredicate(v, k))
|
||||||
|
);
|
||||||
|
return allowRootRemoval && !filterPredicate(result) ? null : (result as never);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function cleanPrimitiveProps<T extends object>(
|
||||||
|
obj: T,
|
||||||
|
allowRootRemoval = false,
|
||||||
|
isNotDeep = false
|
||||||
|
) {
|
||||||
|
return clean(obj, allowRootRemoval, isNotDeep, (v) => !isEmptyPrimitive(v));
|
||||||
|
}
|
1
projects/ng-core/src/lib/utils/index.ts
Normal file
1
projects/ng-core/src/lib/utils/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './clean';
|
@ -1,26 +0,0 @@
|
|||||||
import isEmpty from 'lodash-es/isEmpty';
|
|
||||||
import isNil from 'lodash-es/isNil';
|
|
||||||
import isObject from 'lodash-es/isObject';
|
|
||||||
import { ValuesType } from 'utility-types';
|
|
||||||
|
|
||||||
function isEmptyValue(value: unknown): boolean {
|
|
||||||
return isNil(value) || value === '' || (typeof value === 'object' && isEmpty(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function cleanObject<T extends object>(
|
|
||||||
obj: T,
|
|
||||||
requiredKeys: (keyof T)[] = [],
|
|
||||||
isNotDeep = false
|
|
||||||
): T {
|
|
||||||
if (!isObject(obj)) return obj;
|
|
||||||
if (Array.isArray(obj))
|
|
||||||
return obj
|
|
||||||
.slice()
|
|
||||||
.map((v: unknown) => (isObject(v) && !isNotDeep ? cleanObject(v) : v))
|
|
||||||
.filter((v) => !isEmptyValue(v)) as T;
|
|
||||||
return Object.fromEntries(
|
|
||||||
(Object.entries(obj) as [keyof T, ValuesType<T>][])
|
|
||||||
.map(([k, v]) => [k, isObject(v) && !isNotDeep ? cleanObject(v as object) : v] as const)
|
|
||||||
.filter(([k, v]) => requiredKeys.includes(k) || !isEmptyValue(v))
|
|
||||||
) as T;
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
export * from './clean-object';
|
|
@ -12,5 +12,10 @@
|
|||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"noImplicitAny": true
|
"noImplicitAny": true
|
||||||
},
|
},
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"fullTemplateTypeCheck": true,
|
||||||
|
"strictInjectionParameters": true,
|
||||||
|
"strictTemplates": true
|
||||||
|
},
|
||||||
"exclude": ["src/test.ts", "**/*.spec.ts"]
|
"exclude": ["src/test.ts", "**/*.spec.ts"]
|
||||||
}
|
}
|
||||||
|
@ -1 +1,2 @@
|
|||||||
export * from './party-management.service';
|
export * from './party-management.service';
|
||||||
|
export * from './invoicing.service';
|
||||||
|
22
src/app/api/payment-processing/invoicing.service.ts
Normal file
22
src/app/api/payment-processing/invoicing.service.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { Injectable, Injector } from '@angular/core';
|
||||||
|
import {
|
||||||
|
codegenClientConfig,
|
||||||
|
CodegenClient,
|
||||||
|
} from '@vality/domain-proto/lib/payment_processing-Invoicing';
|
||||||
|
import context from '@vality/domain-proto/lib/payment_processing/context';
|
||||||
|
import * as service from '@vality/domain-proto/lib/payment_processing/gen-nodejs/Invoicing';
|
||||||
|
|
||||||
|
import { createThriftApi } from '@cc/app/api/utils';
|
||||||
|
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class InvoicingService extends createThriftApi<CodegenClient>() {
|
||||||
|
constructor(injector: Injector) {
|
||||||
|
super(injector, {
|
||||||
|
service,
|
||||||
|
path: '/v1/processing/invoicing',
|
||||||
|
metadata: () => import('@vality/domain-proto/lib/metadata.json').then((m) => m.default),
|
||||||
|
context,
|
||||||
|
...codegenClientConfig,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -47,7 +47,7 @@ import {
|
|||||||
/**
|
/**
|
||||||
* For use in specific locations (for example, questionary PDF document)
|
* For use in specific locations (for example, questionary PDF document)
|
||||||
*/
|
*/
|
||||||
moment.locale('en');
|
moment.locale('en-GB');
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [AppComponent],
|
declarations: [AppComponent],
|
||||||
|
@ -2,10 +2,10 @@ import { Component, Injector, Input, OnChanges } from '@angular/core';
|
|||||||
import { Validator } from '@angular/forms';
|
import { Validator } from '@angular/forms';
|
||||||
import { Claim } from '@vality/domain-proto/lib/claim_management';
|
import { Claim } from '@vality/domain-proto/lib/claim_management';
|
||||||
import { Party } from '@vality/domain-proto/lib/domain';
|
import { Party } from '@vality/domain-proto/lib/domain';
|
||||||
import { from, Observable } from 'rxjs';
|
import { from, combineLatest, ReplaySubject, defer } from 'rxjs';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
import { ComponentChanges, MetadataFormExtension } from '@cc/app/shared';
|
import { ComponentChanges } from '@cc/app/shared';
|
||||||
import { DomainMetadataFormExtensionsService } from '@cc/app/shared/services/domain-metadata-form-extensions';
|
import { DomainMetadataFormExtensionsService } from '@cc/app/shared/services/domain-metadata-form-extensions';
|
||||||
import { createControlProviders, ValidatedFormControlSuperclass } from '@cc/utils';
|
import { createControlProviders, ValidatedFormControlSuperclass } from '@cc/utils';
|
||||||
|
|
||||||
@ -25,7 +25,14 @@ export class ModificationFormComponent
|
|||||||
@Input() type: string;
|
@Input() type: string;
|
||||||
|
|
||||||
metadata$ = from(import('@vality/domain-proto/lib/metadata.json').then((m) => m.default));
|
metadata$ = from(import('@vality/domain-proto/lib/metadata.json').then((m) => m.default));
|
||||||
extensions$: Observable<MetadataFormExtension[]>;
|
extensions$ = combineLatest([
|
||||||
|
defer(() => this.claimOrPartyChanged$).pipe(
|
||||||
|
map(() => createPartyClaimMetadataFormExtensions(this.party, this.claim))
|
||||||
|
),
|
||||||
|
this.domainMetadataFormExtensionsService.extensions$,
|
||||||
|
]).pipe(map((extensionGroups) => extensionGroups.flat()));
|
||||||
|
|
||||||
|
private claimOrPartyChanged$ = new ReplaySubject<void>(1);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
injector: Injector,
|
injector: Injector,
|
||||||
@ -37,12 +44,7 @@ export class ModificationFormComponent
|
|||||||
ngOnChanges(changes: ComponentChanges<ModificationFormComponent>) {
|
ngOnChanges(changes: ComponentChanges<ModificationFormComponent>) {
|
||||||
super.ngOnChanges(changes);
|
super.ngOnChanges(changes);
|
||||||
if (changes.party || changes.claim) {
|
if (changes.party || changes.claim) {
|
||||||
this.extensions$ = this.domainMetadataFormExtensionsService.extensions$.pipe(
|
this.claimOrPartyChanged$.next();
|
||||||
map((e) => [
|
|
||||||
...createPartyClaimMetadataFormExtensions(this.party, this.claim),
|
|
||||||
...e,
|
|
||||||
])
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -109,9 +109,5 @@ export function createPartyClaimMetadataFormExtensions(
|
|||||||
isIdentifier: true,
|
isIdentifier: true,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
determinant: (data) => of(isTypeWithAliases(data, 'ID', 'base')),
|
|
||||||
extension: () => of({ generate, isIdentifier: true }),
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { StatPayment } from '@vality/magista-proto';
|
import { StatPayment } from '@vality/magista-proto';
|
||||||
import { cleanObject } from '@vality/ng-core';
|
import { cleanPrimitiveProps, clean } from '@vality/ng-core';
|
||||||
import { Observable, of, Subject } from 'rxjs';
|
import { Observable, of, Subject } from 'rxjs';
|
||||||
import { mergeMap, shareReplay } from 'rxjs/operators';
|
import { mergeMap, shareReplay } from 'rxjs/operators';
|
||||||
|
|
||||||
@ -54,21 +54,21 @@ export class PaymentAdjustmentService {
|
|||||||
terminalID,
|
terminalID,
|
||||||
} = params;
|
} = params;
|
||||||
return this.merchantStatisticsService.SearchPayments(
|
return this.merchantStatisticsService.SearchPayments(
|
||||||
cleanObject({
|
cleanPrimitiveProps({
|
||||||
common_search_query_params: {
|
common_search_query_params: clean({
|
||||||
from_time: fromTime,
|
from_time: fromTime,
|
||||||
to_time: toTime,
|
to_time: toTime,
|
||||||
party_id: partyId,
|
party_id: partyId,
|
||||||
shop_ids: [shopId],
|
shop_ids: [shopId],
|
||||||
continuation_token: continuationToken,
|
continuation_token: continuationToken,
|
||||||
},
|
}),
|
||||||
payment_params: {
|
payment_params: clean({
|
||||||
from_payment_domain_revision: fromRevision,
|
from_payment_domain_revision: fromRevision,
|
||||||
to_payment_domain_revision: toRevision,
|
to_payment_domain_revision: toRevision,
|
||||||
payment_provider_id: providerID,
|
payment_provider_id: providerID,
|
||||||
payment_terminal_id: terminalID,
|
payment_terminal_id: terminalID,
|
||||||
payment_status: status,
|
payment_status: status,
|
||||||
},
|
}),
|
||||||
invoice_ids: invoiceIds,
|
invoice_ids: invoiceIds,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -34,7 +34,7 @@ export class SearchFormComponent implements OnInit {
|
|||||||
fromTime: [moment(), Validators.required],
|
fromTime: [moment(), Validators.required],
|
||||||
toTime: [moment(), Validators.required],
|
toTime: [moment(), Validators.required],
|
||||||
invoiceIds: '',
|
invoiceIds: '',
|
||||||
partyId: ['', Validators.required],
|
partyId: '',
|
||||||
shopId: '',
|
shopId: '',
|
||||||
fromRevision: [0, Validators.required],
|
fromRevision: [0, Validators.required],
|
||||||
toRevision: ['', Validators.required],
|
toRevision: ['', Validators.required],
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
<cc-base-dialog title="Create chargeback">
|
||||||
|
<cc-metadata-form
|
||||||
|
[extensions]="extensions$ | async"
|
||||||
|
[formControl]="form"
|
||||||
|
[metadata]="metadata$ | async"
|
||||||
|
namespace="payment_processing"
|
||||||
|
type="InvoicePaymentChargebackParams"
|
||||||
|
></cc-metadata-form>
|
||||||
|
|
||||||
|
<cc-base-dialog-actions>
|
||||||
|
<button [disabled]="form.invalid" color="primary" mat-raised-button (click)="create()">
|
||||||
|
CREATE
|
||||||
|
</button>
|
||||||
|
</cc-base-dialog-actions>
|
||||||
|
</cc-base-dialog>
|
@ -0,0 +1,58 @@
|
|||||||
|
import { Component, Injector } from '@angular/core';
|
||||||
|
import { FormControl } from '@angular/forms';
|
||||||
|
import { untilDestroyed, UntilDestroy } from '@ngneat/until-destroy';
|
||||||
|
import { InvoicePaymentChargeback } from '@vality/domain-proto';
|
||||||
|
import { InvoicePaymentChargebackParams } from '@vality/domain-proto/lib/payment_processing';
|
||||||
|
import { BaseDialogSuperclass } from '@vality/ng-core';
|
||||||
|
import { from } from 'rxjs';
|
||||||
|
import uuid from 'uuid';
|
||||||
|
|
||||||
|
import { InvoicingService } from '@cc/app/api/payment-processing';
|
||||||
|
import { DomainMetadataFormExtensionsService } from '@cc/app/shared/services';
|
||||||
|
import { ErrorService } from '@cc/app/shared/services/error';
|
||||||
|
import { NotificationService } from '@cc/app/shared/services/notification';
|
||||||
|
|
||||||
|
@UntilDestroy()
|
||||||
|
@Component({
|
||||||
|
selector: 'cc-create-chargeback-dialog',
|
||||||
|
templateUrl: './create-chargeback-dialog.component.html',
|
||||||
|
})
|
||||||
|
export class CreateChargebackDialogComponent extends BaseDialogSuperclass<
|
||||||
|
CreateChargebackDialogComponent,
|
||||||
|
{ invoiceID: string; paymentID: string },
|
||||||
|
InvoicePaymentChargeback
|
||||||
|
> {
|
||||||
|
form = new FormControl<Partial<InvoicePaymentChargebackParams>>({ id: uuid() });
|
||||||
|
metadata$ = from(import('@vality/domain-proto/lib/metadata.json').then((m) => m.default));
|
||||||
|
extensions$ = this.domainMetadataFormExtensionsService.extensions$;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
injector: Injector,
|
||||||
|
private invoicingService: InvoicingService,
|
||||||
|
private domainMetadataFormExtensionsService: DomainMetadataFormExtensionsService,
|
||||||
|
private errorService: ErrorService,
|
||||||
|
private notificationService: NotificationService
|
||||||
|
) {
|
||||||
|
super(injector);
|
||||||
|
}
|
||||||
|
|
||||||
|
create() {
|
||||||
|
this.invoicingService
|
||||||
|
.CreateChargeback(
|
||||||
|
this.dialogData.invoiceID,
|
||||||
|
this.dialogData.paymentID,
|
||||||
|
this.form.value as InvoicePaymentChargebackParams
|
||||||
|
)
|
||||||
|
.pipe(untilDestroyed(this))
|
||||||
|
.subscribe({
|
||||||
|
next: (res) => {
|
||||||
|
this.notificationService.success('Chargeback created');
|
||||||
|
this.closeWithSuccess(res);
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
this.errorService.error(err);
|
||||||
|
this.notificationService.error();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -9,9 +9,10 @@
|
|||||||
></cc-payment-main-info>
|
></cc-payment-main-info>
|
||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
|
|
||||||
|
<h2 class="cc-headline">Refunds</h2>
|
||||||
<mat-card>
|
<mat-card>
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<h2 class="cc-headline">Refunds</h2>
|
|
||||||
<cc-payment-refunds
|
<cc-payment-refunds
|
||||||
[invoiceID]="payment.invoice_id"
|
[invoiceID]="payment.invoice_id"
|
||||||
[partyID]="payment.owner_id"
|
[partyID]="payment.owner_id"
|
||||||
@ -19,6 +20,22 @@
|
|||||||
></cc-payment-refunds>
|
></cc-payment-refunds>
|
||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
|
|
||||||
|
<div fxLayout fxLayoutAlign="space-between">
|
||||||
|
<h2 class="cc-headline">Chargebacks</h2>
|
||||||
|
<cc-actions>
|
||||||
|
<button color="primary" mat-button (click)="createChargeback()">
|
||||||
|
CREATE CHARGEBACK
|
||||||
|
</button>
|
||||||
|
</cc-actions>
|
||||||
|
</div>
|
||||||
|
<mat-card>
|
||||||
|
<cc-chargebacks
|
||||||
|
[chargebacks]="chargebacks$ | async"
|
||||||
|
[invoiceId]="payment.invoice_id"
|
||||||
|
[paymentId]="payment.id"
|
||||||
|
></cc-chargebacks>
|
||||||
|
</mat-card>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<div *ngIf="isLoading$ | async" fxLayout fxLayoutAlign="center center">
|
<div *ngIf="isLoading$ | async" fxLayout fxLayoutAlign="center center">
|
||||||
<mat-spinner diameter="64"></mat-spinner>
|
<mat-spinner diameter="64"></mat-spinner>
|
||||||
|
@ -1,9 +1,15 @@
|
|||||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { pluck } from 'rxjs/operators';
|
import { untilDestroyed, UntilDestroy } from '@ngneat/until-destroy';
|
||||||
|
import { BaseDialogService, BaseDialogResponseStatus } from '@vality/ng-core';
|
||||||
|
import { Subject, merge, defer } from 'rxjs';
|
||||||
|
import { pluck, shareReplay, switchMap, map } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { InvoicingService } from '../../api/payment-processing/invoicing.service';
|
||||||
|
import { CreateChargebackDialogComponent } from './create-chargeback-dialog/create-chargeback-dialog.component';
|
||||||
import { PaymentDetailsService } from './payment-details.service';
|
import { PaymentDetailsService } from './payment-details.service';
|
||||||
|
|
||||||
|
@UntilDestroy()
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: 'payment-details.component.html',
|
templateUrl: 'payment-details.component.html',
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
@ -15,8 +21,37 @@ export class PaymentDetailsComponent {
|
|||||||
isLoading$ = this.paymentDetailsService.isLoading$;
|
isLoading$ = this.paymentDetailsService.isLoading$;
|
||||||
shop$ = this.paymentDetailsService.shop$;
|
shop$ = this.paymentDetailsService.shop$;
|
||||||
|
|
||||||
|
chargebacks$ = merge(
|
||||||
|
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 paymentDetailsService: PaymentDetailsService,
|
private paymentDetailsService: PaymentDetailsService,
|
||||||
private route: ActivatedRoute
|
private route: ActivatedRoute,
|
||||||
|
private invoicingService: InvoicingService,
|
||||||
|
private baseDialogService: BaseDialogService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
createChargeback() {
|
||||||
|
this.baseDialogService
|
||||||
|
.open(
|
||||||
|
CreateChargebackDialogComponent,
|
||||||
|
this.route.snapshot.params as Record<'invoiceID' | 'paymentID', string>
|
||||||
|
)
|
||||||
|
.afterClosed()
|
||||||
|
.pipe(untilDestroyed(this))
|
||||||
|
.subscribe(({ status }) => {
|
||||||
|
if (status === BaseDialogResponseStatus.Success) this.updateChargebacks$.next();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,19 @@
|
|||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||||
|
import { ReactiveFormsModule } from '@angular/forms';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatCardModule } from '@angular/material/card';
|
import { MatCardModule } from '@angular/material/card';
|
||||||
import { MatDialogModule } from '@angular/material/dialog';
|
import { MatDialogModule } from '@angular/material/dialog';
|
||||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||||
|
import { ActionsModule, BaseDialogModule } from '@vality/ng-core';
|
||||||
|
|
||||||
import { StatusModule } from '@cc/app/shared/components';
|
import { StatusModule, MetadataFormModule } from '@cc/app/shared/components';
|
||||||
import { DetailsItemModule } from '@cc/components/details-item';
|
import { DetailsItemModule } from '@cc/components/details-item';
|
||||||
import { HeadlineModule } from '@cc/components/headline';
|
import { HeadlineModule } from '@cc/components/headline';
|
||||||
|
|
||||||
|
import { ChargebacksComponent } from '../../shared/components/chargebacks/chargebacks.component';
|
||||||
|
import { CreateChargebackDialogComponent } from './create-chargeback-dialog/create-chargeback-dialog.component';
|
||||||
import { PaymentDetailsRoutingModule } from './payment-details-routing.module';
|
import { PaymentDetailsRoutingModule } from './payment-details-routing.module';
|
||||||
import { PaymentDetailsComponent } from './payment-details.component';
|
import { PaymentDetailsComponent } from './payment-details.component';
|
||||||
import { PaymentMainInfoModule } from './payment-main-info';
|
import { PaymentMainInfoModule } from './payment-main-info';
|
||||||
@ -31,7 +35,12 @@ import { PaymentRefundsModule } from './payment-refunds';
|
|||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
MatDialogModule,
|
MatDialogModule,
|
||||||
PaymentRefundsModule,
|
PaymentRefundsModule,
|
||||||
|
ChargebacksComponent,
|
||||||
|
ActionsModule,
|
||||||
|
BaseDialogModule,
|
||||||
|
MetadataFormModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
],
|
],
|
||||||
declarations: [PaymentDetailsComponent],
|
declarations: [PaymentDetailsComponent, CreateChargebackDialogComponent],
|
||||||
})
|
})
|
||||||
export class PaymentDetailsModule {}
|
export class PaymentDetailsModule {}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { cleanObject } from '@vality/ng-core';
|
import { cleanPrimitiveProps } from '@vality/ng-core';
|
||||||
import { combineLatest, of } from 'rxjs';
|
import { combineLatest, of } from 'rxjs';
|
||||||
import { map, pluck, shareReplay, switchMap, tap } from 'rxjs/operators';
|
import { map, pluck, shareReplay, switchMap, tap } from 'rxjs/operators';
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ export class PaymentDetailsService {
|
|||||||
switchMap(({ partyID, invoiceID, paymentID }) =>
|
switchMap(({ partyID, invoiceID, paymentID }) =>
|
||||||
this.merchantStatisticsService
|
this.merchantStatisticsService
|
||||||
.SearchPayments(
|
.SearchPayments(
|
||||||
cleanObject({
|
cleanPrimitiveProps({
|
||||||
common_search_query_params: {
|
common_search_query_params: {
|
||||||
from_time: new Date('2020-01-01').toISOString(), // TODO
|
from_time: new Date('2020-01-01').toISOString(), // TODO
|
||||||
to_time: new Date().toISOString(),
|
to_time: new Date().toISOString(),
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { StatRefund, RefundSearchQuery } from '@vality/magista-proto';
|
import { StatRefund, RefundSearchQuery } from '@vality/magista-proto';
|
||||||
import { cleanObject } from '@vality/ng-core';
|
import { cleanPrimitiveProps } from '@vality/ng-core';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { map, shareReplay } from 'rxjs/operators';
|
import { map, shareReplay } from 'rxjs/operators';
|
||||||
import { DeepPartial } from 'utility-types';
|
import { DeepPartial } from 'utility-types';
|
||||||
@ -28,7 +28,7 @@ export class FetchRefundsService extends PartialFetcher<
|
|||||||
): Observable<FetchResult<StatRefund>> {
|
): Observable<FetchResult<StatRefund>> {
|
||||||
return this.merchantStatisticsService
|
return this.merchantStatisticsService
|
||||||
.SearchRefunds(
|
.SearchRefunds(
|
||||||
cleanObject({
|
cleanPrimitiveProps({
|
||||||
...params,
|
...params,
|
||||||
common_search_query_params: Object.assign(
|
common_search_query_params: Object.assign(
|
||||||
{
|
{
|
||||||
|
@ -16,8 +16,7 @@
|
|||||||
<ng-container matColumnDef="amount">
|
<ng-container matColumnDef="amount">
|
||||||
<th *matHeaderCellDef class="cc-caption" mat-header-cell>Amount</th>
|
<th *matHeaderCellDef class="cc-caption" mat-header-cell>Amount</th>
|
||||||
<td *matCellDef="let refund" mat-cell>
|
<td *matCellDef="let refund" mat-cell>
|
||||||
{{ refund.amount | ccFormatAmount }}
|
{{ refund.amount | amountCurrency: refund.currency_symbolic_code }}
|
||||||
{{ refund.currency_symbolic_code | ccCurrency }}
|
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ import { MatButtonModule } from '@angular/material/button';
|
|||||||
import { MatTableModule } from '@angular/material/table';
|
import { MatTableModule } from '@angular/material/table';
|
||||||
|
|
||||||
import { StatusModule } from '@cc/app/shared/components/status';
|
import { StatusModule } from '@cc/app/shared/components/status';
|
||||||
import { CommonPipesModule, ThriftPipesModule } from '@cc/app/shared/pipes';
|
import { CommonPipesModule, ThriftPipesModule, AmountCurrencyPipe } from '@cc/app/shared/pipes';
|
||||||
|
|
||||||
import { RefundsTableComponent } from './refunds-table.component';
|
import { RefundsTableComponent } from './refunds-table.component';
|
||||||
|
|
||||||
@ -18,6 +18,7 @@ import { RefundsTableComponent } from './refunds-table.component';
|
|||||||
StatusModule,
|
StatusModule,
|
||||||
ThriftPipesModule,
|
ThriftPipesModule,
|
||||||
CommonPipesModule,
|
CommonPipesModule,
|
||||||
|
AmountCurrencyPipe,
|
||||||
],
|
],
|
||||||
declarations: [RefundsTableComponent],
|
declarations: [RefundsTableComponent],
|
||||||
exports: [RefundsTableComponent],
|
exports: [RefundsTableComponent],
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
<cc-base-dialog title="Create claim">
|
||||||
|
<cc-merchant-field [formControl]="control"></cc-merchant-field>
|
||||||
|
<cc-base-dialog-actions>
|
||||||
|
<button
|
||||||
|
[disabled]="control.invalid || !!(progress$ | async)"
|
||||||
|
color="primary"
|
||||||
|
mat-raised-button
|
||||||
|
(click)="create()"
|
||||||
|
>
|
||||||
|
CREATE
|
||||||
|
</button>
|
||||||
|
</cc-base-dialog-actions>
|
||||||
|
</cc-base-dialog>
|
@ -0,0 +1,52 @@
|
|||||||
|
import { Component, Injector } from '@angular/core';
|
||||||
|
import { FormControl, Validators } from '@angular/forms';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { untilDestroyed, UntilDestroy } from '@ngneat/until-destroy';
|
||||||
|
import { BaseDialogSuperclass } from '@vality/ng-core';
|
||||||
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
|
||||||
|
import { ClaimManagementService } from '@cc/app/api/claim-management';
|
||||||
|
import { ErrorService } from '@cc/app/shared/services/error';
|
||||||
|
import { NotificationService } from '@cc/app/shared/services/notification';
|
||||||
|
import { progressTo } from '@cc/utils';
|
||||||
|
|
||||||
|
@UntilDestroy()
|
||||||
|
@Component({
|
||||||
|
selector: 'cc-create-claim-dialog',
|
||||||
|
templateUrl: './create-claim-dialog.component.html',
|
||||||
|
})
|
||||||
|
export class CreateClaimDialogComponent extends BaseDialogSuperclass<
|
||||||
|
CreateClaimDialogComponent,
|
||||||
|
{ partyId: string }
|
||||||
|
> {
|
||||||
|
control = new FormControl(this.dialogData.partyId, Validators.required);
|
||||||
|
progress$ = new BehaviorSubject(0);
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
injector: Injector,
|
||||||
|
private claimService: ClaimManagementService,
|
||||||
|
private notificationService: NotificationService,
|
||||||
|
private errorService: ErrorService,
|
||||||
|
private router: Router
|
||||||
|
) {
|
||||||
|
super(injector);
|
||||||
|
}
|
||||||
|
|
||||||
|
create() {
|
||||||
|
this.claimService
|
||||||
|
.CreateClaim(this.dialogData.partyId, [])
|
||||||
|
.pipe(progressTo(this.progress$), untilDestroyed(this))
|
||||||
|
.subscribe({
|
||||||
|
next: (claim) => {
|
||||||
|
this.notificationService.success('Claim successfully created');
|
||||||
|
this.closeWithSuccess();
|
||||||
|
void this.router.navigate([
|
||||||
|
`party/${this.dialogData.partyId}/claim/${claim.id}`,
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
this.errorService.error(err, 'An error occurred while claim creation');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,10 @@
|
|||||||
<div class="search-claims-container" fxLayout="column" fxLayoutGap="32px">
|
<div class="search-claims-container" fxLayout="column" fxLayoutGap="32px">
|
||||||
<h1 class="cc-display-1">Claims</h1>
|
<div fxLayout fxLayoutAlign="space-between">
|
||||||
|
<h1 class="cc-display-1">Claims</h1>
|
||||||
|
<cc-actions>
|
||||||
|
<button color="primary" mat-raised-button (click)="create()">CREATE</button>
|
||||||
|
</cc-actions>
|
||||||
|
</div>
|
||||||
<div fxLayout="column" fxLayoutGap="24px">
|
<div fxLayout="column" fxLayoutGap="24px">
|
||||||
<mat-card>
|
<mat-card>
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
@ -13,15 +18,12 @@
|
|||||||
<cc-empty-search-result *ngIf="claims.length === 0"></cc-empty-search-result>
|
<cc-empty-search-result *ngIf="claims.length === 0"></cc-empty-search-result>
|
||||||
<mat-card *ngIf="claims.length > 0" fxLayout="column" fxLayoutGap="18px">
|
<mat-card *ngIf="claims.length > 0" fxLayout="column" fxLayoutGap="18px">
|
||||||
<cc-search-table [claims]="claims"></cc-search-table>
|
<cc-search-table [claims]="claims"></cc-search-table>
|
||||||
<button
|
<cc-show-more-button
|
||||||
*ngIf="hasMore$ | async"
|
*ngIf="hasMore$ | async"
|
||||||
[disabled]="doAction$ | async"
|
[inProgress]="doAction$ | async"
|
||||||
fxFlex="100"
|
|
||||||
mat-button
|
|
||||||
(click)="fetchMore()"
|
(click)="fetchMore()"
|
||||||
>
|
>
|
||||||
{{ (doAction$ | async) ? 'LOADING...' : 'SHOW MORE' }}
|
</cc-show-more-button>
|
||||||
</button>
|
|
||||||
</mat-card>
|
</mat-card>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
|
import { PartyID } from '@vality/domain-proto';
|
||||||
|
import { BaseDialogService, cleanPrimitiveProps, clean } from '@vality/ng-core';
|
||||||
|
|
||||||
import { ClaimSearchForm } from '@cc/app/shared/components';
|
import { ClaimSearchForm } from '@cc/app/shared/components';
|
||||||
|
|
||||||
|
import { CreateClaimDialogComponent } from './components/create-claim-dialog/create-claim-dialog.component';
|
||||||
import { SearchClaimsService } from './search-claims.service';
|
import { SearchClaimsService } from './search-claims.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -11,10 +14,15 @@ import { SearchClaimsService } from './search-claims.service';
|
|||||||
})
|
})
|
||||||
export class SearchClaimsComponent implements OnInit {
|
export class SearchClaimsComponent implements OnInit {
|
||||||
doAction$ = this.searchClaimService.doAction$;
|
doAction$ = this.searchClaimService.doAction$;
|
||||||
claims$ = this.searchClaimService.claims$;
|
claims$ = this.searchClaimService.searchResult$;
|
||||||
hasMore$ = this.searchClaimService.hasMore$;
|
hasMore$ = this.searchClaimService.hasMore$;
|
||||||
|
private selectedPartyId: PartyID;
|
||||||
|
|
||||||
constructor(private searchClaimService: SearchClaimsService, private snackBar: MatSnackBar) {}
|
constructor(
|
||||||
|
private searchClaimService: SearchClaimsService,
|
||||||
|
private snackBar: MatSnackBar,
|
||||||
|
private baseDialogService: BaseDialogService
|
||||||
|
) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.searchClaimService.errors$.subscribe((e) =>
|
this.searchClaimService.errors$.subscribe((e) =>
|
||||||
@ -23,10 +31,17 @@ export class SearchClaimsComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
search(v: ClaimSearchForm): void {
|
search(v: ClaimSearchForm): void {
|
||||||
this.searchClaimService.search(v);
|
this.selectedPartyId = v?.party_id;
|
||||||
|
this.searchClaimService.search(
|
||||||
|
cleanPrimitiveProps({ ...v, statuses: clean(v.statuses?.map((s) => ({ [s]: {} }))) })
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchMore(): void {
|
fetchMore(): void {
|
||||||
this.searchClaimService.fetchMore();
|
this.searchClaimService.fetchMore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
create() {
|
||||||
|
this.baseDialogService.open(CreateClaimDialogComponent, { partyId: this.selectedPartyId });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,12 +14,16 @@ import { MatProgressBarModule } from '@angular/material/progress-bar';
|
|||||||
import { MatSelectModule } from '@angular/material/select';
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
import { MatSnackBarModule } from '@angular/material/snack-bar';
|
||||||
import { MatTableModule } from '@angular/material/table';
|
import { MatTableModule } from '@angular/material/table';
|
||||||
|
import { ActionsModule, BaseDialogModule } from '@vality/ng-core';
|
||||||
|
|
||||||
import { ClaimSearchFormModule } from '@cc/app/shared/components';
|
import { ClaimSearchFormModule } from '@cc/app/shared/components';
|
||||||
|
import { MerchantFieldModule } from '@cc/app/shared/components/merchant-field';
|
||||||
import { ApiModelPipesModule, ThriftPipesModule } from '@cc/app/shared/pipes';
|
import { ApiModelPipesModule, ThriftPipesModule } from '@cc/app/shared/pipes';
|
||||||
import { EmptySearchResultModule } from '@cc/components/empty-search-result';
|
import { EmptySearchResultModule } from '@cc/components/empty-search-result';
|
||||||
|
import { TableModule } from '@cc/components/table';
|
||||||
|
|
||||||
import { ClaimManagementService } from '../../thrift-services/damsel/claim-management.service';
|
import { ClaimManagementService } from '../../thrift-services/damsel/claim-management.service';
|
||||||
|
import { CreateClaimDialogComponent } from './components/create-claim-dialog/create-claim-dialog.component';
|
||||||
import { SearchClaimsComponentRouting } from './search-claims-routing.module';
|
import { SearchClaimsComponentRouting } from './search-claims-routing.module';
|
||||||
import { SearchClaimsComponent } from './search-claims.component';
|
import { SearchClaimsComponent } from './search-claims.component';
|
||||||
import { SearchClaimsService } from './search-claims.service';
|
import { SearchClaimsService } from './search-claims.service';
|
||||||
@ -48,8 +52,17 @@ import { SearchTableComponent } from './search-table/search-table.component';
|
|||||||
EmptySearchResultModule,
|
EmptySearchResultModule,
|
||||||
ApiModelPipesModule,
|
ApiModelPipesModule,
|
||||||
ThriftPipesModule,
|
ThriftPipesModule,
|
||||||
|
TableModule,
|
||||||
|
ActionsModule,
|
||||||
|
BaseDialogModule,
|
||||||
|
MerchantFieldModule,
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
SearchClaimsComponent,
|
||||||
|
SearchTableComponent,
|
||||||
|
ClaimMailPipePipe,
|
||||||
|
CreateClaimDialogComponent,
|
||||||
],
|
],
|
||||||
declarations: [SearchClaimsComponent, SearchTableComponent, ClaimMailPipePipe],
|
|
||||||
providers: [SearchClaimsService, ClaimManagementService],
|
providers: [SearchClaimsService, ClaimManagementService],
|
||||||
})
|
})
|
||||||
export class SearchClaimsModule {}
|
export class SearchClaimsModule {}
|
||||||
|
@ -1,28 +1,16 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { PartyID } from '@vality/domain-proto';
|
import { Claim, ClaimSearchQuery } from '@vality/domain-proto/lib/claim_management';
|
||||||
import {
|
|
||||||
Claim,
|
|
||||||
ClaimID,
|
|
||||||
ClaimSearchQuery,
|
|
||||||
ClaimStatus,
|
|
||||||
} from '@vality/domain-proto/lib/claim_management';
|
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { ClaimManagementService } from '@cc/app/api/claim-management';
|
||||||
import { FetchResult, PartialFetcher } from '@cc/app/shared/services';
|
import { FetchResult, PartialFetcher } from '@cc/app/shared/services';
|
||||||
|
|
||||||
import { ClaimManagementService } from '../../thrift-services/damsel/claim-management.service';
|
|
||||||
|
|
||||||
type SearchClaimsParams = {
|
|
||||||
claim_id?: ClaimID;
|
|
||||||
statuses?: (keyof ClaimStatus)[];
|
|
||||||
party_id: PartyID;
|
|
||||||
};
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SearchClaimsService extends PartialFetcher<Claim, SearchClaimsParams> {
|
export class SearchClaimsService extends PartialFetcher<
|
||||||
claims$: Observable<Claim[]> = this.searchResult$;
|
Claim,
|
||||||
|
Omit<ClaimSearchQuery, 'continuation_token' | 'limit'>
|
||||||
|
> {
|
||||||
private readonly searchLimit = 10;
|
private readonly searchLimit = 10;
|
||||||
|
|
||||||
constructor(private claimManagementService: ClaimManagementService) {
|
constructor(private claimManagementService: ClaimManagementService) {
|
||||||
@ -30,11 +18,11 @@ export class SearchClaimsService extends PartialFetcher<Claim, SearchClaimsParam
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected fetch(
|
protected fetch(
|
||||||
params: SearchClaimsParams,
|
params: Omit<ClaimSearchQuery, 'continuation_token' | 'limit'>,
|
||||||
continuationToken: string
|
continuationToken: string
|
||||||
): Observable<FetchResult<Claim>> {
|
): Observable<FetchResult<Claim>> {
|
||||||
return this.claimManagementService
|
return this.claimManagementService
|
||||||
.searchClaims({
|
.SearchClaims({
|
||||||
...params,
|
...params,
|
||||||
continuation_token: continuationToken,
|
continuation_token: continuationToken,
|
||||||
limit: this.searchLimit,
|
limit: this.searchLimit,
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
<cc-base-dialog title="Change chargeback">
|
||||||
|
<div gdColumn="1fr" gdGap="16px">
|
||||||
|
<mat-form-field style="width: 100%">
|
||||||
|
<mat-label>Action</mat-label>
|
||||||
|
<mat-select [formControl]="actionControl">
|
||||||
|
<mat-option *ngFor="let k of typeEnum | enumKeys" [value]="typeEnum[k]">{{
|
||||||
|
k
|
||||||
|
}}</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<cc-metadata-form
|
||||||
|
[extensions]="extensions$ | async"
|
||||||
|
[formControl]="control"
|
||||||
|
[metadata]="metadata$ | async"
|
||||||
|
[type]="types[actionControl.value]"
|
||||||
|
namespace="payment_processing"
|
||||||
|
></cc-metadata-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<cc-base-dialog-actions>
|
||||||
|
<button
|
||||||
|
[disabled]="control.invalid || actionControl.invalid || !!(progress$ | async)"
|
||||||
|
color="primary"
|
||||||
|
mat-raised-button
|
||||||
|
(click)="confirm()"
|
||||||
|
>
|
||||||
|
{{ (actionControl.value | enumKey: typeEnum) || 'CHANGE' | uppercase }}
|
||||||
|
</button>
|
||||||
|
</cc-base-dialog-actions>
|
||||||
|
</cc-base-dialog>
|
@ -0,0 +1,112 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { Component, Injector, OnInit } from '@angular/core';
|
||||||
|
import { GridModule } from '@angular/flex-layout';
|
||||||
|
import { ReactiveFormsModule, FormControl, Validators } from '@angular/forms';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
|
import { untilDestroyed, UntilDestroy } from '@ngneat/until-destroy';
|
||||||
|
import { BaseDialogSuperclass, BaseDialogModule, BaseDialogService } from '@vality/ng-core';
|
||||||
|
import { from, BehaviorSubject, Observable } from 'rxjs';
|
||||||
|
|
||||||
|
import { InvoicingService } from '@cc/app/api/payment-processing';
|
||||||
|
import { MetadataFormModule, EnumKeysPipe, EnumKeyPipe } from '@cc/app/shared';
|
||||||
|
import { DomainMetadataFormExtensionsService } from '@cc/app/shared/services';
|
||||||
|
import { ErrorService } from '@cc/app/shared/services/error';
|
||||||
|
import { NotificationService } from '@cc/app/shared/services/notification';
|
||||||
|
import { progressTo } from '@cc/utils';
|
||||||
|
|
||||||
|
enum Action {
|
||||||
|
Accept,
|
||||||
|
Reject,
|
||||||
|
Reopen,
|
||||||
|
Cancel,
|
||||||
|
}
|
||||||
|
|
||||||
|
@UntilDestroy()
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
selector: 'cc-change-chargeback-status-dialog',
|
||||||
|
templateUrl: './change-chargeback-status-dialog.component.html',
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
BaseDialogModule,
|
||||||
|
MatButtonModule,
|
||||||
|
MetadataFormModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
MatFormFieldModule,
|
||||||
|
MatSelectModule,
|
||||||
|
EnumKeysPipe,
|
||||||
|
GridModule,
|
||||||
|
EnumKeyPipe,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class ChangeChargebackStatusDialogComponent
|
||||||
|
extends BaseDialogSuperclass<
|
||||||
|
ChangeChargebackStatusDialogComponent,
|
||||||
|
{ id: string; paymentId: string; invoiceId: string }
|
||||||
|
>
|
||||||
|
implements OnInit
|
||||||
|
{
|
||||||
|
metadata$ = from(import('@vality/domain-proto/lib/metadata.json').then((m) => m.default));
|
||||||
|
extensions$ = this.domainMetadataFormExtensionsService.extensions$;
|
||||||
|
control = new FormControl();
|
||||||
|
actionControl = new FormControl<Action>(null, Validators.required);
|
||||||
|
typeEnum = Action;
|
||||||
|
types = {
|
||||||
|
[Action.Accept]: 'InvoicePaymentChargebackAcceptParams',
|
||||||
|
[Action.Reject]: 'InvoicePaymentChargebackRejectParams',
|
||||||
|
[Action.Reopen]: 'InvoicePaymentChargebackReopenParams',
|
||||||
|
[Action.Cancel]: 'InvoicePaymentChargebackCancelParams',
|
||||||
|
};
|
||||||
|
progress$ = new BehaviorSubject(0);
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
injector: Injector,
|
||||||
|
private invoicingService: InvoicingService,
|
||||||
|
private baseDialogService: BaseDialogService,
|
||||||
|
private notificationService: NotificationService,
|
||||||
|
private errorService: ErrorService,
|
||||||
|
private domainMetadataFormExtensionsService: DomainMetadataFormExtensionsService
|
||||||
|
) {
|
||||||
|
super(injector);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.actionControl.valueChanges.pipe(untilDestroyed(this)).subscribe(() => {
|
||||||
|
this.control.reset();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
confirm() {
|
||||||
|
let action$: Observable<void>;
|
||||||
|
const args = [
|
||||||
|
this.dialogData.invoiceId,
|
||||||
|
this.dialogData.paymentId,
|
||||||
|
this.dialogData.id,
|
||||||
|
this.control.value,
|
||||||
|
] as const;
|
||||||
|
switch (this.actionControl.value) {
|
||||||
|
case Action.Accept:
|
||||||
|
action$ = this.invoicingService.AcceptChargeback(...args);
|
||||||
|
break;
|
||||||
|
case Action.Reject:
|
||||||
|
action$ = this.invoicingService.RejectChargeback(...args);
|
||||||
|
break;
|
||||||
|
case Action.Reopen:
|
||||||
|
action$ = this.invoicingService.ReopenChargeback(...args);
|
||||||
|
break;
|
||||||
|
case Action.Cancel:
|
||||||
|
action$ = this.invoicingService.CancelChargeback(...args);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
action$.pipe(progressTo(this.progress$), untilDestroyed(this)).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.notificationService.success();
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
this.errorService.error(err);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
<table [dataSource]="chargebacks" mat-table style="min-width: 100%">
|
||||||
|
<ng-container [matColumnDef]="cols.def.id">
|
||||||
|
<th *matHeaderCellDef mat-header-cell>ID</th>
|
||||||
|
<td *matCellDef="let c" mat-cell>{{ c.chargeback.id }}</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container [matColumnDef]="cols.def.status">
|
||||||
|
<th *matHeaderCellDef mat-header-cell>Status</th>
|
||||||
|
<td *matCellDef="let c" mat-cell>{{ c.chargeback.status | ccUnionKey }}</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container [matColumnDef]="cols.def.created_at">
|
||||||
|
<th *matHeaderCellDef mat-header-cell>Created At</th>
|
||||||
|
<td *matCellDef="let c" mat-cell>
|
||||||
|
{{ c.chargeback.created_at | date: 'dd.MM.yyyy HH:mm:ss' }}
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container [matColumnDef]="cols.def.body">
|
||||||
|
<th *matHeaderCellDef mat-header-cell>Body</th>
|
||||||
|
<td *matCellDef="let c" mat-cell>
|
||||||
|
{{
|
||||||
|
c.chargeback.body.amount | amountCurrency: c.chargeback.body.currency.symbolic_code
|
||||||
|
}}
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container [matColumnDef]="cols.def.levy">
|
||||||
|
<th *matHeaderCellDef mat-header-cell>Levy</th>
|
||||||
|
<td *matCellDef="let c" mat-cell>
|
||||||
|
{{
|
||||||
|
c.chargeback.levy.amount | amountCurrency: c.chargeback.levy.currency.symbolic_code
|
||||||
|
}}
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container [matColumnDef]="cols.def.stage">
|
||||||
|
<th *matHeaderCellDef mat-header-cell>Stage</th>
|
||||||
|
<td *matCellDef="let c" mat-cell>{{ c.chargeback.stage | ccUnionKey }}</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container [matColumnDef]="cols.def.actions">
|
||||||
|
<th *matHeaderCellDef mat-header-cell></th>
|
||||||
|
<td *matCellDef="let c" mat-cell style="width: 0">
|
||||||
|
<cc-menu-cell>
|
||||||
|
<button mat-menu-item (click)="showDetails(c)">Details</button>
|
||||||
|
<button mat-menu-item (click)="changeStatus(c.chargeback.id)">Change</button>
|
||||||
|
</cc-menu-cell>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<cc-no-data-row></cc-no-data-row>
|
||||||
|
<tr *matHeaderRowDef="cols.list" mat-header-row></tr>
|
||||||
|
<tr *matRowDef="let row; columns: cols.list" mat-row></tr>
|
||||||
|
</table>
|
@ -0,0 +1,56 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { Component, Input } from '@angular/core';
|
||||||
|
import { MatMenuModule } from '@angular/material/menu';
|
||||||
|
import { MatTableModule } from '@angular/material/table';
|
||||||
|
import { UntilDestroy } from '@ngneat/until-destroy';
|
||||||
|
import { InvoicePaymentChargeback } from '@vality/magista-proto/lib/payment_processing';
|
||||||
|
import { BaseDialogService } from '@vality/ng-core';
|
||||||
|
|
||||||
|
import { InvoicingService } from '@cc/app/api/payment-processing';
|
||||||
|
import { AmountCurrencyPipe, ThriftPipesModule } from '@cc/app/shared';
|
||||||
|
import { DetailsDialogComponent } from '@cc/app/shared/components/details-dialog/details-dialog.component';
|
||||||
|
import { TableModule, Columns } from '@cc/components/table';
|
||||||
|
|
||||||
|
import { ChangeChargebackStatusDialogComponent } from '../change-chargeback-status-dialog/change-chargeback-status-dialog.component';
|
||||||
|
|
||||||
|
@UntilDestroy()
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
selector: 'cc-chargebacks',
|
||||||
|
templateUrl: './chargebacks.component.html',
|
||||||
|
imports: [
|
||||||
|
MatTableModule,
|
||||||
|
TableModule,
|
||||||
|
ThriftPipesModule,
|
||||||
|
CommonModule,
|
||||||
|
AmountCurrencyPipe,
|
||||||
|
MatMenuModule,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class ChargebacksComponent {
|
||||||
|
@Input() chargebacks: InvoicePaymentChargeback[];
|
||||||
|
@Input() paymentId: string;
|
||||||
|
@Input() invoiceId: string;
|
||||||
|
|
||||||
|
cols = new Columns('id', 'status', 'created_at', 'body', 'levy', 'stage', 'actions');
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private invoicingService: InvoicingService,
|
||||||
|
private baseDialogService: BaseDialogService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
changeStatus(id: string) {
|
||||||
|
this.baseDialogService.open(ChangeChargebackStatusDialogComponent, {
|
||||||
|
paymentId: this.paymentId,
|
||||||
|
invoiceId: this.invoiceId,
|
||||||
|
id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
showDetails(chargeback: InvoicePaymentChargeback) {
|
||||||
|
this.baseDialogService.open(DetailsDialogComponent, {
|
||||||
|
title: 'Chargeback details',
|
||||||
|
json: chargeback,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -9,15 +9,14 @@ import {
|
|||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { FormBuilder } from '@ngneat/reactive-forms';
|
import { FormBuilder } from '@ngneat/reactive-forms';
|
||||||
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
|
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
|
||||||
import { ClaimStatus } from '@vality/domain-proto/lib/claim_management';
|
|
||||||
import { coerceBoolean } from 'coerce-property';
|
import { coerceBoolean } from 'coerce-property';
|
||||||
import { debounceTime, map, take } from 'rxjs/operators';
|
import { debounceTime, map, take } from 'rxjs/operators';
|
||||||
|
|
||||||
import { queryParamsToFormValue } from '@cc/app/shared/components/claim-search-form/query-params-to-form-value';
|
import { CLAIM_STATUSES } from '@cc/app/api/claim-management';
|
||||||
import { removeEmptyProperties } from '@cc/utils/remove-empty-properties';
|
import { removeEmptyProperties } from '@cc/utils/remove-empty-properties';
|
||||||
|
|
||||||
import { ClaimSearchForm } from './claim-search-form';
|
import { ClaimSearchForm } from './claim-search-form';
|
||||||
import { formValueToSearchParams } from './form-value-to-search-params';
|
import { queryParamsToFormValue } from './query-params-to-form-value';
|
||||||
|
|
||||||
@UntilDestroy()
|
@UntilDestroy()
|
||||||
@Component({
|
@Component({
|
||||||
@ -35,14 +34,7 @@ export class ClaimSearchFormComponent implements OnInit {
|
|||||||
party_id: null,
|
party_id: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
claimStatuses: (keyof ClaimStatus)[] = [
|
claimStatuses = CLAIM_STATUSES;
|
||||||
'pending',
|
|
||||||
'review',
|
|
||||||
'accepted',
|
|
||||||
'denied',
|
|
||||||
'revoked',
|
|
||||||
'pending_acceptance',
|
|
||||||
];
|
|
||||||
|
|
||||||
constructor(private route: ActivatedRoute, private router: Router, private fb: FormBuilder) {}
|
constructor(private route: ActivatedRoute, private router: Router, private fb: FormBuilder) {}
|
||||||
|
|
||||||
@ -51,7 +43,7 @@ export class ClaimSearchFormComponent implements OnInit {
|
|||||||
.pipe(debounceTime(600), map(removeEmptyProperties), untilDestroyed(this))
|
.pipe(debounceTime(600), map(removeEmptyProperties), untilDestroyed(this))
|
||||||
.subscribe((value) => {
|
.subscribe((value) => {
|
||||||
void this.router.navigate([location.pathname], { queryParams: value });
|
void this.router.navigate([location.pathname], { queryParams: value });
|
||||||
this.valueChanges.emit(formValueToSearchParams(value));
|
this.valueChanges.emit(value as never);
|
||||||
});
|
});
|
||||||
this.route.queryParams
|
this.route.queryParams
|
||||||
.pipe(
|
.pipe(
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
import { PartyID } from '@vality/domain-proto';
|
import { PartyID } from '@vality/domain-proto';
|
||||||
import { ClaimID } from '@vality/domain-proto/lib/claim_management';
|
import { ClaimID, ClaimStatus } from '@vality/domain-proto/lib/claim_management';
|
||||||
|
|
||||||
import { ClaimStatus } from '@cc/app/api/claim-management';
|
|
||||||
|
|
||||||
export interface ClaimSearchForm {
|
export interface ClaimSearchForm {
|
||||||
claim_id: ClaimID;
|
claim_id: ClaimID;
|
||||||
statuses: ClaimStatus[];
|
statuses: (keyof ClaimStatus)[];
|
||||||
party_id: PartyID;
|
party_id: PartyID;
|
||||||
}
|
}
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
import pick from 'lodash-es/pick';
|
|
||||||
import pickBy from 'lodash-es/pickBy';
|
|
||||||
|
|
||||||
import { isNumeric } from '@cc/utils/is-numeric';
|
|
||||||
import { mapValuesToNumber } from '@cc/utils/map-values-to-number';
|
|
||||||
import { mapValuesToThriftEnum } from '@cc/utils/map-values-to-thrift-enum';
|
|
||||||
|
|
||||||
import { ClaimSearchForm } from './claim-search-form';
|
|
||||||
|
|
||||||
export const formValueToSearchParams = (params: any): ClaimSearchForm => ({
|
|
||||||
...params,
|
|
||||||
...mapValuesToThriftEnum(pick(params, 'statuses')),
|
|
||||||
...mapValuesToNumber(pickBy(params, isNumeric)),
|
|
||||||
});
|
|
@ -0,0 +1,3 @@
|
|||||||
|
<cc-base-dialog [title]="dialogData.title || 'Details'" noActions>
|
||||||
|
<cc-json-viewer [json]="dialogData.json"></cc-json-viewer>
|
||||||
|
</cc-base-dialog>
|
@ -0,0 +1,17 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { BaseDialogSuperclass, BaseDialogModule, DEFAULT_DIALOG_CONFIG } from '@vality/ng-core';
|
||||||
|
|
||||||
|
import { JsonViewerModule } from '@cc/app/shared/components/json-viewer';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
standalone: true,
|
||||||
|
selector: 'cc-details-dialog',
|
||||||
|
templateUrl: './details-dialog.component.html',
|
||||||
|
imports: [BaseDialogModule, JsonViewerModule],
|
||||||
|
})
|
||||||
|
export class DetailsDialogComponent extends BaseDialogSuperclass<
|
||||||
|
DetailsDialogComponent,
|
||||||
|
{ title?: string; json: unknown }
|
||||||
|
> {
|
||||||
|
static defaultDialogConfig = DEFAULT_DIALOG_CONFIG.large;
|
||||||
|
}
|
@ -3,15 +3,14 @@ import { MatSnackBar } from '@angular/material/snack-bar';
|
|||||||
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
|
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
|
||||||
import { PartyID } from '@vality/domain-proto';
|
import { PartyID } from '@vality/domain-proto';
|
||||||
import { coerceBoolean } from 'coerce-property';
|
import { coerceBoolean } from 'coerce-property';
|
||||||
import { BehaviorSubject, Observable, of, ReplaySubject, Subject } from 'rxjs';
|
import { BehaviorSubject, Observable, of, ReplaySubject, Subject, merge } from 'rxjs';
|
||||||
import { catchError, debounceTime, filter, first, map, switchMap } from 'rxjs/operators';
|
import { catchError, debounceTime, filter, map, switchMap, first, takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { DeanonimusService } from '@cc/app/thrift-services/deanonimus';
|
||||||
import { Option } from '@cc/components/select-search-field';
|
import { Option } from '@cc/components/select-search-field';
|
||||||
|
import { createControlProviders, ValidatedFormControlSuperclass } from '@cc/utils';
|
||||||
import { progressTo } from '@cc/utils/operators';
|
import { progressTo } from '@cc/utils/operators';
|
||||||
|
|
||||||
import { createControlProviders, ValidatedFormControlSuperclass } from '../../../../utils';
|
|
||||||
import { DeanonimusService } from '../../../thrift-services/deanonimus';
|
|
||||||
|
|
||||||
@UntilDestroy()
|
@UntilDestroy()
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'cc-merchant-field',
|
selector: 'cc-merchant-field',
|
||||||
@ -38,8 +37,10 @@ export class MerchantFieldComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.control.valueChanges.pipe(first()).subscribe((v) => this.searchChange$.next(v));
|
merge(
|
||||||
this.searchChange$
|
this.searchChange$,
|
||||||
|
this.control.valueChanges.pipe(filter(Boolean), first(), takeUntil(this.searchChange$))
|
||||||
|
)
|
||||||
.pipe(
|
.pipe(
|
||||||
filter(Boolean),
|
filter(Boolean),
|
||||||
debounceTime(600),
|
debounceTime(600),
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { StatPayment } from '@vality/magista-proto';
|
import { StatPayment } from '@vality/magista-proto';
|
||||||
import { cleanObject } from '@vality/ng-core';
|
import { cleanPrimitiveProps, clean } from '@vality/ng-core';
|
||||||
import * as moment from 'moment';
|
import * as moment from 'moment';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
@ -14,8 +14,6 @@ const SEARCH_LIMIT = 10;
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FetchPaymentsService extends PartialFetcher<StatPayment, SearchFiltersParams> {
|
export class FetchPaymentsService extends PartialFetcher<StatPayment, SearchFiltersParams> {
|
||||||
isLoading$ = this.doAction$;
|
|
||||||
|
|
||||||
constructor(private merchantStatisticsService: MerchantStatisticsService) {
|
constructor(private merchantStatisticsService: MerchantStatisticsService) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
@ -47,36 +45,33 @@ export class FetchPaymentsService extends PartialFetcher<StatPayment, SearchFilt
|
|||||||
} = params;
|
} = params;
|
||||||
return this.merchantStatisticsService
|
return this.merchantStatisticsService
|
||||||
.SearchPayments(
|
.SearchPayments(
|
||||||
cleanObject(
|
cleanPrimitiveProps({
|
||||||
{
|
common_search_query_params: clean({
|
||||||
common_search_query_params: {
|
from_time: moment(fromTime).utc().format(),
|
||||||
from_time: moment(fromTime).utc().format(),
|
to_time: moment(toTime).utc().format(),
|
||||||
to_time: moment(toTime).utc().format(),
|
limit: SEARCH_LIMIT,
|
||||||
limit: SEARCH_LIMIT,
|
continuation_token: continuationToken,
|
||||||
continuation_token: continuationToken,
|
party_id: partyID,
|
||||||
party_id: partyID,
|
shop_ids: shopIDs,
|
||||||
shop_ids: shopIDs,
|
}),
|
||||||
},
|
invoice_ids: clean([invoiceID], true),
|
||||||
invoice_ids: [invoiceID],
|
payment_params: clean({
|
||||||
payment_params: {
|
payment_status: paymentStatus,
|
||||||
payment_status: paymentStatus,
|
payment_tool: paymentMethod,
|
||||||
payment_tool: paymentMethod,
|
payment_email: payerEmail,
|
||||||
payment_email: payerEmail,
|
payment_first6: bin,
|
||||||
payment_first6: bin,
|
payment_system: { id: paymentSystem },
|
||||||
payment_system: { id: paymentSystem },
|
payment_last4: pan,
|
||||||
payment_last4: pan,
|
payment_provider_id: providerID,
|
||||||
payment_provider_id: providerID,
|
payment_terminal_id: terminalID,
|
||||||
payment_terminal_id: terminalID,
|
from_payment_domain_revision: domainRevisionFrom,
|
||||||
from_payment_domain_revision: domainRevisionFrom,
|
to_payment_domain_revision: domainRevisionTo,
|
||||||
to_payment_domain_revision: domainRevisionTo,
|
payment_rrn: rrn,
|
||||||
payment_rrn: rrn,
|
payment_amount_from: paymentAmountFrom,
|
||||||
payment_amount_from: paymentAmountFrom,
|
payment_amount_to: paymentAmountTo,
|
||||||
payment_amount_to: paymentAmountTo,
|
payment_token_provider: { id: tokenProvider },
|
||||||
payment_token_provider: { id: tokenProvider },
|
}),
|
||||||
},
|
})
|
||||||
},
|
|
||||||
['common_search_query_params', 'payment_params']
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
.pipe(
|
.pipe(
|
||||||
map(({ payments, continuation_token }) => ({
|
map(({ payments, continuation_token }) => ({
|
||||||
|
@ -14,16 +14,11 @@
|
|||||||
(valueChanges)="searchParamsChanges($event)"
|
(valueChanges)="searchParamsChanges($event)"
|
||||||
></cc-payments-other-search-filters>
|
></cc-payments-other-search-filters>
|
||||||
</cc-actions>
|
</cc-actions>
|
||||||
<!-- TODO: Remove params (and params?.partyID validation) when backend starts working without merchant -->
|
|
||||||
<cc-empty-search-result
|
<cc-empty-search-result
|
||||||
*ngIf="(!(isLoading$ | async) && (payments$ | async)?.length === 0) || !params?.partyID"
|
*ngIf="!(doAction$ | async) && !(payments$ | async)?.length"
|
||||||
[label]="params?.partyID ? 'Payments not found' : 'Merchant input field is required'"
|
label="Payments not found"
|
||||||
></cc-empty-search-result>
|
></cc-empty-search-result>
|
||||||
<mat-card
|
<mat-card *ngIf="(payments$ | async)?.length > 0" fxLayout="column" fxLayoutGap="16px">
|
||||||
*ngIf="(payments$ | async)?.length > 0 && params?.partyID"
|
|
||||||
fxLayout="column"
|
|
||||||
fxLayoutGap="16px"
|
|
||||||
>
|
|
||||||
<cc-payments-table
|
<cc-payments-table
|
||||||
[payments]="payments$ | async"
|
[payments]="payments$ | async"
|
||||||
(menuItemSelected$)="paymentMenuItemSelected($event)"
|
(menuItemSelected$)="paymentMenuItemSelected($event)"
|
||||||
|
@ -26,7 +26,6 @@ export class PaymentsSearcherComponent implements OnInit {
|
|||||||
@Output() searchParamsChanged$: EventEmitter<SearchFiltersParams> = new EventEmitter();
|
@Output() searchParamsChanged$: EventEmitter<SearchFiltersParams> = new EventEmitter();
|
||||||
@Output() paymentEventFired$: EventEmitter<PaymentMenuItemEvent> = new EventEmitter();
|
@Output() paymentEventFired$: EventEmitter<PaymentMenuItemEvent> = new EventEmitter();
|
||||||
|
|
||||||
isLoading$ = this.fetchPaymentsService.isLoading$;
|
|
||||||
doAction$ = this.fetchPaymentsService.doAction$;
|
doAction$ = this.fetchPaymentsService.doAction$;
|
||||||
payments$ = this.fetchPaymentsService.searchResult$;
|
payments$ = this.fetchPaymentsService.searchResult$;
|
||||||
hasMore$ = this.fetchPaymentsService.hasMore$;
|
hasMore$ = this.fetchPaymentsService.hasMore$;
|
||||||
@ -41,10 +40,7 @@ export class PaymentsSearcherComponent implements OnInit {
|
|||||||
.pipe(untilDestroyed(this))
|
.pipe(untilDestroyed(this))
|
||||||
.subscribe((params) => {
|
.subscribe((params) => {
|
||||||
this.params = params;
|
this.params = params;
|
||||||
// TODO: the partyID is optional, but the backend returns 500
|
this.fetchPaymentsService.search(params);
|
||||||
if (params.partyID) {
|
|
||||||
this.fetchPaymentsService.search(params);
|
|
||||||
}
|
|
||||||
this.searchParamsChanged$.emit(params);
|
this.searchParamsChanged$.emit(params);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
62
src/app/shared/pipes/amount-currency.pipe.ts
Normal file
62
src/app/shared/pipes/amount-currency.pipe.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { formatCurrency, getCurrencySymbol } from '@angular/common';
|
||||||
|
import { Pipe, Inject, LOCALE_ID, DEFAULT_CURRENCY_CODE, PipeTransform } from '@angular/core';
|
||||||
|
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
|
||||||
|
import round from 'lodash-es/round';
|
||||||
|
import { ReplaySubject, combineLatest } from 'rxjs';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { DomainStoreService } from '../../thrift-services/damsel/domain-store.service';
|
||||||
|
|
||||||
|
@UntilDestroy()
|
||||||
|
@Pipe({
|
||||||
|
standalone: true,
|
||||||
|
pure: false,
|
||||||
|
name: 'amountCurrency',
|
||||||
|
})
|
||||||
|
export class AmountCurrencyPipe implements PipeTransform {
|
||||||
|
private params$ = new ReplaySubject<{
|
||||||
|
amount: number;
|
||||||
|
currencyCode: string;
|
||||||
|
format: 'short' | 'long';
|
||||||
|
}>(1);
|
||||||
|
private latestValue: string = '';
|
||||||
|
private isInit = false;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(LOCALE_ID) private _locale: string,
|
||||||
|
@Inject(DEFAULT_CURRENCY_CODE) private _defaultCurrencyCode: string = 'USD',
|
||||||
|
private domainStoreService: DomainStoreService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.isInit = true;
|
||||||
|
combineLatest([this.domainStoreService.getObjects('currency'), this.params$])
|
||||||
|
.pipe(
|
||||||
|
map(([currencies, { amount, currencyCode, format }]) => {
|
||||||
|
const exponent = currencies.find((c) => c.data.symbolic_code === currencyCode)
|
||||||
|
.data.exponent;
|
||||||
|
return formatCurrency(
|
||||||
|
round(amount / 10 ** exponent, exponent),
|
||||||
|
this._locale,
|
||||||
|
getCurrencySymbol(currencyCode, 'narrow', this._locale),
|
||||||
|
currencyCode,
|
||||||
|
format === 'short' ? '0.0-2' : undefined
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
untilDestroyed(this)
|
||||||
|
)
|
||||||
|
.subscribe((value) => {
|
||||||
|
this.latestValue = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
transform(
|
||||||
|
amount: number,
|
||||||
|
currencyCode: string = this._defaultCurrencyCode,
|
||||||
|
format: 'short' | 'long' = 'long'
|
||||||
|
) {
|
||||||
|
this.params$.next({ amount, currencyCode, format });
|
||||||
|
if (!this.isInit) this.init();
|
||||||
|
return this.latestValue;
|
||||||
|
}
|
||||||
|
}
|
15
src/app/shared/pipes/enum-key.pipe.ts
Normal file
15
src/app/shared/pipes/enum-key.pipe.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
import isNil from 'lodash-es/isNil';
|
||||||
|
import { ValuesType } from 'utility-types';
|
||||||
|
|
||||||
|
import { getEnumKey } from '@cc/utils';
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
standalone: true,
|
||||||
|
name: 'enumKey',
|
||||||
|
})
|
||||||
|
export class EnumKeyPipe implements PipeTransform {
|
||||||
|
transform<E extends Record<PropertyKey, unknown>>(value: ValuesType<E>, enumObj: E): keyof E {
|
||||||
|
return isNil(value) ? '' : getEnumKey(enumObj, value);
|
||||||
|
}
|
||||||
|
}
|
13
src/app/shared/pipes/enum-keys.pipe.ts
Normal file
13
src/app/shared/pipes/enum-keys.pipe.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
|
||||||
|
import { getEnumKeys } from '@cc/utils';
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
standalone: true,
|
||||||
|
name: 'enumKeys',
|
||||||
|
})
|
||||||
|
export class EnumKeysPipe implements PipeTransform {
|
||||||
|
transform<E extends Record<PropertyKey, unknown>>(value: E): (keyof E)[] {
|
||||||
|
return getEnumKeys(value);
|
||||||
|
}
|
||||||
|
}
|
@ -2,3 +2,6 @@ export * from './thrift';
|
|||||||
export * from './common';
|
export * from './common';
|
||||||
export * from './api-model-types';
|
export * from './api-model-types';
|
||||||
export * from './value-type-title';
|
export * from './value-type-title';
|
||||||
|
export * from './amount-currency.pipe';
|
||||||
|
export * from './enum-keys.pipe';
|
||||||
|
export * from './enum-key.pipe';
|
||||||
|
@ -1,13 +1,18 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { DomainObject } from '@vality/domain-proto/lib/domain';
|
import { DomainObject } from '@vality/domain-proto/lib/domain';
|
||||||
import { Field } from '@vality/thrift-ts';
|
import { Field } from '@vality/thrift-ts';
|
||||||
import { from, Observable } from 'rxjs';
|
import { from, Observable, of } from 'rxjs';
|
||||||
import { map, shareReplay } from 'rxjs/operators';
|
import { map, shareReplay } from 'rxjs/operators';
|
||||||
|
import uuid from 'uuid';
|
||||||
|
|
||||||
import { ThriftAstMetadata } from '@cc/app/api/utils';
|
import { ThriftAstMetadata } from '@cc/app/api/utils';
|
||||||
|
|
||||||
import { DomainStoreService } from '../../../thrift-services/damsel/domain-store.service';
|
import { DomainStoreService } from '../../../thrift-services/damsel/domain-store.service';
|
||||||
import { MetadataFormData, MetadataFormExtension } from '../../components/metadata-form';
|
import {
|
||||||
|
MetadataFormData,
|
||||||
|
MetadataFormExtension,
|
||||||
|
isTypeWithAliases,
|
||||||
|
} from '../../components/metadata-form';
|
||||||
import { createDomainObjectExtension } from './utils/create-domain-object-extension';
|
import { createDomainObjectExtension } from './utils/create-domain-object-extension';
|
||||||
import {
|
import {
|
||||||
defaultDomainObjectToOption,
|
defaultDomainObjectToOption,
|
||||||
@ -24,7 +29,13 @@ export class DomainMetadataFormExtensionsService {
|
|||||||
(m) => m.default as never as ThriftAstMetadata[]
|
(m) => m.default as never as ThriftAstMetadata[]
|
||||||
)
|
)
|
||||||
).pipe(
|
).pipe(
|
||||||
map((metadata) => this.createDomainObjectsOptions(metadata)),
|
map((metadata) => [
|
||||||
|
...this.createDomainObjectsOptions(metadata),
|
||||||
|
{
|
||||||
|
determinant: (data) => of(isTypeWithAliases(data, 'ID', 'base')),
|
||||||
|
extension: () => of({ generate: () => of(uuid()), isIdentifier: true }),
|
||||||
|
},
|
||||||
|
]),
|
||||||
shareReplay(1)
|
shareReplay(1)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -17,10 +17,10 @@ import { switchMap } from 'rxjs/operators';
|
|||||||
import { KeycloakTokenInfoService } from '../../keycloak-token-info.service';
|
import { KeycloakTokenInfoService } from '../../keycloak-token-info.service';
|
||||||
import { ThriftService } from '../services/thrift/thrift-service';
|
import { ThriftService } from '../services/thrift/thrift-service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
/**
|
/**
|
||||||
* @deprecated use api/ClaimManagement service
|
* @deprecated use api/ClaimManagement service
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
|
||||||
export class ClaimManagementService extends ThriftService {
|
export class ClaimManagementService extends ThriftService {
|
||||||
constructor(zone: NgZone, keycloakTokenInfoService: KeycloakTokenInfoService) {
|
constructor(zone: NgZone, keycloakTokenInfoService: KeycloakTokenInfoService) {
|
||||||
super(zone, keycloakTokenInfoService, '/v1/cm', ClaimManagement);
|
super(zone, keycloakTokenInfoService, '/v1/cm', ClaimManagement);
|
||||||
|
@ -15,4 +15,7 @@ import { RoutingRulesModule } from './routing-rules';
|
|||||||
ClaimManagementService,
|
ClaimManagementService,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
export class DamselModule {}
|
export class DamselModule {}
|
||||||
|
@ -24,6 +24,9 @@ import { ThriftService } from '../services/thrift/thrift-service';
|
|||||||
import { createDamselInstance, damselInstanceToObject } from './utils/create-damsel-instance';
|
import { createDamselInstance, damselInstanceToObject } from './utils/create-damsel-instance';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
export class PaymentProcessingService extends ThriftService {
|
export class PaymentProcessingService extends ThriftService {
|
||||||
constructor(zone: NgZone, keycloakTokenInfoService: KeycloakTokenInfoService) {
|
constructor(zone: NgZone, keycloakTokenInfoService: KeycloakTokenInfoService) {
|
||||||
super(zone, keycloakTokenInfoService, '/v1/processing/invoicing', Invoicing);
|
super(zone, keycloakTokenInfoService, '/v1/processing/invoicing', Invoicing);
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
<cc-base-dialog [title]="title || 'Confirm this action'" noContent>
|
<cc-base-dialog [title]="dialogData?.['title'] ?? 'Confirm this action'" noContent>
|
||||||
<cc-base-dialog-actions>
|
<cc-base-dialog-actions>
|
||||||
<button mat-button (click)="cancel()">CANCEL</button>
|
<button mat-button (click)="cancel()">CANCEL</button>
|
||||||
<button color="primary" mat-raised-button (click)="confirm()">CONFIRM</button>
|
<button color="primary" mat-raised-button (click)="confirm()">
|
||||||
|
{{ dialogData?.['confirmLabel'] || 'CONFIRM' }}
|
||||||
|
</button>
|
||||||
</cc-base-dialog-actions>
|
</cc-base-dialog-actions>
|
||||||
</cc-base-dialog>
|
</cc-base-dialog>
|
||||||
|
@ -8,12 +8,8 @@ import { BaseDialogResponseStatus, BaseDialogSuperclass } from '@vality/ng-core'
|
|||||||
})
|
})
|
||||||
export class ConfirmActionDialogComponent extends BaseDialogSuperclass<
|
export class ConfirmActionDialogComponent extends BaseDialogSuperclass<
|
||||||
ConfirmActionDialogComponent,
|
ConfirmActionDialogComponent,
|
||||||
{ title?: string } | void
|
{ title?: string; confirmLabel?: string } | void
|
||||||
> {
|
> {
|
||||||
get title() {
|
|
||||||
return typeof this.dialogData === 'object' ? this.dialogData.title : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
cancel() {
|
cancel() {
|
||||||
this.dialogRef.close({ status: BaseDialogResponseStatus.Cancelled });
|
this.dialogRef.close({ status: BaseDialogResponseStatus.Cancelled });
|
||||||
}
|
}
|
||||||
|
@ -42,9 +42,12 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
<button *ngIf="selected$ | async" mat-icon-button matSuffix (click)="clear($event)">
|
<button *ngIf="selected$ | async" mat-icon-button matSuffix (click)="clear($event)">
|
||||||
<ng-container *ngIf="svgIcon; else defaultIcon">
|
<mat-icon *ngIf="!options?.length; else clearIcon">sync</mat-icon>
|
||||||
<mat-icon [svgIcon]="svgIcon"></mat-icon>
|
<ng-template #clearIcon>
|
||||||
</ng-container>
|
<ng-container *ngIf="svgIcon; else defaultIcon">
|
||||||
<ng-template #defaultIcon><mat-icon>close</mat-icon></ng-template>
|
<mat-icon [svgIcon]="svgIcon"></mat-icon>
|
||||||
|
</ng-container>
|
||||||
|
<ng-template #defaultIcon><mat-icon>close</mat-icon></ng-template>
|
||||||
|
</ng-template>
|
||||||
</button>
|
</button>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
@ -46,10 +46,10 @@ export class SelectSearchFieldComponent<Value>
|
|||||||
@Output() searchChange = new EventEmitter<string>();
|
@Output() searchChange = new EventEmitter<string>();
|
||||||
|
|
||||||
selectSearchControl = new FormControl<string>('');
|
selectSearchControl = new FormControl<string>('');
|
||||||
filteredOptions$: Observable<Option<Value>[]> = combineLatest(
|
filteredOptions$: Observable<Option<Value>[]> = combineLatest([
|
||||||
getFormValueChanges(this.selectSearchControl),
|
getFormValueChanges(this.selectSearchControl),
|
||||||
defer(() => this.options$)
|
defer(() => this.options$),
|
||||||
).pipe(map(([value, options]) => filterOptions(options, value)));
|
]).pipe(map(([value, options]) => filterOptions(options, value)));
|
||||||
selected$ = new BehaviorSubject<Value>(null);
|
selected$ = new BehaviorSubject<Value>(null);
|
||||||
cachedOption: Option<Value> = null;
|
cachedOption: Option<Value> = null;
|
||||||
|
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
export * from './table.module';
|
export * from './table.module';
|
||||||
export * from './select-column/select-column.component';
|
export * from './select-column/select-column.component';
|
||||||
|
export * from './types/columns';
|
||||||
|
6
src/components/table/menu-cell/menu-cell.component.html
Normal file
6
src/components/table/menu-cell/menu-cell.component.html
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<button [matMenuTriggerFor]="menu" mat-icon-button>
|
||||||
|
<mat-icon>more_vert</mat-icon>
|
||||||
|
</button>
|
||||||
|
<mat-menu #menu>
|
||||||
|
<ng-content></ng-content>
|
||||||
|
</mat-menu>
|
7
src/components/table/menu-cell/menu-cell.component.ts
Normal file
7
src/components/table/menu-cell/menu-cell.component.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'cc-menu-cell',
|
||||||
|
templateUrl: './menu-cell.component.html',
|
||||||
|
})
|
||||||
|
export class MenuCellComponent {}
|
@ -0,0 +1,5 @@
|
|||||||
|
<tr *matNoDataRow class="mat-row">
|
||||||
|
<td class="mat-cell cc-headline cc-secondary-text" colspan="9999" style="text-align: center">
|
||||||
|
No data
|
||||||
|
</td>
|
||||||
|
</tr>
|
27
src/components/table/no-data-row/no-data-row.component.ts
Normal file
27
src/components/table/no-data-row/no-data-row.component.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { SelectionModel } from '@angular/cdk/collections';
|
||||||
|
import { Component, ViewChild, Optional, OnInit, OnDestroy } from '@angular/core';
|
||||||
|
import { MatTable, MatNoDataRow } from '@angular/material/table';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'cc-no-data-row',
|
||||||
|
templateUrl: './no-data-row.component.html',
|
||||||
|
})
|
||||||
|
export class NoDataRowComponent<T> implements OnInit, OnDestroy {
|
||||||
|
@ViewChild(MatNoDataRow, { static: true }) matNoDataRow!: MatNoDataRow;
|
||||||
|
|
||||||
|
selection = new SelectionModel(true, []);
|
||||||
|
|
||||||
|
constructor(@Optional() public table: MatTable<T>) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
if (this.table) {
|
||||||
|
this.table.setNoDataRow(this.matNoDataRow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
if (this.table) {
|
||||||
|
this.table.setNoDataRow(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,9 +3,14 @@ import { NgModule } from '@angular/core';
|
|||||||
import { FlexModule } from '@angular/flex-layout';
|
import { FlexModule } from '@angular/flex-layout';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { MatMenuModule } from '@angular/material/menu';
|
||||||
import { MatSortModule } from '@angular/material/sort';
|
import { MatSortModule } from '@angular/material/sort';
|
||||||
import { MatTableModule } from '@angular/material/table';
|
import { MatTableModule } from '@angular/material/table';
|
||||||
|
|
||||||
|
import { MenuCellComponent } from '@cc/components/table/menu-cell/menu-cell.component';
|
||||||
|
|
||||||
|
import { NoDataRowComponent } from './no-data-row/no-data-row.component';
|
||||||
import { SelectColumnComponent } from './select-column/select-column.component';
|
import { SelectColumnComponent } from './select-column/select-column.component';
|
||||||
import { ShowMoreButtonComponent } from './show-more-button/show-more-button.component';
|
import { ShowMoreButtonComponent } from './show-more-button/show-more-button.component';
|
||||||
|
|
||||||
@ -17,8 +22,20 @@ import { ShowMoreButtonComponent } from './show-more-button/show-more-button.com
|
|||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
FlexModule,
|
FlexModule,
|
||||||
MatSortModule,
|
MatSortModule,
|
||||||
|
MatMenuModule,
|
||||||
|
MatIconModule,
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
SelectColumnComponent,
|
||||||
|
ShowMoreButtonComponent,
|
||||||
|
MenuCellComponent,
|
||||||
|
NoDataRowComponent,
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
SelectColumnComponent,
|
||||||
|
ShowMoreButtonComponent,
|
||||||
|
MenuCellComponent,
|
||||||
|
NoDataRowComponent,
|
||||||
],
|
],
|
||||||
declarations: [SelectColumnComponent, ShowMoreButtonComponent],
|
|
||||||
exports: [SelectColumnComponent, ShowMoreButtonComponent],
|
|
||||||
})
|
})
|
||||||
export class TableModule {}
|
export class TableModule {}
|
||||||
|
9
src/components/table/types/columns.ts
Normal file
9
src/components/table/types/columns.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export class Columns<T extends readonly string[]> {
|
||||||
|
list: T;
|
||||||
|
def: { [N in T[number]]: N };
|
||||||
|
|
||||||
|
constructor(...list: T) {
|
||||||
|
this.list = list;
|
||||||
|
this.def = Object.fromEntries(list.map((k) => [k, k])) as never;
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,13 @@ export function getEnumKeyValues<E extends Record<PropertyKey, unknown>>(srcEnum
|
|||||||
.map(([value, key]) => ({ key, value })) as { key: keyof E; value: ValuesType<E> }[];
|
.map(([value, key]) => ({ key, value })) as { key: keyof E; value: ValuesType<E> }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getEnumKey<E extends Record<PropertyKey, unknown>>(
|
||||||
|
srcEnum: E,
|
||||||
|
value: ValuesType<E>
|
||||||
|
): keyof E {
|
||||||
|
return getEnumKeyValues(srcEnum).find((e) => e.value === String(value)).key;
|
||||||
|
}
|
||||||
|
|
||||||
export function getEnumKeys<E extends Record<PropertyKey, unknown>>(srcEnum: E): (keyof E)[] {
|
export function getEnumKeys<E extends Record<PropertyKey, unknown>>(srcEnum: E): (keyof E)[] {
|
||||||
return Object.values(srcEnum).filter((v) => typeof v === 'string') as string[];
|
return Object.values(srcEnum).filter((v) => typeof v === 'string') as string[];
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
export * from './to-optional';
|
export * from './to-optional';
|
||||||
export * from './get-union-key';
|
export * from './get-union-key';
|
||||||
export * from './remove-empty-properties';
|
export * from './remove-empty-properties';
|
||||||
export * from './map-values-to-thrift-enum';
|
|
||||||
export * from './map-values-to-number';
|
|
||||||
export * from './is-numeric';
|
|
||||||
export * from './wrap-values-to-array';
|
export * from './wrap-values-to-array';
|
||||||
export * from './get-or';
|
export * from './get-or';
|
||||||
export * from './skip-null-values';
|
export * from './skip-null-values';
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
export const isNumeric = (x): boolean =>
|
|
||||||
(typeof x === 'number' || typeof x === 'string') && !isNaN(Number(x));
|
|
@ -1,4 +0,0 @@
|
|||||||
import toNumber from 'lodash-es/toNumber';
|
|
||||||
|
|
||||||
export const mapValuesToNumber = (obj: any): any =>
|
|
||||||
Object.entries(obj).reduce((acc, [k, v]) => ({ ...acc, [k]: toNumber(v) }), {});
|
|
@ -1,6 +0,0 @@
|
|||||||
// Thrift enum ex: [{ enumVal: {} }, ...]
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-return
|
|
||||||
const toThriftEnum = (arr: string[]) => arr.reduce((acc, cv) => [...acc, { [cv]: {} }], []);
|
|
||||||
|
|
||||||
export const mapValuesToThriftEnum = (obj: any): any =>
|
|
||||||
Object.entries(obj).reduce((acc, [k, v]) => ({ ...acc, [k]: toThriftEnum(v as string[]) }), {});
|
|
Loading…
Reference in New Issue
Block a user