Criando painéis ao vivo com Airtable e React
Publicados: 2022-03-11Quer uma empresa seja uma grande empresa ou uma startup iniciante, coletar dados de usuários e clientes e relatar ou visualizar esses dados é crucial para os negócios.
Recentemente trabalhei com uma startup de telemedicina sediada no Brasil. Sua missão é fornecer atendimento e monitoramento remoto, conectando pacientes a profissionais médicos e treinadores de saúde. A necessidade principal era criar uma interface para os treinadores e profissionais de saúde revisarem facilmente as informações de um paciente e as métricas mais importantes relacionadas à sua situação específica: um painel.
Digite Typeform e Airtable.
Tipo de letra
O Typeform é uma das ferramentas de coleta de dados que permite experiências responsivas na Web para usuários que completam uma pesquisa. Ele também vem com vários recursos que tornam as pesquisas mais inteligentes, principalmente quando combinadas:
- Saltos lógicos
- Campos ocultos
As pesquisas podem ser compartilhadas por meio de URLs que podem ser pré-semeadas com valores para os campos ocultos, que podem ser usados para implementar saltos lógicos e alterar o comportamento da pesquisa para o usuário com o link.
Usos da mesa de ar
O Airtable é um híbrido de banco de dados de planilha e uma plataforma de nuvem colaborativa. Seu foco na funcionalidade apontar e clicar significa que usuários não técnicos podem configurá-lo sem codificação. Airtable tem uma infinidade de casos de uso em qualquer negócio ou projeto.
Você pode usar uma Base Airtable para:
- CRM (Gestão de Relacionamento com o Cliente)
- HRIS (Sistema de Informação de Recursos Humanos)
- Gerenciamento de projetos
- Planejamento de conteúdo
- Planejamento de eventos
- Feedback do usuário
Existem muitos outros casos de uso em potencial. Você pode explorar os estudos de caso do Airtable aqui.
Se você não estiver familiarizado com o Airtable, o modelo de dados conceitual se divide assim:
- Espaço de Trabalho - Composto por Bases
- Base - Composto por Tabelas
- Tabela - Composto por Campos (colunas) e linhas
- View - Uma perspectiva sobre os dados da tabela com filtros opcionais e campos reduzidos
- Campo - Uma coluna de uma Tabela com um Tipo de Campo; veja aqui para mais informações sobre Tipos de Campo
Além de fornecer um banco de dados hospedado na nuvem com recursos de planilha familiares, aqui estão alguns dos motivos pelos quais a plataforma é tão poderosa:
Para usuários não técnicos, o Airtable oferece:
- Uma interface front-end fácil de usar
- Automações que podem ser criadas com configuração de apontar e clicar para enviar e-mails, processar linhas de dados, agendar compromissos em calendários e muito mais
- Vários tipos de visualizações que permitem que as equipes colaborem na mesma Base e tabelas
- Aplicativos Airtable que podem ser instalados no mercado para sobrecarregar uma Base
Para desenvolvedores, o Airtable oferece:
- Uma API de back-end bem documentada
- Um ambiente de script que permite aos desenvolvedores automatizar ações dentro de uma Base
- Automações que também podem acionar scripts desenvolvidos personalizados que são executados no ambiente Airtable, estendendo os recursos das automações
Você pode saber mais sobre o Airtable aqui.
Introdução: Typeform para Airtable
As pesquisas Typeform já estavam configuradas pelo cliente, e a próxima etapa foi planejar como esses dados chegariam ao Airtable e depois seriam transformados em um painel. Há muitas questões a serem consideradas ao criar painéis sobre qualquer banco de dados: Como devemos estruturar os dados? Quais dados precisarão ser processados antes da visualização? Devemos sincronizar o Base com o Planilhas Google e usar o Google Data Studio? Devemos exportar e encontrar outra ferramenta de terceiros?
Felizmente para os desenvolvedores, o Airtable não apenas fornece automações e scripts para lidar com as etapas de processamento de dados, mas também possibilitou a criação de aplicativos e interfaces personalizados em cima de uma base Airtable com aplicativos Airtable.
Aplicativos personalizados no Airtable
Aplicativos personalizados no Airtable existem desde que o Airtable Blocks SDK foi lançado no início de 2018 e recentemente foram renomeados para Apps. O lançamento do Blocks foi enorme, pois significava que os criadores agora tinham a capacidade de desenvolver, como Airtable coloca, “um kit Lego infinitamente recombinável”.
Mais recentemente, com a mudança nos aplicativos, o Airtable Marketplace também tornou possível compartilhar aplicativos publicamente.
Os aplicativos Airtable fornecem às empresas um kit Lego infinitamente recombinável que podem ser adaptados às suas necessidades.
Para construir um aplicativo personalizado no Airtable, um desenvolvedor JavaScript deve saber como usar o React, uma das bibliotecas JavaScript mais populares para construir interfaces de usuário. O Airtable fornece uma biblioteca de componentes e ganchos funcionais do React, que são uma grande ajuda para criar rapidamente uma interface do usuário consistente e determinar como você gerenciará o estado dentro do aplicativo e seus componentes.
Confira o artigo de introdução do Airtable para obter mais informações e o Airtable no GitHub para exemplos de aplicativos.
Requisitos do painel Airtable
Após revisar os modelos de dashboard com a equipe do cliente, os tipos de dados a serem utilizados ficaram claros. Precisaríamos de uma série de componentes do painel que seriam exibidos como texto no painel e gráficos de diferentes métricas que pudessem ser rastreadas ao longo do tempo.
Os treinadores e profissionais médicos precisavam criar um painel personalizado para cada paciente, por isso precisávamos de uma maneira flexível de adicionar e remover gráficos. Outros dados estáticos relativos a cada paciente seriam exibidos independentemente do paciente selecionado.
Nesse caso, as seções do painel se resumiam a:
- Informações gerais - nome do paciente, e-mail, número de telefone, preferência de contato, data de nascimento, idade
- Objetivos - Metas que o paciente tem com base nos resultados da pesquisa
- Algumas estatísticas - IMC, altura e peso
- Uso de medicamentos - listando todos os medicamentos prescritos já usados por um paciente
- História Familiar de Condições - Útil no diagnóstico de certas condições
- Gráficos - Uma seção em que o usuário do painel Airtable pode adicionar um gráfico e configurar qual métrica ele visualizaria ao longo do tempo
Uma maneira de abordar todas as seções, exceto os gráficos, seria codificar todas as colunas para objetivos, uso de medicamentos e histórico familiar no painel. No entanto, isso não permitiria que a equipe do cliente adicionasse novas perguntas a uma pesquisa Typeform nem adicionasse uma nova coluna a uma tabela Airtable para apresentar esses dados no painel sem que um desenvolvedor atualizasse o aplicativo personalizado.
Uma solução mais elegante e extensível para esse desafio foi encontrar uma maneira de marcar as colunas como relevantes para uma seção específica do painel e recuperar essas colunas usando os metadados que o Airtable expõe ao usar os modelos Tabela e Campo.
Isso foi feito usando Descrições de Campo como um local para marcar uma coluna da Tabela como relevante para uma seção do painel a ser exibida ao usuário. Então, poderíamos garantir que apenas aqueles com a função de Criador (os administradores) para a Base tivessem a capacidade de modificar essas Descrições de Campo para alterar o que aparece no painel. Para ilustrar esta solução, focaremos principalmente nos itens de Informações Gerais e na forma de apresentação dos Gráficos.
Criando um sistema #TAG#
Dadas as seções do painel, fazia sentido criar tags reutilizáveis para algumas seções e tags específicas para determinadas colunas. Para itens como nome do paciente, e-mail e número de telefone, #NAME# , #EMAIL# e #PHONE# foram adicionados à descrição de cada campo, respectivamente. Isso permitiria que essas informações fossem recuperadas por meio dos metadados da tabela como este:
const name = table ? table.fields.filter(field => field.description?.includes("#NAME#"))Para áreas do painel que precisariam ser extraídas de muitas colunas marcadas, teríamos as seguintes marcas para cada seção do painel:
- OBJ - Objetivos
- FAM - História da Família
- MED - Uso de Medicamentos
- CAN - Histórico Familiar específico para câncer
- GRÁFICO - Qualquer coluna que deve ser originada para adicionar gráficos; deve ser uma quantidade
Além disso, era importante separar o nome de uma coluna em uma Tabela do rótulo que ela receberia no painel, para que qualquer coisa que recebesse uma #TAG# também pudesse receber duas tags #LABEL# em sua Descrição do Campo . Uma descrição de campo ficaria assim:
Caso faltem as tags #LABEL# , exibiremos o nome da coluna da Tabela.
Podemos analisar o rótulo definido na descrição com uma função simples como esta depois de recuperar o campo com o exemplo de código anterior:
// utils.js export const setLabel = (field, labelTag = "#LABEL#") => { const labelTags = (field.description?.match(new RegExp(labelTag, "g")) || []).length; let label; if (labelTags === 2) label = field.description?.split(`${labelTag}`)[1]; if (!label || label?.trim() === '') label = field.name; return {...field, label, name: field.name, description: field.description}; } Com este sistema #TAG# , conseguimos três coisas principais:
- Os nomes das colunas (campos) na Tabela podem ser alterados conforme desejado.
- Os rótulos dos dados no painel podem ser distintos dos nomes das colunas.
- As seções do painel para Objetivos, Uso de Medicamentos, Histórico Familiar e Gráficos podem ser atualizadas pela equipe do cliente sem tocar em uma linha de código.
Estado Persistente no Airtable
No React, usamos o estado e o passamos para os componentes como props para renderizar novamente esse componente se seu estado mudar. Normalmente, isso está vinculado a uma chamada de API que alimenta um componente do painel, mas no Airtable já temos todos os dados e simplesmente precisamos filtrar o que estamos exibindo com base em qual paciente estamos visualizando. Além disso, se usarmos state, ele não persistirá os dados após uma atualização no próprio painel.
Então, como podemos persistir um valor após a atualização para manter um painel filtrado? Felizmente, o Airtable fornece um gancho para isso chamado useGlobalConfig , no qual mantém um armazenamento de valor-chave para a instalação de um aplicativo em um painel. Simplesmente precisamos implementar a lógica de recuperar valores desse armazenamento de chave-valor quando o aplicativo for carregado para alimentar nossos componentes do painel.
O que é ainda mais útil sobre o uso do gancho useGlobalConfig é que quando seus valores são definidos, o componente do painel e seus componentes filho são renderizados novamente, para que você possa usar o Global Config como usaria uma variável de estado em uma implementação típica do React.
Apresentando gráficos
O Airtable fornece exemplos de gráficos com seu Simple Chart App, que usa React Charts, um wrapper React em Chart.js (chart-ception).

No Simple Chart App, temos um gráfico para todo o aplicativo, mas em nosso Dashboard App, precisamos que o usuário possa adicionar e remover seus próprios gráficos de seu próprio painel. Além disso, em discussão com a equipe do cliente, parece que certas métricas seriam melhor visualizadas no mesmo gráfico (como leituras de pressão arterial diastólica e sistólica).
Com isso, temos os seguintes itens para resolver:
- Estado persistente para o gráfico de cada usuário (ou ainda melhor usando o Global Config)
- Permitindo várias métricas por gráfico
É aqui que o poder do Global Config é útil, pois podemos usar o armazenamento de valor-chave para manter as métricas selecionadas e qualquer outra coisa sobre nossa lista de gráficos. À medida que configuramos um gráfico na interface do usuário, o próprio componente do gráfico será renderizado novamente devido a atualizações no Global Config. Para a seção de gráficos do painel, aqui está um resumo com os componentes para referência, com foco no painel charts.js e chart.js único.
A tabela passada para cada gráfico é o que é usado para que seus metadados encontrem os campos, enquanto os registros passados já foram filtrados pelo paciente selecionado no componente de painel de nível superior que importa dashboard_charts/index.js .
Observe que os campos listados como opções no menu suspenso de um gráfico são puxados usando a tag #CHART# que mencionamos anteriormente, com esta linha em um gancho useEffect :
// single_chart/index.js … useEffect(() => { (async () => { ... if (table) { const tempFieldOptions = table.fields.filter(field => field.description?.includes('#CHART#')).map(field => { return { ...setLabel(field), value: field.id } }); setFieldSelectOptions([...tempFieldOptions]); } })(); }, [table, records, fields]); ... O código acima mostra como a função setLabel referenciada anteriormente é usada com a #TAG# para adicionar qualquer coisa fornecida nas tags #LABEL# e exibi-la para a opção na lista suspensa do campo.
Nosso componente de gráfico aproveita os recursos de vários eixos fornecidos pelo Chart.js, que é mostrado com React Charts. Nós apenas o estendemos por meio da interface do usuário com a capacidade do usuário de adicionar um conjunto de dados e um tipo de gráfico (linha ou barra).
A chave para usar o Global Config, neste caso, é saber que cada chave pode conter apenas uma string | booleano | número | nulo | GlobalConfigArray | GlobalConfigObject (consulte a referência de valor de configuração global).
Temos os seguintes itens para manter por gráfico:
- chartTitle que é gerado automaticamente e pode ser renomeado pelo usuário
- array de campos em que cada item possui:
- campo como fieldId do Airtable
- chartOption como uma linha | bar como os documentos Chart.js indicam
- color como a cor Airtable do colorUtils
- hex como o código hex relativo à cor do Airtable
Para gerenciar isso, achei mais conveniente stringificar esses dados como um objeto em vez de definir chaves e valores de configuração global até o fim. Veja o exemplo abaixo (globalConfig.json na essência), que inclui valores de configuração global para filtrar registros por paciente e algumas variáveis relacionadas usadas para suportar um componente de filtragem typeahead (graças a react-bootstrap-typeahead):
{ "xCharts": { "chart-1605425876029": "{\"fields\":[{\"field\":\"fldxLfpjdmYeDOhXT\",\"chartOption\":\"line\",\"color\":\"blueBright\",\"hex\":\"#2d7ff9\"},{\"field\":\"fldqwG8iFazZD5CLH\",\"chartOption\":\"line\",\"color\":\"blueLight1\",\"hex\":\"#9cc7ff\"}],\"chartTitle\":\"Grafico criado em 11/15/2020, 2:37:56 AM\"}", "chart-1605425876288": "{\"fields\":[{\"field\":\"fldGJZIdRlq3V3cKu\",\"chartOption\":\"line\",\"color\":\"blue\",\"hex\":\"#1283da\"}],\"chartTitle\":\"Grafico criado em 11/15/2020, 2:37:56 AM\"}", "chart-1605425876615": "{\"fields\":[{\"field\":\"fld1AnNcfvXm8DiNs\",\"chartOption\":\"line\",\"color\":\"blueLight1\",\"hex\":\"#9cc7ff\"},{\"field\":\"fldryX5N6vUYWbdzy\",\"chartOption\":\"line\",\"color\":\"blueDark1\",\"hex\":\"#2750ae\"}],\"chartTitle\":\"Grafico criado em 11/15/2020, 2:37:56 AM\"}", "chart-1605425994036": "{\"fields\":[{\"field\":\"fld9ak8Ja6DPweMdJ\",\"chartOption\":\"line\",\"color\":\"blueLight2\",\"hex\":\"#cfdfff\"},{\"field\":\"fldxVgXdZSECMVEj6\",\"chartOption\":\"line\",\"color\":\"blue\",\"hex\":\"#1283da\"}],\"chartTitle\":\"Grafico criado em 11/15/2020, 2:39:54 AM\"}", "chart-1605430015978": "{\"fields\":[{\"field\":\"fldwdMJkmEGFFSqMy\",\"chartOption\":\"line\",\"color\":\"blue\",\"hex\":\"#1283da\"},{\"field\":\"fldqwG8iFazZD5CLH\",\"chartOption\":\"line\",\"color\":\"blueLight1\",\"hex\":\"#9cc7ff\"}],\"chartTitle\":\"New Chart\"}", "chart-1605430916029": "{\"fields\":[{\"field\":\"fldCuf3I2V027YAWL\",\"chartOption\":\"line\",\"color\":\"blueLight1\",\"hex\":\"#9cc7ff\"},{\"field\":\"fldBJjtRkWUTuUf60\",\"chartOption\":\"line\",\"color\":\"blueDark1\",\"hex\":\"#2750ae\"}],\"chartTitle\":\"Grafico criado em 11/15/2020, 4:01:56 AM\"}", "chart-1605431704374": "{\"fields\":[{\"field\":\"fld7oBtl3iiHNHqoJ\",\"chartOption\":\"line\",\"color\":\"blue\",\"hex\":\"#1283da\"}],\"chartTitle\":\"Grafico criado em 11/15/2020, 4:15:04 AM\"}" }, "xPatientEmail": "[email protected]", "xTypeaheadValue": "Elle Gold ([email protected])", "xSelectedValue": "[{\"label\":\"Elle Gold ([email protected])\",\"id\":\"[email protected]\",\"name\":\"Elle Gold\",\"email\":\"[email protected]\"}]" }Nota: Todos os dados contidos acima e os dados incluídos nas animações abaixo não são dados reais do paciente.
Veja o resultado final:
E o Typeahead?
Para filtrar por paciente, precisávamos de uma forma de selecionar um paciente e depois filtrar os registros com base nesse paciente. Nesta seção, analisamos como isso foi alcançado.
Para o typeahead, react-bootstrap-typeahead foi uma escolha fácil, pois os únicos passos restantes foram preparar as opções para o typeahead, mixá-lo com uma entrada Airtable para estilizar e carregar bootstrap, e alguns outros estilos para o nosso menu. Soltar componentes de suas bibliotecas de componentes favoritos em um aplicativo Airtable não é tão simples quanto no desenvolvimento típico da Web React; no entanto, há apenas algumas etapas extras para que tudo fique da maneira que você espera.
Aqui está o resultado final:
Para renderizar a entrada do Airtable e manter todos os nossos estilos consistentes, react-bootstrap-typeahead vem com um prop renderInput. Veja mais sobre como modificar a renderização do componente aqui.
Para os estilos de bootstrap e para substituir nossos itens de menu, os dois utilitários a seguir foram usados no Airtable:
- loadCSSFromString
- loadCSSFromURLAsync
Consulte frontend.js na essência para obter um trecho da implementação do typeahead.
Esta linha foi usada para carregar o bootstrap globalmente:
// frontend/index.js loadCSSFromURLAsync('https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css'); Você notará alguma lógica adicionada para coisas como lidar com mudanças de estilo em links de foco ou reestilização ( <a></a> ) para obter a aparência e a sensação de bootstrap familiar. Isso também inclui a manipulação da configuração dos valores de configuração global para a digitação antecipada e a filtragem de registros para que, se um usuário sair do painel, atualizar sua página ou quiser compartilhar esse painel com outras pessoas, o aplicativo persistirá o paciente selecionado no painel Aplicativo. Isso também permite que os usuários instalem várias cópias desse mesmo aplicativo lado a lado no mesmo Airtable Dashboard com diferentes pacientes selecionados ou com gráficos diferentes.
Lembre-se de que um painel no Airtable também está disponível para todos os usuários do Base, portanto, essas instalações de aplicativos personalizados em um painel serão filtradas para os mesmos pacientes e gráficos, independentemente de quais usuários estejam visualizando o painel ao mesmo tempo.
Vamos recapitular o que cobrimos até agora:
- O Airtable permite que usuários não técnicos e técnicos colaborem no Airtable.
- O Typeform vem com uma integração Airtable que permite que usuários não técnicos mapeiem os resultados do Typeform para o Airtable.
- Os aplicativos Airtable fornecem uma maneira poderosa de sobrecarregar sua base Airtable, seja selecionando no mercado ou criando um aplicativo personalizado.
- Os desenvolvedores podem estender o Airtable rapidamente de quase todas as formas imagináveis com esses aplicativos. Nosso exemplo acima levou apenas três semanas para projetar e implementar (com enorme ajuda de bibliotecas existentes, é claro).
- Um sistema
#TAG#pode ser usado para modificar o painel sem exigir alterações de código pelos desenvolvedores. Existem casos de uso melhores e piores para isso. Certifique-se de limitar as permissões à função Criador se estiver usando essa estratégia. - O uso do Global Config permite que os desenvolvedores mantenham dados em uma instalação de aplicativo. Misture isso em sua estratégia de gerenciamento de estado para semear dados para seus componentes.
- Não espere arrastar e soltar componentes de outras bibliotecas e projetos diretamente em seu aplicativo Airtable. Os estilos podem ser carregados usando os
loadCSSFromStringeloadCSSFromURLAsyncfornecidos pelo Airtable.
À prova de futuro
Use um middleware mais sofisticado
Com Typeform e Airtable, é fácil e econômico configurar o mapeamento de perguntas para colunas.
No entanto, há uma grande desvantagem: se você tiver uma pesquisa com mais de 100 perguntas mapeadas no Airtable e precisar modificar um mapeamento, deverá excluir todo o mapeamento e começar novamente. Isso claramente não é o ideal, mas para uma integração livre, podemos lidar com isso.
Outras opções seriam ter uma integração Zapier (ou similar) gerenciando os dados entre Typeform e Airtable. Então você poderia modificar o mapeamento de qualquer pergunta para qualquer coluna sem começar do zero. Isso teria suas próprias considerações de custo para levar em consideração também.
Esperamos que algumas das lições aprendidas e comunicadas aqui ajudem outras pessoas que desejam criar soluções com o Airtable.
Finalmente, você pode conferir a essência com os arquivos discutidos neste artigo.
