feat: tela de dashboard com kpis clicaveis e filtro bloqueados
This commit is contained in:
parent
79d372d67b
commit
56e1fc2379
|
|
@ -30,7 +30,14 @@
|
|||
</div>
|
||||
|
||||
<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">
|
||||
<i [class]="k.icon"></i>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -178,6 +178,7 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
cursor: default;
|
||||
transition: all 0.2s ease;
|
||||
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 {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import {
|
|||
import { CommonModule, isPlatformBrowser } from '@angular/common';
|
||||
import { HttpClient, HttpErrorResponse, HttpParams } from '@angular/common/http';
|
||||
import { PLATFORM_ID } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { RouterModule, Router } from '@angular/router';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
import { environment } from '../../../environments/environment';
|
||||
|
|
@ -31,6 +31,11 @@ type KpiCard = {
|
|||
hint?: string;
|
||||
};
|
||||
|
||||
type KpiNavigationTarget = {
|
||||
route: string;
|
||||
queryParams?: Record<string, string>;
|
||||
};
|
||||
|
||||
type SerieMesDto = {
|
||||
mes: string;
|
||||
total: number;
|
||||
|
|
@ -354,11 +359,29 @@ export class Dashboard implements OnInit, AfterViewInit, OnDestroy {
|
|||
private chartResumoReserva?: Chart;
|
||||
|
||||
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(
|
||||
private http: HttpClient,
|
||||
private resumoService: ResumoService,
|
||||
private authService: AuthService,
|
||||
private router: Router,
|
||||
@Inject(PLATFORM_ID) private platformId: object
|
||||
) {
|
||||
const raw = (environment.apiUrl || '').replace(/\/+$/, '');
|
||||
|
|
@ -1872,6 +1895,24 @@ export class Dashboard implements OnInit, AfterViewInit, OnDestroy {
|
|||
|
||||
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() {
|
||||
return {
|
||||
brand: '#E33DCF',
|
||||
|
|
|
|||
|
|
@ -71,6 +71,27 @@
|
|||
<i class="bi bi-archive me-1"></i> Reservas
|
||||
</button>
|
||||
</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>
|
||||
|
||||
<!-- CLIENTE MULTI-SELECT -->
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import {
|
|||
HttpParams,
|
||||
HttpErrorResponse
|
||||
} 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 { PlanAutoFillService } from '../../services/plan-autofill.service';
|
||||
import { AuthService } from '../../services/auth.service';
|
||||
|
|
@ -42,6 +42,7 @@ type CreateMode = 'NEW_CLIENT' | 'NEW_LINE_IN_GROUP';
|
|||
type CreateEntryMode = 'SINGLE' | 'BATCH';
|
||||
type AdditionalMode = 'ALL' | 'WITH' | 'WITHOUT';
|
||||
type AdditionalServiceKey = 'gvd' | 'skeelo' | 'news' | 'travel' | 'sync' | 'dispositivo';
|
||||
type BlockedStatusMode = 'ALL' | 'PERDA_ROUBO' | 'BLOQUEIO_120';
|
||||
|
||||
interface LineRow {
|
||||
id: string;
|
||||
|
|
@ -290,6 +291,7 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
|
|||
private planAutoFill: PlanAutoFillService,
|
||||
private authService: AuthService,
|
||||
private router: Router,
|
||||
private route: ActivatedRoute,
|
||||
private tenantSyncService: TenantSyncService,
|
||||
private solicitacoesLinhasService: SolicitacoesLinhasService
|
||||
) {}
|
||||
|
|
@ -317,6 +319,8 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
|
|||
|
||||
searchTerm = '';
|
||||
filterSkil: 'ALL' | 'PF' | 'PJ' | 'RESERVA' = 'ALL';
|
||||
filterStatus: 'ALL' | 'BLOCKED' = 'ALL';
|
||||
blockedStatusMode: BlockedStatusMode = 'ALL';
|
||||
additionalMode: AdditionalMode = 'ALL';
|
||||
selectedAdditionalServices: AdditionalServiceKey[] = [];
|
||||
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;
|
||||
}
|
||||
|
||||
get hasClientSideFiltersApplied(): boolean {
|
||||
return this.hasAdditionalFiltersApplied || this.filterStatus === 'BLOCKED';
|
||||
}
|
||||
|
||||
get additionalModeLabel(): string {
|
||||
if (this.additionalMode === 'WITH') return 'Com adicionais';
|
||||
if (this.additionalMode === 'WITHOUT') return 'Sem adicionais';
|
||||
|
|
@ -735,6 +743,8 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
|
|||
|
||||
if (this.isClientRestricted) {
|
||||
this.filterSkil = 'ALL';
|
||||
this.filterStatus = 'ALL';
|
||||
this.blockedStatusMode = 'ALL';
|
||||
this.additionalMode = 'ALL';
|
||||
this.selectedAdditionalServices = [];
|
||||
this.selectedClients = [];
|
||||
|
|
@ -746,6 +756,7 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
|
|||
this.initAnimations();
|
||||
|
||||
setTimeout(() => {
|
||||
this.applyRouteFilters(this.route.snapshot.queryParams);
|
||||
this.refreshData();
|
||||
if (!this.isClientRestricted) {
|
||||
this.loadClients();
|
||||
|
|
@ -766,9 +777,13 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
|
|||
this.navigationSub = this.router.events
|
||||
.pipe(filter((event): event is NavigationEnd => event instanceof NavigationEnd))
|
||||
.subscribe((event) => {
|
||||
const url = (event.urlAfterRedirects || '').toLowerCase();
|
||||
const urlAfterRedirects = event.urlAfterRedirects || '';
|
||||
const url = urlAfterRedirects.toLowerCase();
|
||||
if (!url.includes('/geral')) return;
|
||||
|
||||
const parsed = this.router.parseUrl(urlAfterRedirects);
|
||||
this.applyRouteFilters(parsed.queryParams ?? {});
|
||||
|
||||
this.searchResolvedClient = null;
|
||||
if (!this.isClientRestricted) {
|
||||
this.loadClients();
|
||||
|
|
@ -785,6 +800,137 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
|
|||
}, 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() {
|
||||
try {
|
||||
await this.planAutoFill.load();
|
||||
|
|
@ -895,7 +1041,7 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
|
|||
refreshData(opts?: { keepCurrentPage?: boolean }) {
|
||||
const keepCurrentPage = !!opts?.keepCurrentPage;
|
||||
this.keepPageOnNextGroupsLoad = keepCurrentPage;
|
||||
if (!keepCurrentPage && this.filterSkil === 'RESERVA') {
|
||||
if (!keepCurrentPage && (this.filterSkil === 'RESERVA' || this.filterStatus === 'BLOCKED')) {
|
||||
this.page = 1;
|
||||
}
|
||||
this.searchResolvedClient = null;
|
||||
|
|
@ -921,7 +1067,7 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
|
|||
const s = (term ?? '').trim();
|
||||
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);
|
||||
params = this.applyBaseFilters(params);
|
||||
|
||||
|
|
@ -932,7 +1078,7 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
|
|||
return new Promise((resolve) => {
|
||||
this.http.get<ApiPagedResult<ApiLineList>>(this.apiBase, { params: this.withNoCache(params) }).subscribe({
|
||||
next: (res) => {
|
||||
const source = this.hasAdditionalFiltersApplied
|
||||
const source = this.hasClientSideFiltersApplied
|
||||
? this.applyAdditionalFiltersClientSide(res.items ?? [])
|
||||
: (res.items ?? []);
|
||||
const first = source[0];
|
||||
|
|
@ -984,7 +1130,7 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
|
|||
const requestVersion = ++this.groupsRequestVersion;
|
||||
this.loading = true;
|
||||
|
||||
if (this.hasAdditionalFiltersApplied) {
|
||||
if (this.hasClientSideFiltersApplied) {
|
||||
return this.loadOnlyThisClientGroupFromLines(clientName, requestVersion);
|
||||
}
|
||||
|
||||
|
|
@ -1051,7 +1197,7 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
|
|||
this.loadingClientsList = true;
|
||||
this.clientsList = [];
|
||||
|
||||
if (this.hasAdditionalFiltersApplied) {
|
||||
if (this.hasClientSideFiltersApplied) {
|
||||
void this.loadClientsFromLines(requestVersion);
|
||||
return;
|
||||
}
|
||||
|
|
@ -1147,6 +1293,47 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
|
|||
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) {
|
||||
if (this.isClientRestricted) 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');
|
||||
else if (this.filterSkil === 'PJ') next = next.set('skil', 'PESSOA JURÍDICA');
|
||||
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');
|
||||
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);
|
||||
}
|
||||
|
||||
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 {
|
||||
if (this.filterStatus === 'BLOCKED' && !this.matchesBlockedStatusMode(line?.status ?? '')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const selected = this.selectedAdditionalServices;
|
||||
const hasSelected = selected.length > 0;
|
||||
|
||||
|
|
@ -1296,7 +1522,7 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
|
|||
}
|
||||
|
||||
private async fetchAllGroupsForKpis(): Promise<ClientGroupDto[]> {
|
||||
if (this.hasAdditionalFiltersApplied) {
|
||||
if (this.hasClientSideFiltersApplied) {
|
||||
const lines = await this.fetchLinesForGrouping();
|
||||
let groups = this.buildGroupsFromLines(lines);
|
||||
|
||||
|
|
@ -1411,11 +1637,11 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
|
|||
const keepCurrentPage = this.keepPageOnNextGroupsLoad;
|
||||
this.keepPageOnNextGroupsLoad = false;
|
||||
|
||||
if (!keepCurrentPage && this.filterSkil === 'RESERVA' && !hasSelection && !hasResolved) {
|
||||
if (!keepCurrentPage && (this.filterSkil === 'RESERVA' || this.filterStatus === 'BLOCKED') && !hasSelection && !hasResolved) {
|
||||
this.page = 1;
|
||||
}
|
||||
|
||||
if (this.hasAdditionalFiltersApplied) {
|
||||
if (this.hasClientSideFiltersApplied) {
|
||||
void this.loadGroupsFromLines(hasSelection, hasResolved, requestVersion);
|
||||
return;
|
||||
}
|
||||
|
|
@ -1573,7 +1799,7 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
|
|||
|
||||
const status = ((row?.status ?? '').toString().trim()).toLowerCase();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue