11 KiB
11 KiB
🎯 RemoteSelect Component - Documentação Completa
📖 Visão Geral
O RemoteSelectComponent é um componente universal de busca dinâmica que permite autocomplete e seleção de registros de qualquer domínio da aplicação PraFrota.
✨ Funcionalidades Principais
- 🔍 Autocomplete inteligente com debounce configurável
- ⌨️ Navegação por teclado (setas, Enter, Escape)
- 🔑 Tecla F2 para abrir modal de seleção completa
- 📱 Responsivo e acessível (WCAG 2.1)
- 🔄 Reactive Forms - implementa
ControlValueAccessor - 💾 Cache automático de resultados
- 🎨 Material Design + PraFrota Design System
- ⚡ Performance otimizada para grandes datasets
🚀 Instalação e Setup
1. Importar o Componente
import { RemoteSelectComponent } from './shared/components/remote-select/remote-select.component';
@Component({
// ...
imports: [RemoteSelectComponent]
})
2. Configuração Básica
import { RemoteSelectConfig } from './shared/components/remote-select/interfaces/remote-select.interface';
export class YourComponent {
driversConfig: RemoteSelectConfig = {
service: this.driversService,
searchField: 'name',
displayField: 'name',
valueField: 'id',
modalTitle: 'Selecionar Motorista',
placeholder: 'Digite o nome do motorista...'
};
constructor(private driversService: DriversService) {}
}
3. Uso no Template
<app-remote-select
[config]="driversConfig"
label="Motorista"
[required]="true"
(selectionChange)="onDriverSelected($event)"
></app-remote-select>
🎛️ API do Componente
Inputs
| Propriedade | Tipo | Padrão | Descrição |
|---|---|---|---|
config |
RemoteSelectConfig |
- | Obrigatório. Configuração do componente |
label |
string |
'' |
Label exibido acima do campo |
disabled |
boolean |
false |
Desabilita o componente |
required |
boolean |
false |
Marca o campo como obrigatório |
hideLabel |
boolean |
false |
Oculta o label |
Outputs
| Evento | Tipo | Descrição |
|---|---|---|
selectionChange |
RemoteSelectEvent |
Emitido quando item é selecionado/limpo |
searchQuery |
string |
Emitido durante a busca (para debug) |
modalToggle |
boolean |
Emitido quando modal F2 abre/fecha |
RemoteSelectConfig
interface RemoteSelectConfig {
service: any; // Service com método getEntities()
searchField: string; // Campo usado na busca
displayField: string; // Campo mostrado na UI
valueField: string; // Campo do valor retornado
modalTitle?: string; // Título do modal F2
placeholder?: string; // Placeholder do input
additionalFields?: string[]; // Campos extras para busca
minLength?: number; // Min. caracteres (padrão: 3)
debounceTime?: number; // Debounce em ms (padrão: 300)
multiple?: boolean; // Seleção múltipla (padrão: false)
maxResults?: number; // Max. resultados (padrão: 10)
}
RemoteSelectEvent
interface RemoteSelectEvent {
value: any | any[]; // Valor(es) selecionado(s)
item: RemoteSelectItem | RemoteSelectItem[]; // Item(s) completo(s)
source: 'dropdown' | 'modal' | 'clear'; // Origem da seleção
}
🎯 Exemplos de Uso
1. Busca Simples de Motoristas
// Component
driversConfig: RemoteSelectConfig = {
service: this.driversService,
searchField: 'name',
displayField: 'name',
valueField: 'id',
modalTitle: 'Selecionar Motorista'
};
onDriverSelected(event: RemoteSelectEvent) {
console.log('Motorista:', event.item);
console.log('ID:', event.value);
}
<!-- Template -->
<app-remote-select
[config]="driversConfig"
label="Motorista Responsável"
[required]="true"
(selectionChange)="onDriverSelected($event)"
></app-remote-select>
2. Integração com Reactive Forms
// Component
form = this.fb.group({
driverId: [null, Validators.required],
vehicleId: [null]
});
vehiclesConfig: RemoteSelectConfig = {
service: this.vehiclesService,
searchField: 'license_plate',
displayField: 'license_plate',
valueField: 'id',
additionalFields: ['brand', 'model'], // Busca também em marca/modelo
placeholder: 'Placa, marca ou modelo...'
};
<!-- Template -->
<form [formGroup]="form">
<app-remote-select
[config]="vehiclesConfig"
label="Veículo"
formControlName="vehicleId"
></app-remote-select>
<p>Veículo selecionado: {{ form.get('vehicleId')?.value }}</p>
</form>
3. Busca com Múltiplos Campos
// Busca por nome, CPF ou email
usersConfig: RemoteSelectConfig = {
service: this.usersService,
searchField: 'name',
displayField: 'name',
valueField: 'id',
additionalFields: ['cpf', 'email'],
placeholder: 'Nome, CPF ou email...',
minLength: 2
};
4. Configuração para Formulários de Domínio
// vehicles.component.ts - Exemplo de integração
getFormConfig(): TabFormConfig {
return {
// ...
fields: [
{
key: 'driver_id',
label: 'Motorista Atual',
type: 'remote-select',
remoteConfig: {
service: this.driversService,
searchField: 'name',
displayField: 'name',
valueField: 'id',
modalTitle: 'Selecionar Motorista',
placeholder: 'Digite o nome...'
}
}
]
};
}
⌨️ Navegação por Teclado
| Tecla | Ação |
|---|---|
↓ |
Navegar para próximo item |
↑ |
Navegar para item anterior |
Enter |
Selecionar item destacado |
Escape |
Fechar dropdown |
F2 |
Abrir modal de seleção completa |
🎨 Customização Visual
Variáveis CSS Disponíveis
:root {
--primary-color: #ffc82e;
--text-primary: #333;
--text-secondary: #666;
--surface-hover: #f5f5f5;
--divider: #e0e0e0;
--error-color: #f44336;
--success-color: #4caf50;
}
Classes CSS Personalizáveis
.remote-select-container {
// Container principal
.remote-select-dropdown {
// Dropdown de resultados
.dropdown-item {
// Itens individuais
&.selected {
// Item selecionado via teclado
}
&.highlighted {
// Item já selecionado
}
}
}
}
🔧 Requisitos do Service
O service configurado deve implementar o método getEntities():
interface DomainService<T> {
getEntities(
page: number,
pageSize: number,
filters?: any
): Observable<{
data: T[];
total: number;
page: number;
pageSize: number;
}>;
}
Exemplo de Service Compatível
@Injectable({
providedIn: 'root'
})
export class DriversService {
getEntities(page: number, pageSize: number, filters?: any): Observable<any> {
let params = new HttpParams()
.set('page', page.toString())
.set('pageSize', pageSize.toString());
// Aplicar filtros de busca
if (filters) {
Object.keys(filters).forEach(key => {
if (filters[key]) {
params = params.set(key, filters[key]);
}
});
}
return this.http.get<any>(`${this.apiUrl}/drivers`, { params });
}
}
📱 Responsividade
Desktop (≥768px)
- Botão F2 visível
- Dropdown completo
- Tooltips habilitados
Mobile (<768px)
- Botão F2 oculto
- Dropdown otimizado
- Touch-friendly
🎯 Integração com GenericTabFormComponent
Para usar em formulários de domínio, adicione o tipo remote-select:
// generic-tab-form.component.html
<div *ngIf="field.type === 'remote-select'" class="form-field">
<app-remote-select
[config]="field.remoteConfig"
[label]="field.label"
[required]="field.required"
[disabled]="isFieldDisabled(field)"
[formControlName]="field.key"
></app-remote-select>
</div>
🔮 Funcionalidades Futuras
🚧 Em Desenvolvimento
-
Modal F2 Completo
- Integração com DataTableComponent
- Seleção múltipla com checkboxes
- Filtros avançados
-
Cache Inteligente
- Invalidação automática
- Armazenamento local
- Sincronização offline
-
Validações Avançadas
- Validação de dependências
- Regras de negócio customizáveis
⚡ Performance
Otimizações Implementadas
- Debounce: Evita requisições excessivas
- Cache: Resultados armazenados automaticamente
- Virtual Scrolling: Para listas grandes (futuro)
- Change Detection: OnPush strategy
Métricas Esperadas
- Tempo de resposta: <200ms
- Cache hit rate: >80%
- Memória utilizada: <10MB
🧪 Testing
Exemplo de Teste
describe('RemoteSelectComponent', () => {
let component: RemoteSelectComponent;
let fixture: ComponentFixture<RemoteSelectComponent>;
let mockService: jasmine.SpyObj<any>;
beforeEach(() => {
const spy = jasmine.createSpyObj('MockService', ['getEntities']);
TestBed.configureTestingModule({
imports: [RemoteSelectComponent],
providers: [
{ provide: 'service', useValue: spy }
]
});
mockService = TestBed.inject('service');
});
it('should search on input', fakeAsync(() => {
component.config = {
service: mockService,
searchField: 'name',
displayField: 'name',
valueField: 'id'
};
mockService.getEntities.and.returnValue(of({
data: [{ id: 1, name: 'João' }],
total: 1
}));
component.onSearchInput({ target: { value: 'João' } } as any);
tick(300);
expect(mockService.getEntities).toHaveBeenCalled();
expect(component.state.items.length).toBe(1);
}));
});
🔍 Debugging
Console Logs Disponíveis
// Busca executada
console.log('RemoteSelect: Busca por "joão" executada');
// Cache hit
console.log('RemoteSelect: Cache hit para "joão"');
// Erro na busca
console.error('RemoteSelect: Erro na busca', error);
Debug Template
<!-- Ativar debug temporário -->
<app-remote-select
[config]="config"
#debugRef
></app-remote-select>
<!-- Mostrar estado -->
<pre>{{ debugRef.state | json }}</pre>
📚 Referências
📝 Changelog
v1.0.0 (2024-01-XX)
- ✨ Implementação inicial
- 🔍 Autocomplete com debounce
- ⌨️ Navegação por teclado
- 📱 Design responsivo
- 🔄 Integração com Reactive Forms