diff --git a/src/app/pages/geral/geral.html b/src/app/pages/geral/geral.html index 11a4511..0317570 100644 --- a/src/app/pages/geral/geral.html +++ b/src/app/pages/geral/geral.html @@ -95,6 +95,9 @@ Estoque + diff --git a/src/app/pages/geral/geral.spec.ts b/src/app/pages/geral/geral.spec.ts index 07f461d..a16d540 100644 --- a/src/app/pages/geral/geral.spec.ts +++ b/src/app/pages/geral/geral.spec.ts @@ -1,6 +1,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; import { provideHttpClientTesting } from '@angular/common/http/testing'; +import { HttpParams } from '@angular/common/http'; import { provideRouter } from '@angular/router'; import { Geral } from './geral'; @@ -105,4 +106,69 @@ describe('Geral', () => { expect(filtered.length).toBe(1); expect(filtered[0].conta).toBe('460161507'); }); + + it('should classify reserve-assigned line as RESERVA during smart search resolution', () => { + const skilFilter = (component as any).resolveSkilFilterFromLine({ + cliente: 'AVANCO DISTRIBUIDORA', + usuario: 'RESERVA', + skil: 'PESSOA JURIDICA', + }); + + expect(skilFilter).toBe('RESERVA'); + }); + + it('should classify stock line as ESTOQUE during smart search resolution', () => { + const skilFilter = (component as any).resolveSkilFilterFromLine({ + cliente: 'ESTOQUE', + usuario: 'RESERVA', + skil: 'RESERVA', + }); + + expect(skilFilter).toBe('ESTOQUE'); + }); + + it('should filter only active lines when ACTIVE status filter is selected', () => { + component.filterStatus = 'ACTIVE'; + component.filterOperadora = 'ALL'; + component.filterContaEmpresa = ''; + component.additionalMode = 'ALL'; + component.selectedAdditionalServices = []; + + const filtered = (component as any).applyAdditionalFiltersClientSide([ + { id: '1', item: 1, conta: '1', linha: '11911111111', cliente: 'A', usuario: 'U', vencConta: null, status: 'ATIVO' }, + { id: '2', item: 2, conta: '2', linha: '11922222222', cliente: 'B', usuario: 'U', vencConta: null, status: 'BLOQUEIO 120 DIAS' }, + ]); + + expect(filtered.length).toBe(1); + expect(filtered[0].status).toBe('ATIVO'); + }); + + it('should classify active line as ACTIVE during smart search resolution', () => { + const target = (component as any).buildSmartSearchTarget({ + id: '1', + item: 1, + conta: '1', + linha: '11911111111', + cliente: 'CLIENTE A', + usuario: 'USUARIO A', + vencConta: null, + status: 'ATIVO', + skil: 'PESSOA JURIDICA', + }, true); + + expect(target?.statusFilter).toBe('ACTIVE'); + }); + + it('should request assigned reserve lines in ALL filter only', () => { + component.filterSkil = 'ALL'; + let params = (component as any).applyBaseFilters(new HttpParams()); + + expect(params.get('includeAssignedReservaInAll')).toBe('true'); + + component.filterSkil = 'RESERVA'; + params = (component as any).applyBaseFilters(new HttpParams()); + + expect(params.get('includeAssignedReservaInAll')).toBeNull(); + expect(params.get('skil')).toBe('RESERVA'); + }); }); diff --git a/src/app/pages/geral/geral.ts b/src/app/pages/geral/geral.ts index 8f63072..7f13668 100644 --- a/src/app/pages/geral/geral.ts +++ b/src/app/pages/geral/geral.ts @@ -121,7 +121,7 @@ interface ApiLineList { interface SmartSearchTargetResolution { client: string; skilFilter: SkilFilterMode; - statusFilter: 'ALL' | 'BLOCKED'; + statusFilter: 'ALL' | 'ACTIVE' | 'BLOCKED'; blockedStatusMode: BlockedStatusMode; requiresFilterAdjustment: boolean; } @@ -405,7 +405,7 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy { searchTerm = ''; filterSkil: SkilFilterMode = 'ALL'; - filterStatus: 'ALL' | 'BLOCKED' = 'ALL'; + filterStatus: 'ALL' | 'ACTIVE' | 'BLOCKED' = 'ALL'; blockedStatusMode: BlockedStatusMode = 'ALL'; additionalMode: AdditionalMode = 'ALL'; selectedAdditionalServices: AdditionalServiceKey[] = []; @@ -831,7 +831,7 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy { } get hasClientSideFiltersApplied(): boolean { - return this.hasAdditionalFiltersApplied || this.filterStatus === 'BLOCKED' || this.hasOperadoraEmpresaFiltersApplied; + return this.hasAdditionalFiltersApplied || this.filterStatus !== 'ALL' || this.hasOperadoraEmpresaFiltersApplied; } get additionalModeLabel(): string { @@ -1149,10 +1149,19 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy { return null; } - private parseQueryStatusFilter(value: unknown): 'ALL' | 'BLOCKED' | null { + private parseQueryStatusFilter(value: unknown): 'ALL' | 'ACTIVE' | 'BLOCKED' | null { const token = this.normalizeFilterToken(value); if (!token) return null; if (token === 'ALL' || token === 'TODOS') return 'ALL'; + if ( + token === 'ACTIVE' || + token === 'ATIVAS' || + token === 'ATIVOS' || + token === 'ATIVA' || + token === 'ATIVO' + ) { + return 'ACTIVE'; + } if ( token === 'BLOCKED' || token === 'BLOQUEADAS' || @@ -1396,7 +1405,7 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy { refreshData(opts?: { keepCurrentPage?: boolean }) { const keepCurrentPage = !!opts?.keepCurrentPage; this.keepPageOnNextGroupsLoad = keepCurrentPage; - if (!keepCurrentPage && (this.isReserveContextFilter() || this.filterStatus === 'BLOCKED')) { + if (!keepCurrentPage && (this.isReserveContextFilter() || this.filterStatus !== 'ALL')) { this.page = 1; } this.searchResolvedClient = null; @@ -1422,9 +1431,18 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy { return String(value ?? '').replace(/\D/g, ''); } - private resolveSkilFilterFromLine(skil: unknown, client: unknown): SkilFilterMode { - if (this.isStockClientName(client)) return 'ESTOQUE'; - const parsed = this.parseQuerySkilFilter(skil); + private isReservaValue(value: unknown): boolean { + return this.normalizeFilterToken(value) === 'RESERVA'; + } + + private resolveSkilFilterFromLine(line: Pick | null | undefined): SkilFilterMode { + if (!line) return 'ALL'; + if (this.isStockClientName(line.cliente)) return 'ESTOQUE'; + if (this.isReservaValue(line.cliente) || this.isReservaValue(line.usuario) || this.isReservaValue(line.skil)) { + return 'RESERVA'; + } + + const parsed = this.parseQuerySkilFilter(line.skil); return parsed ?? 'ALL'; } @@ -1482,6 +1500,8 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy { if (!options?.ignoreCurrentFilters) { params = this.applyBaseFilters(params); this.selectedClients.forEach((c) => (params = params.append('client', c))); + } else if (!options?.skilFilter) { + params = params.set('includeAssignedReservaInAll', 'true'); } else if (options?.skilFilter === 'PF') { params = params.set('skil', 'PESSOA FÍSICA'); } else if (options?.skilFilter === 'PJ') { @@ -1514,14 +1534,19 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy { ): SmartSearchTargetResolution | null { if (!line) return null; - const skilFilter = this.resolveSkilFilterFromLine(line?.skil, line?.cliente); + const skilFilter = this.resolveSkilFilterFromLine(line); const blockedStatusMode = this.resolveBlockedStatusMode(line?.status ?? '') ?? 'ALL'; + const statusFilter = blockedStatusMode !== 'ALL' + ? 'BLOCKED' + : this.isActiveStatus(line?.status ?? '') + ? 'ACTIVE' + : 'ALL'; const client = ((line?.cliente ?? '').toString().trim()) || this.getClientFallbackLabel('SEM CLIENTE', skilFilter); return { client, skilFilter, - statusFilter: blockedStatusMode === 'ALL' ? 'ALL' : 'BLOCKED', + statusFilter, blockedStatusMode, requiresFilterAdjustment }; @@ -1780,6 +1805,27 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy { this.refreshData(); } + toggleActiveFilter() { + if (this.filterStatus === 'ACTIVE') { + this.filterStatus = 'ALL'; + } else { + this.filterStatus = 'ACTIVE'; + this.blockedStatusMode = 'ALL'; + } + this.expandedGroup = null; + this.groupLines = []; + this.searchResolvedClient = null; + this.selectedClients = []; + this.clientSearchTerm = ''; + this.page = 1; + + if (!this.isClientRestricted) { + this.loadClients(); + } + + this.refreshData(); + } + toggleBlockedFilter() { if (this.filterStatus === 'BLOCKED') { this.filterStatus = 'ALL'; @@ -1902,6 +1948,8 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy { next = next.set('skil', 'RESERVA'); const reservaMode = this.getReservaModeForApi(); if (reservaMode) next = next.set('reservaMode', reservaMode); + } else { + next = next.set('includeAssignedReservaInAll', 'true'); } if (this.filterStatus === 'BLOCKED') { next = next.set('statusMode', 'blocked'); @@ -1968,6 +2016,11 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy { return this.resolveBlockedStatusMode(status) !== null; } + private isActiveStatus(status: unknown): boolean { + if (this.isBlockedStatus(status)) return false; + return this.normalizeFilterToken(status).includes('ATIVO'); + } + private matchesBlockedStatusMode(status: unknown): boolean { const mode = this.resolveBlockedStatusMode(status); if (!mode) return false; @@ -1979,6 +2032,9 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy { if (this.filterStatus === 'BLOCKED' && !this.matchesBlockedStatusMode(line?.status ?? '')) { return false; } + if (this.filterStatus === 'ACTIVE' && !this.isActiveStatus(line?.status ?? '')) { + return false; + } if (!this.matchesOperadoraContaEmpresaFilters(line)) { return false; @@ -2192,7 +2248,7 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy { const keepCurrentPage = this.keepPageOnNextGroupsLoad; this.keepPageOnNextGroupsLoad = false; - if (!keepCurrentPage && (this.isReserveContextFilter() || this.filterStatus === 'BLOCKED') && !hasSelection && !hasResolved) { + if (!keepCurrentPage && (this.isReserveContextFilter() || this.filterStatus !== 'ALL') && !hasSelection && !hasResolved) { this.page = 1; } @@ -2843,7 +2899,9 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy { else if (this.filterSkil === 'ESTOQUE') parts.push('estoque'); else parts.push('todas'); - if (this.filterStatus === 'BLOCKED') { + if (this.filterStatus === 'ACTIVE') { + parts.push('ativas'); + } else if (this.filterStatus === 'BLOCKED') { if (this.blockedStatusMode === 'PERDA_ROUBO') parts.push('bloq-perda-roubo'); else if (this.blockedStatusMode === 'BLOQUEIO_120') parts.push('bloq-120'); else parts.push('bloqueadas'); @@ -5166,12 +5224,6 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy { return v || '-'; } - private isActiveStatus(status: string | null | undefined): boolean { - const normalized = (status ?? '').toString().trim().toLowerCase(); - if (!normalized) return false; - return normalized.includes('ativo'); - } - private toEditModel(d: ApiLineDetail): any { return { ...d, diff --git a/src/app/pages/mve-auditoria/mve-auditoria.scss b/src/app/pages/mve-auditoria/mve-auditoria.scss index 2945256..7461f91 100644 --- a/src/app/pages/mve-auditoria/mve-auditoria.scss +++ b/src/app/pages/mve-auditoria/mve-auditoria.scss @@ -463,11 +463,11 @@ } thead th:nth-child(4) { - width: 24%; + width: 20%; } thead th:nth-child(5) { - width: 12%; + width: 16%; } thead th { @@ -508,6 +508,12 @@ text-align: center; } +.cell-situation, +.cell-action { + padding-left: 12px; + padding-right: 12px; +} + .cell-compare { text-align: left; } @@ -540,12 +546,16 @@ display: inline-flex; align-items: center; justify-content: center; - padding: 6px 10px; + min-width: min(100%, 190px); + max-width: 100%; + padding: 10px 16px; border-radius: 999px; - font-size: 11px; + font-size: 13px; font-weight: 900; - line-height: 1; + line-height: 1.15; + letter-spacing: 0.02em; border: 1px solid transparent; + text-align: center; } .issue-kind-badge { @@ -709,16 +719,17 @@ .situation-card { display: grid; gap: 12px; - padding: 14px; + width: min(100%, 240px); + min-height: 112px; + margin-inline: auto; + padding: 10px 8px; border-radius: 18px; - background: linear-gradient(180deg, rgba(255, 255, 255, 0.97), rgba(248, 246, 250, 0.93)); - box-shadow: 0 14px 28px rgba(24, 17, 33, 0.05); + background: transparent; + box-shadow: none; + border: 0; + align-content: center; justify-items: center; text-align: center; - - &.is-applied { - background: linear-gradient(180deg, rgba(25, 135, 84, 0.06), rgba(255, 255, 255, 0.98)); - } } .situation-top { @@ -731,23 +742,38 @@ .action-card { display: grid; - gap: 10px; + width: min(100%, 188px); + min-height: 112px; + margin-inline: auto; + padding: 10px 8px; + gap: 12px; + align-content: center; justify-items: center; + text-align: center; + border-radius: 18px; + border: 0; + background: transparent; + box-shadow: none; } .sync-badge { display: inline-flex; align-items: center; justify-content: center; - padding: 7px 12px; + max-width: 100%; + padding: 10px 14px; border-radius: 999px; font-size: 12px; font-weight: 900; + line-height: 1.15; + text-align: center; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; &.ready { background: rgba(255, 178, 0, 0.16); color: #8c6200; - font-size: 11px; } &.applied { @@ -763,6 +789,8 @@ .page-footer { margin-top: 16px; + padding: 14px 24px 0; + border-top: 1px solid rgba(17, 18, 20, 0.06); display: flex; align-items: center; justify-content: space-between; @@ -770,6 +798,35 @@ flex-wrap: wrap; } +.pagination-modern .page-link { + color: #030faa; + font-weight: 900; + border-radius: 10px; + border: 1px solid rgba(17, 18, 20, 0.1); + background: rgba(255, 255, 255, 0.6); + margin: 0 2px; + transition: transform 160ms ease, border-color 160ms ease, color 160ms ease, background-color 160ms ease; + + &:hover { + transform: translateY(-1px); + border-color: var(--brand); + color: var(--brand); + background: rgba(255, 255, 255, 0.92); + } +} + +.pagination-modern .page-item.active .page-link { + background-color: #030faa; + border-color: #030faa; + color: #fff; +} + +.pagination-modern .page-item.disabled .page-link { + color: rgba(24, 17, 33, 0.42); + background: rgba(255, 255, 255, 0.38); + border-color: rgba(17, 18, 20, 0.08); +} + .status-empty, .empty-state { padding: 42px 20px; @@ -908,6 +965,11 @@ padding: 7px 12px; } + .situation-card, + .action-card { + width: min(100%, 220px); + } + .page-footer { justify-content: center; text-align: center; @@ -942,6 +1004,11 @@ font-size: 11px; } + .issue-kind-badge { + font-size: 11px; + padding: 8px 12px; + } + .page-footer .pagination { justify-content: center; flex-wrap: wrap;