testes/Modulos Angular/projects/idt_app/docs/components/REMOTE_SELECT_COMPONENT.md

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

  1. Modal F2 Completo

    • Integração com DataTableComponent
    • Seleção múltipla com checkboxes
    • Filtros avançados
  2. Cache Inteligente

    • Invalidação automática
    • Armazenamento local
    • Sincronização offline
  3. 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