feat: tela de dashboard com kpis clicaveis e filtro bloqueados

This commit is contained in:
Leon 2026-03-06 12:59:29 -03:00
parent 79d372d67b
commit 56e1fc2379
5 changed files with 318 additions and 13 deletions

View File

@ -30,7 +30,14 @@
</div> </div>
<div class="hero-grid fade-in-up" [style.animation-delay]="'100ms'" *ngIf="!isCliente || clientOverview.hasData"> <div class="hero-grid fade-in-up" [style.animation-delay]="'100ms'" *ngIf="!isCliente || clientOverview.hasData">
<div class="hero-card" *ngFor="let k of kpis; trackBy: trackByKpiKey"> <div
class="hero-card"
*ngFor="let k of kpis; trackBy: trackByKpiKey"
[class.hero-card-clickable]="isKpiClickable(k)"
[attr.role]="isKpiClickable(k) ? 'button' : null"
[attr.tabindex]="isKpiClickable(k) ? 0 : null"
(click)="onKpiClick(k)"
(keydown)="onKpiCardKeydown($event, k)">
<div class="hero-icon"> <div class="hero-icon">
<i [class]="k.icon"></i> <i [class]="k.icon"></i>
</div> </div>

View File

@ -178,6 +178,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 12px; gap: 12px;
cursor: default;
transition: all 0.2s ease; transition: all 0.2s ease;
box-shadow: var(--shadow-sm); box-shadow: var(--shadow-sm);
@ -189,6 +190,15 @@
} }
} }
.hero-card.hero-card-clickable {
cursor: pointer;
}
.hero-card.hero-card-clickable:focus-visible {
outline: 2px solid rgba(227, 61, 207, 0.7);
outline-offset: 2px;
}
.hero-icon { .hero-icon {
width: 40px; width: 40px;
height: 40px; height: 40px;

View File

@ -10,7 +10,7 @@ import {
import { CommonModule, isPlatformBrowser } from '@angular/common'; import { CommonModule, isPlatformBrowser } from '@angular/common';
import { HttpClient, HttpErrorResponse, HttpParams } from '@angular/common/http'; import { HttpClient, HttpErrorResponse, HttpParams } from '@angular/common/http';
import { PLATFORM_ID } from '@angular/core'; import { PLATFORM_ID } from '@angular/core';
import { RouterModule } from '@angular/router'; import { RouterModule, Router } from '@angular/router';
import { firstValueFrom } from 'rxjs'; import { firstValueFrom } from 'rxjs';
import { environment } from '../../../environments/environment'; import { environment } from '../../../environments/environment';
@ -31,6 +31,11 @@ type KpiCard = {
hint?: string; hint?: string;
}; };
type KpiNavigationTarget = {
route: string;
queryParams?: Record<string, string>;
};
type SerieMesDto = { type SerieMesDto = {
mes: string; mes: string;
total: number; total: number;
@ -354,11 +359,29 @@ export class Dashboard implements OnInit, AfterViewInit, OnDestroy {
private chartResumoReserva?: Chart; private chartResumoReserva?: Chart;
private readonly baseApi: string; private readonly baseApi: string;
private readonly kpiNavigationMap: Record<string, KpiNavigationTarget> = {
linhas_total: { route: '/geral' },
linhas_ativas: { route: '/geral' },
linhas_bloqueadas: { route: '/geral', queryParams: { statusMode: 'blocked' } },
linhas_reserva: { route: '/geral', queryParams: { skil: 'RESERVA' } },
franquia_vivo_total: { route: '/geral' },
franquia_line_total: { route: '/geral' },
vig_vencidos: { route: '/vigencia' },
vig_30: { route: '/vigencia' },
mureg_30: { route: '/mureg' },
troca_30: { route: '/trocanumero' },
cadastros_total: { route: '/dadosusuarios' },
travel_com: { route: '/geral', queryParams: { additionalMode: 'with', additionalServices: 'travel' } },
adicional_pago: { route: '/geral', queryParams: { additionalMode: 'with' } },
planos_contratados: { route: '/resumo', queryParams: { tab: 'planos' } },
usuarios_com_linha: { route: '/dadosusuarios' },
};
constructor( constructor(
private http: HttpClient, private http: HttpClient,
private resumoService: ResumoService, private resumoService: ResumoService,
private authService: AuthService, private authService: AuthService,
private router: Router,
@Inject(PLATFORM_ID) private platformId: object @Inject(PLATFORM_ID) private platformId: object
) { ) {
const raw = (environment.apiUrl || '').replace(/\/+$/, ''); const raw = (environment.apiUrl || '').replace(/\/+$/, '');
@ -1872,6 +1895,24 @@ export class Dashboard implements OnInit, AfterViewInit, OnDestroy {
trackByKpiKey = (_: number, item: KpiCard) => item.key; trackByKpiKey = (_: number, item: KpiCard) => item.key;
isKpiClickable(card: KpiCard): boolean {
return !!this.kpiNavigationMap[card.key];
}
onKpiClick(card: KpiCard): void {
const target = this.kpiNavigationMap[card.key];
if (!target) return;
void this.router.navigate([target.route], {
queryParams: target.queryParams
});
}
onKpiCardKeydown(event: KeyboardEvent, card: KpiCard): void {
if (event.key !== 'Enter' && event.key !== ' ') return;
event.preventDefault();
this.onKpiClick(card);
}
private getPalette() { private getPalette() {
return { return {
brand: '#E33DCF', brand: '#E33DCF',

View File

@ -71,6 +71,27 @@
<i class="bi bi-archive me-1"></i> Reservas <i class="bi bi-archive me-1"></i> Reservas
</button> </button>
</ng-container> </ng-container>
<button type="button" class="filter-tab" [class.active]="filterStatus === 'BLOCKED'" (click)="toggleBlockedFilter()" [disabled]="loading">
<i class="bi bi-slash-circle me-1"></i> Bloqueadas
</button>
<ng-container *ngIf="filterStatus === 'BLOCKED'">
<button
type="button"
class="filter-tab"
[class.active]="blockedStatusMode === 'PERDA_ROUBO'"
(click)="setBlockedStatusMode('PERDA_ROUBO')"
[disabled]="loading">
Perda/Roubo
</button>
<button
type="button"
class="filter-tab"
[class.active]="blockedStatusMode === 'BLOQUEIO_120'"
(click)="setBlockedStatusMode('BLOQUEIO_120')"
[disabled]="loading">
120 dias
</button>
</ng-container>
</div> </div>
<!-- CLIENTE MULTI-SELECT --> <!-- CLIENTE MULTI-SELECT -->

View File

@ -17,7 +17,7 @@ import {
HttpParams, HttpParams,
HttpErrorResponse HttpErrorResponse
} from '@angular/common/http'; } from '@angular/common/http';
import { NavigationEnd, Router } from '@angular/router'; import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
import { CustomSelectComponent } from '../../components/custom-select/custom-select'; import { CustomSelectComponent } from '../../components/custom-select/custom-select';
import { PlanAutoFillService } from '../../services/plan-autofill.service'; import { PlanAutoFillService } from '../../services/plan-autofill.service';
import { AuthService } from '../../services/auth.service'; import { AuthService } from '../../services/auth.service';
@ -42,6 +42,7 @@ type CreateMode = 'NEW_CLIENT' | 'NEW_LINE_IN_GROUP';
type CreateEntryMode = 'SINGLE' | 'BATCH'; type CreateEntryMode = 'SINGLE' | 'BATCH';
type AdditionalMode = 'ALL' | 'WITH' | 'WITHOUT'; type AdditionalMode = 'ALL' | 'WITH' | 'WITHOUT';
type AdditionalServiceKey = 'gvd' | 'skeelo' | 'news' | 'travel' | 'sync' | 'dispositivo'; type AdditionalServiceKey = 'gvd' | 'skeelo' | 'news' | 'travel' | 'sync' | 'dispositivo';
type BlockedStatusMode = 'ALL' | 'PERDA_ROUBO' | 'BLOQUEIO_120';
interface LineRow { interface LineRow {
id: string; id: string;
@ -290,6 +291,7 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
private planAutoFill: PlanAutoFillService, private planAutoFill: PlanAutoFillService,
private authService: AuthService, private authService: AuthService,
private router: Router, private router: Router,
private route: ActivatedRoute,
private tenantSyncService: TenantSyncService, private tenantSyncService: TenantSyncService,
private solicitacoesLinhasService: SolicitacoesLinhasService private solicitacoesLinhasService: SolicitacoesLinhasService
) {} ) {}
@ -317,6 +319,8 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
searchTerm = ''; searchTerm = '';
filterSkil: 'ALL' | 'PF' | 'PJ' | 'RESERVA' = 'ALL'; filterSkil: 'ALL' | 'PF' | 'PJ' | 'RESERVA' = 'ALL';
filterStatus: 'ALL' | 'BLOCKED' = 'ALL';
blockedStatusMode: BlockedStatusMode = 'ALL';
additionalMode: AdditionalMode = 'ALL'; additionalMode: AdditionalMode = 'ALL';
selectedAdditionalServices: AdditionalServiceKey[] = []; selectedAdditionalServices: AdditionalServiceKey[] = [];
readonly additionalServiceOptions: Array<{ key: AdditionalServiceKey; label: string }> = [ readonly additionalServiceOptions: Array<{ key: AdditionalServiceKey; label: string }> = [
@ -647,6 +651,10 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
return this.additionalMode !== 'ALL' || this.selectedAdditionalServices.length > 0; return this.additionalMode !== 'ALL' || this.selectedAdditionalServices.length > 0;
} }
get hasClientSideFiltersApplied(): boolean {
return this.hasAdditionalFiltersApplied || this.filterStatus === 'BLOCKED';
}
get additionalModeLabel(): string { get additionalModeLabel(): string {
if (this.additionalMode === 'WITH') return 'Com adicionais'; if (this.additionalMode === 'WITH') return 'Com adicionais';
if (this.additionalMode === 'WITHOUT') return 'Sem adicionais'; if (this.additionalMode === 'WITHOUT') return 'Sem adicionais';
@ -735,6 +743,8 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
if (this.isClientRestricted) { if (this.isClientRestricted) {
this.filterSkil = 'ALL'; this.filterSkil = 'ALL';
this.filterStatus = 'ALL';
this.blockedStatusMode = 'ALL';
this.additionalMode = 'ALL'; this.additionalMode = 'ALL';
this.selectedAdditionalServices = []; this.selectedAdditionalServices = [];
this.selectedClients = []; this.selectedClients = [];
@ -746,6 +756,7 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
this.initAnimations(); this.initAnimations();
setTimeout(() => { setTimeout(() => {
this.applyRouteFilters(this.route.snapshot.queryParams);
this.refreshData(); this.refreshData();
if (!this.isClientRestricted) { if (!this.isClientRestricted) {
this.loadClients(); this.loadClients();
@ -766,9 +777,13 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
this.navigationSub = this.router.events this.navigationSub = this.router.events
.pipe(filter((event): event is NavigationEnd => event instanceof NavigationEnd)) .pipe(filter((event): event is NavigationEnd => event instanceof NavigationEnd))
.subscribe((event) => { .subscribe((event) => {
const url = (event.urlAfterRedirects || '').toLowerCase(); const urlAfterRedirects = event.urlAfterRedirects || '';
const url = urlAfterRedirects.toLowerCase();
if (!url.includes('/geral')) return; if (!url.includes('/geral')) return;
const parsed = this.router.parseUrl(urlAfterRedirects);
this.applyRouteFilters(parsed.queryParams ?? {});
this.searchResolvedClient = null; this.searchResolvedClient = null;
if (!this.isClientRestricted) { if (!this.isClientRestricted) {
this.loadClients(); this.loadClients();
@ -785,6 +800,137 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
}, 100); }, 100);
} }
private applyRouteFilters(query: Record<string, unknown>): void {
const skil = this.parseQuerySkilFilter(query['skil']);
if (skil && (!this.isClientRestricted || skil === 'ALL')) {
this.filterSkil = skil;
}
const status = this.parseQueryStatusFilter(query['statusMode'] ?? query['statusFilter']);
if (status) {
this.filterStatus = status;
}
if (this.filterStatus !== 'BLOCKED') {
this.blockedStatusMode = 'ALL';
}
const blockedMode = this.parseQueryBlockedStatusMode(query['blockedMode'] ?? query['blockedType'] ?? query['statusSubtype']);
if (blockedMode) {
this.blockedStatusMode = blockedMode;
this.filterStatus = 'BLOCKED';
}
if (!this.isClientRestricted) {
const additionalMode = this.parseQueryAdditionalMode(query['additionalMode']);
if (additionalMode) {
this.additionalMode = additionalMode;
}
const additionalServices = this.parseQueryAdditionalServices(query['additionalServices']);
if (additionalServices) {
this.selectedAdditionalServices = additionalServices;
}
}
this.expandedGroup = null;
this.groupLines = [];
this.selectedClients = [];
this.clientSearchTerm = '';
this.page = 1;
}
private parseQuerySkilFilter(value: unknown): 'ALL' | 'PF' | 'PJ' | 'RESERVA' | null {
const token = this.normalizeFilterToken(value);
if (!token) return null;
if (token === 'ALL' || token === 'TODOS') return 'ALL';
if (token === 'PF' || token === 'PESSOAFISICA') return 'PF';
if (token === 'PJ' || token === 'PESSOAJURIDICA') return 'PJ';
if (token === 'RESERVA' || token === 'RESERVAS') return 'RESERVA';
return null;
}
private parseQueryStatusFilter(value: unknown): 'ALL' | 'BLOCKED' | null {
const token = this.normalizeFilterToken(value);
if (!token) return null;
if (token === 'ALL' || token === 'TODOS') return 'ALL';
if (
token === 'BLOCKED' ||
token === 'BLOQUEADAS' ||
token === 'BLOQUEADOS' ||
token === 'BLOQUEADA' ||
token === 'BLOQUEADO' ||
token === 'BLOQUEIO'
) {
return 'BLOCKED';
}
return null;
}
private parseQueryBlockedStatusMode(value: unknown): BlockedStatusMode | null {
const token = this.normalizeFilterToken(value);
if (!token) return null;
if (token === 'ALL' || token === 'TODOS') return 'ALL';
if (
token === 'PERDAROUBO' ||
token === 'PERDAEROUBO' ||
token === 'PERDA' ||
token === 'ROUBO'
) {
return 'PERDA_ROUBO';
}
if (
token === '120' ||
token === '120DIAS' ||
token === 'BLOQUEIO120' ||
token === 'BLOQUEIO120DIAS'
) {
return 'BLOQUEIO_120';
}
return null;
}
private parseQueryAdditionalMode(value: unknown): AdditionalMode | null {
const token = this.normalizeFilterToken(value);
if (!token) return null;
if (token === 'ALL' || token === 'TODOS') return 'ALL';
if (token === 'WITH' || token === 'COM') return 'WITH';
if (token === 'WITHOUT' || token === 'SEM') return 'WITHOUT';
return null;
}
private parseQueryAdditionalServices(value: unknown): AdditionalServiceKey[] | null {
if (value === undefined || value === null) return null;
const asString = Array.isArray(value) ? value.join(',') : String(value ?? '');
const chunks = asString
.split(',')
.map((part) => this.mapAdditionalServiceToken(part))
.filter((part): part is AdditionalServiceKey => !!part);
const unique = Array.from(new Set(chunks));
return unique;
}
private mapAdditionalServiceToken(value: unknown): AdditionalServiceKey | null {
const token = this.normalizeFilterToken(value);
if (!token) return null;
if (token === 'GVD' || token === 'GESTAOVOZDADOS' || token === 'GESTAOVOZEDADOS') return 'gvd';
if (token === 'SKEELO') return 'skeelo';
if (token === 'NEWS' || token === 'VIVONEWS' || token === 'VIVONEWSPLUS') return 'news';
if (token === 'TRAVEL' || token === 'TRAVELMUNDO' || token === 'VIVOTRAVELMUNDO') return 'travel';
if (token === 'SYNC' || token === 'VIVOSYNC') return 'sync';
if (token === 'DISPOSITIVO' || token === 'GESTAODISPOSITIVO' || token === 'VIVOGESTAODISPOSITIVO') return 'dispositivo';
return null;
}
private normalizeFilterToken(value: unknown): string {
return String(value ?? '')
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.replace(/[^A-Za-z0-9]/g, '')
.toUpperCase()
.trim();
}
private async loadPlanRules() { private async loadPlanRules() {
try { try {
await this.planAutoFill.load(); await this.planAutoFill.load();
@ -895,7 +1041,7 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
refreshData(opts?: { keepCurrentPage?: boolean }) { refreshData(opts?: { keepCurrentPage?: boolean }) {
const keepCurrentPage = !!opts?.keepCurrentPage; const keepCurrentPage = !!opts?.keepCurrentPage;
this.keepPageOnNextGroupsLoad = keepCurrentPage; this.keepPageOnNextGroupsLoad = keepCurrentPage;
if (!keepCurrentPage && this.filterSkil === 'RESERVA') { if (!keepCurrentPage && (this.filterSkil === 'RESERVA' || this.filterStatus === 'BLOCKED')) {
this.page = 1; this.page = 1;
} }
this.searchResolvedClient = null; this.searchResolvedClient = null;
@ -921,7 +1067,7 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
const s = (term ?? '').trim(); const s = (term ?? '').trim();
if (!s) return Promise.resolve(null); if (!s) return Promise.resolve(null);
const pageSize = this.hasAdditionalFiltersApplied ? '500' : '1'; const pageSize = this.hasClientSideFiltersApplied ? '500' : '1';
let params = new HttpParams().set('page', '1').set('pageSize', pageSize).set('search', s); let params = new HttpParams().set('page', '1').set('pageSize', pageSize).set('search', s);
params = this.applyBaseFilters(params); params = this.applyBaseFilters(params);
@ -932,7 +1078,7 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
return new Promise((resolve) => { return new Promise((resolve) => {
this.http.get<ApiPagedResult<ApiLineList>>(this.apiBase, { params: this.withNoCache(params) }).subscribe({ this.http.get<ApiPagedResult<ApiLineList>>(this.apiBase, { params: this.withNoCache(params) }).subscribe({
next: (res) => { next: (res) => {
const source = this.hasAdditionalFiltersApplied const source = this.hasClientSideFiltersApplied
? this.applyAdditionalFiltersClientSide(res.items ?? []) ? this.applyAdditionalFiltersClientSide(res.items ?? [])
: (res.items ?? []); : (res.items ?? []);
const first = source[0]; const first = source[0];
@ -984,7 +1130,7 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
const requestVersion = ++this.groupsRequestVersion; const requestVersion = ++this.groupsRequestVersion;
this.loading = true; this.loading = true;
if (this.hasAdditionalFiltersApplied) { if (this.hasClientSideFiltersApplied) {
return this.loadOnlyThisClientGroupFromLines(clientName, requestVersion); return this.loadOnlyThisClientGroupFromLines(clientName, requestVersion);
} }
@ -1051,7 +1197,7 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
this.loadingClientsList = true; this.loadingClientsList = true;
this.clientsList = []; this.clientsList = [];
if (this.hasAdditionalFiltersApplied) { if (this.hasClientSideFiltersApplied) {
void this.loadClientsFromLines(requestVersion); void this.loadClientsFromLines(requestVersion);
return; return;
} }
@ -1147,6 +1293,47 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
this.refreshData(); this.refreshData();
} }
toggleBlockedFilter() {
if (this.filterStatus === 'BLOCKED') {
this.filterStatus = 'ALL';
this.blockedStatusMode = 'ALL';
} else {
this.filterStatus = 'BLOCKED';
}
this.expandedGroup = null;
this.groupLines = [];
this.searchResolvedClient = null;
this.selectedClients = [];
this.clientSearchTerm = '';
this.page = 1;
if (!this.isClientRestricted) {
this.loadClients();
}
this.refreshData();
}
setBlockedStatusMode(mode: Exclude<BlockedStatusMode, 'ALL'>) {
if (this.filterStatus !== 'BLOCKED') {
this.filterStatus = 'BLOCKED';
}
this.blockedStatusMode = this.blockedStatusMode === mode ? 'ALL' : mode;
this.expandedGroup = null;
this.groupLines = [];
this.searchResolvedClient = null;
this.selectedClients = [];
this.clientSearchTerm = '';
this.page = 1;
if (!this.isClientRestricted) {
this.loadClients();
}
this.refreshData();
}
setAdditionalMode(mode: AdditionalMode) { setAdditionalMode(mode: AdditionalMode) {
if (this.isClientRestricted) return; if (this.isClientRestricted) return;
if (this.additionalMode === mode) return; if (this.additionalMode === mode) return;
@ -1199,6 +1386,11 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
if (this.filterSkil === 'PF') next = next.set('skil', 'PESSOA FÍSICA'); if (this.filterSkil === 'PF') next = next.set('skil', 'PESSOA FÍSICA');
else if (this.filterSkil === 'PJ') next = next.set('skil', 'PESSOA JURÍDICA'); else if (this.filterSkil === 'PJ') next = next.set('skil', 'PESSOA JURÍDICA');
else if (this.filterSkil === 'RESERVA') next = next.set('skil', 'RESERVA'); else if (this.filterSkil === 'RESERVA') next = next.set('skil', 'RESERVA');
if (this.filterStatus === 'BLOCKED') {
next = next.set('statusMode', 'blocked');
if (this.blockedStatusMode === 'PERDA_ROUBO') next = next.set('statusSubtype', 'perda_roubo');
else if (this.blockedStatusMode === 'BLOQUEIO_120') next = next.set('statusSubtype', '120_dias');
}
if (this.additionalMode === 'WITH') next = next.set('additionalMode', 'with'); if (this.additionalMode === 'WITH') next = next.set('additionalMode', 'with');
else if (this.additionalMode === 'WITHOUT') next = next.set('additionalMode', 'without'); else if (this.additionalMode === 'WITHOUT') next = next.set('additionalMode', 'without');
@ -1236,7 +1428,41 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
(this.getAdditionalValue(line, 'dispositivo') > 0); (this.getAdditionalValue(line, 'dispositivo') > 0);
} }
private resolveBlockedStatusMode(status: unknown): Exclude<BlockedStatusMode, 'ALL'> | null {
const normalized = this.normalizeFilterToken(status);
if (!normalized) return null;
const hasBlockedToken =
normalized.includes('BLOQUE') ||
normalized.includes('PERDA') ||
normalized.includes('ROUBO') ||
normalized.includes('FURTO');
if (!hasBlockedToken) return null;
if (normalized.includes('120')) return 'BLOQUEIO_120';
if (normalized.includes('PERDA') || normalized.includes('ROUBO') || normalized.includes('FURTO')) {
return 'PERDA_ROUBO';
}
return 'PERDA_ROUBO';
}
private isBlockedStatus(status: unknown): boolean {
return this.resolveBlockedStatusMode(status) !== null;
}
private matchesBlockedStatusMode(status: unknown): boolean {
const mode = this.resolveBlockedStatusMode(status);
if (!mode) return false;
if (this.blockedStatusMode === 'ALL') return true;
return mode === this.blockedStatusMode;
}
private matchesAdditionalFilters(line: ApiLineList): boolean { private matchesAdditionalFilters(line: ApiLineList): boolean {
if (this.filterStatus === 'BLOCKED' && !this.matchesBlockedStatusMode(line?.status ?? '')) {
return false;
}
const selected = this.selectedAdditionalServices; const selected = this.selectedAdditionalServices;
const hasSelected = selected.length > 0; const hasSelected = selected.length > 0;
@ -1296,7 +1522,7 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
} }
private async fetchAllGroupsForKpis(): Promise<ClientGroupDto[]> { private async fetchAllGroupsForKpis(): Promise<ClientGroupDto[]> {
if (this.hasAdditionalFiltersApplied) { if (this.hasClientSideFiltersApplied) {
const lines = await this.fetchLinesForGrouping(); const lines = await this.fetchLinesForGrouping();
let groups = this.buildGroupsFromLines(lines); let groups = this.buildGroupsFromLines(lines);
@ -1411,11 +1637,11 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
const keepCurrentPage = this.keepPageOnNextGroupsLoad; const keepCurrentPage = this.keepPageOnNextGroupsLoad;
this.keepPageOnNextGroupsLoad = false; this.keepPageOnNextGroupsLoad = false;
if (!keepCurrentPage && this.filterSkil === 'RESERVA' && !hasSelection && !hasResolved) { if (!keepCurrentPage && (this.filterSkil === 'RESERVA' || this.filterStatus === 'BLOCKED') && !hasSelection && !hasResolved) {
this.page = 1; this.page = 1;
} }
if (this.hasAdditionalFiltersApplied) { if (this.hasClientSideFiltersApplied) {
void this.loadGroupsFromLines(hasSelection, hasResolved, requestVersion); void this.loadGroupsFromLines(hasSelection, hasResolved, requestVersion);
return; return;
} }
@ -1573,7 +1799,7 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
const status = ((row?.status ?? '').toString().trim()).toLowerCase(); const status = ((row?.status ?? '').toString().trim()).toLowerCase();
if (status.includes('ativo')) group.ativos += 1; if (status.includes('ativo')) group.ativos += 1;
if (status.includes('bloque') || status.includes('perda') || status.includes('roubo')) { if (this.isBlockedStatus(row?.status ?? '')) {
group.bloqueados += 1; group.bloqueados += 1;
} }
} }