diff --git a/package-lock.json b/package-lock.json index 97af5ad4f..f1ee6c5d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "@vality/fistful-proto": "2.0.1-6600be9.0", "@vality/machinegun-proto": "1.0.0", "@vality/magista-proto": "2.0.2-28d11b9.0", - "@vality/ng-core": "17.2.1-pr-61-d842667.0", + "@vality/ng-core": "17.2.1-pr-62-e7ae3c9.0", "@vality/ng-thrift": "17.0.1-pr-5-2ce0f11.0", "@vality/payout-manager-proto": "2.0.1-eb4091a.0", "@vality/repairer-proto": "2.0.2-07b73e9.0", @@ -6462,9 +6462,9 @@ "integrity": "sha512-BsDy5ejotfTtUlwuoX3kz+PYJ5NSTW6m5ZRGv+p5HaKXSjR7tserPdv0q133Wp4T+sg0ED0Qr9Peqsrn+9XlDQ==" }, "node_modules/@vality/ng-core": { - "version": "17.2.1-pr-61-d842667.0", - "resolved": "https://registry.npmjs.org/@vality/ng-core/-/ng-core-17.2.1-pr-61-d842667.0.tgz", - "integrity": "sha512-QquwzzrFUHfc9vixSsX1ZkjQ+lkYbtEgT0tlqp1BnJ2N/kSR9eWZSV3N488j0ZPnDoJmktnwmUjDituQN855QA==", + "version": "17.2.1-pr-62-e7ae3c9.0", + "resolved": "https://registry.npmjs.org/@vality/ng-core/-/ng-core-17.2.1-pr-62-e7ae3c9.0.tgz", + "integrity": "sha512-7x3TctfL6SAwmucbWo78i8Qk/ytC9qn4R2PXLxux1/6aHOaI+JB02Cv9zdTfXrKLQiPbzF/rAW/lbC8PtXVduQ==", "dependencies": { "@angular/material-date-fns-adapter": "^17.2.0", "@ng-matero/extensions": "^17.1.0", diff --git a/package.json b/package.json index 4b8fee72b..4c4ff11c6 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "@vality/fistful-proto": "2.0.1-6600be9.0", "@vality/machinegun-proto": "1.0.0", "@vality/magista-proto": "2.0.2-28d11b9.0", - "@vality/ng-core": "17.2.1-pr-61-d842667.0", + "@vality/ng-core": "17.2.1-pr-62-e7ae3c9.0", "@vality/ng-thrift": "17.0.1-pr-5-2ce0f11.0", "@vality/payout-manager-proto": "2.0.1-eb4091a.0", "@vality/repairer-proto": "2.0.2-07b73e9.0", diff --git a/src/app/api/dominator/dominator.service.ts b/src/app/api/dominator/dominator.service.ts index d0ce6b7d3..321008c45 100644 --- a/src/app/api/dominator/dominator.service.ts +++ b/src/app/api/dominator/dominator.service.ts @@ -24,7 +24,7 @@ export class DominatorService { map(toWachterHeaders('Dominator')), ); const metadata$ = from( - import('@vality/fistful-proto/metadata.json').then( + import('@vality/dominator-proto/metadata.json').then( (m) => m.default as ThriftAstMetadata[], ), ); diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 767c66c36..2f158b6e7 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { Component, isDevMode } from '@angular/core'; import { Link } from '@vality/ng-core'; import { KeycloakService } from 'keycloak-angular'; import sortBy from 'lodash-es/sortBy'; @@ -18,6 +18,7 @@ import { ROUTING_CONFIG as REPAIRING_ROUTING_CONFIG } from './sections/repairing import { ROUTING_CONFIG as PARTIES_ROUTING_CONFIG } from './sections/search-parties/routing-config'; import { SHOPS_ROUTING_CONFIG } from './sections/shops'; import { ROUTING_CONFIG as SOURCES_ROUTING_CONFIG } from './sections/sources/routing-config'; +import { ROUTING_CONFIG as TARIFFS_ROUTING_CONFIG } from './sections/tariffs/routing-config'; import { ROUTING_CONFIG as TERMINALS_ROUTING_CONFIG } from './sections/terminals'; import { ROUTING_CONFIG as WALLETS_ROUTING_CONFIG } from './sections/wallets/routing-config'; import { ROUTING_CONFIG as WITHDRAWALS_ROUTING_CONFIG } from './sections/withdrawals/routing-config'; @@ -80,6 +81,15 @@ export class AppComponent { url: '/sources', services: SOURCES_ROUTING_CONFIG.services, }, + ...(isDevMode() + ? [ + { + label: 'Tariffs', + url: '/tariffs', + services: TARIFFS_ROUTING_CONFIG.services, + }, + ] + : []), ], [ { @@ -103,41 +113,40 @@ export class AppComponent { services: CLAIMS_ROUTING_CONFIG.services, }, ], - sortBy( - [ - { - label: 'Payments', - url: '/payments', - services: PAYMENTS_ROUTING_CONFIG.services, - }, - { - label: 'Payouts', - url: '/payouts', - services: PAYOUTS_ROUTING_CONFIG.services, - }, - { - label: 'Chargebacks', - url: '/chargebacks', - services: WALLETS_ROUTING_CONFIG.services, - }, - { - label: 'Deposits', - url: '/deposits', - services: DEPOSITS_ROUTING_CONFIG.services, - }, - { - label: 'Withdrawals', - url: '/withdrawals', - services: WITHDRAWALS_ROUTING_CONFIG.services, - }, - ], - 'label', - ), + [ + { + label: 'Payments', + url: '/payments', + services: PAYMENTS_ROUTING_CONFIG.services, + }, + { + label: 'Payouts', + url: '/payouts', + services: PAYOUTS_ROUTING_CONFIG.services, + }, + { + label: 'Chargebacks', + url: '/chargebacks', + services: WALLETS_ROUTING_CONFIG.services, + }, + { + label: 'Deposits', + url: '/deposits', + services: DEPOSITS_ROUTING_CONFIG.services, + }, + { + label: 'Withdrawals', + url: '/withdrawals', + services: WITHDRAWALS_ROUTING_CONFIG.services, + }, + ], ]; - return menuItems.map((group) => - group.filter((item) => - this.appAuthGuardService.userHasSomeServiceMethods(item.services), - ), - ); + return menuItems + .map((group) => + group.filter((item) => + this.appAuthGuardService.userHasSomeServiceMethods(item.services), + ), + ) + .map((group) => sortBy(group, 'label')); } } diff --git a/src/app/sections/sections-routing.module.ts b/src/app/sections/sections-routing.module.ts index 960c81569..c63d25214 100644 --- a/src/app/sections/sections-routing.module.ts +++ b/src/app/sections/sections-routing.module.ts @@ -64,6 +64,10 @@ const ROUTES: Routes = [ path: 'shops', loadChildren: () => import('./shops').then((m) => m.ShopsModule), }, + { + path: 'tariffs', + loadChildren: () => import('./tariffs').then((m) => m.TariffsModule), + }, { path: '404', loadChildren: () => import('./not-found').then((m) => m.NotFoundModule), diff --git a/src/app/sections/tariffs/components/shops-tariffs/shops-tariffs.component.html b/src/app/sections/tariffs/components/shops-tariffs/shops-tariffs.component.html new file mode 100644 index 000000000..70fc6dda6 --- /dev/null +++ b/src/app/sections/tariffs/components/shops-tariffs/shops-tariffs.component.html @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + diff --git a/src/app/sections/tariffs/components/shops-tariffs/shops-tariffs.component.ts b/src/app/sections/tariffs/components/shops-tariffs/shops-tariffs.component.ts new file mode 100644 index 000000000..18a17458f --- /dev/null +++ b/src/app/sections/tariffs/components/shops-tariffs/shops-tariffs.component.ts @@ -0,0 +1,143 @@ +import { CommonModule } from '@angular/common'; +import { Component, DestroyRef, Inject, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { NonNullableFormBuilder, ReactiveFormsModule } from '@angular/forms'; +import { TermSetHierarchyRef } from '@vality/domain-proto/internal/domain'; +import { + CommonSearchQueryParams, + ShopSearchQuery, + ShopTermSet, +} from '@vality/dominator-proto/internal/dominator'; +import { + clean, + Column, + countChanged, + createControls, + debounceTimeWithFirst, + FiltersModule, + getValueChanges, + InputFieldModule, + ListFieldModule, + LoadOptions, + QueryParamsService, + TableModule, + UpdateOptions, +} from '@vality/ng-core'; +import { map, shareReplay } from 'rxjs/operators'; +import { Overwrite } from 'utility-types'; + +import { + createContractColumn, + createPartyColumn, + createShopColumn, + PageLayoutModule, + ShopFieldModule, +} from '@cc/app/shared'; +import { CurrencyFieldComponent } from '@cc/app/shared/components/currency-field'; +import { MerchantFieldModule } from '@cc/app/shared/components/merchant-field'; +import { DEBOUNCE_TIME_MS } from '@cc/app/tokens'; + +import { ShopsTariffsService } from './shops-tariffs.service'; + +type Params = Pick & + Overwrite< + Omit, + { term_sets_ids?: TermSetHierarchyRef['id'][] } + >; + +@Component({ + selector: 'cc-shops-tariffs', + standalone: true, + imports: [ + CommonModule, + PageLayoutModule, + TableModule, + InputFieldModule, + FiltersModule, + ReactiveFormsModule, + MerchantFieldModule, + ShopFieldModule, + ListFieldModule, + CurrencyFieldComponent, + ], + templateUrl: './shops-tariffs.component.html', +}) +export class ShopsTariffsComponent implements OnInit { + filtersForm = this.fb.group( + createControls({ + currencies: null, + party_id: null, + shop_ids: null, + term_sets_names: null, + term_sets_ids: null, + }), + ); + tariffs$ = this.shopsTariffsService.result$; + hasMore$ = this.shopsTariffsService.hasMore$; + isLoading$ = this.shopsTariffsService.isLoading$; + columns: Column[] = [ + createShopColumn('shop_id', (d) => d.owner_id), + createPartyColumn('owner_id'), + createContractColumn( + (d) => d.contract_id, + (d) => d.owner_id, + (d) => d.shop_id, + ), + { field: 'currency' }, + { + field: 'current_term_set', + formatter: (d) => d.current_term_set?.data?.name, + description: (d) => d.current_term_set?.data?.description, + tooltip: (d) => d.current_term_set, + }, + { + field: 'term_set_history', + formatter: (d) => d.term_set_history?.length, + tooltip: (d) => d.term_set_history, + }, + ]; + active$ = getValueChanges(this.filtersForm).pipe( + map((filters) => countChanged(this.initFiltersValue, filters)), + shareReplay({ refCount: true, bufferSize: 1 }), + ); + + private initFiltersValue = this.filtersForm.value; + + constructor( + private shopsTariffsService: ShopsTariffsService, + private fb: NonNullableFormBuilder, + private qp: QueryParamsService, + @Inject(DEBOUNCE_TIME_MS) private debounceTimeMs: number, + private dr: DestroyRef, + ) {} + + ngOnInit() { + this.filtersForm.patchValue(this.qp.params); + getValueChanges(this.filtersForm) + .pipe(debounceTimeWithFirst(this.debounceTimeMs), takeUntilDestroyed(this.dr)) + .subscribe((filters) => { + void this.qp.set(filters); + this.load(filters); + }); + } + + load(params: Params, options?: LoadOptions) { + const { currencies, term_sets_ids, ...otherParams } = params; + this.shopsTariffsService.load( + clean({ + common_search_query_params: { currencies }, + term_sets_ids: term_sets_ids?.map((id) => ({ id })), + ...otherParams, + }), + options, + ); + } + + update(options?: UpdateOptions) { + this.shopsTariffsService.reload(options); + } + + more() { + this.shopsTariffsService.more(); + } +} diff --git a/src/app/sections/tariffs/components/shops-tariffs/shops-tariffs.service.ts b/src/app/sections/tariffs/components/shops-tariffs/shops-tariffs.service.ts new file mode 100644 index 000000000..6a074da3f --- /dev/null +++ b/src/app/sections/tariffs/components/shops-tariffs/shops-tariffs.service.ts @@ -0,0 +1,43 @@ +import { Injectable } from '@angular/core'; +import { ShopSearchQuery, ShopTermSet } from '@vality/dominator-proto/internal/dominator'; +import { + FetchOptions, + FetchSuperclass, + handleError, + NotifyLogService, + clean, +} from '@vality/ng-core'; +import { map } from 'rxjs/operators'; + +import { DominatorService } from '@cc/app/api/dominator'; + +@Injectable({ + providedIn: 'root', +}) +export class ShopsTariffsService extends FetchSuperclass { + constructor( + private dominatorService: DominatorService, + private log: NotifyLogService, + ) { + super(); + } + + protected fetch(params: ShopSearchQuery, options: FetchOptions) { + return this.dominatorService + .SearchShopTermSets({ + ...params, + common_search_query_params: clean({ + continuation_token: options.continuationToken, + limit: options.size, + currencies: params?.common_search_query_params?.currencies, + }), + }) + .pipe( + map(({ terms, continuation_token }) => ({ + result: terms, + continuationToken: continuation_token, + })), + handleError(this.log.error), + ); + } +} diff --git a/src/app/sections/tariffs/index.ts b/src/app/sections/tariffs/index.ts new file mode 100644 index 000000000..4d934e2ca --- /dev/null +++ b/src/app/sections/tariffs/index.ts @@ -0,0 +1 @@ +export * from './tariffs.module'; diff --git a/src/app/sections/tariffs/routing-config.ts b/src/app/sections/tariffs/routing-config.ts new file mode 100644 index 000000000..65244fa38 --- /dev/null +++ b/src/app/sections/tariffs/routing-config.ts @@ -0,0 +1,5 @@ +import { Services, RoutingConfig } from '@cc/app/shared/services'; + +export const ROUTING_CONFIG: RoutingConfig = { + services: [Services.Dominator], +}; diff --git a/src/app/sections/tariffs/tariffs-routing.module.ts b/src/app/sections/tariffs/tariffs-routing.module.ts new file mode 100644 index 000000000..53fc98d8d --- /dev/null +++ b/src/app/sections/tariffs/tariffs-routing.module.ts @@ -0,0 +1,34 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +import { AppAuthGuardService } from '../../shared/services'; + +import { ShopsTariffsComponent } from './components/shops-tariffs/shops-tariffs.component'; +import { ROUTING_CONFIG } from './routing-config'; +import { TariffsComponent } from './tariffs.component'; + +@NgModule({ + imports: [ + RouterModule.forChild([ + { + path: '', + component: TariffsComponent, + canActivate: [AppAuthGuardService], + data: ROUTING_CONFIG, + children: [ + { + path: 'shops', + component: ShopsTariffsComponent, + }, + { + path: '', + redirectTo: 'shops', + pathMatch: 'full', + }, + ], + }, + ]), + ], + exports: [RouterModule], +}) +export class TariffsRoutingModule {} diff --git a/src/app/sections/tariffs/tariffs.component.html b/src/app/sections/tariffs/tariffs.component.html new file mode 100644 index 000000000..8bde70fb7 --- /dev/null +++ b/src/app/sections/tariffs/tariffs.component.html @@ -0,0 +1,15 @@ + + + + + diff --git a/src/app/sections/tariffs/tariffs.component.ts b/src/app/sections/tariffs/tariffs.component.ts new file mode 100644 index 000000000..25a2818b0 --- /dev/null +++ b/src/app/sections/tariffs/tariffs.component.ts @@ -0,0 +1,24 @@ +import { CommonModule } from '@angular/common'; +import { Component } from '@angular/core'; +import { MatSidenavModule } from '@angular/material/sidenav'; +import { RouterOutlet } from '@angular/router'; +import { Link, NavComponent } from '@vality/ng-core'; + +import { SidenavInfoService } from '@cc/app/shared/components/sidenav-info'; + +@Component({ + selector: 'cc-tariffs', + standalone: true, + imports: [CommonModule, RouterOutlet, MatSidenavModule, NavComponent], + templateUrl: './tariffs.component.html', +}) +export class TariffsComponent { + links: Link[] = [ + { + label: 'Shops', + url: 'shops', + }, + ]; + + constructor(public sidenavInfoService: SidenavInfoService) {} +} diff --git a/src/app/sections/tariffs/tariffs.module.ts b/src/app/sections/tariffs/tariffs.module.ts new file mode 100644 index 000000000..4fe09a6f2 --- /dev/null +++ b/src/app/sections/tariffs/tariffs.module.ts @@ -0,0 +1,10 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; + +import { ShopsTariffsComponent } from './components/shops-tariffs/shops-tariffs.component'; +import { TariffsRoutingModule } from './tariffs-routing.module'; + +@NgModule({ + imports: [CommonModule, TariffsRoutingModule, ShopsTariffsComponent], +}) +export class TariffsModule {} diff --git a/src/app/shared/services/app-auth-guard/services.ts b/src/app/shared/services/app-auth-guard/services.ts index 04a333a7b..a738c4bc9 100644 --- a/src/app/shared/services/app-auth-guard/services.ts +++ b/src/app/shared/services/app-auth-guard/services.ts @@ -8,4 +8,5 @@ export enum Services { Invoicing = 'Invoicing', RepairManagement = 'RepairManagement', PayoutManagement = 'PayoutManagement', + Dominator = 'Dominator', } diff --git a/src/app/shared/utils/table/create-contract-column.ts b/src/app/shared/utils/table/create-contract-column.ts new file mode 100644 index 000000000..251b17cc7 --- /dev/null +++ b/src/app/shared/utils/table/create-contract-column.ts @@ -0,0 +1,35 @@ +import { inject } from '@angular/core'; +import { ContractID, PartyID, ShopID } from '@vality/domain-proto/domain'; +import { Column, PossiblyAsync, getPossiblyAsyncObservable } from '@vality/ng-core'; +import { combineLatest } from 'rxjs'; +import { take } from 'rxjs/operators'; + +import { ShopContractCardComponent } from '../../components/shop-contract-card/shop-contract-card.component'; +import { SidenavInfoService } from '../../components/sidenav-info'; + +export function createContractColumn( + selectContractId: (d: T) => PossiblyAsync, + selectPartyId: (d: T) => PossiblyAsync, + selectShopId: (d: T) => PossiblyAsync, +): Column { + const sidenavInfoService = inject(SidenavInfoService); + + return { + field: 'contract', + header: 'Contract', + formatter: selectContractId, + click: (d) => { + combineLatest([ + getPossiblyAsyncObservable(selectPartyId(d)), + getPossiblyAsyncObservable(selectShopId(d)), + ]) + .pipe(take(1)) + .subscribe(([partyId, id]) => { + sidenavInfoService.toggle(ShopContractCardComponent, { + partyId, + id, + }); + }); + }, + }; +} diff --git a/src/app/shared/utils/table/create-shop-column.ts b/src/app/shared/utils/table/create-shop-column.ts index c1e124128..e4bb0e33f 100644 --- a/src/app/shared/utils/table/create-shop-column.ts +++ b/src/app/shared/utils/table/create-shop-column.ts @@ -2,9 +2,11 @@ import { inject } from '@angular/core'; import { PossiblyAsync, ColumnObject, getPossiblyAsyncObservable } from '@vality/ng-core'; import get from 'lodash-es/get'; import { combineLatest } from 'rxjs'; -import { map, switchMap } from 'rxjs/operators'; +import { map, switchMap, take } from 'rxjs/operators'; import { PartiesStoreService } from '../../../api/payment-processing'; +import { ShopCardComponent } from '../../components/shop-card/shop-card.component'; +import { SidenavInfoService } from '../../components/sidenav-info'; export function createShopColumn( field: ColumnObject['field'], @@ -16,7 +18,7 @@ export function createShopColumn( selectShopId = (d) => get(d, field); } const partiesStoreService = inject(PartiesStoreService); - // const sidenavInfoService = inject(SidenavInfoService); + const sidenavInfoService = inject(SidenavInfoService); return { field, header: 'Shop', @@ -31,14 +33,16 @@ export function createShopColumn( ), map(([party, shopId]) => party.shops.get(shopId).details.name), ), - // TODO - // click: (d) => { - // combineLatest([selectPartyId(d), selectShopId(d)]) - // .pipe(take(1)) - // .subscribe(([partyId, id]) => { - // sidenavInfoService.toggle(ShopCardComponent, { id, partyId }); - // }); - // }, + click: (d) => { + combineLatest([ + getPossiblyAsyncObservable(selectPartyId(d)), + getPossiblyAsyncObservable(selectShopId(d)), + ]) + .pipe(take(1)) + .subscribe(([partyId, id]) => { + sidenavInfoService.toggle(ShopCardComponent, { id, partyId }); + }); + }, ...params, } as ColumnObject; } diff --git a/src/app/shared/utils/table/index.ts b/src/app/shared/utils/table/index.ts index e813fb8a9..fde0d0b10 100644 --- a/src/app/shared/utils/table/index.ts +++ b/src/app/shared/utils/table/index.ts @@ -3,3 +3,4 @@ export * from './create-party-column'; export * from './create-shop-column'; export * from './create-predicate-column'; export * from './create-failure-column'; +export * from './create-contract-column';