diff --git a/src/app/components/custom-select/custom-select.html b/src/app/components/custom-select/custom-select.html
index 9619462..6fd6bb0 100644
--- a/src/app/components/custom-select/custom-select.html
+++ b/src/app/components/custom-select/custom-select.html
@@ -11,10 +11,31 @@
+
+
+
+
+
+
-
diff --git a/src/app/components/custom-select/custom-select.scss b/src/app/components/custom-select/custom-select.scss
index acfa098..4ffab52 100644
--- a/src/app/components/custom-select/custom-select.scss
+++ b/src/app/components/custom-select/custom-select.scss
@@ -111,6 +111,59 @@
padding: 6px;
}
+.app-select-search {
+ position: sticky;
+ top: 0;
+ z-index: 2;
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ margin: 0 0 6px;
+ padding: 6px 8px;
+ border: 1px solid rgba(15, 23, 42, 0.1);
+ border-radius: 9px;
+ background: #fff;
+
+ i {
+ color: #64748b;
+ font-size: 12px;
+ }
+}
+
+.app-select-search-input {
+ flex: 1 1 auto;
+ min-width: 0;
+ border: none;
+ background: transparent;
+ outline: none;
+ font-size: 12px;
+ color: #0f172a;
+ padding: 0;
+
+ &::placeholder {
+ color: #94a3b8;
+ }
+}
+
+.app-select-search-clear {
+ width: 20px;
+ height: 20px;
+ border: none;
+ border-radius: 999px;
+ background: transparent;
+ color: #94a3b8;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0;
+ cursor: pointer;
+
+ &:hover {
+ background: rgba(148, 163, 184, 0.2);
+ color: #475569;
+ }
+}
+
.app-select-option {
width: 100%;
border: none;
diff --git a/src/app/components/custom-select/custom-select.ts b/src/app/components/custom-select/custom-select.ts
index 6bd1df3..3ef4bcb 100644
--- a/src/app/components/custom-select/custom-select.ts
+++ b/src/app/components/custom-select/custom-select.ts
@@ -23,9 +23,12 @@ export class CustomSelectComponent implements ControlValueAccessor {
@Input() valueKey = 'value';
@Input() size: 'sm' | 'md' = 'md';
@Input() disabled = false;
+ @Input() searchable = false;
+ @Input() searchPlaceholder = 'Pesquisar...';
isOpen = false;
value: any = null;
+ searchTerm = '';
private onChange: (value: any) => void = () => {};
private onTouched: () => void = () => {};
@@ -63,10 +66,12 @@ export class CustomSelectComponent implements ControlValueAccessor {
toggle(): void {
if (this.disabled) return;
this.isOpen = !this.isOpen;
+ if (!this.isOpen) this.searchTerm = '';
}
close(): void {
this.isOpen = false;
+ this.searchTerm = '';
}
selectOption(option: any): void {
@@ -84,6 +89,26 @@ export class CustomSelectComponent implements ControlValueAccessor {
trackByValue = (_: number, option: any) => this.getOptionValue(option);
+ get filteredOptions(): any[] {
+ const opts = this.options || [];
+ const term = this.normalizeText(this.searchTerm);
+ if (!this.searchable || !term) return opts;
+ return opts.filter((opt) => this.normalizeText(this.getOptionLabel(opt)).includes(term));
+ }
+
+ onSearchInput(value: string): void {
+ this.searchTerm = value ?? '';
+ }
+
+ clearSearch(event?: Event): void {
+ event?.stopPropagation();
+ this.searchTerm = '';
+ }
+
+ onSearchKeydown(event: KeyboardEvent): void {
+ event.stopPropagation();
+ }
+
private getOptionValue(option: any): any {
if (option && typeof option === 'object') {
return option[this.valueKey];
@@ -103,6 +128,14 @@ export class CustomSelectComponent implements ControlValueAccessor {
return (this.options || []).find((o) => this.getOptionValue(o) === value);
}
+ private normalizeText(value: string): string {
+ return (value ?? '')
+ .normalize('NFD')
+ .replace(/[\u0300-\u036f]/g, '')
+ .toLowerCase()
+ .trim();
+ }
+
@HostListener('document:click', ['$event'])
onDocumentClick(event: MouseEvent): void {
if (!this.isOpen) return;
diff --git a/src/app/pages/dados-usuarios/dados-usuarios.html b/src/app/pages/dados-usuarios/dados-usuarios.html
index 2e34ac1..a3ebad1 100644
--- a/src/app/pages/dados-usuarios/dados-usuarios.html
+++ b/src/app/pages/dados-usuarios/dados-usuarios.html
@@ -231,34 +231,31 @@
-
+
+
+
+
@@ -357,7 +357,26 @@
-
+
+
+
+
+
diff --git a/src/app/pages/dados-usuarios/dados-usuarios.ts b/src/app/pages/dados-usuarios/dados-usuarios.ts
index 6ed40b2..b94a1c9 100644
--- a/src/app/pages/dados-usuarios/dados-usuarios.ts
+++ b/src/app/pages/dados-usuarios/dados-usuarios.ts
@@ -24,6 +24,7 @@ interface LineOptionDto {
item: number;
linha: string | null;
usuario: string | null;
+ franquiaLine?: number | null;
label?: string;
}
@@ -94,13 +95,15 @@ export class DadosUsuarios implements OnInit {
createSaving = false;
createModel: any = null;
createDateNascimento = '';
- clientsFromGeral: string[] = [];
+ createFranquiaLineTotal = 0;
+ editFranquiaLineTotal = 0;
+ editSelectedLineId = '';
+ editLineOptions: LineOptionDto[] = [];
lineOptionsCreate: LineOptionDto[] = [];
readonly tipoPessoaOptions: SimpleOption[] = [
{ label: 'Pessoa Física', value: 'PF' },
{ label: 'Pessoa Jurídica', value: 'PJ' },
];
- createClientsLoading = false;
createLinesLoading = false;
isSysAdmin = false;
@@ -295,7 +298,11 @@ export class DadosUsuarios implements OnInit {
razaoSocial: fullData.razaoSocial || (tipo === 'PJ' ? fullData.cliente : '')
};
this.editDateNascimento = this.toDateInput(fullData.dataNascimento);
+ this.editFranquiaLineTotal = 0;
+ this.editSelectedLineId = '';
+ this.editLineOptions = [];
this.editOpen = true;
+ this.loadReserveLinesForSelects();
},
error: () => this.showToast('Erro ao abrir edição', 'danger')
});
@@ -307,6 +314,9 @@ export class DadosUsuarios implements OnInit {
this.editModel = null;
this.editDateNascimento = '';
this.editingId = null;
+ this.editSelectedLineId = '';
+ this.editLineOptions = [];
+ this.editFranquiaLineTotal = 0;
}
onEditTipoChange() {
@@ -369,13 +379,14 @@ export class DadosUsuarios implements OnInit {
if (!this.isSysAdmin) return;
this.resetCreateModel();
this.createOpen = true;
- this.preloadGeralClients();
+ this.loadReserveLinesForSelects();
}
closeCreate() {
this.createOpen = false;
this.createSaving = false;
this.createModel = null;
+ this.createFranquiaLineTotal = 0;
}
private resetCreateModel() {
@@ -397,33 +408,9 @@ export class DadosUsuarios implements OnInit {
telefoneFixo: ''
};
this.createDateNascimento = '';
+ this.createFranquiaLineTotal = 0;
this.lineOptionsCreate = [];
this.createLinesLoading = false;
- this.createClientsLoading = false;
- }
-
- private preloadGeralClients() {
- this.createClientsLoading = true;
- this.linesService.getClients().subscribe({
- next: (list) => {
- this.clientsFromGeral = list ?? [];
- this.createClientsLoading = false;
- },
- error: () => {
- this.clientsFromGeral = [];
- this.createClientsLoading = false;
- }
- });
- }
-
- onCreateClientChange() {
- const c = (this.createModel?.selectedClient ?? '').trim();
- this.createModel.mobileLineId = '';
- this.createModel.linha = '';
- this.createModel.cliente = c;
- this.lineOptionsCreate = [];
-
- if (c) this.loadLinesForClient(c);
}
onCreateTipoChange() {
@@ -438,12 +425,9 @@ export class DadosUsuarios implements OnInit {
}
}
- private loadLinesForClient(cliente: string) {
- const c = (cliente ?? '').trim();
- if (!c) return;
-
+ private loadReserveLinesForSelects(onDone?: () => void) {
this.createLinesLoading = true;
- this.linesService.getLinesByClient(c).subscribe({
+ this.linesService.getLinesByClient('RESERVA').subscribe({
next: (items: any[]) => {
const mapped: LineOptionDto[] = (items ?? [])
.filter(x => !!String(x?.id ?? '').trim())
@@ -457,12 +441,16 @@ export class DadosUsuarios implements OnInit {
.filter(x => !!String(x.linha ?? '').trim());
this.lineOptionsCreate = mapped;
+ if (this.editModel) this.syncEditLineOptions();
this.createLinesLoading = false;
+ onDone?.();
},
error: () => {
this.lineOptionsCreate = [];
+ this.editLineOptions = [];
this.createLinesLoading = false;
- this.showToast('Erro ao carregar linhas da GERAL.', 'danger');
+ this.showToast('Erro ao carregar linhas da Reserva.', 'danger');
+ onDone?.();
}
});
}
@@ -477,13 +465,56 @@ export class DadosUsuarios implements OnInit {
});
}
+ onEditLineChange() {
+ const id = String(this.editSelectedLineId ?? '').trim();
+ if (!id || id === '__CURRENT__') return;
+ this.linesService.getById(id).subscribe({
+ next: (d: MobileLineDetail) => this.applyLineDetailToEdit(d),
+ error: () => this.showToast('Erro ao carregar dados da linha.', 'danger')
+ });
+ }
+
+ private syncEditLineOptions() {
+ if (!this.editModel) {
+ this.editLineOptions = [];
+ this.editSelectedLineId = '';
+ return;
+ }
+
+ const currentLine = String(this.editModel.linha ?? '').trim();
+ const fromReserva = this.lineOptionsCreate.find((x) => String(x.linha ?? '').trim() === currentLine);
+ const options = [...this.lineOptionsCreate];
+
+ if (currentLine && !fromReserva) {
+ options.unshift({
+ id: '__CURRENT__',
+ item: Number(this.editModel.item ?? 0),
+ linha: currentLine,
+ usuario: this.editModel.cliente ?? null,
+ label: `Atual • ${currentLine}`
+ });
+ }
+
+ this.editLineOptions = options;
+ this.editSelectedLineId = fromReserva?.id ?? (currentLine ? '__CURRENT__' : '');
+ if (fromReserva?.id) {
+ this.onEditLineChange();
+ }
+ }
+
private applyLineDetailToCreate(d: MobileLineDetail) {
this.createModel.linha = d.linha ?? '';
- this.createModel.cliente = d.cliente ?? this.createModel.cliente ?? '';
+ this.createFranquiaLineTotal = this.toNullableNumber(d.franquiaLine) ?? 0;
if (!String(this.createModel.item ?? '').trim() && d.item) {
this.createModel.item = String(d.item);
}
+ const lineClient = String(d.cliente ?? '').trim();
+ const isReserva = lineClient.localeCompare('RESERVA', 'pt-BR', { sensitivity: 'base' }) === 0;
+ if (!isReserva && lineClient) {
+ this.createModel.cliente = lineClient;
+ }
+
if ((this.createModel.tipoPessoa ?? '').toUpperCase() === 'PJ') {
if (!this.createModel.razaoSocial) this.createModel.razaoSocial = this.createModel.cliente;
} else {
@@ -491,6 +522,15 @@ export class DadosUsuarios implements OnInit {
}
}
+ private applyLineDetailToEdit(d: MobileLineDetail) {
+ if (!this.editModel) return;
+ this.editModel.linha = d.linha ?? this.editModel.linha;
+ this.editFranquiaLineTotal = this.toNullableNumber(d.franquiaLine) ?? 0;
+ if (!String(this.editModel.item ?? '').trim() && d.item) {
+ this.editModel.item = d.item;
+ }
+ }
+
saveCreate() {
if (!this.createModel) return;
this.createSaving = true;
@@ -584,6 +624,11 @@ export class DadosUsuarios implements OnInit {
return Number.isNaN(n) ? null : n;
}
+ formatFranquiaLine(value: any): string {
+ const n = this.toNullableNumber(value) ?? 0;
+ return `${n.toLocaleString('pt-BR', { minimumFractionDigits: 0, maximumFractionDigits: 2 })} GB`;
+ }
+
private normalizeTipo(row: UserDataRow | null | undefined): 'PF' | 'PJ' {
const t = (row?.tipoPessoa ?? '').toString().trim().toUpperCase();
if (t === 'PJ') return 'PJ';
diff --git a/src/app/pages/dashboard/dashboard.html b/src/app/pages/dashboard/dashboard.html
index 7ae6145..c76e8b0 100644
--- a/src/app/pages/dashboard/dashboard.html
+++ b/src/app/pages/dashboard/dashboard.html
@@ -354,20 +354,10 @@
Ativas
{{ statusResumo.ativos | number:'1.0-0' }}
-
-
- Bloqueadas
- {{ statusResumo.bloqueadas | number:'1.0-0' }}
-
-
-
- Reserva
- {{ statusResumo.reservas | number:'1.0-0' }}
-
- Outros Status
- {{ clientOverview.outrosStatus | number:'1.0-0' }}
+ Demais Linhas
+ {{ clientDemaisLinhas | number:'1.0-0' }}
Total
@@ -393,44 +383,30 @@