Camada de Apresentação
Introdução
A camada de apresentação é composta dos componentes React que representam as páginas web da aplicação.
Cada página é um componente React declarado em módulo JavaScript de nome page.js
, conforme
convenções do AppRouter do Next.js.
A rota, ou path da URL da página, é estabelecida conforme a estrutura hierárquica das pastas onde o
arquivo page.js
está localizado.
A página é instanciada dentro de um template, defindo no arquivo layout.js
. O Next.js
aplica o layout que está mais próximo do arquivo page.js
, em termos de estrutura de pastas. Todo layout é instanciado, por sua vez, dentro de um layout de hierarquia
superior, até que tudo esteja abraçado pela layout raiz da aplicação.
Consulte o manual do Next.js para mais informações sobre páginas, layouts, rotas e outras funcionalidades do framework.
Estrutura de Arquivos
As páginas web, nos padrões da presente arquitetura, ficam localizadas dentro da pasta
app/(pages)
, dedicada à camada de apresentação.
Segure-se que a camada de apresentação tenha a seguinte estrutura interna:
layout.js (*1)
error.js
(client)/
layout.js (*2)
(protected)/
layout.js (*3)
[...] (*4)
(public)/
[...] (*5)
(*1) Layout raiz da aplicação, que contém a estrutura HTML estática (tags
<html>
,<head>
e<body>
), sem componentes React. Neste arquivo são importados os resources globais, essencialmente os arquivos CSS.(*2) Layout dedicado à parte que é carregada no lado cliente (browser). Contém componentes React globais da aplicação. Envolve essencialmente os elementos estruturantes do
Cloudsupport for React
. Consulte a seção Configuração.(*3) Layout aplicado nas páginas protegidas, ou seja, em que o usuário foi autenticado com sucesso. Contém elementos como cabeçalho, rodapé, menu principal e menu do usuário.
(*4) Todas as páginas protegidas são colocadas na pasta
(protected)
e suas subpastas.(*5) Diretório dedicado às páginas públicas, como eventual landing page e página de pós logout.
Lembrete: Pelo Next.js, diretórios com parânteses não determinam path na rota (URL) das páginas.
Eles são utilizados como organizadores do código-fonte, como é o caso das pastas (protected)
, que agrupa
as páginas de acesso restrito (autenticado), e (public)
, que agrupa as páginas sem requisito de autenticação.
A pasta (client)
apenas indica que todo seu conteúdo é client-side, ou seja, processado no lado do
cliente (o browser). Entrentanto, os diretórios com parânteses são considerados na composição do layout.
Ou seja, páginas dentro de (protected)
são abraçadas pelo layout (3), que por sua vez é abraçado pelo
layout (2) e por fim pelo layout raiz (1).
Exemplo de conteúdo da pasta (protected)
, que contém as páginas com acesso restrito:
layout.js
ajustes/
page.js
apoio/
page.js
executores/
page.js
home/
page.js (*1)
pageAcaoAtestar.js
pageAcaoCancelar.js
pageAcaoEncaminhar.js
pageAcaoExecutar.js
pageAcaoGerar.js
pageAcaoGlosar.js
pageAcaoExecutar.js
pageTableColumnDataAbertura.js
pageTableColumnProtocolo.js
pageTableColumnSituacao.js
pageTableMenuContexto.js
pageTableRowExpansion.js
O exemplo acima mapeia quatro páginas, nos endereços:
/ajutes
,/apoio
,/executores
e/home
.(*1) Esta página foi dividada em módulos para melhor legibilidade e manutenção do código. Foram criados componentes React para tratar cada ação transacional da página, referenciados nos botões e menu de contexto, e componentes/funções utilizados na tabela principal (para células de colunas, menu de contexto e expansão de linha).
Note que os componentes da página
/home
seguem um padrão em que os nomes dos arquivos são prefixados pelo tipo de componente no qual serão inseridos na página.
Exemplo de conteúdo da pasta (public)
, que contém as páginas com acesso sem restrição:
(index)/
page.js
loggedOut/
page.js
O diretório “(index)” contém uma landing page, mapeada no path raiz (
/
). O diretório “loggedOut”, de path/loggedOut
, contém a página a ser exibida após o logout com sucesso.
Páginas
Implementar cada página web na forma de um componente React, conforme as diretrizes:
-
Evitar a implementação de regras de negócio, que é função do backend, com exceção de redundâncias para fins de UX, especialmente validações de formulários.
-
Evitar componentes React com mais de 200 linhas de código.
-
Prefira dividí-los em componentes menores. Para melhor legibilidade, estes componentes ficam localizados no mesmo diretório da página.
-
Cada componente adicional deve ser declarado em arquivo JavaScript distinto e exportado de maneira nomeada. O
export default
é utilizado tão somente quando requisitado pelo Next.js.
-
-
Não consumir webservices diretamente na página.
- Deve-se implementar o consumo na camada de integração, na forma de serviço remoto, e importar a função correspondente na página. Consulte a seção de Integração para mais informações.
Sugere-se o seguinte template de código para um componente React de página:
'use client'
import React from 'react';
// Demais imports
// Documentação dos parâmetros de URL, caso se aplicarem
export default function Page() {
// Hooks do Next
// Hooks do Cloudsupport
// Refs
// States
// Funções de apoio
// Funções de eventos de UI
// Effects
return (
<>
{/* Elementos da página */}
</>
);
}
Componentes de UI
A presente arquitetura é agnóstica quanto ao framework de componentes de interface gráfica. Sugere-se o:
-
Mantine: Para aplicações de interface de menor complexidade e mais focadas em responsividade. O Mantine possui mais de 100 componentes, com visual elegante e moderno, 50 Hooks para simplificar tarefas comuns, gerenciamento de tema e uma solução validação de formulários.
-
PrimeReact: Para aplicações de uso geral. O PrimeReact possui uma ampla quantidade de componentes, que inclui tabelas avançadas, sendo uma opção conveniente para aplicações de interface gráfica mais complexa, como por exemplo sistemas de gestão empresarial (ERP, Enterprise resource planning). O PrimeReact oferece um conjunto de Hooks para simplificar tarefas comuns. Quanto à validação de formulários, sugere-se um framework complementar, a citar o React Hook Form.
-
PatternFly: Para aplicações de uso geral ou que tenham caráter mais técnico. Há componentes para exibição de código-fonte, visualização de logs e diagramas de topologia. É um framework rico em opções visuais. Inclui guia de boas práticas de UX, incluindo acessibilidade.
O Cloudsupport for React
considera o PrimeReact
em seus exemplos. Também é utilizado o PrimeFlex
,
associado ao PrimeReact, para fins de layout e formatação padronizada, sem necessidade de declaração
explícita de estilos CSS.
O Cloudsupport for React
oferece alguns componentes de UI suplementares para PrimeReact:
-
Componentes relativos a controle de acesso, a serem declarados no layout da aplicação:
AuthRequired
: Protege páginas contra acesso não autenticado por OIDC.SessionExpiredBanner
: Exibe aviso de sessão OIDC expirada.
-
Componentes relativos a controle de acesso, a serem utilizados em páginas:
LoggedOut
: Exibe aviso de sessão encerrada.
-
Componente relativo à detecção de nova versão da aplicação, a ser declarado no layout:
ReloadBanner
: Exibe aviso de que nova versão da aplicação está disponível.
-
Componentes relativos a indicação de processamento:
ProcessingIndicator
: Exibe indicador global de processamento.RefreshingIcon
: Exibe ícone processamento, para uso em locais específicos da página.LoadingBar
: Exibe uma barra de progresso, para uso em locais específicos da página.
-
Outros componentes:
Box
: Aplica uma margem padrão no conteúdo.ContextBar
: Um Toobar com espaço para o título da página.Field
: Permite alinhar campos de valor ou de formulários, com responsividade.InlineField
: Permite alinhar horizontalmente campos de valor ou de formulários.RowExpansionBox
: Um container para uso dentro de RowExpansion de tabelas.MenuButton
: Um botão com estilo indicado para uso dentro de células de tabelas.MessageBlock
: Exibe uma mensagem em tela cheia.ErrorMessage
: Exibe uma mensagem de erro.
O Cloudsupport for React
oferece também os seguintes componentes de UI, que não dependem o PrimeReact:
AuthRequired
: Protege páginas contra acesso não autenticado por OIDC.HasAuthority
: Protege elementos contra acesso não autorizado OIDC.HasAnyAuthority
: Protege elementos contra acesso não autorizado OIDC.ProcessingIndicator
: Exibe indicador global de processamento.
A seção Documentação da API contém a documentação dos componentes de UI.
A seção Configuração contém a documentação
sobre como habilitar o Cloudsupport for React
no projeto, necessário para que os recursos
e componentes da arquitetura funcionem.
Hooks
O Cloudsupport for React
oferece alguns Hooks:
useAuth
: Acesso aos dados do usuário OIDC autenticado e API para login/logout.useAccessToken
: Acesso ao AccessToken, usualmente o JWT.hasAuthority
: Protege elementos contra acesso não autorizado OIDC.hasAnyAuthority
: Protege elementos contra acesso não autorizado OIDC.useProfile
: Acesso às propriedades de ambiente da aplicação.useProfileStatus
: Indica se a aplicação está atualizada.useProcessing
: Acesso à API para indicação de processamento, permite feedback visual para o usuário.useGlobal
: Acesso a um estado globa, para uso livre em escopo do aplicação.
Para aplicações que utilizam o PrimeReact, o Cloudsupport for React
oferece o seguinte Hook adicional:
useToast
: Acesso à API global para exibição de notificações (info, warning, error).
A seção Documentação da API contém a documentação dos Hooks.
A seção Configuração contém a documentação
sobre como habilitar o Cloudsupport for React
no projeto, necessário para que os recursos
e Hooks da arquitetura funcionem.
Exemplos
O projeto de arquétipo web implementa um exemplo de aplicação de atendimento a ordens de serviços. O arquétipo contém quatro exemplos de página web, cada exemplo em determinado nível de complexidade, citados a seguir.
Nível 1: Página estática
- Página estática que mostra as configurações da aplicação.
Screenshot:
Código-fonte:
export default function Page() {
const profile = useProfile(); // Hook que obtém o profile da aplicação
const atributos = Object.keys(profile); // Lista os campos do objeto profile
return (
<>
<ContextBar title='Configurações da Aplicação' />
<Box>
{atributos.map(at =>
<Field key={at} label={at}>{JSON.stringify(profile[at])}</Field>)}
</Box>
</>
);
}
Hook
useProfile
e componentesBox
,Field
eContextBar
são oferecidos pela arquitetura. Consulte mais informações na documentação da API.O cabeçalho, que contém logo, menu principal e menu do usuário, está declarado no arquivo
layout.js
.
Nível 2: Consulta de dados
-
Página com consumo de webservices para consulta de dados remotos.
-
Exibição em abas.
Screenshot:
Código-fonte:
export default function Page() {
const processing = useProcessing(); // Indicador global de processamento do Cloudsupport
const toast = useToast(); // Toast global de notificações do Cloudsupport
const [situacoesOs, setSituacoesOs] = React.useState();
const [motivosCancelamento, setMotivosCancelamento] = React.useState();
const [tiposExecutor, setTiposExecutor] = React.useState();
// Carrega a página consultando os dados remotos
const carregar = () => {
processing.notifyStart();
// Realiza as três consultas no backend (o browser pode eventualmente paralelizar)
const promise1 = pesquisarSituacoesOs().then(setSituacoesOs);
const promise2 = pesquisarMotivosCancelamento().then(setMotivosCancelamento);
const promise3 = pesquisarTiposExecutores().then(setTiposExecutor);
Promise.all([promise1, promise2, promise3]) // Aguarda a conclusão dos 3 requests
.catch(toast.showError)
.finally(processing.notifyEnd);
}
// Carregamento inicial da página
useMountEffect(carregar);
return (
<>
<ContextBar title='Consulta de Tabelas de Apoio' />
<TabView>
<TabPanel header="Situações de OS">
{situacoesOs && situacoesOs.map(i =>
<Field key={i.id}>{i.id} - {i.descricao}</Field>)}
</TabPanel>
<TabPanel header="Motivos de Cancelamento de OS">
{motivosCancelamento && motivosCancelamento.map(i =>
<Field key={i.id}>{i.id} - {i.descricao}</Field>)}
</TabPanel>
<TabPanel header="Tipos de Executor">
{tiposExecutor && tiposExecutor.map(i =>
<Field key={i.id}>{i.id} - {i.descricao}</Field>)}
</TabPanel>
</TabView>
</>
);
}
Hooks
useProcessing
euseToast
e componentesField
eContextBar
são oferecidos pela arquitetura. Consulte mais informações na documentação da API.Hook
useMountEffect
e componentesTabView
eTabPanel
são do PrimeReact.O cabeçalho, que contém logo, menu principal e menu do usuário, está declarado no arquivo
layout.js
.
Nível 3: Alteração de dados, tabela e botões
-
Página com consumo de webservices para consulta e alteração de dados remotos.
-
Exibição em tabela simples.
-
Botões de ação.
Screenshot:
Código-fonte:
export default function Page() {
const processing = useProcessing(); // Indicador global de processamento do Cloudsupport
const toast = useToast(); // Toast global de notificações do Cloudsupport
const [executores, setExecutores] = React.useState(); // Lista de executores
// Carrega a página consultando os dados remotos
const carregar = () => {
processing.notifyStart();
pesquisarExecutores()
.then(setExecutores)
.catch(toast.showError)
.finally(processing.notifyEnd);
}
// Evento do botão que inativa o executor
const inativar = (executor) => {
confirmDialog({
message: 'Deseja inativar este executor?',
header: 'Confirmação',
icon: 'pi pi-info-circle',
acceptLabel: 'Confirmar',
rejectLabel: 'Cancelar',
accept: () => {
processing.notifyStart();
inativarExecutor({ uid: executor.uid })
.then(() => toast.showInfo(`Executor ${executor.nome} foi inativado.`))
.catch(toast.showError)
.then(pesquisarExecutores) // Sempre recarrega os registros
.then(setExecutores)
.catch(toast.showError)
.finally(processing.notifyEnd);
},
});
}
// Evento do botão que reativa o executor
const reativar = (executor) => {
// (ocultado por similaridade ao método acima)
}
// Carregamento inicial da página
useMountEffect(carregar);
// Renderiza a coluna com menu de ações
const renderColumnMenu = (executor) =>
<>
{executor.dataInativacao &&
<Button label='Ativar' size="small" severity="success" onClick={() => reativar(executor)} />}
{!executor.dataInativacao &&
<Button label='Inativar' size="small" severity="warning" onClick={() => inativar(executor)} />}
</>;
return (
<>
<ContextBar title='Executores' />
{executores &&
<DataTable value={executores.lista} emptyMessage="Nenhum executor cadastrado">
<Column header="Nome do executor" field="nome" />
<Column header="Tipo" field="tipoExecutorDescricao" />
<Column header="Inativado em" body={item => fmtDate(item.dataInativacao, 'datetime')} />
<Column body={item => renderColumnMenu(item)} className='w-1rem' />
</DataTable>}
</>
);
}
Hooks
useProcessing
euseToast
e componenteContextBar
são oferecidos pela arquitetura. Consulte mais informações na documentação da API.Hook
useMountEffect
e componentesDataTable
,Column
,Button
econfirmDialog
são do PrimeReact.O cabeçalho, que contém logo, menu principal e menu do usuário, está declarado no arquivo
layout.js
.
Nível 4: Paginação, menu, popup, formulário
O exemplo de nível 4 inclui os recursos do nível 3, mais:
-
Paginação em backend (“Lazy” do PrimeReact).
-
Expansão de linha.
-
Customização de células de tabela (componente React em vez de texto simples).
-
Pesquisa com filtro.
-
Menu de contexto com ações em três (3) níveis de complexidade:
-
Ação nível 1: Apenas solicita uma confirmação do usuário antes de executar a ação.
-
Ação nível 2: Popup com formulário simples e botões de ação.
-
Ação nível 3: Popup com carregamento de dados sob demanda.
-
Screenshot:
Observação: No momento, orienta-se no sentido de utilizar o framework React Hook Form para validação de formulários. Ressalta-se que as validações, ainda que implementadas na camada de apresentação, devem ser implementadas no backend.
Código-fonte:
export default function Page() {
const processing = useProcessing(); // Indicador global de processamento do Cloudsupport
const toast = useToast(); // Toast global de notificações do Cloudsupport
const acaoGerar = React.useRef();
const acaoEncaminhar = React.useRef();
const acaoExecutar = React.useRef();
const acaoCancelar = React.useRef();
const acaoAtestar = React.useRef();
const acaoGlosar = React.useRef();
const menuContexto = React.useRef();
const refreshing = React.useRef();
const [filtroDescricao, setFiltroDescricao] = React.useState(''); // Filtro para pesquisa de ordens de serviço
const [ordensServico, setOrdensServico] = React.useState(); // Lista de ordens de serviço
const [osSelecionada, setOsSelecionada] = React.useState(); // Ordem de serviço selecionada ao clicar no menu
const [expandedRows, setExpandedRows] = React.useState(); // Necessário para rowExpansion
const [lazy, setLazy] = React.useState({first: 0, rows: 8}); // Necessário para lazy loading
// Carrega a página consultando os dados remotos
const carregar = () => {
processing.notifyStart(); // Indicador geral de processamento
refreshing.current.show(); // Animação para requisições rápidas, feedback importante de UX devido ao botão de pesquisa
pesquisarOrdensServico({ de: lazy.first, qtd: lazy.rows, descricao: filtroDescricao })
.then(setOrdensServico)
.catch(toast.showError)
.finally(processing.notifyEnd)
.finally(refreshing.current.hide);
}
// Evento do botão que abre o menu de contexto
const exibirMenuContexto = (event, ordemServico) => {
setOsSelecionada(ordemServico);
menuContexto.current?.show(event);
};
// Evento executado no lazy loading da tabela
useUpdateEffect(carregar, [lazy]);
// Carregamento inicial da página
useMountEffect(carregar);
return (
<>
{/* Barra de contexto */}
<ContextBar title='Ordens de Serviço'
start={
<>
<InputText
placeholder='Buscar pela descrição' size={30}
onChange={e => setFiltroDescricao(e.target.value)}
onKeyDown={e => e.key === 'Enter' && carregar()} />
<Button icon="fa fa-search" size='small' onClick={carregar} />
<RefreshingIcon ref={refreshing} />
</>}
end={<Button label='Nova ᶜ²' icon='fa fa-plus' onClick={() => acaoGerar.current.iniciar()}/>}
/>
{/* Tabela principal */}
{ordensServico &&
<DataTable value={ordensServico.lista}
emptyMessage="Nenhuma ordem de serviço cadastrada"
rowExpansionTemplate={renderRowExpansion} /* RowExpansion */
expandedRows={expandedRows} onRowToggle={e => setExpandedRows(e.data)} /* RowExpansion */
paginator rows={lazy.rows} /* Paginator */
lazy first={lazy.first} totalRecords={ordensServico.total} onPage={setLazy} /* Lazy loading */
>
<Column expander={true} className='w-1rem' />
<Column header="Abertura" body={renderColumnDataAbertura} />
<Column header="Protocolo" body={renderColumnProtocolo} />
<Column header="Descrição" field="descricao" />
<Column header="Situação" body={renderColumnSituacao} />
<Column body={item =>
<MenuButton onClick={e => exibirMenuContexto(e, item)} />
} className='w-1rem' />
</DataTable>}
{/* Componentes que executam ação transacional, usados no menu de contexto e na barra de contexto */}
<AcaoGerar ref={acaoGerar} onComplete={carregar} />
<AcaoEncaminhar ref={acaoEncaminhar} onComplete={carregar} />
<AcaoExecutar ref={acaoExecutar} onComplete={carregar} />
<AcaoCancelar ref={acaoCancelar} onComplete={carregar} />
<AcaoAtestar ref={acaoAtestar} onComplete={carregar} />
<AcaoGlosar ref={acaoGlosar} onComplete={carregar} />
{/* Menu de contexto para a tabela */}
<MenuContexto ref={menuContexto}
osSelecionada={osSelecionada}
acaoEncaminhar={acaoEncaminhar}
acaoExecutar={acaoExecutar}
acaoCancelar={acaoCancelar}
acaoAtestar={acaoAtestar}
acaoGlosar={acaoGlosar} />
</>
);
}
Consulte no arquétipo o código-fonte das funções e componentes referenciados no exemplo acima.
O cabeçalho, que contém logo, menu principal e menu do usuário, está declarado no arquivo
layout.js
.
Note que a estrutura de componentes do page.js
é enxuta para a quantidade de funcionalidades
presentes (8 funções transacionais, paginação, expansão de linha, popups e menus). Isso se deve
à divisão da página em componentes internos, a citar:
-
Componente para a barra de contexto.
-
Componentes para células da tabela.
-
Componente para a expansão de linha.
-
Um componente para cada ação do usuário.
-
Componente do menu de contexto.
É fundamental organizar o código-fonte em componentes pequenos para garantir uma boa manutenção da aplicação e mitigar bugs na interface do usuário.
Outros Exemplos
Além dos exemplos de páginas apresentados acima, o projeto de arquétipo inclui:
-
Exemplo de página pública de landing.
-
Exemplo de página pública pós logoff.
-
Exemplo de páginas autenticadas por SSO OIDC.
-
Exemplo de template via PrimeFlex e sem imagens (somente CSS).
-
Exemplo de aplicação de favicon multicanal (Android, iOS e desktop) e logo SVG.
-
Exemplo de menu do usuário autenticado.
-
Exemplo de configuração mínima do
Cloudsupport for React
que ativa os recursos da arquitetura, em(client)/layout.js
. -
Exemplo de página de erro customizada.
-
Exemplo de Dockerização.
Próximos Passos
A próxima leitura sugerida é a seção Integração, que detalha o consumo de serviços do backend.
A seção Documentação da API contém a referência completa de todos os módulos e componentes da arquitetura.