diff --git a/src/app/components/custom-select/custom-select.html b/src/app/components/custom-select/custom-select.html
index 6fd6bb0..6b77859 100644
--- a/src/app/components/custom-select/custom-select.html
+++ b/src/app/components/custom-select/custom-select.html
@@ -7,7 +7,7 @@
[attr.aria-disabled]="disabled"
>
{{ displayLabel }}
-
+
diff --git a/src/app/components/custom-select/custom-select.scss b/src/app/components/custom-select/custom-select.scss
index 4ffab52..117324d 100644
--- a/src/app/components/custom-select/custom-select.scss
+++ b/src/app/components/custom-select/custom-select.scss
@@ -75,8 +75,7 @@
white-space: nowrap;
}
-
-.app-select-trigger i {
+.app-select-trigger .app-select-chevron {
position: absolute;
right: 8px;
top: 50%;
diff --git a/src/app/pages/geral/geral.html b/src/app/pages/geral/geral.html
index 0317570..796667f 100644
--- a/src/app/pages/geral/geral.html
+++ b/src/app/pages/geral/geral.html
@@ -98,27 +98,19 @@
-
-
-
-
-
+
+
+
+
@@ -347,34 +339,36 @@
-
-
- Itens por pág:
-
-
-
-
+
+
+
+ Selecionadas: {{ batchStatusSelectionCount }}
+
+
+
-
-
diff --git a/src/app/pages/geral/geral.scss b/src/app/pages/geral/geral.scss
index 2056380..6519cc5 100644
--- a/src/app/pages/geral/geral.scss
+++ b/src/app/pages/geral/geral.scss
@@ -143,13 +143,88 @@
.filters-row-top {
justify-content: center;
+ z-index: 60;
}
.filters-row-bottom {
justify-content: center;
+ z-index: 40;
+}
+.filter-tabs { display: flex; gap: 4px; padding: 4px; background: rgba(255, 255, 255, 0.6); border: 1px solid rgba(17, 18, 20, 0.08); border-radius: 12px; backdrop-filter: blur(8px); flex-wrap: wrap; justify-content: center; }
+.filter-tab { border: none; background: transparent; padding: 8px 16px; border-radius: 8px; font-size: 0.85rem; font-weight: 700; color: var(--muted); transition: all 0.2s ease; display: flex; align-items: center; gap: 6px; flex: 0 0 auto; white-space: nowrap; line-height: 1.1; &:hover { color: var(--text); background: rgba(255, 255, 255, 0.5); } &.active { background: #fff; color: var(--brand); box-shadow: 0 2px 8px rgba(227, 61, 207, 0.15); } &:disabled { opacity: 0.5; cursor: not-allowed; } }
+.filter-blocked-select-box { width: 196px; flex: 0 0 auto; position: relative; z-index: 80; }
+.blocked-status-select-icon {
+ position: absolute;
+ left: 11px;
+ top: 50%;
+ transform: translateY(-50%);
+ z-index: 2;
+ pointer-events: none;
+ color: rgba(17, 18, 20, 0.68);
+ font-size: 0.82rem;
+}
+.blocked-status-native-select {
+ width: 100%;
+ height: 36px;
+ border-radius: 12px;
+ border: 1px solid rgba(17, 18, 20, 0.15);
+ background: rgba(255, 255, 255, 0.7);
+ color: #0f172a;
+ font-size: 0.78rem;
+ font-weight: 800;
+ padding: 0 30px 0 30px;
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.04);
+ appearance: none;
+ -webkit-appearance: none;
+ transition: border-color 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease;
+
+ &:hover {
+ background: #fff;
+ border-color: rgba(17, 18, 20, 0.7);
+ box-shadow: 0 4px 12px rgba(3, 15, 170, 0.1);
+ }
+
+ &:focus {
+ outline: none;
+ border-color: #e33dcf;
+ box-shadow: 0 0 0 3px rgba(227, 61, 207, 0.15);
+ }
+
+ &:disabled {
+ background-color: #f1f5f9;
+ color: #94a3b8;
+ cursor: not-allowed;
+ }
+}
+
+@media (max-width: 1366px) {
+ .filter-tabs {
+ gap: 3px;
+ padding: 3px;
+ }
+
+ .filter-tab {
+ padding: 7px 12px;
+ font-size: 0.78rem;
+ gap: 5px;
+ }
+
+ .filter-blocked-select-box {
+ width: 184px;
+ }
+}
+
+@media (max-width: 1200px) {
+ .filter-tab {
+ padding: 6px 10px;
+ font-size: 0.74rem;
+ gap: 4px;
+ }
+
+ .filter-blocked-select-box {
+ width: 176px;
+ }
}
-.filter-tabs { display: flex; gap: 4px; padding: 4px; background: rgba(255, 255, 255, 0.6); border: 1px solid rgba(17, 18, 20, 0.08); border-radius: 12px; backdrop-filter: blur(8px); }
-.filter-tab { border: none; background: transparent; padding: 8px 16px; border-radius: 8px; font-size: 0.85rem; font-weight: 700; color: var(--muted); transition: all 0.2s ease; display: flex; align-items: center; gap: 6px; &:hover { color: var(--text); background: rgba(255, 255, 255, 0.5); } &.active { background: #fff; color: var(--brand); box-shadow: 0 2px 8px rgba(227, 61, 207, 0.15); } &:disabled { opacity: 0.5; cursor: not-allowed; } }
.client-filter-wrap { position: relative; z-index: 40; }
.btn-client-filter { display: flex; align-items: center; gap: 8px; padding: 6px 12px; border-radius: 12px; border: 1px solid rgba(17, 18, 20, 0.08); background: rgba(255, 255, 255, 0.6); color: var(--muted); font-weight: 700; font-size: 0.85rem; backdrop-filter: blur(8px); transition: all 0.2s; min-height: 38px; height: auto; flex-wrap: wrap; &:hover { background: #fff; border-color: var(--blue); color: var(--blue); } &.active, &.has-selection { background: #fff; border-color: var(--brand); } }
@@ -335,17 +410,32 @@
/* Controls */
.controls { display: flex; gap: 12px; align-items: center; justify-content: space-between; flex-wrap: wrap; }
+.controls-right {
+ margin-left: auto;
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 12px;
+ flex-wrap: wrap;
+
+ @media (max-width: 900px) {
+ width: 100%;
+ justify-content: space-between;
+ }
+
+ @media (max-width: 500px) {
+ gap: 8px;
+ }
+}
.search-group { max-width: 270px; border-radius: 12px; overflow: hidden; display: flex; align-items: stretch; background: #fff; border: 1px solid rgba(17, 18, 20, 0.15); box-shadow: 0 2px 6px rgba(0, 0, 0, 0.04); transition: all 0.2s ease; &:focus-within { border-color: var(--brand); box-shadow: 0 4px 12px rgba(227, 61, 207, 0.15); transform: translateY(-1px); } .input-group-text { background: transparent; border: none; color: var(--muted); padding-left: 14px; padding-right: 8px; display: flex; align-items: center; i { font-size: 1rem; } } .form-control { border: none; background: transparent; padding: 10px 0; font-size: 0.9rem; color: var(--text); box-shadow: none; &::placeholder { color: rgba(17, 18, 20, 0.4); font-weight: 500; } &:focus { outline: none; } } .btn-clear { background: transparent; border: none; color: var(--muted); padding: 0 12px; display: flex; align-items: center; cursor: pointer; transition: color 0.2s; &:hover { color: #dc3545; } i { font-size: 1rem; } } }
-.page-size { margin-left: auto; @media (max-width: 500px) { margin-left: 0; width: 100%; justify-content: space-between; } }
+.page-size { @media (max-width: 500px) { width: 100%; justify-content: space-between; } }
.batch-status-tools {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
- margin-left: auto;
@media (max-width: 900px) {
- margin-left: 0;
width: 100%;
}
}
diff --git a/src/app/pages/geral/geral.spec.ts b/src/app/pages/geral/geral.spec.ts
index 4f94d6b..8cc0768 100644
--- a/src/app/pages/geral/geral.spec.ts
+++ b/src/app/pages/geral/geral.spec.ts
@@ -144,6 +144,23 @@ describe('Geral', () => {
expect(filtered[0].status).toBe('ATIVO');
});
+ it('should filter only pre-activation blocked lines when PRE_ATIVACAO is selected', () => {
+ component.filterStatus = 'BLOCKED';
+ component.blockedStatusMode = 'PRE_ATIVACAO';
+ 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: 'BLOQUEIO DE PRE ATIVACAO' },
+ { 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('BLOQUEIO DE PRE ATIVACAO');
+ });
+
it('should classify active line as ACTIVE during smart search resolution', () => {
const target = (component as any).buildSmartSearchTarget({
id: '1',
@@ -160,6 +177,33 @@ describe('Geral', () => {
expect(target?.statusFilter).toBe('ACTIVE');
});
+ it('should classify pre-activation line as blocked 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: 'BLOQUEIO DE PRE ATIVACAO',
+ skil: 'PESSOA JURIDICA',
+ }, true);
+
+ expect(target?.statusFilter).toBe('BLOCKED');
+ expect(target?.blockedStatusMode).toBe('PRE_ATIVACAO');
+ });
+
+ it('should toggle blocked select filter off when selecting the same blocked option again', () => {
+ component.setBlockedStatusFilter('BLOQUEIO_120');
+ expect(component.filterStatus).toBe('BLOCKED');
+ expect(component.blockedStatusMode).toBe('BLOQUEIO_120');
+
+ component.setBlockedStatusFilter('BLOQUEIO_120');
+ expect(component.filterStatus).toBe('ALL');
+ expect(component.blockedStatusMode).toBe('ALL');
+ });
+
it('should request assigned reserve lines in ALL filter only', () => {
component.filterSkil = 'ALL';
let params = (component as any).applyBaseFilters(new HttpParams());
diff --git a/src/app/pages/geral/geral.ts b/src/app/pages/geral/geral.ts
index 06efc75..3ccfba5 100644
--- a/src/app/pages/geral/geral.ts
+++ b/src/app/pages/geral/geral.ts
@@ -68,7 +68,7 @@ type CreateEntryMode = 'SINGLE' | 'BATCH';
type AdditionalMode = 'ALL' | 'WITH' | 'WITHOUT';
type OperadoraFilterMode = 'ALL' | 'VIVO' | 'CLARO' | 'TIM';
type AdditionalServiceKey = 'gvd' | 'skeelo' | 'news' | 'travel' | 'sync' | 'dispositivo';
-type BlockedStatusMode = 'ALL' | 'PERDA_ROUBO' | 'BLOQUEIO_120';
+type BlockedStatusMode = 'ALL' | 'PERDA_ROUBO' | 'BLOQUEIO_120' | 'PRE_ATIVACAO';
type SkilFilterMode = 'ALL' | 'PF' | 'PJ' | 'RESERVA' | 'ESTOQUE';
interface LineRow {
@@ -418,6 +418,12 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
selectedAdditionalServices: AdditionalServiceKey[] = [];
filterOperadora: OperadoraFilterMode = 'ALL';
filterContaEmpresa = '';
+ readonly blockedStatusFilterOptions: Array<{ label: string; value: BlockedStatusMode }> = [
+ { label: 'Bloqueadas', value: 'ALL' },
+ { label: 'Bloqueio Perda/Roubo', value: 'PERDA_ROUBO' },
+ { label: 'Bloqueio por 120 dias', value: 'BLOQUEIO_120' },
+ { label: 'Bloqueio de Pré Ativação', value: 'PRE_ATIVACAO' },
+ ];
readonly additionalServiceOptions: Array<{ key: AdditionalServiceKey; label: string }> = [
{ key: 'gvd', label: 'Gestão Voz e Dados' },
{ key: 'skeelo', label: 'Skeelo' },
@@ -533,7 +539,7 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
kpiAtivas = 0;
kpiBloqueadas = 0;
- readonly statusOptions = ['ATIVO', 'BLOQUEIO PERDA/ROUBO', 'BLOQUEIO 120 DIAS'];
+ readonly statusOptions = ['ATIVO', 'BLOQUEIO PERDA/ROUBO', 'BLOQUEIO 120 DIAS', 'BLOQUEIO DE PRÉ ATIVAÇÃO'];
readonly skilOptions = ['PESSOA FÍSICA', 'PESSOA JURÍDICA', 'RESERVA'];
planOptions = [
@@ -1214,6 +1220,13 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
) {
return 'BLOQUEIO_120';
}
+ if (
+ token === 'PREATIVACAO' ||
+ token === 'PREATIV' ||
+ token === 'BLOQUEIOPREATIVACAO'
+ ) {
+ return 'PRE_ATIVACAO';
+ }
return null;
}
@@ -1845,13 +1858,18 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
this.refreshData();
}
- toggleBlockedFilter() {
- if (this.filterStatus === 'BLOCKED') {
+ setBlockedStatusFilter(mode: BlockedStatusMode) {
+ const normalizedMode = mode ?? 'ALL';
+ const sameSelection = this.filterStatus === 'BLOCKED' && this.blockedStatusMode === normalizedMode;
+
+ if (sameSelection) {
this.filterStatus = 'ALL';
this.blockedStatusMode = 'ALL';
} else {
this.filterStatus = 'BLOCKED';
+ this.blockedStatusMode = normalizedMode;
}
+
this.expandedGroup = null;
this.groupLines = [];
this.searchResolvedClient = null;
@@ -1866,24 +1884,10 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
this.refreshData();
}
- setBlockedStatusMode(mode: Exclude) {
- 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();
+ onBlockedStatusSelectChange(event: Event) {
+ const target = event.target;
+ const value = target instanceof HTMLSelectElement ? target.value : 'ALL';
+ this.setBlockedStatusFilter(value as BlockedStatusMode);
}
setAdditionalMode(mode: AdditionalMode) {
@@ -1974,6 +1978,7 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
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');
+ else if (this.blockedStatusMode === 'PRE_ATIVACAO') next = next.set('statusSubtype', 'pre_ativacao');
}
if (this.additionalMode === 'WITH') next = next.set('additionalMode', 'with');
@@ -2020,15 +2025,17 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
normalized.includes('BLOQUE') ||
normalized.includes('PERDA') ||
normalized.includes('ROUBO') ||
- normalized.includes('FURTO');
+ normalized.includes('FURTO') ||
+ normalized.includes('PREATIV');
if (!hasBlockedToken) return null;
if (normalized.includes('120')) return 'BLOQUEIO_120';
+ if (normalized.includes('PREATIV')) return 'PRE_ATIVACAO';
if (normalized.includes('PERDA') || normalized.includes('ROUBO') || normalized.includes('FURTO')) {
return 'PERDA_ROUBO';
}
- return 'PERDA_ROUBO';
+ return 'PRE_ATIVACAO';
}
private isBlockedStatus(status: unknown): boolean {
@@ -2939,6 +2946,7 @@ export class Geral implements OnInit, AfterViewInit, OnDestroy {
} 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 if (this.blockedStatusMode === 'PRE_ATIVACAO') parts.push('bloq-pre-ativacao');
else parts.push('bloqueadas');
}