Field Layout Service
Gerencia operações relacionadas aos campos individuais de layouts, permitindo buscar configurações detalhadas de campos para renderização de formulários dinâmicos.
Visão Geral
O FieldLayoutService é responsável por:
- Buscar campos de layout (Field Layouts) da API remota
- Filtrar campos por ID de layout ou ID de campo
- Fornecer configurações detalhadas de cada campo (visibilidade, obrigatoriedade, editabilidade, opções)
Os Field Layouts contêm metadados sobre cada campo de um formulário, incluindo validações, valores padrão e opções disponíveis quando aplicável.
Localização: services/field-layout/field-layout.service.ts
Endpoints Utilizados
Base URL
Todos os endpoints são relativos à base URL configurada em services/api.service.ts
Métodos
fetchRemote(params)
Busca campos de layout remotamente com opções de filtro e paginação.
Endpoint: GET /campoLayout
Parâmetros:
type FetchFieldLayoutByLayoutIdParams = {
page?: number; // Número da página (padrão: 0)
size?: number; // Quantidade de itens por página (padrão: 999)
dataHora?: string;
layoutId?: string; // Filtro por ID do layout
fieldId?: string; // Filtro por ID do campo
};
Query Params enviados:
page: número da páginasize: tamanho da páginadataHora: timestamp (opcional)layoutId: ID do layout (opcional)fieldId: ID do campo (opcional)
Retorno:
Promise<FieldLayout[]>;
Exemplo de uso:
import { fieldLayoutService } from "@/services/field-layout/field-layout.instance";
// Buscar todos os campos de layout
const allFields = await fieldLayoutService.fetchRemote();
// Buscar campos de um layout específico
const layoutFields = await fieldLayoutService.fetchRemote({
layoutId: "L-001",
});
// Buscar um campo específico
const specificField = await fieldLayoutService.fetchRemote({
fieldId: "F-001",
});
// Buscar com paginação
const fieldsPage = await fieldLayoutService.fetchRemote({
page: 0,
size: 50,
layoutId: "L-002",
});
Tratamento de erros:
- Lança
Errorse o status da resposta não for 2xx - Mensagem:
FieldLayoutService.fetchRemote failed: {status}
Estruturas de Dados
Interface FieldLayout
type FieldLayout = {
fieldLayoutId: string;
requerido: "S" | "N";
editavel: "S" | "N";
visivel: "S" | "N";
valorPadrao: string;
aba: string;
field: FieldBasic;
layout: LayoutBasic;
Opcoes: Option[];
};
Type FieldBasic
type FieldBasic = {
id: string; // ID único do campo
nome: string; // Nome técnico do campo (usado como key)
descricao: string; // Descrição legível do campo (label)
entidade: LayoutEntity; // Entidade a qual o campo pertence
tipo: string; // Tipo de dados (string, integer, decimal, etc.)
};
type LayoutEntity = "Cliente" | "Produto" | "Cabeçalho" | "Contato";
Type LayoutBasic
type LayoutBasic = {
id: string; // ID do layout
idOrganization: string; // ID da organização
nome: string; // Nome do layout
entidade: LayoutEntity; // Entidade do layout
organization: string; // Nome da organização
};
Type Option
type Option = {
id: string; // ID único da opção
idCampo?: string; // ID do campo (quando opção é de campo)
idConfiguracao?: string; // ID da configuração (quando opção é de config)
campo: string | null; // Nome do campo
configuracao: string | null; // Nome da configuração
nome: string; // Nome legível da opção (label)
valor: string; // Valor da opção (value)
};
Componentes Relacionados
buildQueryParams Helper
Função utilitária que converte objeto de parâmetros em URLSearchParams.
Localização: helpers/buildQueryParams.ts
PageResponse Interface
Interface compartilhada para respostas paginadas.
Localização: services/client/client.service.ts
interface PageResponse<T> {
content: T[]; // Array de itens da página
page?: number; // Número da página atual
size?: number; // Tamanho da página
totalPages?: number; // Total de páginas
totalElements?: number; // Total de elementos
}
Fluxo de Integração
Fluxo de Renderização de Formulário
1. App obtém layoutId da entidade
2. App chama fieldLayoutService.fetchRemote({ layoutId })
3. GET /campoLayout é enviado com query params
4. API retorna campos do layout
5. Para cada campo:
a. Verificar visivel === "S" (renderizar ou ocultar)
b. Verificar requerido === "S" (adicionar validação)
c. Verificar editavel === "N" (desabilitar input)
d. Aplicar valorPadrao se definido
e. Renderizar Opcoes se disponíveis
f. Agrupar por aba
6. Formulário é renderizado dinamicamente
Fluxo de Validação de Formulário
1. Usuário preenche formulário
2. Antes de submeter:
a. Para cada campo com requerido === "S":
- Verificar se valor foi preenchido
- Adicionar erro se vazio
b. Para cada campo com tipo específico:
- Validar formato (email, número, etc.)
c. Para campos com Opcoes:
- Verificar se valor está nas opções válidas
3. Se houver erros, exibir e bloquear submissão
4. Se válido, prosseguir com submissão
Casos de Uso
Renderizar Campos de um Layout (exemplo)
// Buscar campos do layout de Cliente
const fields = await fieldLayoutService.fetchRemote({
layoutId: "L-001",
});
// Filtrar campos visíveis
const visibleFields = fields.filter((f) => f.visivel === "S");
// Agrupar por aba
const fieldsByTab = visibleFields.reduce(
(acc, field) => {
if (!acc[field.aba]) acc[field.aba] = [];
acc[field.aba].push(field);
return acc;
},
{} as Record<string, FieldLayout[]>,
);
// Renderizar cada aba
Object.entries(fieldsByTab).forEach(([tab, tabFields]) => {
console.log(`\n=== ${tab} ===`);
tabFields.forEach((field) => {
console.log(`\nCampo: ${field.field.descricao}`);
console.log(` Nome técnico: ${field.field.nome}`);
console.log(` Tipo: ${field.field.tipo}`);
console.log(` Obrigatório: ${field.requerido === "S" ? "Sim" : "Não"}`);
console.log(` Editável: ${field.editavel === "S" ? "Sim" : "Não"}`);
if (field.valorPadrao) {
console.log(` Valor padrão: ${field.valorPadrao}`);
}
if (field.Opcoes.length > 0) {
console.log(` Opções disponíveis:`);
field.Opcoes.forEach((opt) => {
console.log(` - ${opt.nome} (${opt.valor})`);
});
}
});
});
Processar Campos com Opções (Select/Radio) (exemplo)
// Buscar campos
const fields = await fieldLayoutService.fetchRemote({
layoutId: "L-002",
});
// Encontrar campos que têm opções
const fieldsWithOptions = fields.filter((f) => f.Opcoes.length > 0);
fieldsWithOptions.forEach((field) => {
console.log(`\nCampo: ${field.field.descricao}`);
console.log("Tipo de componente sugerido: Select ou Radio");
console.log("Opções:");
field.Opcoes.forEach((option) => {
console.log(` <option value="${option.valor}">${option.nome}</option>`);
});
// Valor padrão (pré-selecionado)
if (field.valorPadrao) {
const defaultOption = field.Opcoes.find(
(o) => o.valor === field.valorPadrao,
);
if (defaultOption) {
console.log(`Valor padrão: ${defaultOption.nome}`);
}
}
});
Aplicar Regras de Edição (exemplo)
// Buscar campos
const fields = await fieldLayoutService.fetchRemote({
layoutId: "L-001",
});
// Separar campos por editabilidade
const editableFields = fields.filter(
(f) => f.editavel === "S" && f.visivel === "S",
);
const readonlyFields = fields.filter(
(f) => f.editavel === "N" && f.visivel === "S",
);
console.log(
"Campos editáveis:",
editableFields.map((f) => f.field.nome),
);
console.log(
"Campos somente leitura:",
readonlyFields.map((f) => f.field.nome),
);
// Ao renderizar, aplicar atributo disabled/readonly
readonlyFields.forEach((field) => {
console.log(
`Campo ${field.field.nome} deve ser renderizado com readonly/disabled`,
);
});
Testes
Localização
services/field-layout/field-layout.service.test.ts
Executar Testes
# Teste específico do serviço
npx jest services/field-layout/field-layout.service.test.ts
# Modo watch
npx jest services/field-layout/field-layout.service.test.ts --watch
# Com coverage
npx jest services/field-layout/field-layout.service.test.ts --coverage
Casos de Teste
- ✅
fetchRemote: busca de campos sem filtros - ✅
fetchRemote: busca filtrada por layoutId - ✅
fetchRemote: busca filtrada por fieldId - ✅
fetchRemote: busca com paginação - ✅ Tratamento de erros para status não-2xx
- ✅ Retorno correto de estrutura FieldLayout
Mocks
Localização
__mocks__/field-layout.mock.ts
Dados Disponíveis
Array extenso de campos de layout de exemplo para diferentes tipos:
- Campos de texto (string)
- Campos numéricos (integer, decimal)
- Campos com opções (select/radio)
- Campos somente leitura
- Campos obrigatórios e opcionais
Exemplo de campos no mock:
// Campo obrigatório de texto
{
fieldLayoutId: "FL-002-03",
requerido: "S",
editavel: "S",
visivel: "S",
valorPadrao: "",
aba: "Geral",
field: {
id: "F-012",
nome: "NomeProduto",
descricao: "Nome do produto",
entidade: "Produto",
tipo: "string"
},
Opcoes: []
}
// Campo com opções
{
fieldLayoutId: "FL-002-05",
requerido: "N",
editavel: "S",
visivel: "S",
valorPadrao: "un",
aba: "Geral",
field: {
id: "F-014",
nome: "UnidadeMedida",
descricao: "Unidade de medida do produto",
entidade: "Produto",
tipo: "string"
},
Opcoes: [
{ id: "O-011", nome: "Unidade", valor: "un" },
{ id: "O-012", nome: "Quilograma", valor: "kg" }
]
}
Exemplo de uso em testes:
import { FieldLayouts } from "@/__mocks__/field-layout.mock";
// Mock da API
const apiMock = {
get: jest.fn(async () => ({
status: 200,
data: { content: FieldLayouts },
})),
};
const service = new FieldLayoutService({ apiWithoutAccessToken: apiMock });
const result = await service.fetchRemote({ layoutId: "L-002" });
// Filtrar fields do layout específico
const layoutFields = result.filter((f) => f.layout.id === "L-002");
expect(layoutFields.length).toBeGreaterThan(0);