testes/Modulos Angular/projects/idt_app/docs/general/CURSOR.md

940 lines
28 KiB
Markdown

# IDT App - Documentação
## Visão Geral
O IDT App é uma aplicação Angular para gerenciamento de veículos, desenvolvida como parte do sistema Prafrota. A aplicação permite o gerenciamento completo de frota de veículos, incluindo cadastro, edição, localização e simulação de financiamento.
## Estrutura do Projeto
### Componentes Principais
- `VehiclesComponent`: Componente principal para gerenciamento de veículos
- `VehicleMapComponent`: Componente para visualização de veículos em mapa
- `DataTableComponent`: Componente reutilizável para exibição de dados em tabela
### Funcionalidades Principais
1. **Gerenciamento de Veículos**
- Cadastro de novos veículos
- Edição de veículos existentes
- Visualização em tabela com filtros e ordenação
- Paginação de resultados
2. **Localização**
- Visualização de veículos em mapa
- Localização específica por placa
3. **Financiamento**
- Simulação de financiamento para veículos
- Cálculo de valores e parcelas
### Campos do Veículo
- Placa
- Chassi (VIN)
- Tipo de Carroceria
- Marca
- Cor
- Descrição
- Combustível
- Grupo
- Ano de Fabricação
- Marca Registrada
- Número de Portas
- RENAVAM
- Número de Assentos
- Status
- Transmissão
- Tipo de Veículo
### Recursos Técnicos
- Angular Material para componentes UI
- Componentes standalone
- Gerenciamento de estado
- Serviços para comunicação com backend
- Sistema de formulários dinâmicos
- Upload de imagens
- Integração com mapas
## API Backend - PraFrota
### Endpoint Base
- **URL**: `https://prafrota-be-bff-tenant-api.grupopra.tech`
- **Versão**: v1
- **Protocolo**: HTTPS
- **Formato**: JSON
### Autenticação
- **Tipo**: Bearer Token (JWT)
- **Header**: `Authorization: Bearer <token>`
- **Renovação**: Automática via refresh token
- **Expiração**: Configurável por tenant
### Estrutura de Rotas da API
#### Veículos
- `GET /api/v1/vehicles` - Listar veículos
- `POST /api/v1/vehicles` - Criar veículo
- `PUT /api/v1/vehicles/{id}` - Atualizar veículo
- `DELETE /api/v1/vehicles/{id}` - Excluir veículo
- `GET /api/v1/vehicles/{id}` - Obter veículo específico
#### Motoristas
- `GET /api/v1/drivers` - Listar motoristas
- `POST /api/v1/drivers` - Criar motorista
- `PUT /api/v1/drivers/{id}` - Atualizar motorista
- `DELETE /api/v1/drivers/{id}` - Excluir motorista
#### Finanças
- `GET /api/v1/finances/accounts-payable` - Contas a pagar
- `POST /api/v1/finances/accounts-payable` - Criar conta a pagar
- `PUT /api/v1/finances/accounts-payable/{id}` - Atualizar conta
- `DELETE /api/v1/finances/accounts-payable/{id}` - Excluir conta
#### Rotas
- `GET /api/v1/routes/mercado-live` - Rotas Mercado Live
- `GET /api/v1/routes/shopee` - Rotas Shopee
- `POST /api/v1/routes/sync` - Sincronizar rotas
- `POST /api/v1/routes/optimize` - Otimizar rotas
### Padrões de Resposta
#### Sucesso (200/201)
```json
{
"success": true,
"data": [...],
"totalCount": 100,
"pageCount": 10,
"currentPage": 1,
"timestamp": "2024-12-13T10:00:00Z"
}
```
#### Erro (400/401/500)
```json
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Dados inválidos",
"details": ["Campo obrigatório: placa"]
},
"timestamp": "2024-12-13T10:00:00Z"
}
```
### Paginação
- **Parâmetros**: `page`, `limit`, `sort`, `order`
- **Limite máximo**: 100 itens por página
- **Padrão**: 10 itens por página
### Filtros
- **Formato**: Query parameters
- **Exemplo**: `?status=active&brand=toyota&year=2023`
- **Operadores**: `eq`, `like`, `gt`, `lt`, `between`
### Headers Obrigatórios
```http
Content-Type: application/json
Authorization: Bearer <token>
X-Tenant-ID: <tenant-id>
X-Client-Version: 1.0.0
```
### Códigos de Status
- `200` - Sucesso
- `201` - Criado com sucesso
- `400` - Dados inválidos
- `401` - Não autorizado
### Pré-requisitos
- Node.js
- Angular CLI
- Git
### Instalação
1. Clone o repositório
2. Instale as dependências:
```bash
npm install
```
3. Execute o projeto:
```bash
ng serve
```
## Convenções de Código
- Uso de TypeScript
- Componentes standalone
- Estilo de código seguindo as diretrizes do Angular
- Documentação de código em inglês
## 🎯 PADRÃO OBRIGATÓRIO - Template HTML para Componentes de Domínio
### Template Padrão BaseDomainComponent
TODOS os componentes que estendem BaseDomainComponent DEVEM usar exatamente este template HTML:
```html
<div class="domain-container">
<div class="main-content">
<app-tab-system
#tabSystem
[config]="tabConfig"
[events]="tabEvents"
[showDebugInfo]="false"
(tabSelected)="onTabSelected($event)"
(tabClosed)="onTabClosed($event)"
(tabAdded)="onTabAdded($event)"
(tableEvent)="onTableEvent($event)">
</app-tab-system>
</div>
</div>
```
### ❌ NUNCA FAZER:
- `<app-tab-system></app-tab-system>` (sem bindings)
- Templates customizados para domínios ERP
- Estruturas HTML diferentes
### ✅ SEMPRE FAZER:
- Usar exatamente o template acima
- Incluir todos os bindings de eventos
- Manter a estrutura domain-container > main-content
- Referenciar #tabSystem para controle programático
### Componentes que DEVEM seguir este padrão:
- VehiclesComponent ✅
- DriversComponent ✅
- RoutesComponent ✅
- FinancialCategoriesComponent ✅
- AccountPayableComponent ✅
- Qualquer novo componente de domínio ERP
## 🚫 PADRÃO OBRIGATÓRIO - Services com ApiClientService
### ❌ NUNCA FAZER:
```typescript
// ❌ ERRADO - Usar HttpClient diretamente
import { HttpClient } from '@angular/common/http';
constructor(private http: HttpClient) {}
this.http.get('api/endpoint') // NUNCA FAZER ISSO!
```
### ✅ SEMPRE FAZER:
```typescript
// ✅ CORRETO - Usar ApiClientService
import { ApiClientService } from '../../shared/services/api/api-client.service';
constructor(private apiClient: ApiClientService) {}
this.apiClient.get('endpoint') // SEMPRE ASSIM!
```
### Template Completo de Service:
```typescript
import { Injectable } from '@angular/core';
import { Observable, map } from 'rxjs';
import { ApiClientService } from '../../shared/services/api/api-client.service';
import { DomainService } from '../../shared/components/base-domain/base-domain.component';
import { PaginatedResponse } from '../../shared/interfaces/paginate.interface';
@Injectable({
providedIn: 'root'
})
export class ExampleService implements DomainService<Entity> {
constructor(
private apiClient: ApiClientService
) {}
getEntities(page: number, pageSize: number, filters: any): Observable<{
data: Entity[];
totalCount: number;
pageCount: number;
currentPage: number;
}> {
return this.getExamples(page, pageSize, filters).pipe(
map(response => ({
data: response.data,
totalCount: response.totalCount,
pageCount: response.pageCount,
currentPage: response.currentPage
}))
);
}
create(data: any): Observable<Entity> {
return this.apiClient.post<Entity>('examples', data);
}
update(id: any, data: any): Observable<Entity> {
return this.apiClient.patch<Entity>(`examples/${id}`, data);
}
getExamples(page = 1, limit = 10, filters?: any): Observable<PaginatedResponse<Entity>> {
let url = `examples?page=${page}&limit=${limit}`;
if (filters) {
const params = new URLSearchParams();
for (const [key, value] of Object.entries(filters)) {
if (value) {
params.append(key, value.toString());
}
}
if (params.toString()) {
url += `&${params.toString()}`;
}
}
return this.apiClient.get<PaginatedResponse<Entity>>(url);
}
getById(id: string): Observable<Entity> {
return this.apiClient.get<Entity>(`examples/${id}`);
}
delete(id: string): Observable<void> {
return this.apiClient.delete<void>(`examples/${id}`);
}
}
```
## 🚫 PADRÕES DE NOMENCLATURA CRÍTICOS - Services
### ⚠️ REGRAS OBRIGATÓRIAS DE NOMENCLATURA
**NUNCA QUEBRAR ESTES PADRÕES!** A inconsistência na nomenclatura quebra toda a arquitetura do projeto.
#### ✅ MÉTODOS OBRIGATÓRIOS (SEMPRE seguir):
```typescript
// Interface DomainService - OBRIGATÓRIOS
getEntities(page: number, pageSize: number, filters: any): Observable<any>
create(data: any): Observable<Entity>
update(id: any, data: any): Observable<Entity>
// Métodos específicos - PADRÃO ESTABELECIDO
getById(id: string): Observable<Entity>
delete(id: string): Observable<void>
get[Domain]s(page: number, limit: number, filters?: any): Observable<PaginatedResponse<Entity>>
```
#### ❌ MÉTODOS PROIBIDOS (NUNCA usar):
```typescript
// 🚫 SUFIXOS ESPECÍFICOS - PROIBIDO
createRoute(), createVehicle(), createDriver() // ❌ Usar: create()
updateRoute(), updateVehicle(), updateDriver() // ❌ Usar: update()
deleteRoute(), deleteVehicle(), deleteDriver() // ❌ Usar: delete()
getRoute(), getVehicle(), getDriver() // ❌ Usar: getById()
// 🚫 NOMENCLATURA ALTERNATIVA - PROIBIDO
addEntity(), editEntity(), removeEntity() // ❌ Usar: create(), update(), delete()
findById(), searchById(), retrieveById() // ❌ Usar: getById()
saveEntity(), persistEntity() // ❌ Usar: create() ou update()
```
#### 📋 AUDITORIA DE CONFORMIDADE:
| Service | ✅ create | ✅ update | ✅ delete | ✅ getById | ✅ get[Domain]s |
|---------|-----------|-----------|-----------|-----------|----------------|
| VehiclesService | ✅ | ✅ | ✅ | ✅ | getVehicles ✅ |
| DriversService | ✅ | ✅ | ✅ | ✅ | getDrivers ✅ |
| RoutesService | ✅ | ✅ | ✅ | ✅ | getRoutes ✅ |
| FinancialCategoriesService | ✅ | ✅ | ✅ | ✅ | getCategories ✅ |
| AccountPayableService | ✅ | ✅ | ✅ | ✅ | getAccounts ✅ |
### 🎯 MOTIVOS TÉCNICOS:
1. **BaseDomainComponent** espera métodos `create`, `update`, `delete`
2. **DomainService interface** define contratos específicos
3. **Consistency** com todo o ecosistema Angular do projeto
4. **Maintainability** - padrões claros facilitam manutenção
5. **Team Standards** - evita confusão entre desenvolvedores
## Contribuição
1. Crie uma branch para sua feature
2. Faça commit das alterações
3. Envie um pull request
## Padrão de Branches
### Estrutura de Branches
- `main`: Branch principal de produção
- `develop`: Branch de desenvolvimento
- `release/*`: Branches para preparação de releases
- `feature/*`: Branches para novas funcionalidades
- `bugfix/*`: Branches para correções de bugs
- `hotfix/*`: Branches para correções urgentes em produção
### Convenções de Nomenclatura
1. **Feature Branches**
- Formato: `feature/nome-da-feature`
- Exemplo: `feature/vehicle-location-map`
2. **Bugfix Branches**
- Formato: `bugfix/descricao-do-bug`
- Exemplo: `bugfix/fix-vehicle-form-validation`
3. **Hotfix Branches**
- Formato: `hotfix/descricao-do-problema`
- Exemplo: `hotfix/fix-critical-security-issue`
4. **Release Branches**
- Formato: `release/versao`
- Exemplo: `release/v1.2.0`
### Fluxo de Trabalho
1. **Desenvolvimento de Features**
- Criar branch a partir de `develop`
- Desenvolver feature
- Criar PR para `develop`
- Após aprovação, merge em `develop`
2. **Correção de Bugs**
- Criar branch a partir de `develop`
- Corrigir bug
- Criar PR para `develop`
- Após aprovação, merge em `develop`
3. **Preparação de Release**
- Criar branch a partir de `develop`
- Realizar testes e ajustes finais
- Criar PR para `main` e `develop`
- Após aprovação, merge em ambas as branches
4. **Hotfixes**
- Criar branch a partir de `main`
- Corrigir problema
- Criar PR para `main` e `develop`
- Após aprovação, merge em ambas as branches
### Regras Importantes
- Manter branches atualizadas com a branch base
- Realizar rebase antes de criar PR
- Seguir padrão de commits convencionais
- Manter histórico de commits limpo e organizado
- Deletar branches após merge bem-sucedido
## 📊 Padrões de Paginação e Listagem
### Problema Comum: Paginação com Múltiplas APIs
Quando há necessidade de consultar múltiplas APIs e consolidar os dados, a paginação deve ser implementada localmente.
#### Estrutura Recomendada
```typescript
loadData(currentPage = this.currentPage, itemsPerPage = this.itemsPerPage) {
this.isLoading = true;
this.data = [];
this.totalItems = 0;
const sources = ['source1', 'source2', 'source3'];
let completedRequests = 0;
let allData: any[] = [];
// Buscar todos os dados primeiro
for (const source of sources) {
this.service.getData(1, 1000, source, this.currentFilters)
.subscribe({
next: (response) => {
if (response.data[0]?.data_string) {
const datapack = JSON.parse(response.data[0].data_string);
allData = [...allData, ...datapack];
}
completedRequests++;
// Aplicar paginação local quando todas as requisições terminarem
if (completedRequests === sources.length) {
// Ordenação opcional
allData.sort((a, b) => /* critério de ordenação */);
// Aplicar paginação
this.totalItems = allData.length;
this.totalPages = Math.ceil(this.totalItems / itemsPerPage);
const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
this.data = allData.slice(startIndex, endIndex);
this.isLoading = false;
}
},
error: (error) => {
completedRequests++;
if (completedRequests === sources.length) {
this.isLoading = false;
}
}
});
}
}
```
#### Controle de Mudança de Página
```typescript
onPageChange(event: { page: number; pageSize: number }) {
const maxPage = Math.ceil(this.totalItems / event.pageSize);
const validPage = Math.min(event.page, maxPage);
if (this.currentPage !== validPage || this.itemsPerPage !== event.pageSize) {
this.currentPage = validPage;
this.itemsPerPage = event.pageSize;
this.loadData(this.currentPage, this.itemsPerPage);
}
}
```
### DataTable Component Integration
- **Componente**: `DataTableComponent` localizado em `shared/components/data-table/`
- **Configuração**: Usar `TableConfig` para definir colunas, ações e comportamentos
- **Paginação**: Sempre implementar `totalDataItems`, `currentPage` e `pageChange`
#### Exemplo de Configuração de Tabela
```typescript
tableConfig: TableConfig = {
columns: [
{
field: "campo",
header: "Cabeçalho",
sortable: true,
filterable: true,
label: (data: any) => {
// Transformação de dados para exibição
return data;
}
}
],
pageSize: this.itemsPerPage,
pageSizeOptions: [5, 10, 25, 50],
showFirstLastButtons: true,
actions: [
{
icon: "fas fa-edit",
label: "Editar",
action: "edit",
}
]
};
```
## 📝 Logger Service
### Implementação Baseada no Backend
O Logger service foi criado seguindo o mesmo padrão da interface do backend, mantendo compatibilidade de API.
#### Estrutura do Logger
- **Interface**: `LoggerService` com métodos `log`, `error`, `warn`, `debug`, `verbose`, `fatal`
- **Tipos**: `LogLevel` com todos os níveis de log suportados
- **Classe**: `Logger` que implementa `LoggerService`
#### Uso Básico
```typescript
import { Logger } from '../../../../shared/services/logger';
// Método estático (uso global)
Logger.log('Mensagem de log');
Logger.error('Erro ocorreu', 'MeuComponente');
Logger.warn('Aviso importante');
Logger.debug('Info de debug');
Logger.verbose('Log detalhado');
Logger.fatal('Erro crítico');
// Instância com contexto (recomendado para componentes)
export class MeuComponent {
private logger = new Logger('MeuComponent');
ngOnInit() {
this.logger.log('Componente inicializado');
this.logger.debug('Dados carregados:', dados);
}
onError(error: any) {
this.logger.error('Erro no processamento:', error);
}
}
```
#### Funcionalidades Avançadas
```typescript
// Configuração personalizada
const logger = new Logger('DataService', {
timestamp: true,
environment: 'production',
enableConsole: true,
enableRemoteLogging: false
});
// Controle de níveis de log
Logger.overrideLogger(['error', 'warn']); // Só mostra erros e avisos
Logger.overrideLogger(false); // Desabilita todos os logs
Logger.overrideLogger(true); // Habilita todos os logs
// Buffer para logs de inicialização
Logger.attachBuffer();
Logger.log('Log armazenado no buffer');
Logger.detachBuffer(); // Flush todos os logs
// Verificação condicional
if (Logger.isLevelEnabled('debug')) {
const expensiveData = calculateDebugData();
Logger.debug('Debug data:', expensiveData);
}
```
#### Cores e Formatação
O Logger inclui formatação colorida no console:
- **LOG**: Azul (#2196F3)
- **ERROR/FATAL**: Vermelho (#f44336/#d32f2f)
- **WARN**: Laranja (#ff9800)
- **DEBUG**: Verde (#4caf50)
- **VERBOSE**: Roxo (#9c27b0)
#### Localização
- **Arquivos**: `shared/services/logger/`
- **Interface**: `logger.interface.ts`
- **Implementação**: `logger.service.ts`
- **Exemplos**: `logger.example.ts`
- **Export**: `index.ts`
## 🔄 Padrões de Mapeamento de Dados
### Mapeamento de APIs Heterogêneas para Interface Unificada
Quando trabalhando com múltiplas APIs que retornam estruturas diferentes, use o padrão de mapeamento específico por tipo.
#### Estrutura de Mapeamento
```typescript
// Método principal de mapeamento
private mapRoutesToInterface(datapack: any[], type: string): InterfacePadrao[] {
return datapack.map(item => {
switch (type) {
case 'tipo1':
return this.mapTipo1(item);
case 'tipo2':
return this.mapTipo2(item);
default:
return this.mapGenerico(item, type);
}
});
}
// Mapeamentos específicos por tipo
private mapTipo1(data: any): InterfacePadrao {
return {
id: data.id?.toString() || '',
campo1: data.campo_api || 'valor_padrao',
status: this.mapStatus(data.status),
// ... outros campos
};
}
```
#### Exemplo: Rotas Mercado Livre
```typescript
// Aplicação no recebimento de dados
const datapack = JSON.parse(response.data[0].data_string);
const mappedRoutes = this.mapRoutesToInterface(datapack, type);
allRoutes = [...allRoutes, ...mappedRoutes];
// Mapeamento específico por tipo de rota
private mapFirstMileRoute(route: any): MercadoLiveRoute {
return {
id: route.id?.toString() || '',
customerName: route.carrierName || 'N/A',
estimatedPackages: route.estimatedPackages || 0,
priority: route.warnings?.length > 0 ? 'high' : 'medium'
};
}
```
#### Padrões de Status Normalization
```typescript
private mapStatus(status: string): StatusPadrao {
const statusMap: { [key: string]: StatusPadrao } = {
'active': 'in_transit',
'pending': 'pending',
'finished': 'delivered',
'cancelled': 'cancelled'
};
return statusMap[status?.toLowerCase()] || 'pending';
}
```
#### Tratamento de Dados Opcionais
```typescript
// Safe navigation e fallbacks
campo: data.nivel1?.nivel2?.campo || 'valor_padrao',
data: data.timestamp ? new Date(data.timestamp * 1000) : new Date(),
numero: data.valor || 0,
array: data.lista?.length ? data.lista : []
```
### Interfaces Específicas por Tipo
Criar interfaces específicas para cada estrutura de dados diferente:
```typescript
// Interfaces específicas por tipo de fonte
export interface FirstMileRoute {
id: number;
routeType: string;
facilityName: string;
estimatedPackages: number;
vehicleName: string;
driverName: string;
warnings: FirstMileWarning[];
}
export interface LineHaulRoute {
carrier_id: number;
carrier: string;
drivers: LineHaulDriver[];
vehicles: LineHaulVehicle[];
steps: LineHaulStep[];
stops: LineHaulStop[];
}
export interface LastMileRoute {
id: string;
cluster: string;
driver: LastMileDriver;
counters: LastMileCounters;
timingData: LastMileTimingData;
}
// Union type para tipagem de entrada
export type MercadoLiveRouteRaw = FirstMileRoute | LineHaulRoute | LastMileRoute;
```
#### Mapeamento com Type Safety
```typescript
private mapRoutesToInterface(datapack: MercadoLiveRouteRaw[], type: string): MercadoLiveRoute[] {
return datapack.map(route => {
switch (type) {
case 'first_mile':
return this.mapFirstMileRoute(route as FirstMileRoute);
case 'line_haul':
return this.mapLineHaulRoute(route as LineHaulRoute);
case 'last_mile':
return this.mapLastMileRoute(route as LastMileRoute);
}
});
}
private mapFirstMileRoute(route: FirstMileRoute): MercadoLiveRoute {
return {
id: route.id.toString(),
customerName: route.carrierName,
estimatedPackages: route.estimatedPackages,
vehicleType: route.vehicleType, // FirstMileRoute.vehicleType
locationName: route.facilityName, // FirstMileRoute.facilityName
driverName: route.driverName, // FirstMileRoute.driverName
DepartureDate: new Date(route.initDate * 1000), // timestamp → Date
priority: route.warnings.length > 0 ? 'high' : 'medium'
};
}
private mapLineHaulRoute(route: LineHaulRoute): MercadoLiveRoute {
return {
// ...
vehicleType: route.vehicle_type, // LineHaulRoute.vehicle_type
locationName: route.site_id, // LineHaulRoute.site_id
driverName: route.drivers[0]?.name, // LineHaulRoute.drivers[0].name
DepartureDate: new Date(route.departure_date), // datetime → Date
// ...
};
}
private mapLastMileRoute(route: LastMileRoute): MercadoLiveRoute {
return {
// ...
vehicleType: route.vehicle.description, // LastMileRoute.vehicle.description
locationName: route.facilityId, // LastMileRoute.facilityId
driverName: route.driver.driverName, // LastMileRoute.driver.driverName
DepartureDate: new Date(route.initDate * 1000), // timestamp → Date
// ...
};
}
```
### Conversão de Formatos de Data
Quando diferentes APIs retornam datas em formatos distintos, implemente conversões específicas:
```typescript
// Função concisa para conversão automática de datas
private convertToDate(dateValue: any): Date {
if (!dateValue) return new Date();
if (typeof dateValue === 'number') {
// Se menor que 10^12, está em segundos; senão, em milissegundos
return new Date(dateValue < 1000000000000 ? dateValue * 1000 : dateValue);
}
if (typeof dateValue === 'string') {
return new Date(dateValue);
}
return new Date();
}
// Exemplos de conversão por tipo usando a função auxiliar
DepartureDate: this.convertToDate(route.initDate), // auto-detecta formato
DepartureDate: this.convertToDate(route.departure_date), // auto-detecta formato
estimatedDelivery: this.convertToDate(route.finalDate), // auto-detecta formato
// Exemplos de dados reais:
// "initDate": 1748351864 (timestamp segundos) → 25/01/2025
// "departure_date": "2025-05-25T16:48:47Z" (ISO string) → 25/05/2025
```
### Vantagens do Padrão
**Consistência**: Dados uniformizados independente da origem
**Manutenibilidade**: Fácil adição de novos tipos
**Legibilidade**: Mapeamentos específicos e organizados
**Robustez**: Tratamento de dados faltantes
**Type Safety**: Garantia de tipos através de interfaces específicas
**IntelliSense**: Autocompletar e validação em tempo de desenvolvimento
**Detecção de Erros**: Erros de tipagem detectados em build time
**Conversão de Formatos**: Normalização automática de timestamps e datetime strings
## 🎨 Componentes de Interface Avançados
### Color Input Component
Componente especializado para seleção de cores com interface visual intuitiva.
#### Funcionalidades
- **Dropdown Visual**: Grid de círculos coloridos com nomes
- **Preview Seleção**: Mostra cor selecionada no botão principal
- **Botão Limpar**: Opção para remover seleção
- **Overlay Inteligente**: Fecha ao clicar fora
- **Responsive**: Layout adaptado para mobile
- **Tema Escuro**: Suporte completo a temas
#### Implementação
```typescript
{
key: 'color',
label: 'Cor',
type: 'color-input',
required: false,
options: [
{ value: { name: 'Branco', code: '#ffffff' }, label: 'Branco' },
{ value: { name: 'Preto', code: '#000000' }, label: 'Preto' },
// ... outras cores
]
}
```
#### Integração com Data Table
- **Renderização HTML**: Círculos de cor nas células da tabela
- **DomSanitizer**: HTML seguro com `bypassSecurityTrustHtml()`
- **Fallback Inteligente**: Mapa de cores para objetos sem código hex
- **Configuração**: `allowHtml: true` na configuração da coluna
### Indicadores de Campos Obrigatórios
Sistema unificado de sinalização visual para campos obrigatórios em formulários.
#### Componentes Atualizados
-**custom-input**: Asterisco vermelho nos labels
-**color-input**: Suporte nativo no template inline
-**kilometer-input**: Asterisco + interface TypeScript atualizada
-**generic-tab-form**: Labels dos selects nativos com asterisco
-**remote-select**: Sistema `required-asterisk` já implementado
-**multi-select**: Sistema `required-asterisk` já implementado
#### CSS Unificado
```scss
.required-indicator {
color: var(--idt-danger, #dc3545);
margin-left: 4px;
font-weight: 700;
font-size: 16px;
line-height: 1;
}
```
#### Vantagens
-**UX Melhorada**: Usuários sabem quais campos são obrigatórios
-**Consistência Visual**: Mesmo padrão em todos os componentes
-**Acessibilidade**: Indicação clara de campos obrigatórios
-**Prevenção de Erros**: Reduz tentativas de submit com dados incompletos
### Data Table HTML Rendering
Sistema seguro para renderização de HTML personalizado em células de tabela.
#### Configuração de Coluna
```typescript
{
field: "color",
header: "Cor",
allowHtml: true,
label: (value: any) => {
const colorCode = value.code || '#999999';
return `<span style="display: inline-flex; align-items: center; gap: 6px;">
<span style="width: 12px; height: 12px; border-radius: 50%;
background-color: ${colorCode}; border: 1px solid #ddd;"></span>
<span>${value.name}</span>
</span>`;
}
}
```
#### Segurança
- **DomSanitizer**: Uso de `bypassSecurityTrustHtml()` para HTML seguro
- **Validação**: Verificação de `allowHtml` antes da renderização
- **Fallback**: Renderização de texto simples quando HTML não é permitido
## 🔧 Padrões de Validação e Formulários
### Validação Condicional
Sistema inteligente que aplica validação apenas quando necessário.
#### Implementação
```typescript
// Validação aplicada apenas para campos required: true
createOptionValidator(field: TabFormField): ValidatorFn | null {
if (!field.required) return null;
return (control: AbstractControl): ValidationErrors | null => {
if (!control.value) return { required: true };
if (field.returnObjectSelected) {
return this.isValidObjectSelection(control.value, field) ? null : { invalidOption: true };
}
return this.isValidPrimitiveSelection(control.value, field) ? null : { invalidOption: true };
};
}
```
#### Vantagens
-**Performance**: Validação apenas quando necessário
-**Flexibilidade**: Campos opcionais não geram erros
-**Robustez**: Suporte a objetos complexos e valores primitivos
### Serialização de Objetos em Formulários
Tratamento correto de campos que retornam objetos complexos.
#### Problema Comum
```html
<!-- ❌ INCORRETO: Serializa como "[object Object]" -->
<option [value]="option.value">{{ option.label }}</option>
<!-- ✅ CORRETO: Preserva objeto completo -->
<option [ngValue]="option.value">{{ option.label }}</option>
```
#### Processamento no Submit
```typescript
onSubmit(): void {
const formData = { ...this.form.value };
// Processar campos com returnObjectSelected
this.config.fields
.filter(field => field.returnObjectSelected)
.forEach(field => {
if (formData[field.key] && typeof formData[field.key] === 'object') {
// Objeto já está correto, não precisa processar
console.log(`✅ Campo ${field.key} já é objeto:`, formData[field.key]);
}
});
this.submitData.emit(formData);
}
```
## Suporte
Para suporte ou dúvidas, entre em contato com a equipe de desenvolvimento.