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;