UNPKG

@horizon-apps/domain-schema-core

Version:

Core domain schema utilities for Horizon Platform - Schema generators, data enrichers, converters and specifications

528 lines (439 loc) 14.9 kB
# 🚀 Domain Data Display Enricher - Guia Frontend > Motor de enriquecimento para transformar dados brutos em objetos enriquecidos para display na UI ## 📋 Índice 1. [Como Usar no Frontend](#-como-usar-no-frontend) 2. [Formato dos EnrichMappers](#-formato-dos-enrichmappers) 3. [Estrutura de Saída](#-estrutura-de-saída) 4. [Schemas SSOT](#-schemas-ssot) 5. [Exemplos Completos](#-exemplos-completos) 6. [Troubleshooting](#-troubleshooting) --- ## 🎯 Como Usar no Frontend ### **1. Instalação** ```typescript import { DomainDataDisplayEnricher } from '@horizon-apps/domain-schema-core' ``` ### **2. Criar Registry de Domínios** ```typescript const registry = { property: { key: "property", enrichMapper: propertyEnrichMapper }, broker: { key: "broker", enrichMapper: brokerEnrichMapper } } ``` ### **3. Instanciar Motor** ```typescript const enricher = new DomainDataDisplayEnricher(registry, { locale: "pt-BR", currency: "BRL", includeMetadata: false, // true = copia COMPLETA do FieldMetadata (todos os campos) getIcon: (iconName: string) => `<icon-${iconName} />` }) ``` ### **4. Processar Dados** ```typescript const rawData = { id: 1001, title: "Casa teste", valor: 1500000, broker: { name: "João Silva", creci: "12345" } } const enrichedData = enricher.enrich(rawData, "property") ``` --- ## 🏗️ Formato dos EnrichMappers **FORMATO OBRIGATÓRIO** que cada domínio deve implementar: ```typescript type EnrichMapper = (rawData: Record<string, any>) => EnrichMapperResult interface EnrichMapperResult { data: Record<string, any> // Dados originais schema: FieldMetadata[] // Schema SSOT - ARRAY! computedSchema: FieldMetadata[] // Schema dos campos computados - ARRAY! computedData: Record<string, any> // Dados computados } ``` ### **Exemplo Property EnrichMapper:** ```typescript const propertyEnrichMapper = (rawData: Record<string, any>): EnrichMapperResult => { // Cálculos complexos const area_total = (rawData.area_privativa || 0) + (rawData.area_comum || 0) const valor_m2 = area_total > 0 ? Math.round(rawData.valor / area_total) : 0 // Categorizar preço let categoria_preco = "economico" if (valor_m2 > 15000) categoria_preco = "luxo" else if (valor_m2 > 10000) categoria_preco = "alto" else if (valor_m2 > 6000) categoria_preco = "medio" return { data: rawData, // Dados originais schema: propertySchemaArray, // Schema principal ARRAY! computedSchema: propertyComputedSchemaArray, // Schema computados ARRAY! computedData: { // Valores computados area_total, valor_m2, categoria_preco } } } ``` ### **Exemplo Broker EnrichMapper:** ```typescript const brokerEnrichMapper = (rawData: Record<string, any>): EnrichMapperResult => { const display_name = `${rawData.name} (CRECI: ${rawData.creci})` const contact_preference = rawData.phone ? "WhatsApp/Telefone" : "E-mail" return { data: rawData, schema: brokerSchemaArray, // ARRAY DIRETO! computedSchema: brokerComputedSchemaArray, // ARRAY DIRETO! computedData: { display_name, contact_preference } } } ``` --- ## 📤 Estrutura de Saída ### **EnrichedField (Campo Individual):** ```typescript interface EnrichedField { // SEMPRE PRIMEIRO key: string // Chave original do campo (sempre presente, primeiro) // VALORES CORE (sempre presente) value: any // RAW para números/medidas, RESOLVIDO para enums label: string // ui.label do schema // VALORES PROCESSADOS (quando aplicável) valueLabel?: string | string[] // Formatado: moeda, data, enum resolvido displayLabel?: string // Template processado com emojis // VISUAL iconName?: string // Nome do ícone (nunca resolvido) icon?: string // Ícone resolvido pela função getIcon (só quando configurada) // METADADOS BÁSICOS categories?: string[] // Categorias para agrupamento/filtros // METADADOS COMPLETOS (só quando includeMetadata: true) // Cópia EXATA e COMPLETA do FieldMetadata original (incluindo campos undefined) metadata?: FieldMetadata; } ``` ### **Lógica de Values:** | Tipo Campo | Value | ValueLabel | Exemplo | |------------|-------|------------|---------| | **Número/Medida** | RAW | Formatado | `value: 1500000` `valueLabel: "R$ 1.500.000,00"` | | **Enum String** | RAW | Resolvido | `value: "economico"` `valueLabel: "Econômico"` | | **Enum Array** | RAW | Resolvido | `value: ["wifi", "piscina"]` `valueLabel: ["WiFi", "Piscina"]` | | **Boolean** | RAW | Traduzido | `value: true` `valueLabel: "Sim"` | ### **Exemplo de Saída Completa:** ```typescript { // Campo simples valor: { key: "valor", // Key sempre primeiro value: 1500000, // RAW para conversões label: "Valor de Venda", valueLabel: "R$ 1.500.000,00", // Formatado para display displayLabel: "💰 R$ 1.500.000,00", // Com template // METADADOS BÁSICOS (sempre presentes quando existem) categories: ["valores"], type: "Number", // Tipo do campo format: "currency", // Formato do campo unit: "BRL", // Unidade do campo // METADADOS COMPLETOS (só com includeMetadata: true) // CÓPIA COMPLETA do FieldMetadata - TODO campo que vier no schema metadata: { key: "valor", type: "Number", format: "currency", unit: "BRL", categories: ["valores"], validation: undefined, ui: { label: "Valor de Venda", displayTemplate: "💰 {{valueLabel}}", filterable: true, sortable: true }, audit: undefined, enum: undefined, rules: undefined, db: undefined } }, // Campo com enum tags: { key: "tags", // Key sempre primeiro value: ["wifi", "piscina"], // RAW (chaves) label: "Tags", valueLabel: ["WiFi", "Piscina"] // RESOLVIDO (labels) }, // Relacionamento nested broker: { name: { key: "name", value: "João Silva", label: "Nome" }, // ... outros campos do broker computed: { display_name: { key: "display_name", value: "João Silva (CRECI: 12345)", label: "Nome Completo" } } }, // Campos computados na raiz computed: { area_total: { key: "area_total", // Key sempre primeiro value: 225, // RAW para conversões label: "Área Total", valueLabel: "225 m²", // Formatado displayLabel: "📏 225 m² total" } } } ``` --- ## 📋 Schemas SSOT ### **Estrutura Completa (Formato Array):** ```typescript interface FieldMetadata { // RAIZ - Identificação básica key: string type: "String" | "Number" | "Boolean" | "String[]" | "Json" | "Json[]" enum?: Record<string, string> // Para resolução de chaves labels format?: "currency" | "date" | "area" | "distance" | "percent" | "count" | "year" unit?: string // BRL, m2, etc categories?: string[] // CONTEXTOS rules?: { parent?: string, conditions?: string[] } validation?: { required?: boolean, min?: number, max?: number } db?: { type?: string, unique?: boolean, index?: boolean } ui?: UiContext audit?: { origin?: string, modifiedBy?: string[] } } interface UiContext { label?: string // Label para display description?: string placeholder?: string iconName?: string // Nome do ícone (ui.iconName!) displayTemplate?: string // Template com {{value}}, {{valueLabel}} mask?: string searchable?: boolean filterable?: boolean sortable?: boolean } ``` ### **Exemplo Schema Property (Array Format):** ```typescript const propertySchemaArray: FieldMetadata[] = [ { key: "valor", type: "Number", format: "currency", unit: "BRL", ui: { label: "Valor de Venda", displayTemplate: "💰 {{valueLabel}}", filterable: true, sortable: true } }, { key: "tags", type: "String[]", enum: { "wifi": "WiFi gratuito", "piscina": "Piscina", "garagem": "Garagem" }, ui: { label: "Tags", displayTemplate: "🏷️ {{valueLabel}}", filterable: true } }, { key: "quartos", type: "Number", format: "count", ui: { label: "Quartos", displayTemplate: "🛏️ {{value}} quarto{{p:s}}", // Pluralização iconName: "bedroom" } } ] ``` --- ## 🎨 Templates ### **Sintaxe Suportada:** - `{{value}}` - Valor raw - `{{valueLabel}}` - Valor formatado - `{{p:texto}}` - Texto apenas no plural (valor 1) - `{{s:texto}}` - Texto apenas no singular (valor = 1) ### **Exemplos:** ```typescript // Pluralização "{{value}} quarto{{p:s}}" // 1 quarto | 3 quartos // Com emoji "💰 {{valueLabel}}" // 💰 R$ 1.500.000,00 // Condicional "{{s:⭐ }}{{valueLabel}}" // Sim | Não ``` --- ## 🔗 Relacionamentos Nested O motor detecta automaticamente relacionamentos por: 1. **Nome do campo** (broker, images, user, etc) 2. **Estrutura** (objeto ou array de objetos) ### **Suportados:** ```typescript // Relacionamento único broker: { name: "João", creci: "123" } // Array de relacionamentos images: [ { url: "img1.jpg", caption: "Fachada" }, { url: "img2.jpg", caption: "Sala" } ] ``` --- ## 🧮 Campos Computados ### **Como Funcionam:** 1. **EnrichMapper** calcula valores derivados 2. **Motor** aplica schema dos computados 3. **Resultado** fica em `computed: {}` ### **Exemplo:** ```typescript // No EnrichMapper computedData: { area_total: 180 + 45, // = 225 valor_m2: 1500000 / 225 // = 6666 } // Na saída computed: { area_total: { value: 225, // RAW para conversões label: "Área Total", valueLabel: "225 m²", // Formatado displayLabel: "📏 225 m² total" } } ``` --- ## 🛠️ Troubleshooting ### **Problema: Campo não aparece enriquecido** - Verificar se o campo existe no schema array - Verificar se o EnrichMapper retorna os dados corretos ### **Problema: ValueLabel não resolve enum** - Verificar se `enum` está definido no schema - Verificar se os valores raw correspondem às chaves do enum ### **Problema: DisplayTemplate não funciona** - Verificar sintaxe: `{{value}}`, `{{valueLabel}}` - Arrays não processam templates (retorna `undefined`) ### **Problema: IconName não aparece** - Verificar se `ui.iconName` está definido no schema (não mais `ui.icon`!) - IconName sempre como string, nunca objeto resolvido ### **Problema: Schema não funciona** - Verificar se está retornando `FieldMetadata[]` (array) e não objeto - EnrichMapper deve retornar `schema` e `computedSchema` como arrays ### **Problema: Campos null sendo enriquecidos desnecessariamente** - **Comportamento correto**: Campos `null` e `undefined` são **automaticamente pulados** - **Resultado**: Campos null não aparecem no resultado (economiza payload) - **Arrays vazios `[]`**: São enriquecidos normalmente (comportamento esperado) - **Semântica**: `null` = "não aplicável", `[]` = "lista vazia" --- ## 📝 Checklist de Implementação Frontend ### **1. Criar EnrichMappers:** - [ ] Implementar função `(rawData) => EnrichMapperResult` - [ ] Definir schemas principal e computado como **arrays** - [ ] Calcular campos derivados - [ ] Usar `ui.iconName` (não `ui.icon`) ### **2. Configurar Registry:** - [ ] Mapear domínios para EnrichMappers - [ ] Configurar opções (locale, currency, getIcon) ### **3. Usar no Componente:** - [ ] Instanciar `DomainDataDisplayEnricher` - [ ] Chamar `enricher.enrich(data, domain)` - [ ] Renderizar campos enriquecidos ### **4. Tratar Saída:** - [ ] `value` para conversões/formulários - [ ] `valueLabel` para display simples - [ ] `displayLabel` para display com template - [ ] `iconName` para resolução de ícones --- ## 🎣 React Hooks ### **useEnrichList() - Para Listas** ```typescript // Grid de resultados de busca export function GridResultado({ data }) { const { enrichedData, loading, error } = useEnrichList( data, // Array de dados brutos 'property', // Domínio registry, // Registry de domínios { locale: 'pt-BR' } // Opções (opcional) ); if (loading) return <div>Enriquecendo dados...</div>; if (error) return <div>Erro: {error.message}</div>; return ( <div className="grid"> {enrichedData.map((item, i) => ( <PropertyCard key={i} {...item} /> ))} </div> ); } ``` ### **useEnrich() - Para Item Único** ```typescript // Página single de imóvel export function PropertySingle({ propertyId }) { const { data: rawProperty } = useQuery(`/property/${propertyId}`); const { enrichedData, loading, error } = useEnrich( rawProperty, // Dados brutos do imóvel 'property', // Domínio registry // Registry de domínios ); if (loading) return <div>Carregando imóvel...</div>; if (error) return <div>Erro: {error.message}</div>; if (!enrichedData) return null; return ( <div className="property-single"> <h1>{enrichedData.title.displayLabel}</h1> <div className="price">{enrichedData.valor.displayLabel}</div> <div className="details"> <span>{enrichedData.quartos.displayLabel}</span> <span>{enrichedData.area_privativa.displayLabel}</span> </div> {/* Campos computados */} {enrichedData.computed && ( <div className="computed"> <span>{enrichedData.computed.area_total.displayLabel}</span> <span>{enrichedData.computed.valor_m2.displayLabel}</span> </div> )} {/* Relacionamento broker */} {enrichedData.broker && ( <div className="broker"> <h3>{enrichedData.broker.name.displayLabel}</h3> <p>{enrichedData.broker.phone.displayLabel}</p> </div> )} </div> ); } ``` ### **useEnricher() - Para Controle Manual** ```typescript // Quando quiser controle total export function CustomComponent({ data }) { const enricher = useEnricher(registry, { locale: 'pt-BR' }); const handleEnrich = () => { const enriched = enricher.enrich(data, 'property'); // fazer algo com enriched... }; const handleEnrichList = () => { const enrichedList = enricher.enrichList(dataArray, 'property'); // fazer algo com enrichedList... }; return <button onClick={handleEnrich}>Enriquecer</button>; } ```