From 7a946ecb0e80c9067f314eed8b3211c4628c783e Mon Sep 17 00:00:00 2001 From: Rinat Arsaev <11846445+A77AY@users.noreply.github.com> Date: Thu, 12 Feb 2026 22:45:56 +0700 Subject: [PATCH 01/20] fix party rr page --- .../routing-rules-list.component.html | 1 + .../party-routing-ruleset.component.ts | 69 ++++++++++--------- 2 files changed, 37 insertions(+), 33 deletions(-) diff --git a/src/app/parties/party/routing-rules/components/routing-rules-list/routing-rules-list.component.html b/src/app/parties/party/routing-rules/components/routing-rules-list/routing-rules-list.component.html index ddb6448fc..df152c828 100644 --- a/src/app/parties/party/routing-rules/components/routing-rules-list/routing-rules-list.component.html +++ b/src/app/parties/party/routing-rules/components/routing-rules-list/routing-rules-list.component.html @@ -2,6 +2,7 @@ [columns]="columns()" [data]="data" [progress]="progress" + [sort]="{ active: 'name', direction: 'asc' }" name="routingRulesList" standaloneFilter > diff --git a/src/app/parties/party/routing-rules/party-routing-ruleset/party-routing-ruleset.component.ts b/src/app/parties/party/routing-rules/party-routing-ruleset/party-routing-ruleset.component.ts index 8ee283096..dd63688e3 100644 --- a/src/app/parties/party/routing-rules/party-routing-ruleset/party-routing-ruleset.component.ts +++ b/src/app/parties/party/routing-rules/party-routing-ruleset/party-routing-ruleset.component.ts @@ -17,7 +17,7 @@ import { import { RoutingRulesStoreService } from '~/api/domain-config'; import { SidenavInfoService } from '~/components/sidenav-info'; import { DomainObjectCardComponent } from '~/components/thrift-api-crud/domain'; -import { createShopColumn, createWalletColumn } from '~/utils'; +import { createDomainObjectColumn, createShopColumn, createWalletColumn } from '~/utils'; import { RoutingRulesListItem } from '../components/routing-rules-list'; import { PartyDelegateRulesetsService } from '../party-delegate-rulesets'; @@ -56,43 +56,46 @@ export class PartyRoutingRulesetComponent { shareReplay({ refCount: true, bufferSize: 1 }), ); + private baseColumns: Column>[] = [ + createDomainObjectColumn((d) => ({ ref: { routing_rules: d.item?.ruleset } }), { + header: 'Delegate', + }), + ]; shopsDisplayedColumns: Column>[] = [ - { - field: 'id', - header: 'Delegate (Ruleset Ref ID)', - cell: (d) => ({ - value: d.item?.description || `#${d.item?.ruleset?.id}`, - description: d.item?.ruleset?.id, - click: () => this.navigateToDelegate(d.parentRefId, d.delegateIdx), - }), - }, - createShopColumn((d) => - this.partyRoutingRulesetService.partyID$.pipe( - map((partyId) => ({ - shopId: d.item?.allowed?.condition?.party?.definition?.shop_is, - partyId, - })), - ), + createShopColumn( + (d) => + this.partyRoutingRulesetService.partyID$.pipe( + map((partyId) => ({ + shopId: d.item?.allowed?.condition?.party?.definition?.shop_is, + partyId, + })), + ), + { + field: 'name', + cell: (d) => ({ + click: () => this.navigateToDelegate(d.parentRefId, d.delegateIdx), + }), + }, ), + ...this.baseColumns, ]; walletsDisplayedColumns: Column>[] = [ - { - field: 'id', - header: 'Delegate (Ruleset Ref ID)', - cell: (d) => ({ - value: d.item?.description || `#${d.item?.ruleset?.id}`, - description: d.item?.ruleset?.id, - click: () => this.navigateToDelegate(d.parentRefId, d.delegateIdx), - }), - }, - createWalletColumn((d) => - this.partyRoutingRulesetService.partyID$.pipe( - map((partyId) => ({ - id: d.item?.allowed?.condition?.party?.definition?.wallet_is, - partyId, - })), - ), + createWalletColumn( + (d) => + this.partyRoutingRulesetService.partyID$.pipe( + map((partyId) => ({ + id: d.item?.allowed?.condition?.party?.definition?.wallet_is, + partyId, + })), + ), + { + field: 'name', + cell: (d) => ({ + click: () => this.navigateToDelegate(d.parentRefId, d.delegateIdx), + }), + }, ), + ...this.baseColumns, ]; shopsData$ = this.partyRuleset$.pipe( filter(Boolean), From da094b5102b24af91e328ced3f0c23f4ac18d95b Mon Sep 17 00:00:00 2001 From: Rinat Arsaev <11846445+A77AY@users.noreply.github.com> Date: Thu, 12 Feb 2026 23:43:07 +0700 Subject: [PATCH 02/20] add simple mode --- .../lib/services/app-mode/app-mode.service.ts | 28 ++++++++++++++++ .../matez/src/lib/services/app-mode/index.ts | 1 + projects/matez/src/lib/services/index.ts | 1 + src/app/app.component.html | 33 +++++++++++++++---- src/app/app.component.ts | 6 ++++ .../party-routing-ruleset.component.ts | 8 +++-- 6 files changed, 68 insertions(+), 9 deletions(-) create mode 100644 projects/matez/src/lib/services/app-mode/app-mode.service.ts create mode 100644 projects/matez/src/lib/services/app-mode/index.ts diff --git a/projects/matez/src/lib/services/app-mode/app-mode.service.ts b/projects/matez/src/lib/services/app-mode/app-mode.service.ts new file mode 100644 index 000000000..d5ab2413d --- /dev/null +++ b/projects/matez/src/lib/services/app-mode/app-mode.service.ts @@ -0,0 +1,28 @@ +import { Injectable, computed, effect, signal } from '@angular/core'; + +type AppMode = 'simple' | 'advanced'; + +@Injectable({ + providedIn: 'root', +}) +export class AppModeService { + mode = signal(this.getInitialMode()); + + isSimple = computed(() => this.mode() === 'simple'); + isAdvanced = computed(() => this.mode() === 'advanced'); + + constructor() { + effect(() => { + localStorage.setItem('app-mode', this.mode()); + }); + } + + private getInitialMode(): AppMode { + const stored = localStorage.getItem('app-mode'); + return (stored as AppMode) || 'simple'; + } + + setMode(mode: AppMode): void { + this.mode.set(mode); + } +} diff --git a/projects/matez/src/lib/services/app-mode/index.ts b/projects/matez/src/lib/services/app-mode/index.ts new file mode 100644 index 000000000..258e19693 --- /dev/null +++ b/projects/matez/src/lib/services/app-mode/index.ts @@ -0,0 +1 @@ +export * from './app-mode.service'; diff --git a/projects/matez/src/lib/services/index.ts b/projects/matez/src/lib/services/index.ts index cd96d871a..142d41a01 100644 --- a/projects/matez/src/lib/services/index.ts +++ b/projects/matez/src/lib/services/index.ts @@ -5,3 +5,4 @@ export * from './query-params'; export * from './url.service'; export * from './toast'; export * from './theme'; +export * from './app-mode'; diff --git a/src/app/app.component.html b/src/app/app.component.html index 7c6ee2a75..05ab91d01 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -31,13 +31,22 @@ > {{ username().slice(0, 2).toUpperCase() }} - +
+ + +
+ + + + diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 84e0589d1..99a8b9004 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -13,6 +13,7 @@ import { MatTooltipModule } from '@angular/material/tooltip'; import { Router, RouterOutlet } from '@angular/router'; import { + AppModeService, BaseLink, CmdkModule, CmdkOption, @@ -228,6 +229,7 @@ export class AppComponent { sidenavInfoService = inject(SidenavInfoService); themeService = inject(ThemeService); + protected modeService = inject(AppModeService); links = createNavLinks(); username = this.keycloakUserService.username; @@ -282,6 +284,10 @@ export class AppComponent { } } + getModeIcon(mode = this.modeService.mode()): string { + return mode === 'advanced' ? 'school' : 'wand_stars'; + } + private registerConsoleUtils() { Object.assign(window as never as object, { ccSwitchLogging: () => { diff --git a/src/app/parties/party/routing-rules/party-routing-ruleset/party-routing-ruleset.component.ts b/src/app/parties/party/routing-rules/party-routing-ruleset/party-routing-ruleset.component.ts index dd63688e3..5a2e51820 100644 --- a/src/app/parties/party/routing-rules/party-routing-ruleset/party-routing-ruleset.component.ts +++ b/src/app/parties/party/routing-rules/party-routing-ruleset/party-routing-ruleset.component.ts @@ -1,12 +1,13 @@ import { combineLatest } from 'rxjs'; import { filter, first, map, shareReplay, startWith, switchMap, take } from 'rxjs/operators'; -import { Component, DestroyRef, inject } from '@angular/core'; -import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { Component, DestroyRef, Injector, inject } from '@angular/core'; +import { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop'; import { ActivatedRoute, Router } from '@angular/router'; import { RoutingDelegate } from '@vality/domain-proto/domain'; import { + AppModeService, Column, DialogResponseStatus, DialogService, @@ -44,6 +45,8 @@ export class PartyRoutingRulesetComponent { private sidenavInfoService = inject(SidenavInfoService); private log = inject(NotifyLogService); protected routingRulesTypeService = inject(RoutingRulesTypeService); + private appMode = inject(AppModeService); + private injector = inject(Injector); partyRuleset$ = this.partyRoutingRulesetService.partyRuleset$; partyID$ = this.partyRoutingRulesetService.partyID$; @@ -59,6 +62,7 @@ export class PartyRoutingRulesetComponent { private baseColumns: Column>[] = [ createDomainObjectColumn((d) => ({ ref: { routing_rules: d.item?.ruleset } }), { header: 'Delegate', + hidden: toObservable(this.appMode.isSimple, { injector: this.injector }), }), ]; shopsDisplayedColumns: Column>[] = [ From d21bbb309aae5c991062459ef74880d29a8be690 Mon Sep 17 00:00:00 2001 From: Rinat Arsaev <11846445+A77AY@users.noreply.github.com> Date: Wed, 18 Feb 2026 02:50:56 +0700 Subject: [PATCH 03/20] fix --- src/app/deposits/deposits.component.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/deposits/deposits.component.ts b/src/app/deposits/deposits.component.ts index 96de89378..888203721 100644 --- a/src/app/deposits/deposits.component.ts +++ b/src/app/deposits/deposits.component.ts @@ -26,7 +26,7 @@ import { import { getUnionKey } from '@vality/ng-thrift'; import { QueryDsl } from '~/api/fistful-stat'; -import { createCurrencyColumn } from '~/utils'; +import { createCurrencyColumn, createDomainObjectColumn } from '~/utils'; import { DATE_RANGE_DAYS, DEBOUNCE_TIME_MS } from '../tokens'; @@ -89,9 +89,9 @@ export class DepositsComponent implements OnInit { field: 'created_at', cell: { type: 'datetime' }, }, - { - field: 'destination_id', - }, + createDomainObjectColumn((d) => ({ ref: { wallet_config: { id: d.destination_id } } }), { + header: 'Destination', + }), { field: 'identity_id', }, From d6ea9a8746ffc398f10096de97ea9b138bb79aaf Mon Sep 17 00:00:00 2001 From: Rinat Arsaev <11846445+A77AY@users.noreply.github.com> Date: Wed, 18 Feb 2026 02:57:17 +0700 Subject: [PATCH 04/20] ren --- .../routing-rules/{routing-ruleset => candidates}/index.ts | 0 .../{routing-ruleset => candidates}/routing-config.ts | 0 .../routing-ruleset-routing.module.ts | 0 .../routing-ruleset.component.html | 0 .../routing-ruleset.component.ts | 0 .../{routing-ruleset => candidates}/routing-ruleset.module.ts | 0 .../{routing-ruleset => candidates}/routing-ruleset.service.ts | 0 .../party-delegate-rulesets-routing.module.ts | 2 +- 8 files changed, 1 insertion(+), 1 deletion(-) rename src/app/parties/party/routing-rules/{routing-ruleset => candidates}/index.ts (100%) rename src/app/parties/party/routing-rules/{routing-ruleset => candidates}/routing-config.ts (100%) rename src/app/parties/party/routing-rules/{routing-ruleset => candidates}/routing-ruleset-routing.module.ts (100%) rename src/app/parties/party/routing-rules/{routing-ruleset => candidates}/routing-ruleset.component.html (100%) rename src/app/parties/party/routing-rules/{routing-ruleset => candidates}/routing-ruleset.component.ts (100%) rename src/app/parties/party/routing-rules/{routing-ruleset => candidates}/routing-ruleset.module.ts (100%) rename src/app/parties/party/routing-rules/{routing-ruleset => candidates}/routing-ruleset.service.ts (100%) diff --git a/src/app/parties/party/routing-rules/routing-ruleset/index.ts b/src/app/parties/party/routing-rules/candidates/index.ts similarity index 100% rename from src/app/parties/party/routing-rules/routing-ruleset/index.ts rename to src/app/parties/party/routing-rules/candidates/index.ts diff --git a/src/app/parties/party/routing-rules/routing-ruleset/routing-config.ts b/src/app/parties/party/routing-rules/candidates/routing-config.ts similarity index 100% rename from src/app/parties/party/routing-rules/routing-ruleset/routing-config.ts rename to src/app/parties/party/routing-rules/candidates/routing-config.ts diff --git a/src/app/parties/party/routing-rules/routing-ruleset/routing-ruleset-routing.module.ts b/src/app/parties/party/routing-rules/candidates/routing-ruleset-routing.module.ts similarity index 100% rename from src/app/parties/party/routing-rules/routing-ruleset/routing-ruleset-routing.module.ts rename to src/app/parties/party/routing-rules/candidates/routing-ruleset-routing.module.ts diff --git a/src/app/parties/party/routing-rules/routing-ruleset/routing-ruleset.component.html b/src/app/parties/party/routing-rules/candidates/routing-ruleset.component.html similarity index 100% rename from src/app/parties/party/routing-rules/routing-ruleset/routing-ruleset.component.html rename to src/app/parties/party/routing-rules/candidates/routing-ruleset.component.html diff --git a/src/app/parties/party/routing-rules/routing-ruleset/routing-ruleset.component.ts b/src/app/parties/party/routing-rules/candidates/routing-ruleset.component.ts similarity index 100% rename from src/app/parties/party/routing-rules/routing-ruleset/routing-ruleset.component.ts rename to src/app/parties/party/routing-rules/candidates/routing-ruleset.component.ts diff --git a/src/app/parties/party/routing-rules/routing-ruleset/routing-ruleset.module.ts b/src/app/parties/party/routing-rules/candidates/routing-ruleset.module.ts similarity index 100% rename from src/app/parties/party/routing-rules/routing-ruleset/routing-ruleset.module.ts rename to src/app/parties/party/routing-rules/candidates/routing-ruleset.module.ts diff --git a/src/app/parties/party/routing-rules/routing-ruleset/routing-ruleset.service.ts b/src/app/parties/party/routing-rules/candidates/routing-ruleset.service.ts similarity index 100% rename from src/app/parties/party/routing-rules/routing-ruleset/routing-ruleset.service.ts rename to src/app/parties/party/routing-rules/candidates/routing-ruleset.service.ts diff --git a/src/app/parties/party/routing-rules/party-delegate-rulesets/party-delegate-rulesets-routing.module.ts b/src/app/parties/party/routing-rules/party-delegate-rulesets/party-delegate-rulesets-routing.module.ts index 65a285a9d..554c39de1 100644 --- a/src/app/parties/party/routing-rules/party-delegate-rulesets/party-delegate-rulesets-routing.module.ts +++ b/src/app/parties/party/routing-rules/party-delegate-rulesets/party-delegate-rulesets-routing.module.ts @@ -28,7 +28,7 @@ import { ROUTING_CONFIG } from './routing-config'; { path: '', loadChildren: () => - import('../routing-ruleset').then((m) => m.RoutingRulesetModule), + import('../candidates').then((m) => m.RoutingRulesetModule), }, ], }, From cade6dcaddc090942747096a1fcd523ec1b9af2b Mon Sep 17 00:00:00 2001 From: Rinat Arsaev <11846445+A77AY@users.noreply.github.com> Date: Wed, 18 Feb 2026 03:07:11 +0700 Subject: [PATCH 05/20] to candidates --- ...mponent.html => candidates.component.html} | 0 ...t.component.ts => candidates.component.ts} | 55 ++++++++++++++++--- ...leset.service.ts => candidates.service.ts} | 2 +- .../party/routing-rules/candidates/index.ts | 2 +- .../candidates/routing-config.ts | 5 -- .../routing-ruleset-routing.module.ts | 21 ------- .../candidates/routing-ruleset.module.ts | 55 ------------------- .../party-delegate-rulesets-routing.module.ts | 11 ++-- src/utils/thrift/provide-thrift-services.ts | 12 ++-- 9 files changed, 60 insertions(+), 103 deletions(-) rename src/app/parties/party/routing-rules/candidates/{routing-ruleset.component.html => candidates.component.html} (100%) rename src/app/parties/party/routing-rules/candidates/{routing-ruleset.component.ts => candidates.component.ts} (87%) rename src/app/parties/party/routing-rules/candidates/{routing-ruleset.service.ts => candidates.service.ts} (98%) delete mode 100644 src/app/parties/party/routing-rules/candidates/routing-config.ts delete mode 100644 src/app/parties/party/routing-rules/candidates/routing-ruleset-routing.module.ts delete mode 100644 src/app/parties/party/routing-rules/candidates/routing-ruleset.module.ts diff --git a/src/app/parties/party/routing-rules/candidates/routing-ruleset.component.html b/src/app/parties/party/routing-rules/candidates/candidates.component.html similarity index 100% rename from src/app/parties/party/routing-rules/candidates/routing-ruleset.component.html rename to src/app/parties/party/routing-rules/candidates/candidates.component.html diff --git a/src/app/parties/party/routing-rules/candidates/routing-ruleset.component.ts b/src/app/parties/party/routing-rules/candidates/candidates.component.ts similarity index 87% rename from src/app/parties/party/routing-rules/candidates/routing-ruleset.component.ts rename to src/app/parties/party/routing-rules/candidates/candidates.component.ts index 932796bb9..ab24e07d4 100644 --- a/src/app/parties/party/routing-rules/candidates/routing-ruleset.component.ts +++ b/src/app/parties/party/routing-rules/candidates/candidates.component.ts @@ -2,25 +2,43 @@ import cloneDeep from 'lodash-es/cloneDeep'; import { Observable, combineLatest, filter } from 'rxjs'; import { first, map, switchMap, take, withLatestFrom } from 'rxjs/operators'; +import { CommonModule } from '@angular/common'; import { Component, DestroyRef, inject } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { ReactiveFormsModule } from '@angular/forms'; +import { MatAutocompleteModule } from '@angular/material/autocomplete'; +import { MatButtonModule } from '@angular/material/button'; +import { MatCardModule } from '@angular/material/card'; +import { MatDialogModule } from '@angular/material/dialog'; +import { MatDividerModule } from '@angular/material/divider'; +import { MatExpansionModule } from '@angular/material/expansion'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatPaginatorModule } from '@angular/material/paginator'; +import { MatRadioModule } from '@angular/material/radio'; +import { MatSelectModule } from '@angular/material/select'; import { Sort } from '@angular/material/sort'; -import { ActivatedRoute } from '@angular/router'; +import { ActivatedRoute, RouterModule } from '@angular/router'; import { RoutingCandidate } from '@vality/domain-proto/domain'; import { Column, + DialogModule, DialogResponseStatus, DialogService, DragDrop, NotifyLogService, + TableModule, correctPriorities, createMenuColumn, } from '@vality/matez'; -import { toJson } from '@vality/ng-thrift'; +import { ThriftViewerModule, toJson } from '@vality/ng-thrift'; import { DomainService, RoutingRulesStoreService } from '~/api/domain-config'; import { CandidateCardComponent } from '~/components/candidate-card/candidate-card.component'; +import { PageLayoutModule } from '~/components/page-layout'; import { SidenavInfoService } from '~/components/sidenav-info'; import { DomainThriftFormDialogComponent, @@ -33,16 +51,37 @@ import { RoutingRulesService } from '../services/routing-rules'; import { RoutingRulesType } from '../types/routing-rules-type'; import { changeCandidatesAllowed } from '../utils/toggle-candidate-allowed'; -import { RoutingRulesetService } from './routing-ruleset.service'; +import { CandidatesService } from './candidates.service'; @Component({ - templateUrl: 'routing-ruleset.component.html', - providers: [RoutingRulesetService], - standalone: false, + templateUrl: 'candidates.component.html', + providers: [CandidatesService], + imports: [ + CommonModule, + MatButtonModule, + MatDialogModule, + MatDividerModule, + ReactiveFormsModule, + MatFormFieldModule, + MatInputModule, + RouterModule, + MatIconModule, + MatMenuModule, + MatPaginatorModule, + MatCardModule, + MatSelectModule, + MatRadioModule, + MatExpansionModule, + MatAutocompleteModule, + TableModule, + ThriftViewerModule, + DialogModule, + PageLayoutModule, + ], }) -export class RoutingRulesetComponent { +export class CandidatesComponent { private dialog = inject(DialogService); - private routingRulesetService = inject(RoutingRulesetService); + private routingRulesetService = inject(CandidatesService); private routingRulesService = inject(RoutingRulesService); private routingRulesStoreService = inject(RoutingRulesStoreService); private domainService = inject(DomainService); diff --git a/src/app/parties/party/routing-rules/candidates/routing-ruleset.service.ts b/src/app/parties/party/routing-rules/candidates/candidates.service.ts similarity index 98% rename from src/app/parties/party/routing-rules/candidates/routing-ruleset.service.ts rename to src/app/parties/party/routing-rules/candidates/candidates.service.ts index 874331f3a..68bcfb05e 100644 --- a/src/app/parties/party/routing-rules/candidates/routing-ruleset.service.ts +++ b/src/app/parties/party/routing-rules/candidates/candidates.service.ts @@ -15,7 +15,7 @@ import { import { RoutingRulesService as RoutingRulesDamselService } from '../services/routing-rules'; @Injectable() -export class RoutingRulesetService { +export class CandidatesService { private routingRulesService = inject(RoutingRulesDamselService); private route = inject(ActivatedRoute); private log = inject(NotifyLogService); diff --git a/src/app/parties/party/routing-rules/candidates/index.ts b/src/app/parties/party/routing-rules/candidates/index.ts index 9b2055501..9ed61249d 100644 --- a/src/app/parties/party/routing-rules/candidates/index.ts +++ b/src/app/parties/party/routing-rules/candidates/index.ts @@ -1 +1 @@ -export * from './routing-ruleset.module'; +export * from './candidates.component'; diff --git a/src/app/parties/party/routing-rules/candidates/routing-config.ts b/src/app/parties/party/routing-rules/candidates/routing-config.ts deleted file mode 100644 index fa8001e6f..000000000 --- a/src/app/parties/party/routing-rules/candidates/routing-config.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { RoutingConfig, Service } from '~/services'; - -export const ROUTING_CONFIG: RoutingConfig = { - services: [Service.DMT], -}; diff --git a/src/app/parties/party/routing-rules/candidates/routing-ruleset-routing.module.ts b/src/app/parties/party/routing-rules/candidates/routing-ruleset-routing.module.ts deleted file mode 100644 index ff5979916..000000000 --- a/src/app/parties/party/routing-rules/candidates/routing-ruleset-routing.module.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { NgModule } from '@angular/core'; -import { RouterModule } from '@angular/router'; - -import { canActivateAuthRole } from '~/services'; - -import { ROUTING_CONFIG } from './routing-config'; -import { RoutingRulesetComponent } from './routing-ruleset.component'; - -@NgModule({ - imports: [ - RouterModule.forChild([ - { - path: ':partyRefID/delegate/:refID', - component: RoutingRulesetComponent, - canActivate: [canActivateAuthRole], - data: ROUTING_CONFIG, - }, - ]), - ], -}) -export class RoutingRulesetRoutingModule {} diff --git a/src/app/parties/party/routing-rules/candidates/routing-ruleset.module.ts b/src/app/parties/party/routing-rules/candidates/routing-ruleset.module.ts deleted file mode 100644 index e5471d289..000000000 --- a/src/app/parties/party/routing-rules/candidates/routing-ruleset.module.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { CommonModule } from '@angular/common'; -import { NgModule } from '@angular/core'; -import { ReactiveFormsModule } from '@angular/forms'; -import { MatAutocompleteModule } from '@angular/material/autocomplete'; -import { MatButtonModule } from '@angular/material/button'; -import { MatCardModule } from '@angular/material/card'; -import { MatDialogModule } from '@angular/material/dialog'; -import { MatDividerModule } from '@angular/material/divider'; -import { MatExpansionModule } from '@angular/material/expansion'; -import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatIconModule } from '@angular/material/icon'; -import { MatInputModule } from '@angular/material/input'; -import { MatMenuModule } from '@angular/material/menu'; -import { MatPaginatorModule } from '@angular/material/paginator'; -import { MatRadioModule } from '@angular/material/radio'; -import { MatSelectModule } from '@angular/material/select'; -import { RouterModule } from '@angular/router'; - -import { DialogModule, TableModule } from '@vality/matez'; -import { ThriftViewerModule } from '@vality/ng-thrift'; - -import { PageLayoutModule } from '~/components/page-layout'; -import { DomainThriftViewerComponent } from '~/components/thrift-api-crud'; - -import { RoutingRulesetRoutingModule } from './routing-ruleset-routing.module'; -import { RoutingRulesetComponent } from './routing-ruleset.component'; - -@NgModule({ - imports: [ - RoutingRulesetRoutingModule, - CommonModule, - MatButtonModule, - MatDialogModule, - MatDividerModule, - ReactiveFormsModule, - MatFormFieldModule, - MatInputModule, - RouterModule, - MatIconModule, - MatMenuModule, - MatPaginatorModule, - MatCardModule, - MatSelectModule, - MatRadioModule, - MatExpansionModule, - MatAutocompleteModule, - TableModule, - DomainThriftViewerComponent, - ThriftViewerModule, - DialogModule, - PageLayoutModule, - ], - declarations: [RoutingRulesetComponent], -}) -export class RoutingRulesetModule {} diff --git a/src/app/parties/party/routing-rules/party-delegate-rulesets/party-delegate-rulesets-routing.module.ts b/src/app/parties/party/routing-rules/party-delegate-rulesets/party-delegate-rulesets-routing.module.ts index 554c39de1..7ab2ba883 100644 --- a/src/app/parties/party/routing-rules/party-delegate-rulesets/party-delegate-rulesets-routing.module.ts +++ b/src/app/parties/party/routing-rules/party-delegate-rulesets/party-delegate-rulesets-routing.module.ts @@ -1,7 +1,9 @@ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; -import { canActivateAuthRole } from '~/services'; +import { Service, canActivateAuthRole } from '~/services'; + +import { CandidatesComponent } from '../candidates'; import { PartyDelegateRulesetsComponent } from './party-delegate-rulesets.component'; import { ROUTING_CONFIG } from './routing-config'; @@ -26,9 +28,10 @@ import { ROUTING_CONFIG } from './routing-config'; ), }, { - path: '', - loadChildren: () => - import('../candidates').then((m) => m.RoutingRulesetModule), + path: ':partyRefID/delegate/:refID', + component: CandidatesComponent, + canActivate: [canActivateAuthRole], + data: { services: [Service.DMT] }, }, ], }, diff --git a/src/utils/thrift/provide-thrift-services.ts b/src/utils/thrift/provide-thrift-services.ts index 2cc0ca47a..cee4c9361 100644 --- a/src/utils/thrift/provide-thrift-services.ts +++ b/src/utils/thrift/provide-thrift-services.ts @@ -87,10 +87,8 @@ const logger: ConnectOptions['loggingFn'] = (params) => { if (LOGGING.fullLogging) { console.log('Arguments'); console.log(JSON.stringify(toJson(params.args), null, 2)); - - console.groupCollapsed('Headers'); - console.table(params.headers); - console.groupEnd(); + console.log('Headers'); + console.log(params.headers); } console.groupEnd(); return; @@ -102,10 +100,8 @@ const logger: ConnectOptions['loggingFn'] = (params) => { console.log(JSON.stringify(toJson(params.args), null, 2)); console.log('Response'); console.log(JSON.stringify(toJson(params.response), null, 2)); - - console.groupCollapsed('Headers'); - console.table(params.headers); - console.groupEnd(); + console.log('Headers'); + console.log(params.headers); console.groupEnd(); } return; From 4faf162b22198f4e353e3ec3838fb728a8b1c828 Mon Sep 17 00:00:00 2001 From: Rinat Arsaev <11846445+A77AY@users.noreply.github.com> Date: Wed, 18 Feb 2026 03:38:23 +0700 Subject: [PATCH 06/20] init cc-dnd-cards --- .../candidates/candidates.component.html | 22 ++++---- .../candidates/candidates.component.ts | 4 ++ .../components/dnd-cards.component.css | 24 +++++++++ .../components/dnd-cards.component.html | 42 +++++++++++++++ .../components/dnd-cards.component.ts | 53 +++++++++++++++++++ 5 files changed, 136 insertions(+), 9 deletions(-) create mode 100644 src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.css create mode 100644 src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.html create mode 100644 src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.ts diff --git a/src/app/parties/party/routing-rules/candidates/candidates.component.html b/src/app/parties/party/routing-rules/candidates/candidates.component.html index 98a085023..f69c8c7de 100644 --- a/src/app/parties/party/routing-rules/candidates/candidates.component.html +++ b/src/app/parties/party/routing-rules/candidates/candidates.component.html @@ -15,13 +15,17 @@ - + @if (appMode.isAdvanced()) { + + } @else { + + } diff --git a/src/app/parties/party/routing-rules/candidates/candidates.component.ts b/src/app/parties/party/routing-rules/candidates/candidates.component.ts index ab24e07d4..d8282adff 100644 --- a/src/app/parties/party/routing-rules/candidates/candidates.component.ts +++ b/src/app/parties/party/routing-rules/candidates/candidates.component.ts @@ -24,6 +24,7 @@ import { ActivatedRoute, RouterModule } from '@angular/router'; import { RoutingCandidate } from '@vality/domain-proto/domain'; import { + AppModeService, Column, DialogModule, DialogResponseStatus, @@ -52,6 +53,7 @@ import { RoutingRulesType } from '../types/routing-rules-type'; import { changeCandidatesAllowed } from '../utils/toggle-candidate-allowed'; import { CandidatesService } from './candidates.service'; +import { DndCardsComponent } from './components/dnd-cards.component'; @Component({ templateUrl: 'candidates.component.html', @@ -77,6 +79,7 @@ import { CandidatesService } from './candidates.service'; ThriftViewerModule, DialogModule, PageLayoutModule, + DndCardsComponent, ], }) export class CandidatesComponent { @@ -89,6 +92,7 @@ export class CandidatesComponent { private route = inject(ActivatedRoute); private sidenavInfoService = inject(SidenavInfoService); private destroyRef = inject(DestroyRef); + protected appMode = inject(AppModeService); ruleset$ = this.routingRulesetService.ruleset$; partyID$ = this.routingRulesetService.partyID$; diff --git a/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.css b/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.css new file mode 100644 index 000000000..553b566c6 --- /dev/null +++ b/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.css @@ -0,0 +1,24 @@ +.insert-zone { + width: 100%; + min-height: 24px; + border-radius: 4px; + transition: min-height 150ms ease; +} + +.cdk-drag-preview { + box-sizing: border-box; + border-radius: 4px; + box-shadow: + 0 5px 5px -3px rgba(0, 0, 0, 0.2), + 0 8px 10px 1px rgba(0, 0, 0, 0.14), + 0 3px 14px 2px rgba(0, 0, 0, 0.12); +} +.cdk-drag-placeholder { + background: rgba(63, 81, 181, 0.12); + border: 2px dashed rgba(63, 81, 181, 0.5); + border-radius: 4px; + box-sizing: border-box; +} +.cdk-drag-animating { + transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); +} diff --git a/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.html b/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.html new file mode 100644 index 000000000..bff178db3 --- /dev/null +++ b/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.html @@ -0,0 +1,42 @@ +
+
+ @for (row of rows; track $index) { +
+ @for (item of row; track item) { +
+ {{ item }} +
+ } +
+
+ } +
diff --git a/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.ts b/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.ts new file mode 100644 index 000000000..ff0abf7cc --- /dev/null +++ b/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.ts @@ -0,0 +1,53 @@ +import { + CdkDrag, + CdkDragDrop, + CdkDropList, + CdkDropListGroup, + moveItemInArray, + transferArrayItem, +} from '@angular/cdk/drag-drop'; +import { Component } from '@angular/core'; + +@Component({ + selector: 'cc-dnd-cards', + templateUrl: 'dnd-cards.component.html', + styleUrl: 'dnd-cards.component.css', + imports: [CdkDropListGroup, CdkDropList, CdkDrag], +}) +export class DndCardsComponent { + rows: string[][] = [ + ['Zero', 'One'], + ['Two'], + ['Three'], + ['Four', 'Five'], + ['Six', 'Seven', 'Eight', 'Nine'], + ]; + readonly zoneData: string[][] = Array.from({ length: 30 }, (): string[] => []); + isDragging = false; + + dropInRow(event: CdkDragDrop) { + if (event.previousContainer === event.container) { + moveItemInArray(event.container.data, event.previousIndex, event.currentIndex); + } else { + transferArrayItem( + event.previousContainer.data, + event.container.data, + event.previousIndex, + event.currentIndex, + ); + this.rows = this.rows.filter((r) => r.length > 0); + } + } + + dropInZone(event: CdkDragDrop, insertIndex: number) { + const source = event.previousContainer.data; + const [item] = source.splice(event.previousIndex, 1); + const sourceRowIndex = this.rows.indexOf(source); + let adjusted = insertIndex; + if (source.length === 0 && sourceRowIndex !== -1 && sourceRowIndex < insertIndex) { + adjusted--; + } + this.rows = this.rows.filter((r) => r.length > 0); + this.rows.splice(adjusted, 0, [item]); + } +} From a4aa17f2121dbc04e290cacfc21440488ceb4193 Mon Sep 17 00:00:00 2001 From: Rinat Arsaev <11846445+A77AY@users.noreply.github.com> Date: Wed, 18 Feb 2026 21:47:34 +0700 Subject: [PATCH 07/20] to card --- .../components/dnd-cards.component.css | 24 ----------------- .../components/dnd-cards.component.html | 21 ++++++++------- .../components/dnd-cards.component.scss | 26 +++++++++++++++++++ .../components/dnd-cards.component.ts | 6 +++-- 4 files changed, 41 insertions(+), 36 deletions(-) delete mode 100644 src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.css create mode 100644 src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.scss diff --git a/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.css b/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.css deleted file mode 100644 index 553b566c6..000000000 --- a/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.css +++ /dev/null @@ -1,24 +0,0 @@ -.insert-zone { - width: 100%; - min-height: 24px; - border-radius: 4px; - transition: min-height 150ms ease; -} - -.cdk-drag-preview { - box-sizing: border-box; - border-radius: 4px; - box-shadow: - 0 5px 5px -3px rgba(0, 0, 0, 0.2), - 0 8px 10px 1px rgba(0, 0, 0, 0.14), - 0 3px 14px 2px rgba(0, 0, 0, 0.12); -} -.cdk-drag-placeholder { - background: rgba(63, 81, 181, 0.12); - border: 2px dashed rgba(63, 81, 181, 0.5); - border-radius: 4px; - box-sizing: border-box; -} -.cdk-drag-animating { - transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); -} diff --git a/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.html b/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.html index bff178db3..5ccfee6a8 100644 --- a/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.html +++ b/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.html @@ -1,6 +1,6 @@
@@ -9,7 +9,7 @@ [class.active]="isDragging" cdkDropList cdkDropListSortingDisabled - class="insert-zone" + class="insert-zone-col" (cdkDropListDropped)="dropInZone($event, 0)" >
@for (row of rows; track $index) { @@ -18,16 +18,17 @@ [class.active]="isDragging" cdkDropList cdkDropListOrientation="horizontal" - class="row-zone flex flex-row flex-nowrap gap-[10px] min-h-[50px] overflow-x-auto" + class="flex flex-row gap-2" (cdkDropListDropped)="dropInRow($event)" > @for (item of row; track item) { -
- {{ item }} -
+ + +
+ {{ item }} +
+
+
}
} diff --git a/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.scss b/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.scss new file mode 100644 index 000000000..ef49a794e --- /dev/null +++ b/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.scss @@ -0,0 +1,26 @@ +.insert-zone-row, +.insert-zone-col { + min-height: 24px; +} + +.cdk-drag-preview { + opacity: 0.8; +} + +.cdk-drag-placeholder { + border-style: dashed; + + .content { + visibility: hidden; + } + + .insert-zone-row & { + margin: 6px 0; + height: 12px; + overflow: hidden; + } +} + +.cdk-drag-animating { + transition: transform 300ms cubic-bezier(0, 0, 0.2, 1); +} diff --git a/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.ts b/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.ts index ff0abf7cc..a96f836df 100644 --- a/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.ts +++ b/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.ts @@ -6,13 +6,15 @@ import { moveItemInArray, transferArrayItem, } from '@angular/cdk/drag-drop'; +import { CommonModule } from '@angular/common'; import { Component } from '@angular/core'; +import { MatCardModule } from '@angular/material/card'; @Component({ selector: 'cc-dnd-cards', templateUrl: 'dnd-cards.component.html', - styleUrl: 'dnd-cards.component.css', - imports: [CdkDropListGroup, CdkDropList, CdkDrag], + styleUrl: 'dnd-cards.component.scss', + imports: [CommonModule, CdkDropListGroup, CdkDropList, CdkDrag, MatCardModule], }) export class DndCardsComponent { rows: string[][] = [ From 912de1e072a311cbed15f597e575500be3332f45 Mon Sep 17 00:00:00 2001 From: Rinat Arsaev <11846445+A77AY@users.noreply.github.com> Date: Wed, 18 Feb 2026 22:47:03 +0700 Subject: [PATCH 08/20] candidateGroups --- .../candidates/candidates.component.html | 8 +++- .../candidates/candidates.component.ts | 17 +++++++- .../components/dnd-cards.component.html | 17 ++++---- .../components/dnd-cards.component.scss | 22 ++++++---- .../components/dnd-cards.component.ts | 41 +++++++++++-------- 5 files changed, 71 insertions(+), 34 deletions(-) diff --git a/src/app/parties/party/routing-rules/candidates/candidates.component.html b/src/app/parties/party/routing-rules/candidates/candidates.component.html index f69c8c7de..c14ef1eb8 100644 --- a/src/app/parties/party/routing-rules/candidates/candidates.component.html +++ b/src/app/parties/party/routing-rules/candidates/candidates.component.html @@ -26,6 +26,12 @@ (rowDropped)="drop($any($event))" > } @else { - + @if (candidateGroups$ | async; as groups) { + + + {{ candidate.terminal?.id }} {{ candidate.description }} + + + } } diff --git a/src/app/parties/party/routing-rules/candidates/candidates.component.ts b/src/app/parties/party/routing-rules/candidates/candidates.component.ts index d8282adff..46d81a94b 100644 --- a/src/app/parties/party/routing-rules/candidates/candidates.component.ts +++ b/src/app/parties/party/routing-rules/candidates/candidates.component.ts @@ -1,6 +1,6 @@ import cloneDeep from 'lodash-es/cloneDeep'; import { Observable, combineLatest, filter } from 'rxjs'; -import { first, map, switchMap, take, withLatestFrom } from 'rxjs/operators'; +import { first, map, shareReplay, switchMap, take, withLatestFrom } from 'rxjs/operators'; import { CommonModule } from '@angular/common'; import { Component, DestroyRef, inject } from '@angular/core'; @@ -103,6 +103,21 @@ export class CandidatesComponent { candidates$ = this.routingRulesetService.ruleset$.pipe(map((r) => r.data.decisions.candidates)); isLoading$ = this.routingRulesStoreService.isLoading$; + candidateGroups$ = this.candidates$.pipe( + map((candidates) => + candidates.reduce((groups, candidate) => { + if (groups.has(candidate.priority)) { + groups.get(candidate.priority)?.push(candidate); + } else { + groups.set(candidate.priority, [candidate]); + } + return groups; + }, new Map()), + ), + map((groups): RoutingCandidate[][] => Array.from(groups.values())), + shareReplay({ refCount: true, bufferSize: 1 }), + ); + columns: Column[] = [ { field: 'priority' }, { diff --git a/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.html b/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.html index 5ccfee6a8..407ea0611 100644 --- a/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.html +++ b/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.html @@ -5,38 +5,41 @@ (pointerup)="isDragging = false" >
- @for (row of rows; track $index) { + @for (row of rows(); track $index) {
@for (item of row; track item) {
- {{ item }} +
}
} diff --git a/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.scss b/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.scss index ef49a794e..1e65df142 100644 --- a/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.scss +++ b/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.scss @@ -1,6 +1,13 @@ -.insert-zone-row, -.insert-zone-col { - min-height: 24px; +$v-gap: 22px; +$h-gap: 12px; +$insert-card-margin: 2px; + +.card-wrapper { + gap: $h-gap; +} + +.insert-zone { + min-height: $v-gap; } .cdk-drag-preview { @@ -14,10 +21,11 @@ visibility: hidden; } - .insert-zone-row & { - margin: 6px 0; - height: 12px; - overflow: hidden; + .insert-zone & { + $height: $v-gap - ($insert-card-margin * 2); + + margin: $insert-card-margin 0; + height: $height; } } diff --git a/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.ts b/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.ts index a96f836df..892f5c0da 100644 --- a/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.ts +++ b/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.ts @@ -6,28 +6,31 @@ import { moveItemInArray, transferArrayItem, } from '@angular/cdk/drag-drop'; -import { CommonModule } from '@angular/common'; -import { Component } from '@angular/core'; +import { CommonModule, NgTemplateOutlet } from '@angular/common'; +import { Component, TemplateRef, contentChild, model } from '@angular/core'; import { MatCardModule } from '@angular/material/card'; @Component({ selector: 'cc-dnd-cards', templateUrl: 'dnd-cards.component.html', styleUrl: 'dnd-cards.component.scss', - imports: [CommonModule, CdkDropListGroup, CdkDropList, CdkDrag, MatCardModule], + imports: [ + CommonModule, + NgTemplateOutlet, + CdkDropListGroup, + CdkDropList, + CdkDrag, + MatCardModule, + ], }) -export class DndCardsComponent { - rows: string[][] = [ - ['Zero', 'One'], - ['Two'], - ['Three'], - ['Four', 'Five'], - ['Six', 'Seven', 'Eight', 'Nine'], - ]; - readonly zoneData: string[][] = Array.from({ length: 30 }, (): string[] => []); +export class DndCardsComponent { + rows = model.required(); + cardTpl = contentChild.required>('card'); isDragging = false; + zoneData = []; - dropInRow(event: CdkDragDrop) { + dropInRow(event: CdkDragDrop) { + const rows = this.rows(); if (event.previousContainer === event.container) { moveItemInArray(event.container.data, event.previousIndex, event.currentIndex); } else { @@ -37,19 +40,21 @@ export class DndCardsComponent { event.previousIndex, event.currentIndex, ); - this.rows = this.rows.filter((r) => r.length > 0); } + this.rows.set(rows.filter((r) => r.length > 0)); } - dropInZone(event: CdkDragDrop, insertIndex: number) { + dropInZone(event: CdkDragDrop, insertIndex: number) { + const rows = this.rows(); const source = event.previousContainer.data; const [item] = source.splice(event.previousIndex, 1); - const sourceRowIndex = this.rows.indexOf(source); + const sourceRowIndex = rows.indexOf(source); let adjusted = insertIndex; if (source.length === 0 && sourceRowIndex !== -1 && sourceRowIndex < insertIndex) { adjusted--; } - this.rows = this.rows.filter((r) => r.length > 0); - this.rows.splice(adjusted, 0, [item]); + const filtered = rows.filter((r) => r.length > 0); + filtered.splice(adjusted, 0, [item]); + this.rows.set(filtered); } } From 17a1451a3bd351ca964bc094f7d94c546c5399c9 Mon Sep 17 00:00:00 2001 From: Rinat Arsaev <11846445+A77AY@users.noreply.github.com> Date: Thu, 19 Feb 2026 00:27:33 +0700 Subject: [PATCH 09/20] add weight --- .../candidates/candidates.component.html | 13 ++++- .../candidates/candidates.component.ts | 50 +++++++++++++++---- .../components/dnd-cards.component.html | 11 ++-- .../components/dnd-cards.component.scss | 5 -- .../components/dnd-cards.component.ts | 13 +++-- 5 files changed, 69 insertions(+), 23 deletions(-) diff --git a/src/app/parties/party/routing-rules/candidates/candidates.component.html b/src/app/parties/party/routing-rules/candidates/candidates.component.html index c14ef1eb8..f29694e97 100644 --- a/src/app/parties/party/routing-rules/candidates/candidates.component.html +++ b/src/app/parties/party/routing-rules/candidates/candidates.component.html @@ -28,8 +28,17 @@ } @else { @if (candidateGroups$ | async; as groups) { - - {{ candidate.terminal?.id }} {{ candidate.description }} + + @if (!!item?.terminal) { +
+
+ {{ item.terminal.name }} +
+
{{ item.weightPercent }}%
+
+ } @else { + Loading... + }
} diff --git a/src/app/parties/party/routing-rules/candidates/candidates.component.ts b/src/app/parties/party/routing-rules/candidates/candidates.component.ts index 46d81a94b..b0202a6c2 100644 --- a/src/app/parties/party/routing-rules/candidates/candidates.component.ts +++ b/src/app/parties/party/routing-rules/candidates/candidates.component.ts @@ -20,9 +20,11 @@ import { MatPaginatorModule } from '@angular/material/paginator'; import { MatRadioModule } from '@angular/material/radio'; import { MatSelectModule } from '@angular/material/select'; import { Sort } from '@angular/material/sort'; +import { MatTooltipModule } from '@angular/material/tooltip'; import { ActivatedRoute, RouterModule } from '@angular/router'; import { RoutingCandidate } from '@vality/domain-proto/domain'; +import { LimitedVersionedObject } from '@vality/domain-proto/domain_config_v2'; import { AppModeService, Column, @@ -37,7 +39,11 @@ import { } from '@vality/matez'; import { ThriftViewerModule, toJson } from '@vality/ng-thrift'; -import { DomainService, RoutingRulesStoreService } from '~/api/domain-config'; +import { + DomainObjectsStoreService, + DomainService, + RoutingRulesStoreService, +} from '~/api/domain-config'; import { CandidateCardComponent } from '~/components/candidate-card/candidate-card.component'; import { PageLayoutModule } from '~/components/page-layout'; import { SidenavInfoService } from '~/components/sidenav-info'; @@ -80,6 +86,7 @@ import { DndCardsComponent } from './components/dnd-cards.component'; DialogModule, PageLayoutModule, DndCardsComponent, + MatTooltipModule, ], }) export class CandidatesComponent { @@ -93,6 +100,7 @@ export class CandidatesComponent { private sidenavInfoService = inject(SidenavInfoService); private destroyRef = inject(DestroyRef); protected appMode = inject(AppModeService); + protected domainObjectsStoreService = inject(DomainObjectsStoreService); ruleset$ = this.routingRulesetService.ruleset$; partyID$ = this.routingRulesetService.partyID$; @@ -103,18 +111,42 @@ export class CandidatesComponent { candidates$ = this.routingRulesetService.ruleset$.pipe(map((r) => r.data.decisions.candidates)); isLoading$ = this.routingRulesStoreService.isLoading$; - candidateGroups$ = this.candidates$.pipe( - map((candidates) => - candidates.reduce((groups, candidate) => { + candidateGroups$ = combineLatest([ + this.candidates$, + this.domainObjectsStoreService.getLimitedObjects('terminal').value$, + ]).pipe( + map(([candidates, terminals]) => { + const groups = candidates.reduce((groups, candidate) => { + const newItem = { + candidate, + terminal: terminals.find((t) => t.ref.terminal.id === candidate.terminal.id), + }; if (groups.has(candidate.priority)) { - groups.get(candidate.priority)?.push(candidate); + groups.get(candidate.priority)?.push(newItem); } else { - groups.set(candidate.priority, [candidate]); + groups.set(candidate.priority, [newItem]); } return groups; - }, new Map()), - ), - map((groups): RoutingCandidate[][] => Array.from(groups.values())), + }, new Map()); + return Array.from(groups.values()).map((group) => { + const sum = group.reduce((acc, item) => acc + (item.candidate.weight || 0), 0); + return group.map((item) => { + const weight = item.candidate.weight || 0; + let weightPercent = 0; + if (group.length === 1) { + weightPercent = 100; + } else if (sum > 0) { + weightPercent = Math.round((weight / sum) * 100); + } else { + weightPercent = 0; + } + return { + value: { ...item, weightPercent }, + width: weightPercent, + }; + }); + }); + }), shareReplay({ refCount: true, bufferSize: 1 }), ); diff --git a/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.html b/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.html index 407ea0611..ada42d498 100644 --- a/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.html +++ b/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.html @@ -18,16 +18,21 @@ [class.active]="isDragging" cdkDropList cdkDropListOrientation="horizontal" - class="card-wrapper flex flex-row" + class="flex flex-row overflow-auto gap-2" (cdkDropListDropped)="dropInRow($event)" > @for (item of row; track item) { - +
diff --git a/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.scss b/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.scss index 1e65df142..923c80e58 100644 --- a/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.scss +++ b/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.scss @@ -1,11 +1,6 @@ $v-gap: 22px; -$h-gap: 12px; $insert-card-margin: 2px; -.card-wrapper { - gap: $h-gap; -} - .insert-zone { min-height: $v-gap; } diff --git a/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.ts b/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.ts index 892f5c0da..dea628666 100644 --- a/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.ts +++ b/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.ts @@ -10,6 +10,11 @@ import { CommonModule, NgTemplateOutlet } from '@angular/common'; import { Component, TemplateRef, contentChild, model } from '@angular/core'; import { MatCardModule } from '@angular/material/card'; +export interface Item { + value: T; + width: number; +} + @Component({ selector: 'cc-dnd-cards', templateUrl: 'dnd-cards.component.html', @@ -24,12 +29,12 @@ import { MatCardModule } from '@angular/material/card'; ], }) export class DndCardsComponent { - rows = model.required(); + rows = model.required[][]>(); cardTpl = contentChild.required>('card'); isDragging = false; - zoneData = []; + zoneData: Item[] = []; - dropInRow(event: CdkDragDrop) { + dropInRow(event: CdkDragDrop[]>) { const rows = this.rows(); if (event.previousContainer === event.container) { moveItemInArray(event.container.data, event.previousIndex, event.currentIndex); @@ -44,7 +49,7 @@ export class DndCardsComponent { this.rows.set(rows.filter((r) => r.length > 0)); } - dropInZone(event: CdkDragDrop, insertIndex: number) { + dropInZone(event: CdkDragDrop[]>, insertIndex: number) { const rows = this.rows(); const source = event.previousContainer.data; const [item] = source.splice(event.previousIndex, 1); From ed9047995474a042e416297961f6cd535740baba Mon Sep 17 00:00:00 2001 From: Rinat Arsaev <11846445+A77AY@users.noreply.github.com> Date: Thu, 19 Feb 2026 02:32:58 +0700 Subject: [PATCH 10/20] show allowed --- .../candidates/candidates.component.html | 17 ++++-- .../candidates/candidates.component.ts | 53 ++++++++++++++----- .../components/dnd-cards.component.html | 7 +-- 3 files changed, 58 insertions(+), 19 deletions(-) diff --git a/src/app/parties/party/routing-rules/candidates/candidates.component.html b/src/app/parties/party/routing-rules/candidates/candidates.component.html index f29694e97..d56e10a1f 100644 --- a/src/app/parties/party/routing-rules/candidates/candidates.component.html +++ b/src/app/parties/party/routing-rules/candidates/candidates.component.html @@ -30,11 +30,22 @@ @if (!!item?.terminal) { -
+
- {{ item.terminal.name }} + {{ item.terminal?.object?.terminal?.data?.name }} +
+
+ {{ item.weightPercent }}% +
+
+ {{ + item.candidate.allowed ? 'Allowed' : 'Disallowed' + }}
-
{{ item.weightPercent }}%
} @else { Loading... diff --git a/src/app/parties/party/routing-rules/candidates/candidates.component.ts b/src/app/parties/party/routing-rules/candidates/candidates.component.ts index b0202a6c2..51d3947a2 100644 --- a/src/app/parties/party/routing-rules/candidates/candidates.component.ts +++ b/src/app/parties/party/routing-rules/candidates/candidates.component.ts @@ -3,7 +3,7 @@ import { Observable, combineLatest, filter } from 'rxjs'; import { first, map, shareReplay, switchMap, take, withLatestFrom } from 'rxjs/operators'; import { CommonModule } from '@angular/common'; -import { Component, DestroyRef, inject } from '@angular/core'; +import { Component, DestroyRef, Injector, inject, runInInjectionContext } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { ReactiveFormsModule } from '@angular/forms'; import { MatAutocompleteModule } from '@angular/material/autocomplete'; @@ -19,12 +19,13 @@ import { MatMenuModule } from '@angular/material/menu'; import { MatPaginatorModule } from '@angular/material/paginator'; import { MatRadioModule } from '@angular/material/radio'; import { MatSelectModule } from '@angular/material/select'; +import { MatSlideToggleModule } from '@angular/material/slide-toggle'; import { Sort } from '@angular/material/sort'; import { MatTooltipModule } from '@angular/material/tooltip'; import { ActivatedRoute, RouterModule } from '@angular/router'; import { RoutingCandidate } from '@vality/domain-proto/domain'; -import { LimitedVersionedObject } from '@vality/domain-proto/domain_config_v2'; +import { VersionedObject } from '@vality/domain-proto/domain_config_v2'; import { AppModeService, Column, @@ -52,7 +53,12 @@ import { UpdateThriftDialogComponent, } from '~/components/thrift-api-crud'; import { DomainObjectCardComponent } from '~/components/thrift-api-crud/domain'; -import { createDomainObjectColumn, createPredicateColumn, getPredicateBoolean } from '~/utils'; +import { + createDomainObjectColumn, + createPredicateColumn, + formatPredicate, + getPredicateBoolean, +} from '~/utils'; import { RoutingRulesService } from '../services/routing-rules'; import { RoutingRulesType } from '../types/routing-rules-type'; @@ -87,6 +93,7 @@ import { DndCardsComponent } from './components/dnd-cards.component'; PageLayoutModule, DndCardsComponent, MatTooltipModule, + MatSlideToggleModule, ], }) export class CandidatesComponent { @@ -101,6 +108,7 @@ export class CandidatesComponent { private destroyRef = inject(DestroyRef); protected appMode = inject(AppModeService); protected domainObjectsStoreService = inject(DomainObjectsStoreService); + private injector = inject(Injector); ruleset$ = this.routingRulesetService.ruleset$; partyID$ = this.routingRulesetService.partyID$; @@ -113,13 +121,30 @@ export class CandidatesComponent { candidateGroups$ = combineLatest([ this.candidates$, - this.domainObjectsStoreService.getLimitedObjects('terminal').value$, + this.candidates$.pipe( + switchMap((candidates) => + this.domainService.get( + candidates.map((c) => ({ terminal: { id: c.terminal.id } })), + ), + ), + ), + this.routingRulesType$, ]).pipe( - map(([candidates, terminals]) => { - const groups = candidates.reduce((groups, candidate) => { + map(([candidates, terminals, type]) => { + const groups = candidates.reduce((groups, candidate, idx) => { + const terminal = terminals.find( + (t) => t.object.terminal.ref.id === candidate.terminal.id, + ); + const terms = terminal?.object?.terminal?.data?.terms; const newItem = { + idx, candidate, - terminal: terminals.find((t) => t.ref.terminal.id === candidate.terminal.id), + terminal, + globalAllow: formatPredicate( + type === RoutingRulesType.Payment + ? terms?.payments?.global_allow + : terms?.wallet?.withdrawals?.global_allow, + ), }; if (groups.has(candidate.priority)) { groups.get(candidate.priority)?.push(newItem); @@ -127,7 +152,7 @@ export class CandidatesComponent { groups.set(candidate.priority, [newItem]); } return groups; - }, new Map()); + }, new Map()); return Array.from(groups.values()).map((group) => { const sum = group.reduce((acc, item) => acc + (item.candidate.weight || 0), 0); return group.map((item) => { @@ -359,11 +384,13 @@ export class CandidatesComponent { } toggleAllow(candidateIdx: number) { - this.routingRulesetService.refID$ - .pipe(first(), takeUntilDestroyed(this.destroyRef)) - .subscribe((refId) => { - changeCandidatesAllowed([{ refId, candidateIdx }]); - }); + runInInjectionContext(this.injector, () => + this.routingRulesetService.refID$ + .pipe(first(), takeUntilDestroyed(this.destroyRef)) + .subscribe((refId) => { + changeCandidatesAllowed([{ refId, candidateIdx }]); + }), + ); } removeRule(idx: number) { diff --git a/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.html b/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.html index ada42d498..3097a36d1 100644 --- a/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.html +++ b/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.html @@ -18,18 +18,19 @@ [class.active]="isDragging" cdkDropList cdkDropListOrientation="horizontal" - class="flex flex-row overflow-auto gap-2" + class="flex flex-row gap-2 overflow-auto" (cdkDropListDropped)="dropInRow($event)" > @for (item of row; track item) { -
+
Date: Thu, 19 Feb 2026 02:54:18 +0700 Subject: [PATCH 11/20] show allowed --- .../candidates/candidates.component.html | 4 +- .../candidates/candidates.component.ts | 111 ++++++++++++------ 2 files changed, 76 insertions(+), 39 deletions(-) diff --git a/src/app/parties/party/routing-rules/candidates/candidates.component.html b/src/app/parties/party/routing-rules/candidates/candidates.component.html index d56e10a1f..52ece6bfa 100644 --- a/src/app/parties/party/routing-rules/candidates/candidates.component.html +++ b/src/app/parties/party/routing-rules/candidates/candidates.component.html @@ -39,10 +39,10 @@
{{ - item.candidate.allowed ? 'Allowed' : 'Disallowed' + item.fullAllowedStr }}
diff --git a/src/app/parties/party/routing-rules/candidates/candidates.component.ts b/src/app/parties/party/routing-rules/candidates/candidates.component.ts index 51d3947a2..f0396b054 100644 --- a/src/app/parties/party/routing-rules/candidates/candidates.component.ts +++ b/src/app/parties/party/routing-rules/candidates/candidates.component.ts @@ -1,3 +1,4 @@ +import { isNil, upperFirst } from 'lodash-es'; import cloneDeep from 'lodash-es/cloneDeep'; import { Observable, combineLatest, filter } from 'rxjs'; import { first, map, shareReplay, switchMap, take, withLatestFrom } from 'rxjs/operators'; @@ -24,7 +25,7 @@ import { Sort } from '@angular/material/sort'; import { MatTooltipModule } from '@angular/material/tooltip'; import { ActivatedRoute, RouterModule } from '@angular/router'; -import { RoutingCandidate } from '@vality/domain-proto/domain'; +import { Predicate, RoutingCandidate } from '@vality/domain-proto/domain'; import { VersionedObject } from '@vality/domain-proto/domain_config_v2'; import { AppModeService, @@ -67,6 +68,14 @@ import { changeCandidatesAllowed } from '../utils/toggle-candidate-allowed'; import { CandidatesService } from './candidates.service'; import { DndCardsComponent } from './components/dnd-cards.component'; +function getAllowStr(predicate: Predicate, defaultStr = 'allowed') { + if (isNil(predicate)) return defaultStr; + const allowed = formatPredicate(predicate).toLowerCase(); + if (allowed === 'true') return 'allowed'; + if (allowed === 'false') return 'denied'; + return allowed; +} + @Component({ templateUrl: 'candidates.component.html', providers: [CandidatesService], @@ -131,46 +140,74 @@ export class CandidatesComponent { this.routingRulesType$, ]).pipe( map(([candidates, terminals, type]) => { - const groups = candidates.reduce((groups, candidate, idx) => { - const terminal = terminals.find( - (t) => t.object.terminal.ref.id === candidate.terminal.id, - ); - const terms = terminal?.object?.terminal?.data?.terms; - const newItem = { - idx, - candidate, - terminal, - globalAllow: formatPredicate( + const groups = candidates.reduce( + (groups, candidate, idx) => { + const terminal = terminals.find( + (t) => t.object.terminal.ref.id === candidate.terminal.id, + ); + const terms = terminal?.object?.terminal?.data?.terms; + const globalAllowPredicate = type === RoutingRulesType.Payment ? terms?.payments?.global_allow - : terms?.wallet?.withdrawals?.global_allow, - ), - }; - if (groups.has(candidate.priority)) { - groups.get(candidate.priority)?.push(newItem); - } else { - groups.set(candidate.priority, [newItem]); - } - return groups; - }, new Map()); - return Array.from(groups.values()).map((group) => { - const sum = group.reduce((acc, item) => acc + (item.candidate.weight || 0), 0); - return group.map((item) => { - const weight = item.candidate.weight || 0; - let weightPercent = 0; - if (group.length === 1) { - weightPercent = 100; - } else if (sum > 0) { - weightPercent = Math.round((weight / sum) * 100); + : terms?.wallet?.withdrawals?.global_allow; + const newItem = { + idx, + candidate, + terminal, + + allowed: getPredicateBoolean(candidate.allowed), + globalAllow: getPredicateBoolean(globalAllowPredicate), + + fullAllowedStr: [ + upperFirst(getAllowStr(candidate.allowed)), + getAllowStr(globalAllowPredicate, '') && + `(Global ${upperFirst(getAllowStr(globalAllowPredicate))})`, + ] + .filter(Boolean) + .join(' '), + }; + if (groups.has(candidate.priority)) { + groups.get(candidate.priority)?.push(newItem); } else { - weightPercent = 0; + groups.set(candidate.priority, [newItem]); } - return { - value: { ...item, weightPercent }, - width: weightPercent, - }; - }); - }); + return groups; + }, + new Map< + number, + { + idx: number; + candidate: RoutingCandidate; + terminal: VersionedObject; + allowed: boolean; + globalAllow: boolean; + fullAllowedStr: string; + }[] + >(), + ); + return Array.from(groups.values()) + .map((group) => { + const sum = group.reduce((acc, item) => acc + (item.candidate.weight || 0), 0); + return group.map((item) => { + const weight = item.candidate.weight || 0; + let weightPercent = 0; + if (group.length === 1) { + weightPercent = 100; + } else if (sum > 0) { + weightPercent = Math.round((weight / sum) * 100); + } else { + weightPercent = 0; + } + return { + value: { ...item, weightPercent }, + width: weightPercent, + }; + }); + }) + .sort( + (a, b) => + (b[0].value.candidate.priority || 0) - (a[0].value.candidate.priority || 0), + ); }), shareReplay({ refCount: true, bufferSize: 1 }), ); From 8a5c09654330a4ac6a7a01877c65a0169a095b59 Mon Sep 17 00:00:00 2001 From: Rinat Arsaev <11846445+A77AY@users.noreply.github.com> Date: Thu, 19 Feb 2026 03:13:25 +0700 Subject: [PATCH 12/20] add disabled state --- .../candidates/candidates.component.html | 12 +++++---- .../candidates/candidates.component.ts | 1 + .../components/dnd-cards.component.html | 1 + .../components/dnd-cards.component.scss | 4 +++ .../components/dnd-cards.component.ts | 25 ++++++++++++++++--- 5 files changed, 34 insertions(+), 9 deletions(-) diff --git a/src/app/parties/party/routing-rules/candidates/candidates.component.html b/src/app/parties/party/routing-rules/candidates/candidates.component.html index 52ece6bfa..0da0decd6 100644 --- a/src/app/parties/party/routing-rules/candidates/candidates.component.html +++ b/src/app/parties/party/routing-rules/candidates/candidates.component.html @@ -31,11 +31,13 @@ @if (!!item?.terminal) {
-
- {{ item.terminal?.object?.terminal?.data?.name }} -
-
- {{ item.weightPercent }}% +
+
+ {{ item.terminal?.object?.terminal?.data?.name }} +
+
+ {{ item.weightPercent }}% +
@for (item of row; track item) { { value: T; - width: number; + width?: number; + disabled?: boolean; } @Component({ @@ -34,6 +35,15 @@ export class DndCardsComponent { isDragging = false; zoneData: Item[] = []; + constructor() { + effect(() => { + const rows = this.rows(); + const sorted = rows.map((r) => this.sortRow(r)); + const changed = sorted.some((r, i) => r.some((item, j) => item !== rows[i][j])); + if (changed) untracked(() => this.rows.set(sorted)); + }); + } + dropInRow(event: CdkDragDrop[]>) { const rows = this.rows(); if (event.previousContainer === event.container) { @@ -46,7 +56,7 @@ export class DndCardsComponent { event.currentIndex, ); } - this.rows.set(rows.filter((r) => r.length > 0)); + this.rows.set(rows.filter((r) => r.length > 0).map((r) => this.sortRow(r))); } dropInZone(event: CdkDragDrop[]>, insertIndex: number) { @@ -60,6 +70,13 @@ export class DndCardsComponent { } const filtered = rows.filter((r) => r.length > 0); filtered.splice(adjusted, 0, [item]); - this.rows.set(filtered); + this.rows.set(filtered.map((r) => this.sortRow(r))); + } + + private sortRow(row: Item[]): Item[] { + return [...row].sort((a, b) => { + if (a.disabled !== b.disabled) return a.disabled ? 1 : -1; + return (b.width ?? 0) - (a.width ?? 0); + }); } } From d2fb2c655cd1e5df3be4b6ce709cf2131b5d8127 Mon Sep 17 00:00:00 2001 From: Rinat Arsaev <11846445+A77AY@users.noreply.github.com> Date: Thu, 19 Feb 2026 04:06:10 +0700 Subject: [PATCH 13/20] add menu --- .../candidates/candidates.component.html | 33 ++++++++++++++++--- .../candidates/candidates.component.ts | 11 +++++-- .../components/dnd-cards.component.html | 5 ++- 3 files changed, 40 insertions(+), 9 deletions(-) diff --git a/src/app/parties/party/routing-rules/candidates/candidates.component.html b/src/app/parties/party/routing-rules/candidates/candidates.component.html index 0da0decd6..859dabd43 100644 --- a/src/app/parties/party/routing-rules/candidates/candidates.component.html +++ b/src/app/parties/party/routing-rules/candidates/candidates.component.html @@ -32,21 +32,46 @@ @if (!!item?.terminal) {
-
+
{{ item.terminal?.object?.terminal?.data?.name }}
-
+
{{ item.weightPercent }}%
-
+
{{ + >{{ item.fullAllowedStr }} + + + + + +
} @else { diff --git a/src/app/parties/party/routing-rules/candidates/candidates.component.ts b/src/app/parties/party/routing-rules/candidates/candidates.component.ts index 6442ce9e5..aa2799323 100644 --- a/src/app/parties/party/routing-rules/candidates/candidates.component.ts +++ b/src/app/parties/party/routing-rules/candidates/candidates.component.ts @@ -187,11 +187,18 @@ export class CandidatesComponent { ); return Array.from(groups.values()) .map((group) => { - const sum = group.reduce((acc, item) => acc + (item.candidate.weight || 0), 0); + const sum = group.reduce( + (acc, item) => + acc + (item.candidate.allowed ? item.candidate.weight || 0 : 0), + 0, + ); + const allowedCount = group.filter((item) => item.allowed).length; return group.map((item) => { const weight = item.candidate.weight || 0; let weightPercent = 0; - if (group.length === 1) { + if (item.allowed === false) { + weightPercent = 0; + } else if (allowedCount === 1) { weightPercent = 100; } else if (sum > 0) { weightPercent = Math.round((weight / sum) * 100); diff --git a/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.html b/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.html index 2569503f8..759506c45 100644 --- a/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.html +++ b/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.html @@ -24,11 +24,10 @@ @for (item of row; track item) {
From f8fbbd1e2d5e7cc6ee7a370ddd5d9311ceff33a1 Mon Sep 17 00:00:00 2001 From: Rinat Arsaev <11846445+A77AY@users.noreply.github.com> Date: Thu, 19 Feb 2026 04:20:53 +0700 Subject: [PATCH 14/20] clean --- .../candidates/candidates.component.html | 19 +++++++++++++------ .../candidates/candidates.component.ts | 13 +++++++------ .../components/dnd-cards.component.html | 4 ++-- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/app/parties/party/routing-rules/candidates/candidates.component.html b/src/app/parties/party/routing-rules/candidates/candidates.component.html index 859dabd43..f236912d1 100644 --- a/src/app/parties/party/routing-rules/candidates/candidates.component.html +++ b/src/app/parties/party/routing-rules/candidates/candidates.component.html @@ -30,7 +30,9 @@ @if (!!item?.terminal) { -
+
+ @if (!!item?.fullAllowedStr) { +
+ {{ item.fullAllowedStr }} +
+ }
{{ - item.fullAllowedStr - }} + > diff --git a/src/app/parties/party/routing-rules/candidates/candidates.component.ts b/src/app/parties/party/routing-rules/candidates/candidates.component.ts index aa2799323..e219358b3 100644 --- a/src/app/parties/party/routing-rules/candidates/candidates.component.ts +++ b/src/app/parties/party/routing-rules/candidates/candidates.component.ts @@ -68,11 +68,10 @@ import { changeCandidatesAllowed } from '../utils/toggle-candidate-allowed'; import { CandidatesService } from './candidates.service'; import { DndCardsComponent } from './components/dnd-cards.component'; -function getAllowStr(predicate: Predicate, defaultStr = 'allowed') { - if (isNil(predicate)) return defaultStr; +function getAllowStr(predicate: Predicate, hasTrueFalse = false): string { const allowed = formatPredicate(predicate).toLowerCase(); - if (allowed === 'true') return 'allowed'; - if (allowed === 'false') return 'denied'; + if (isNil(predicate) || allowed === 'true') return hasTrueFalse ? 'allowed' : ''; + if (allowed === 'false') return hasTrueFalse ? 'denied' : ''; return allowed; } @@ -159,8 +158,10 @@ export class CandidatesComponent { globalAllow: getPredicateBoolean(globalAllowPredicate), fullAllowedStr: [ - upperFirst(getAllowStr(candidate.allowed)), - getAllowStr(globalAllowPredicate, '') && + upperFirst( + getAllowStr(candidate.allowed, !!getAllowStr(globalAllowPredicate)), + ), + getAllowStr(globalAllowPredicate) && `(Global ${upperFirst(getAllowStr(globalAllowPredicate))})`, ] .filter(Boolean) diff --git a/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.html b/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.html index 759506c45..0a89b255a 100644 --- a/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.html +++ b/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.html @@ -29,8 +29,8 @@ cdkDrag class="cursor-move flex-1 min-w-[200px]" > - -
+ +
Date: Thu, 19 Feb 2026 04:31:16 +0700 Subject: [PATCH 15/20] clean --- .../routing-rules/candidates/candidates.component.html | 9 +++++++-- .../candidates/components/dnd-cards.component.html | 6 +++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/app/parties/party/routing-rules/candidates/candidates.component.html b/src/app/parties/party/routing-rules/candidates/candidates.component.html index f236912d1..d39a01784 100644 --- a/src/app/parties/party/routing-rules/candidates/candidates.component.html +++ b/src/app/parties/party/routing-rules/candidates/candidates.component.html @@ -35,13 +35,18 @@ >
{{ item.terminal?.object?.terminal?.data?.name }}
{{ item.weightPercent }}% diff --git a/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.html b/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.html index 0a89b255a..87843605f 100644 --- a/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.html +++ b/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.html @@ -24,13 +24,13 @@ @for (item of row; track item) { -
+
Date: Thu, 19 Feb 2026 04:35:24 +0700 Subject: [PATCH 16/20] fix min-w --- .../candidates/components/dnd-cards.component.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.html b/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.html index 87843605f..5b74c25d4 100644 --- a/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.html +++ b/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.html @@ -25,9 +25,10 @@
From b82eaa7e78b2fa9438690f7848364fce53e7c195 Mon Sep 17 00:00:00 2001 From: Rinat Arsaev <11846445+A77AY@users.noreply.github.com> Date: Fri, 20 Feb 2026 00:56:46 +0700 Subject: [PATCH 17/20] change priority --- .../candidates/candidates.component.html | 2 +- .../candidates/candidates.component.ts | 145 ++++++++++++------ .../components/dnd-cards.component.ts | 17 +- 3 files changed, 114 insertions(+), 50 deletions(-) diff --git a/src/app/parties/party/routing-rules/candidates/candidates.component.html b/src/app/parties/party/routing-rules/candidates/candidates.component.html index d39a01784..0dd97db53 100644 --- a/src/app/parties/party/routing-rules/candidates/candidates.component.html +++ b/src/app/parties/party/routing-rules/candidates/candidates.component.html @@ -27,7 +27,7 @@ > } @else { @if (candidateGroups$ | async; as groups) { - + @if (!!item?.terminal) {
(undefined); + ruleset$ = this.routingRulesetService.ruleset$; partyID$ = this.routingRulesetService.partyID$; partyRulesetRefID$ = this.routingRulesetService.partyRulesetRefID$; @@ -127,7 +137,7 @@ export class CandidatesComponent { candidates$ = this.routingRulesetService.ruleset$.pipe(map((r) => r.data.decisions.candidates)); isLoading$ = this.routingRulesStoreService.isLoading$; - candidateGroups$ = combineLatest([ + candidateGroups$: Observable[][]> = combineLatest([ this.candidates$, this.candidates$.pipe( switchMap((candidates) => @@ -137,55 +147,43 @@ export class CandidatesComponent { ), ), this.routingRulesType$, + this.updateCandidateGroups$, ]).pipe( map(([candidates, terminals, type]) => { - const groups = candidates.reduce( - (groups, candidate, idx) => { - const terminal = terminals.find( - (t) => t.object.terminal.ref.id === candidate.terminal.id, - ); - const terms = terminal?.object?.terminal?.data?.terms; - const globalAllowPredicate = - type === RoutingRulesType.Payment - ? terms?.payments?.global_allow - : terms?.wallet?.withdrawals?.global_allow; - const newItem = { - idx, - candidate, - terminal, + const groups = candidates.reduce((groups, candidate, idx) => { + const terminal = terminals.find( + (t) => t.object.terminal.ref.id === candidate.terminal.id, + ); + const terms = terminal?.object?.terminal?.data?.terms; + const globalAllowPredicate = + type === RoutingRulesType.Payment + ? terms?.payments?.global_allow + : terms?.wallet?.withdrawals?.global_allow; + const newItem = { + idx, + candidate, + terminal, - allowed: getPredicateBoolean(candidate.allowed), - globalAllow: getPredicateBoolean(globalAllowPredicate), + allowed: getPredicateBoolean(candidate.allowed), + globalAllow: getPredicateBoolean(globalAllowPredicate), - fullAllowedStr: [ - upperFirst( - getAllowStr(candidate.allowed, !!getAllowStr(globalAllowPredicate)), - ), - getAllowStr(globalAllowPredicate) && - `(Global ${upperFirst(getAllowStr(globalAllowPredicate))})`, - ] - .filter(Boolean) - .join(' '), - }; - if (groups.has(candidate.priority)) { - groups.get(candidate.priority)?.push(newItem); - } else { - groups.set(candidate.priority, [newItem]); - } - return groups; - }, - new Map< - number, - { - idx: number; - candidate: RoutingCandidate; - terminal: VersionedObject; - allowed: boolean; - globalAllow: boolean; - fullAllowedStr: string; - }[] - >(), - ); + fullAllowedStr: [ + upperFirst( + getAllowStr(candidate.allowed, !!getAllowStr(globalAllowPredicate)), + ), + getAllowStr(globalAllowPredicate) && + `(Global ${upperFirst(getAllowStr(globalAllowPredicate))})`, + ] + .filter(Boolean) + .join(' '), + }; + if (groups.has(candidate.priority)) { + groups.get(candidate.priority)?.push(newItem); + } else { + groups.set(candidate.priority, [newItem]); + } + return groups; + }, new Map()); return Array.from(groups.values()) .map((group) => { const sum = group.reduce( @@ -498,6 +496,57 @@ export class CandidatesComponent { }); } + cardDrop(newRows: Item[][]) { + const maxPriority = newRows.length; + const newCandidates: RoutingCandidate[] = newRows + .map((group, idx) => { + const priority = maxPriority - idx; + return group.map((item) => ({ + ...item.value.candidate, + priority, + })); + }) + .flat(); + this.candidates$ + .pipe( + first(), + switchMap((candidates) => + this.dialog + .open(UpdateThriftDialogComponent, { + object: newCandidates, + prevObject: candidates, + }) + .afterClosed(), + ), + withLatestFrom(this.routingRulesetService.ruleset$), + switchMap(([res, ruleset]) => { + if (res.status === DialogResponseStatus.Success) { + const newRuleset = cloneDeep(ruleset); + newRuleset.data.decisions.candidates = res.data + .object as RoutingCandidate[]; + return this.routingRulesStoreService.commit([ + { update: { object: { routing_rules: newRuleset } } }, + ]); + } else { + return of(null); + } + }), + ) + .subscribe({ + next: (res) => { + if (res) { + this.log.successOperation('update', 'candidates'); + this.routingRulesStoreService.reload(); + } else { + this.updateCandidateGroups$.next(); + } + }, + error: (err) => { + this.log.error(err); + }, + }); + } + openRefId() { this.ruleset$.pipe(take(1), filter(Boolean)).subscribe(({ ref }) => { this.sidenavInfoService.toggle(DomainObjectCardComponent, { diff --git a/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.ts b/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.ts index e733d3c01..895e600ac 100644 --- a/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.ts +++ b/src/app/parties/party/routing-rules/candidates/components/dnd-cards.component.ts @@ -7,7 +7,15 @@ import { transferArrayItem, } from '@angular/cdk/drag-drop'; import { CommonModule, NgTemplateOutlet } from '@angular/common'; -import { Component, TemplateRef, contentChild, effect, model, untracked } from '@angular/core'; +import { + Component, + TemplateRef, + contentChild, + effect, + model, + output, + untracked, +} from '@angular/core'; import { MatCardModule } from '@angular/material/card'; export interface Item { @@ -34,6 +42,7 @@ export class DndCardsComponent { cardTpl = contentChild.required>('card'); isDragging = false; zoneData: Item[] = []; + dropped = output[][]>(); constructor() { effect(() => { @@ -57,6 +66,7 @@ export class DndCardsComponent { ); } this.rows.set(rows.filter((r) => r.length > 0).map((r) => this.sortRow(r))); + this.drop(); } dropInZone(event: CdkDragDrop[]>, insertIndex: number) { @@ -71,6 +81,11 @@ export class DndCardsComponent { const filtered = rows.filter((r) => r.length > 0); filtered.splice(adjusted, 0, [item]); this.rows.set(filtered.map((r) => this.sortRow(r))); + this.drop(); + } + + drop() { + this.dropped.emit(this.rows()); } private sortRow(row: Item[]): Item[] { From 7f87753f12653af4bddf980d0f545c9a055d083f Mon Sep 17 00:00:00 2001 From: Rinat Arsaev <11846445+A77AY@users.noreply.github.com> Date: Wed, 25 Feb 2026 02:36:21 +0700 Subject: [PATCH 18/20] init edit --- .../candidates/candidates.component.html | 13 ++- .../candidates/candidates.component.ts | 49 ++++++++++- .../{ => dnd-cards}/dnd-cards.component.html | 0 .../{ => dnd-cards}/dnd-cards.component.scss | 0 .../{ => dnd-cards}/dnd-cards.component.ts | 0 .../edit-candidate-dialog.component.html | 23 +++++ .../edit-candidate-dialog.component.ts | 85 +++++++++++++++++++ 7 files changed, 165 insertions(+), 5 deletions(-) rename src/app/parties/party/routing-rules/candidates/components/{ => dnd-cards}/dnd-cards.component.html (100%) rename src/app/parties/party/routing-rules/candidates/components/{ => dnd-cards}/dnd-cards.component.scss (100%) rename src/app/parties/party/routing-rules/candidates/components/{ => dnd-cards}/dnd-cards.component.ts (100%) create mode 100644 src/app/parties/party/routing-rules/candidates/components/edit-candidate-dialog/edit-candidate-dialog.component.html create mode 100644 src/app/parties/party/routing-rules/candidates/components/edit-candidate-dialog/edit-candidate-dialog.component.ts diff --git a/src/app/parties/party/routing-rules/candidates/candidates.component.html b/src/app/parties/party/routing-rules/candidates/candidates.component.html index 0dd97db53..ff74ef072 100644 --- a/src/app/parties/party/routing-rules/candidates/candidates.component.html +++ b/src/app/parties/party/routing-rules/candidates/candidates.component.html @@ -67,13 +67,18 @@ class="min-w-0" (change)="toggleAllow(item.idx)" > - +
+ + +
+ + diff --git a/src/app/parties/party/routing-rules/candidates/components/edit-candidate-dialog/edit-candidate-dialog.component.ts b/src/app/parties/party/routing-rules/candidates/components/edit-candidate-dialog/edit-candidate-dialog.component.ts new file mode 100644 index 000000000..40f31cdfa --- /dev/null +++ b/src/app/parties/party/routing-rules/candidates/components/edit-candidate-dialog/edit-candidate-dialog.component.ts @@ -0,0 +1,85 @@ +import { round } from 'lodash-es'; +import { distinctUntilChanged, map, shareReplay } from 'rxjs'; + +import { CommonModule } from '@angular/common'; +import { ChangeDetectionStrategy, Component, DestroyRef, OnInit, inject } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { FormBuilder, ReactiveFormsModule } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; + +import { RoutingCandidate } from '@vality/domain-proto/domain'; +import { + DialogModule, + DialogSuperclass, + InputFieldModule, + getValue, + getValueChanges, +} from '@vality/matez'; + +@Component({ + selector: 'cc-edit-candidate-dialog', + templateUrl: 'edit-candidate-dialog.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [CommonModule, DialogModule, ReactiveFormsModule, MatButtonModule, InputFieldModule], +}) +export class EditCandidateDialogComponent + extends DialogSuperclass< + EditCandidateDialogComponent, + { candidate: RoutingCandidate; othersWeight: number }, + RoutingCandidate + > + implements OnInit +{ + private fb = inject(FormBuilder); + private dr = inject(DestroyRef); + + form = this.fb.nonNullable.group({ + description: this.dialogData.candidate.description, + weight: this.dialogData.candidate.weight, + weightPercent: this.calcPercent(this.dialogData.candidate.weight), + }); + percent$ = getValueChanges(this.form.controls.weight).pipe( + distinctUntilChanged(), + map((weight) => this.calcPercent(Number(weight || 0))), + shareReplay({ bufferSize: 1, refCount: true }), + ); + weight$ = getValueChanges(this.form.controls.weightPercent).pipe( + distinctUntilChanged(), + map((weightPercent) => this.calcWeight(Number(weightPercent || 0))), + shareReplay({ bufferSize: 1, refCount: true }), + ); + + ngOnInit() { + this.weight$.pipe(takeUntilDestroyed(this.dr)).subscribe((weight) => { + this.form.controls.weight.setValue(Math.round(weight), { + emitEvent: false, + }); + }); + this.percent$.pipe(takeUntilDestroyed(this.dr)).subscribe((weightPercent) => { + this.form.controls.weightPercent.setValue(weightPercent, { + emitEvent: false, + }); + }); + } + + confirm() { + const value = getValue(this.form); + this.closeWithSuccess({ ...this.dialogData.candidate, ...value }); + } + + private calcPercent(weight: number): number { + const res = this.dialogData.othersWeight + ? (weight / (this.dialogData.othersWeight + weight)) * 100 + : 100; + return round(Math.max(Math.min(res, 100), 0), 2); + } + + private calcWeight(weightPercent: number): number { + const res = this.dialogData.othersWeight + ? (weightPercent * this.dialogData.othersWeight) / (100 - weightPercent) + : weightPercent + ? 1 + : 0; + return round(Math.max(Math.min(res, 1_000_000), 0), 2); + } +} From 5359b7f2d2e1153bb0e54103b12d7cafeb20a4a2 Mon Sep 17 00:00:00 2001 From: Rinat Arsaev <11846445+A77AY@users.noreply.github.com> Date: Thu, 26 Feb 2026 17:51:55 +0700 Subject: [PATCH 19/20] fix edit candidate --- .../select-field/select-field.component.ts | 19 ++++++++++++-- .../edit-candidate-dialog.component.html | 8 ++++++ .../edit-candidate-dialog.component.ts | 25 ++++++++++++++++--- .../domain-object-field.component.ts | 4 ++- 4 files changed, 50 insertions(+), 6 deletions(-) diff --git a/projects/matez/src/lib/components/select-autocomplete/select-field/select-field.component.ts b/projects/matez/src/lib/components/select-autocomplete/select-field/select-field.component.ts index 85c72dd50..2c09f9f4c 100644 --- a/projects/matez/src/lib/components/select-autocomplete/select-field/select-field.component.ts +++ b/projects/matez/src/lib/components/select-autocomplete/select-field/select-field.component.ts @@ -1,5 +1,6 @@ -import { Component, EventEmitter, Input, Output, booleanAttribute } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output, booleanAttribute } from '@angular/core'; import { MatFormFieldAppearance } from '@angular/material/form-field'; +import { timer, filter, take, first } from 'rxjs'; import { FormControlSuperclass, createControlProviders } from '../../../utils'; import { Option } from '../types'; @@ -13,7 +14,10 @@ import { getHintText } from '../utils/get-hint-text'; providers: createControlProviders(() => SelectFieldComponent), standalone: false, }) -export class SelectFieldComponent extends FormControlSuperclass { +export class SelectFieldComponent + extends FormControlSuperclass + implements OnInit +{ @Input() options: Option[] = []; @Output() searchChange = new EventEmitter(); @@ -32,6 +36,17 @@ export class SelectFieldComponent extends FormControlSuperclass { + this.searchChange.emit(String(this.control.value)); + }); + } + } + get hintText() { return getHintText( this.options, diff --git a/src/app/parties/party/routing-rules/candidates/components/edit-candidate-dialog/edit-candidate-dialog.component.html b/src/app/parties/party/routing-rules/candidates/components/edit-candidate-dialog/edit-candidate-dialog.component.html index 1239b61db..787dc014c 100644 --- a/src/app/parties/party/routing-rules/candidates/components/edit-candidate-dialog/edit-candidate-dialog.component.html +++ b/src/app/parties/party/routing-rules/candidates/components/edit-candidate-dialog/edit-candidate-dialog.component.html @@ -1,5 +1,6 @@
+
+
diff --git a/src/app/parties/party/routing-rules/candidates/components/edit-candidate-dialog/edit-candidate-dialog.component.ts b/src/app/parties/party/routing-rules/candidates/components/edit-candidate-dialog/edit-candidate-dialog.component.ts index 40f31cdfa..c05383da2 100644 --- a/src/app/parties/party/routing-rules/candidates/components/edit-candidate-dialog/edit-candidate-dialog.component.ts +++ b/src/app/parties/party/routing-rules/candidates/components/edit-candidate-dialog/edit-candidate-dialog.component.ts @@ -16,11 +16,24 @@ import { getValueChanges, } from '@vality/matez'; +import { + DomainObjectFieldComponent, + DomainThriftFormComponent, +} from '~/components/thrift-api-crud'; + @Component({ selector: 'cc-edit-candidate-dialog', templateUrl: 'edit-candidate-dialog.component.html', changeDetection: ChangeDetectionStrategy.OnPush, - imports: [CommonModule, DialogModule, ReactiveFormsModule, MatButtonModule, InputFieldModule], + imports: [ + CommonModule, + DialogModule, + ReactiveFormsModule, + MatButtonModule, + InputFieldModule, + DomainObjectFieldComponent, + DomainThriftFormComponent, + ], }) export class EditCandidateDialogComponent extends DialogSuperclass< @@ -34,9 +47,11 @@ export class EditCandidateDialogComponent private dr = inject(DestroyRef); form = this.fb.nonNullable.group({ + terminal: this.dialogData.candidate.terminal.id, description: this.dialogData.candidate.description, weight: this.dialogData.candidate.weight, weightPercent: this.calcPercent(this.dialogData.candidate.weight), + allowed: this.dialogData.candidate.allowed, }); percent$ = getValueChanges(this.form.controls.weight).pipe( distinctUntilChanged(), @@ -63,8 +78,12 @@ export class EditCandidateDialogComponent } confirm() { - const value = getValue(this.form); - this.closeWithSuccess({ ...this.dialogData.candidate, ...value }); + const { terminal, ...value } = getValue(this.form); + this.closeWithSuccess({ + ...this.dialogData.candidate, + terminal: { id: terminal }, + ...value, + }); } private calcPercent(weight: number): number { diff --git a/src/components/thrift-api-crud/domain/domain-object-field/domain-object-field.component.ts b/src/components/thrift-api-crud/domain/domain-object-field/domain-object-field.component.ts index 1493a89ff..542ff0fad 100644 --- a/src/components/thrift-api-crud/domain/domain-object-field/domain-object-field.component.ts +++ b/src/components/thrift-api-crud/domain/domain-object-field/domain-object-field.component.ts @@ -45,7 +45,9 @@ export class DomainObjectFieldComponent extends FormC objs.map((obj) => ({ value: getReferenceId(obj.ref), label: obj.name || `#${getReferenceId(obj.ref)}`, - description: obj.description, + description: [`#${getReferenceId(obj.ref)}`, obj.description] + .filter(Boolean) + .join(' - '), })), ), ); From 724bb64b5ea1b113e6e083b01eb6fb743cfccb35 Mon Sep 17 00:00:00 2001 From: Rinat Arsaev <11846445+A77AY@users.noreply.github.com> Date: Thu, 26 Feb 2026 19:09:18 +0700 Subject: [PATCH 20/20] fix --- .../select-autocomplete/select-field/select-field.component.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/projects/matez/src/lib/components/select-autocomplete/select-field/select-field.component.ts b/projects/matez/src/lib/components/select-autocomplete/select-field/select-field.component.ts index 2c09f9f4c..86ea81779 100644 --- a/projects/matez/src/lib/components/select-autocomplete/select-field/select-field.component.ts +++ b/projects/matez/src/lib/components/select-autocomplete/select-field/select-field.component.ts @@ -1,6 +1,7 @@ +import { first, timer } from 'rxjs'; + import { Component, EventEmitter, Input, OnInit, Output, booleanAttribute } from '@angular/core'; import { MatFormFieldAppearance } from '@angular/material/form-field'; -import { timer, filter, take, first } from 'rxjs'; import { FormControlSuperclass, createControlProviders } from '../../../utils'; import { Option } from '../types';