testes/Modulos Angular/projects/idt_app/docs/tab-system/SUB_TABS_SYSTEM.md

11 KiB
Raw Blame History

🎯 Sistema de Sub-Abas Configuráveis

📖 Visão Geral

O sistema de sub-abas foi projetado para tornar o GenericTabFormComponent verdadeiramente genérico e escalável. Agora você pode configurar dinamicamente quais sub-abas aparecem em cada domínio, pensando em desempenho e flexibilidade.

🏗️ Arquitetura da Solução

1. Interface SubTabConfig

export interface SubTabConfig {
  id: string;                    // Identificador único ('dados', 'endereco', etc.)
  label: string;                 // Rótulo exibido ('Dados Pessoais', 'Endereço')
  icon: string;                  // Ícone FontAwesome ('fa-user', 'fa-map-marker-alt')
  component?: 'address' | 'documents' | 'fines' | 'custom'; // Componente especializado
  enabled?: boolean;             // Se a aba está habilitada (default: true)
  order?: number;                // Ordem de exibição (default: 999)
  data?: any;                    // Dados adicionais para a aba
  requiredFields?: string[];     // Campos obrigatórios desta aba
}

2. Integração com TabFormConfig

export interface TabFormConfig {
  // ... campos existentes
  subTabs?: SubTabConfig[];      // ✨ Nova configuração de sub-abas
}

🎮 Como Usar

Configuração Básica - Apenas Dados Pessoais

const config = tabFormConfigService.getDriverFormConfigWithSubTabs(['dados']);
// Resultado: Apenas uma aba, renderizada diretamente (sem headers de sub-abas)

Configuração com Endereço

const config = tabFormConfigService.getDriverFormConfigWithSubTabs(['dados', 'endereco']);
// Resultado: Duas sub-abas - Dados Pessoais + Endereço

Configuração Completa

const config = tabFormConfigService.getDriverFormConfigWithSubTabs([
  'dados', 'endereco', 'documentos', 'multas'
]);
// Resultado: Quatro sub-abas com sistema completo

Usando Presets Pré-Definidos

const presets = tabFormConfigService.getDriverFormPresets();

// Apenas dados básicos
const basicConfig = presets.basic();

// Com endereço
const withAddressConfig = presets.withAddress();

// Com documentos
const withDocsConfig = presets.withDocs();

// Completo
const completeConfig = presets.complete();

🚀 Exemplos Práticos

1. Domínio Motoristas - Completo

// Em drivers.component.ts ou tab-system.service.ts
await tabSystemService.openDriverTabWithPreset('complete', driverData);
// Abas: Dados + Endereço + Documentos + Multas

2. Domínio Veículos - Só Dados

await tabSystemService.openInTab('vehicle', vehicleData);
// Resultado: Apenas aba de dados (sem sub-abas)

3. Configuração Dinâmica

// Baseado no tipo de usuário ou permissões
const enabledTabs = user.canManageDocuments 
  ? ['dados', 'endereco', 'documentos'] 
  : ['dados', 'endereco'];

await tabSystemService.openDriverTab(driverData, enabledTabs);

Benefícios de Desempenho

1. Lazy Loading de Componentes

  • Só carrega AddressFormComponent se aba 'endereco' estiver habilitada
  • Componentes futuros (documentos, multas) só são instanciados quando necessário

2. Renderização Condicional

// Template otimizado
<div *ngIf="isSubTabAvailable('endereco')">
  <app-address-form ...>
</div>

3. Fallback Inteligente

// Se há apenas 1 sub-aba, remove overhead das headers
<div *ngIf="getAvailableSubTabs().length === 1" class="single-tab-content">
  <!-- Renderização direta sem sub-abas -->
</div>

🎨 Interface de Usuário

Múltiplas Sub-Abas

┌─────────────────────────────────────┐
│ [👤 Dados Básicos]  [📍 Endereço]    │
├─────────────────────────────────────┤
│                                     │
│     Conteúdo da aba selecionada     │
│                                     │
└─────────────────────────────────────┘

Aba Única (Otimizada)

┌─────────────────────────────────────┐
│                                     │
│      Conteúdo direto (sem tabs)     │
│                                     │
└─────────────────────────────────────┘

🔧 Métodos Principais

No GenericTabFormComponent

// Obtém sub-abas disponíveis
getAvailableSubTabs(): SubTabConfig[]

// Verifica se sub-aba está disponível
isSubTabAvailable(tabId: string): boolean

// Obtém configuração de sub-aba
getSubTabConfig(tabId: string): SubTabConfig | undefined

// Seleciona sub-aba
selectSubTab(tabId: string): void

No TabFormConfigService

// Configuração customizada
getDriverFormConfigWithSubTabs(enabledSubTabs: string[]): TabFormConfig

// Presets pré-definidos
getDriverFormPresets(): { basic, withAddress, withDocs, complete }

No TabSystemService

// Abre aba com sub-abas específicas
openDriverTab(itemData?, enabledSubTabs?, customTitle?): Promise<boolean>

// Abre aba com preset
openDriverTabWithPreset(preset, itemData?, customTitle?): Promise<boolean>

🔮 Expansão Futura

1. Novas Sub-Abas

// Adicionar nova sub-aba é simples:
{
  id: 'historico',
  label: 'Histórico',
  icon: 'fa-history',
  component: 'history',
  enabled: true,
  order: 5
}

2. Componentes Especializados

// Template se expande automaticamente
<div *ngIf="isSubTabAvailable('historico')">
  <app-history-component [data]="getSubTabConfig('historico')?.data">
  </app-history-component>
</div>

3. Configuração por Domínio

// Cada domínio pode ter suas sub-abas específicas
const vehicleSubTabs = ['dados', 'manutencao', 'historico'];
const userSubTabs = ['dados', 'endereco', 'permissoes'];

💾 Sistema de Salvamento Genérico

🚀 Nova Arquitetura

O sistema foi refatorado para utilizar uma arquitetura genérica e escalável de salvamento que funciona com qualquer domínio e qualquer configuração de sub-abas.

Como Funciona

1. Fluxo de Eventos

// 1⃣ TabSystemComponent emite evento genérico
this.tableEvent.emit({
  event: 'formSubmit',
  data: {
    tab,                    // Contexto da aba
    formData,               // Dados do formulário
    isNewItem,              // Se é criação ou edição
    onSuccess: (response) => this.onSaveSuccess(tab, response, component),
    onError: (error) => this.onSaveError(tab, error, component)
  }
});

// 2⃣ BaseDomainComponent recebe e processa
protected onFormSubmit(data: any): void {
  const { tab, formData, isNewItem, onSuccess, onError } = data;
  
  const operation = isNewItem 
    ? this.createEntity(formData)
    : this.updateEntity(tab.data.id, formData);
    
  operation?.subscribe({
    next: (response) => onSuccess(response),
    error: (error) => onError(error)
  });
}

2. Callbacks Inteligentes

// ✅ Sucesso: Marca formulário como salvo e remove modificações
private onSaveSuccess(tab, response, component): void {
  component.markAsSavedSuccessfully();
  this.tabSystemService.setTabModified(tabIndex, false);
  tab.data = { ...tab.data, ...response };
}

// ❌ Erro: Restaura estado de submissão
private onSaveError(tab, error, component): void {
  component.setSubmitting(false);
  // Pode mostrar notificação de erro
}

💡 Vantagens

  • Escalável: Funciona automaticamente para qualquer novo domínio
  • Limpo: Sem código específico no TabSystemComponent
  • Flexível: Permite customizações quando necessário
  • Consistente: Mesma API para todos os domínios
  • Sub-Abas: Funciona com qualquer configuração de sub-abas

🎯 Implementação por Domínio

// Para novos domínios - ZERO CÓDIGO adicional
export class ClientsComponent extends BaseDomainComponent<Client> {
  // ✨ Salvamento já funciona automaticamente!
}

// Para customizações específicas
export class VehiclesComponent extends BaseDomainComponent<Vehicle> {
  protected createEntity(data: any): Observable<any> {
    // Lógica específica de validação/processamento
    return this.vehiclesService.createVehicleWithSpecialValidation(data);
  }
}

🔄 Compatibilidade com Sub-Abas

O sistema de salvamento genérico funciona perfeitamente com qualquer configuração de sub-abas:

// Configuração simples (1 aba)
await openDriverTab(data, ['dados']);
// Salvamento: onFormSubmit() → createEntity() → onSuccess()

// Configuração completa (4 abas)  
await openDriverTab(data, ['dados', 'endereco', 'documentos', 'multas']);
// Salvamento: Mesmo fluxo, funciona automaticamente

// Preset customizado
await openDriverTabWithPreset('withAddress', data);
// Salvamento: Funciona independente do preset escolhido

🧪 Testing

// Teste do fluxo de salvamento
it('should save driver data using generic system', async () => {
  const component = new DriversComponent(...);
  const mockData = { name: 'João', cpf: '123.456.789-00' };
  
  // Sistema genérico deve lidar automaticamente
  await component.onFormSubmit({
    tab: mockTab,
    formData: mockData,
    isNewItem: true,
    onSuccess: jasmine.createSpy(),
    onError: jasmine.createSpy()
  });
  
  expect(mockService.createDriver).toHaveBeenCalledWith(mockData);
});

vehicles: ['dados', 'manutencao', 'rotas'] users: ['dados', 'permissoes'] clients: ['dados', 'endereco', 'contratos']


## 📱 **Responsividade**

- ✅ Headers de sub-abas se adaptam em mobile
- ✅ Ícones e textos otimizados para telas pequenas
- ✅ Conteúdo das abas com padding responsivo

## 🧪 **Como Testar**

### **1. Console do Navegador**
```javascript
// Testar diferentes configurações
debugHelpers.getTabFormComponent().getAvailableSubTabs()

// Trocar sub-aba
debugHelpers.switchSubTab('endereco')

// Ver estado atual
debugHelpers.showFormState()

2. Configuração Dinâmica

// No DriversComponent, você pode criar métodos:
testBasicDriverForm() {
  this.tabSystemService.openDriverTabWithPreset('basic', mockDriver);
}

testCompleteDriverForm() {
  this.tabSystemService.openDriverTabWithPreset('complete', mockDriver);
}

🎯 Conclusão

Esta implementação resolve todos os problemas identificados:

  • Parametrização: Sub-abas configuráveis via enabledSubTabs
  • Desempenho: Lazy loading + renderização condicional
  • Escalabilidade: Fácil adição de novas abas/domínios
  • Genericidade: Funciona para qualquer domínio
  • Flexibilidade: Presets + configuração customizada

O sistema está pronto para motoristas, veículos, documentos, multas e qualquer futuro domínio que precise de sub-abas!